前回(保護モード+αのプロセス上で動作する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との兼ね合いなど、いろいろ試してみたいです。
スポンサード リンク |