前回(保護モード+αのプロセス上で動作するCOMオブジェクトを作る)では、CreateProcessAsUserを使いました。今回はCoCreateInstanceやCoGetClassObjectで同じように制限のあるプロセスを作る方法です。


CoCreateInstanceやCoGetClassObjectでは、指定したクラスがアウトプロセスで実装されていた場合、必要に応じてプロセスが作られます。特に指定がなければ、新しいプロセスのトークンは、CreateProcessのように呼び出し元のものが使用されます。

ユーザーを指定する方法はいくつかありますが、今回は呼び出し元で用意した任意のトークンを使ってプロセスを作ってくれる方法を探し出しました。試した結果、以下のようにすれば良いことが分かりました。

  • EOAC_STATIC_CLOAKINGまたはCLSCTX_ENABLE_CLOAKINGを使用する(MSDNライブラリ: Cloaking)。
  • ImpersonateLoggedOnUserか何かでトークンを偽装しておく。

ImpersonateLoggedOnUserで指定するアクセストークンは、前々回(保護モードよりもう少しだけ制限の強いプロセスを作る)までと同じものです。

  • Integrity Levelを低にする
  • CreateRestrictedTokenのDISABLE_MAX_PRIVILEGE | LUA_TOKENで制限付きのアクセストークンを作る

まずはEOAC_STATIC_CLOAKINGでコードを書いてみました。これは、CoInitializeSecurityの実引数に使います。

COMサーバー側のコードです。アウトプロセスサーバ製作の利便性のため、C++/CLIからATL使用のC++に変えました。この実装クラスHogeは以前の記事OBJREFモニカーを元にしています。

// cl factory.cpp /link /SUBSYSTEM:Console,5.01
#define UNICODE
#define _UNICODE
#define WIN32_LEAN_AND_MEAN
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _ATL_XP_TARGETING
 
#include <sstream>
#include <windows.h>
#include <atlbase.h>
#include <atlcom.h>
 
// {DCC853C7-52FD-446E-8BFC-6662D97FD935}
extern const GUID CLSID_Hoge =
{ 0xdcc853c7, 0x52fd, 0x446e,
{ 0x8b, 0xfc, 0x66, 0x62, 0xd9, 0x7f, 0xd9, 0x35 } };
 
class Module : public ATL::CAtlExeModuleT<Module>
{
public:
  Module() { m_bDelayShutdown = false; }
};
 
Module module;
 
class ATL_NO_VTABLE Hoge
  : public ATL::CComObjectRootEx<ATL::CComMultiThreadModel>
  , public ATL::CComCoClass<Hoge, &CLSID_Hoge>
  , public IDispatch
{
public:
  BEGIN_COM_MAP(Hoge)
    COM_INTERFACE_ENTRY(IDispatch)
  END_COM_MAP()
 
  DECLARE_NOT_AGGREGATABLE(Hoge)
  DECLARE_NO_REGISTRY()
 
  IFACEMETHOD(GetTypeInfoCount)(
    __RPC__out UINT*) override
  {
    return E_NOTIMPL;
  }
 
  IFACEMETHOD(GetTypeInfo)(
    UINT,
    LCID,
    __RPC__deref_out_opt ITypeInfo**) override
  {
    return E_NOTIMPL;
  }
 
  IFACEMETHOD(GetIDsOfNames)(
    __RPC__in REFIID,
    /*__RPC__in_ecount_full(cNames)*/ LPOLESTR*,
    __RPC__in_range(0,16384) UINT,
    LCID,
    /*__RPC__out_ecount_full(cNames)*/ DISPID*) override
  {
    return E_NOTIMPL;
  }
 
  IFACEMETHOD(Invoke)(
    _In_ DISPID dispIdMember,
    _In_ REFIID riid,
    _In_ LCID,
    _In_ WORD flags,
    _In_ DISPPARAMS *pDispParams,
    _Out_opt_ VARIANT* pVarResult,
    _Out_opt_ EXCEPINFO*,
    _Out_opt_ UINT*) override
  {
    if (dispIdMember != DISPID_VALUE || flags != DISPATCH_METHOD)
      return DISP_E_MEMBERNOTFOUND;
    if (riid != IID_NULL)
      return DISP_E_UNKNOWNINTERFACE;
    if (pDispParams->cArgs != 0)
      return DISP_E_BADPARAMCOUNT;
    if (pDispParams->cNamedArgs != 0)
      return DISP_E_NONAMEDARGS;
 
    if (pVarResult == nullptr)
      return S_FALSE;
 
    VariantInit(pVarResult);
    std::wostringstream os;
    ATL::CHandle hThreadToken;
    auto r = OpenThreadToken(
      GetCurrentThread(), GENERIC_READ, FALSE, &hThreadToken.m_h);
    auto e = GetLastError();
    os << !!r << ' ' << (r ? 0 : e);
    auto s = os.str();
    pVarResult->vt = VT_BSTR;
    pVarResult->bstrVal = SysAllocStringLen(
      s.data(), static_cast<int>(s.size()));
    return S_OK;
  }
};
 
OBJECT_ENTRY_AUTO(CLSID_Hoge, Hoge)
 
int WINAPI WinMain(HINSTANCE, HINSTANCE, PSTR, int cmdShow)
{
  return module.WinMain(cmdShow);
}

レジストリへの登録が必要なので、regファイルで作りました。

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{DCC853C7-52FD-446E-8BFC-6662D97FD935}\LocalServer32]
@="C:\\Test\\hoge.exe"

そしてやっと、オブジェクトを作る側のコードです。

#define UNICODE
#define _UNICODE
#define WIN32_LEAN_AND_MEAN
#define _ATL_NO_AUTOMATIC_NAMESPACE
#define _ATL_XP_TARGETING
 
#include <iostream>
#include <windows.h>
#include <VersionHelpers.h>
#include <atlbase.h>
#include <atlcom.h>
#include <atlutil.h>
#include <boost/scope_exit.hpp>
#include <boost/implicit_cast.hpp>
 
// {DCC853C7-52FD-446E-8BFC-6662D97FD935}
static const GUID CLSID_Hoge =
{ 0xdcc853c7, 0x52fd, 0x446e,
{ 0x8b, 0xfc, 0x66, 0x62, 0xd9, 0x7f, 0xd9, 0x35 } };
 
HRESULT OutputErrorMessgae(_In_ PCWSTR functionName, HRESULT hr)
{
  std::wclog << functionName << '\n';
  std::wclog << std::showbase << std::hex << hr << '\n';
  std::wclog << ATL::AtlGetErrorDescription(hr).GetString() << std::endl;
  return hr;
}
 
HRESULT OutputLastError(_In_ PCWSTR functionName)
{
  auto hr = ATL::AtlHresultFromLastError();
  OutputErrorMessgae(functionName, hr);
  return hr;
}
 
HANDLE CreateProtectedToken()
{
  ATL::CHandle hOriginalToken, hToken;
  if (!OpenProcessToken(GetCurrentProcess(),
    TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE
    | TOKEN_QUERY | TOKEN_ADJUST_DEFAULT,
    &hOriginalToken.m_h))
  {
    OutputLastError(L"OpenProcessToken");
    return nullptr;
  }
  DWORD flags = DISABLE_MAX_PRIVILEGE;
  if (IsWindowsVistaOrGreater())
  {
    flags |= LUA_TOKEN;
  }
  if (!CreateRestrictedToken(hOriginalToken, flags,
    0, nullptr, 0, nullptr, 0, nullptr, &hToken.m_h))
  {
    OutputLastError(L"CreateRestrictedToken");
    return nullptr;
  }
  if (IsWindowsVistaOrGreater())
  {
    SID lowIL{
      SID_REVISION,
      1,
      SECURITY_MANDATORY_LABEL_AUTHORITY,
      SECURITY_MANDATORY_LOW_RID,
    };
    TOKEN_MANDATORY_LABEL tml{
      {
        &lowIL,
        SE_GROUP_INTEGRITY,
      },
    };
    if (!SetTokenInformation(
      hToken, TokenIntegrityLevel, &tml, sizeof tml))
    {
      OutputLastError(L"SetTokenInformation");
      return nullptr;
    }
  }
  return hToken.Detach();
}
 
ATL::CComPtr<IDispatch> CreateHoge()
{
  ATL::CHandle hToken(CreateProtectedToken());
  if (!ImpersonateLoggedOnUser(hToken))
  {
    throw ATL::CAtlException(OutputLastError(L"ImpersonateLoggedOnUser"));
  }
  BOOST_SCOPE_EXIT_ALL(&hToken)
  {
    if (!RevertToSelf())
    {
      abort();
    }
  };
  ATL::CComPtr<IDispatch> obj;
#if 0 // 1にしても動きます。
  ATL::CComPtr<IClassFactory> cf;
  ATLENSURE_SUCCEEDED(
    CoGetClassObject(CLSID_Hoge, CLSCTX_ALL, nullptr, IID_PPV_ARGS(&cf)));
  ATLENSURE_SUCCEEDED(cf->CreateInstance(nullptr, IID_PPV_ARGS(&obj)));
#else
  ATLENSURE_SUCCEEDED(obj.CoCreateInstance(CLSID_Hoge));
#endif
  return obj;
}
 
int main()
{
  std::locale loc(std::locale::classic(), "", std::locale::ctype);
  std::wclog.imbue(loc);
  std::wcout.imbue(loc);
  try
  {
    ATLENSURE_SUCCEEDED(CoInitializeEx(
      nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE));
    ATLENSURE_SUCCEEDED(CoInitializeSecurity(
      nullptr, -1, nullptr, nullptr,
      RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE,
      nullptr, EOAC_STATIC_CLOAKING, nullptr));
    auto obj = CreateHoge();
    ATL::CComVariant v;
    DISPID dispid = DISPID_VALUE;
    ATLENSURE_SUCCEEDED(obj.Invoke0(dispid, &v));
    ATLENSURE_SUCCEEDED(v.ChangeType(VT_BSTR));
    if (v.bstrVal != nullptr)
    {
      std::wcout << v.bstrVal << std::endl;
    }
 
    std::cin.get();
  }
  catch(const ATL::CAtlException& e)
  {
    std::wclog << std::hex << std::showbase << e.m_hr << std::endl;
    std::wclog <<
      boost::implicit_cast<PCWSTR>(ATL::AtlGetErrorDescription(e)) << std::endl;
  }
  CoUninitialize();
}

相変わらずエラー処理が微妙です。実際に使うときには真似しないでください。

このプログラムを実行すると「0 1008」と出力されます。1008はERROR_NO_TOKENで、OpenThreadTokenの場合スレッドが偽装中でないことを意味します。「プロセスのトークンは変わらず、スレッドトークンだけが偽装されている」という状態でないことを確認したかったので、このような処理を入れました。

また、Process Explorerで特権が削除され、ILがLowになっていることを確認しました。試したバージョンはWindows XPと8.1です。

次回、もう1つ似たような方法についての予定です。

余談です。今回のコードを試していて、疑問は増える一方です。プライマリトークンではなく偽装トークンだった場合、セッションIDが異なるトークンの場合、レジストリのAppIDのRunAsでユーザーが指定されている場合、ImpersonateAnonymousTokenで偽装した場合、CoGetClassObjectで渡せるCOAUTHINFOとの兼ね合いなど、いろいろ試してみたいです。


スポンサード リンク

この記事のカテゴリ

  • ⇒ 保護モード+αなプロセスをCoCreateInstanceで作る(その1)
  • ⇒ 保護モード+αなプロセスをCoCreateInstanceで作る(その1)
  • ⇒ 保護モード+αなプロセスをCoCreateInstanceで作る(その1)