Created
November 2, 2017 14:50
-
-
Save sevaa/bf47ad2dbaac26f4cff78cc2e37fdef2 to your computer and use it in GitHub Desktop.
Native process isolation using COM and ROT
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <windows.h> | |
#include <process.h> | |
#include <comdef.h> | |
#include "..\\Worker\\Protocol.h" | |
#include <string> | |
using namespace std; | |
volatile static unsigned int s_CookieGen = 0; | |
static bool StartWorkerProcess(LPCWSTR Cookie) | |
{ | |
wchar_t EvName[150] = WORKER_STARTED_EVENT_NAME; | |
wcscat_s(EvName, Cookie); | |
HANDLE hStartedEvent = CreateEvent(0, 0, 0, EvName); | |
wchar_t Params[200] = L"Worker.exe /n:", s[20]; | |
wcscat_s(Params, Cookie); | |
wcscat_s(Params, L" /p:"); | |
_itow_s(GetCurrentProcessId(), s, 10); | |
wcscat_s(Params, s); | |
STARTUPINFO si; | |
ZeroMemory(&si, sizeof si); | |
si.cb = sizeof si; | |
PROCESS_INFORMATION pi; | |
if (CreateProcess(L"Worker.exe", Params, 0, 0, 0, CREATE_SUSPENDED, 0, 0, &si, &pi)) | |
{ | |
ResumeThread(pi.hThread); //To connect with a debugger, if necessary | |
CloseHandle(pi.hThread); | |
HANDLE h[2] = { hStartedEvent, pi.hProcess }; | |
DWORD dw = WaitForMultipleObjects(2, h, 0, INFINITE); //Either the process reports readiness, or it quits during startup | |
CloseHandle(pi.hProcess); //Do we ever need this? Probably not. | |
CloseHandle(hStartedEvent); | |
if (dw == WAIT_OBJECT_0) //Started and reported readiness | |
{ | |
return true; | |
} | |
else //Process dies shortly upon startup | |
{ | |
//Log the exit code maybe? | |
return false; | |
} | |
} | |
else | |
{ | |
//Log that the process can't be started | |
return false; | |
} | |
} | |
//Starts a process, retrieves an worker object from the ROT | |
static HRESULT ConnectToWorker(IDispatchPtr &pDisp) | |
{ | |
wchar_t Cookie[20]; | |
_itow_s(InterlockedIncrement(&s_CookieGen), Cookie, 10); | |
if (StartWorkerProcess(Cookie)) | |
{ | |
HRESULT hr; | |
IUnknownPtr pUnk; | |
IRunningObjectTablePtr rot; | |
GetRunningObjectTable(0, &rot); //Good news :) | |
IMonikerPtr mk; | |
wchar_t MkName[100] = WORKER_MONIKER; | |
wcscat_s(MkName, Cookie); | |
CreateItemMoniker(L"", MkName, &mk); //Good news :) | |
hr = rot->GetObjectW(mk, &pUnk); | |
//if (FAILED(hr)) Log the ROT error | |
pDisp = pUnk; | |
return hr; | |
} | |
else | |
return E_FAIL; | |
} | |
unsigned __stdcall ThreadProc(void *a) | |
{ | |
Sleep(rand() % 300); //Random delay | |
CoInitializeEx(0, COINIT_MULTITHREADED); | |
IDispatchPtr pDisp; | |
if(SUCCEEDED(ConnectToWorker(pDisp))) | |
{ | |
DWORD TID = GetCurrentThreadId(); | |
_variant_t Args[] = | |
{ | |
L"Hello from caller thread", | |
TID | |
}; | |
_variant_t Result; | |
DISPPARAMS DispParams = { (VARIANTARG*)(VARIANT*)&Args[0], 0, _countof(Args), 0 }; | |
HRESULT hr = pDisp->Invoke(DISPID_HELLO, IID_NULL, 0, DISPATCH_METHOD, &DispParams, &Result, 0, 0); | |
if (hr == HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE) || //Server crashed in between calls | |
hr == HRESULT_FROM_WIN32(RPC_E_SERVERFAULT) || //Server crashed within the call | |
hr == HRESULT_FROM_WIN32(RPC_S_CALL_FAILED) || //Not sure when, but it happens | |
hr == RPC_E_DISCONNECTED) //Sometimes this one is thrown, not sure when and why | |
{ | |
wprintf(L"Worker crashed in thread %u, hr=0x%x\n", TID, hr); | |
} | |
else if (FAILED(hr)) | |
{ | |
wprintf(L"Worker call failed in thread %u, hr=0x%x\n", TID, hr); | |
} | |
else | |
{ | |
wprintf(L"Worker says: %s\n", (LPCWSTR)_bstr_t(Result)); | |
} | |
} | |
else | |
wprintf(L"Error connecting to the worker\n"); | |
CoUninitialize(); | |
return 0; | |
} | |
int wmain() | |
{ | |
CoInitialize(0); | |
int i; | |
for (i = 0; i < 20; i++) | |
{ | |
HANDLE h = (HANDLE)_beginthreadex(0, 0, ThreadProc, (void*)0, 0, 0); | |
if (h) | |
CloseHandle(h); | |
} | |
Sleep(10000); | |
CoUninitialize(); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#define WORKER_STARTED_EVENT_NAME L"MyWorkerStarted" | |
#define WORKER_MONIKER L"MyProject/Worker/" | |
#define DISPID_HELLO 1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <windows.h> | |
#include <stdio.h> | |
#include "Protocol.h" | |
class WorkerMain : public IDispatch | |
{ | |
bool m_InROT; | |
DWORD m_dwROTRegister; | |
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, void **ppvObject); | |
ULONG STDMETHODCALLTYPE AddRef(void) { return 1; } | |
ULONG STDMETHODCALLTYPE Release(void) { return 1; } | |
HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT *pctinfo) { return E_NOTIMPL; } | |
HRESULT STDMETHODCALLTYPE GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) { return E_NOTIMPL; } | |
HRESULT STDMETHODCALLTYPE GetIDsOfNames(REFIID, LPOLESTR *, UINT, LCID, DISPID *) { return E_NOTIMPL; } | |
HRESULT STDMETHODCALLTYPE Invoke( | |
_In_ DISPID dispIdMember, | |
_In_ REFIID riid, | |
_In_ LCID lcid, | |
_In_ WORD wFlags, | |
_In_ DISPPARAMS *pDispParams, | |
_Out_opt_ VARIANT *pVarResult, | |
_Out_opt_ EXCEPINFO *pExcepInfo, | |
_Out_opt_ UINT *puArgErr); | |
public: | |
WorkerMain() | |
:m_InROT(false) | |
{} | |
HRESULT Register(int Cookie) | |
{ | |
HRESULT hr; | |
IRunningObjectTable *rot; | |
if (FAILED(hr = GetRunningObjectTable(0, &rot))) | |
return hr; | |
IMoniker *mk; | |
wchar_t MkName[100] = WORKER_MONIKER, s[20]; | |
_itow_s(Cookie, s, 10); | |
wcscat_s(MkName, s); | |
if (FAILED(hr = CreateItemMoniker(L"", MkName, &mk))) | |
{ | |
rot->Release(); | |
return hr; | |
} | |
hr = rot->Register(0, (IDispatch*)&g_Main, mk, &m_dwROTRegister); | |
m_InROT = SUCCEEDED(hr); | |
mk->Release(); | |
rot->Release(); | |
return hr; | |
} | |
void Unregister() | |
{ | |
if (m_InROT) | |
{ | |
IRunningObjectTable *rot; | |
if (SUCCEEDED(GetRunningObjectTable(0, &rot))) | |
{ | |
rot->Revoke(m_dwROTRegister); | |
rot->Release(); | |
m_InROT = false; | |
} | |
} | |
} | |
} g_Main; | |
/* | |
Accepts options: | |
/n:Cookie | |
/p:ParentPID | |
*/ | |
static void ParseCmdLine(LPCWSTR CmdLine, int &Cookie, DWORD &PID) | |
{ | |
while (*CmdLine) | |
{ | |
if (wcsncmp(CmdLine, L"/n:", 3) == 0 && CmdLine[3] >= L'0' && CmdLine[3] <= L'9') | |
Cookie = _wtoi(CmdLine + 3); | |
else if(wcsncmp(CmdLine, L"/p:", 3) == 0 && CmdLine[3] >= L'0' && CmdLine[3] <= L'9') | |
PID = _wtoi(CmdLine + 3); | |
CmdLine += wcscspn(CmdLine, L" "); //Skip to the next space | |
CmdLine += wcsspn(CmdLine, L" "); //Skip past the spaces | |
} | |
} | |
static bool ReportReadiness(int Cookie) | |
{ | |
wchar_t EvName[100] = WORKER_STARTED_EVENT_NAME, s[20]; | |
_itow_s(Cookie, s, 10); | |
wcscat_s(EvName, s); | |
HANDLE hStartedEvent = OpenEvent(EVENT_MODIFY_STATE, 0, EvName); | |
if (!hStartedEvent) | |
return false; | |
SetEvent(hStartedEvent); | |
CloseHandle(hStartedEvent); | |
return true; | |
} | |
int CALLBACK wWinMain(HINSTANCE, HINSTANCE, LPWSTR CmdLine, int) | |
{ | |
if (!*CmdLine) | |
return 1; | |
int Cookie = 0; | |
DWORD CallerPID = 0; | |
ParseCmdLine(CmdLine, Cookie, CallerPID); | |
if (!Cookie || !CallerPID) | |
return 2; | |
if (FAILED(CoInitializeEx(0, COINIT_APARTMENTTHREADED))) | |
return 3; | |
{ | |
//Initialization goes here... | |
srand(GetTickCount()); | |
//Register self in ROT, report readiness to the caller | |
if (FAILED(g_Main.Register(Cookie))) | |
return 4; | |
HANDLE hCaller = OpenProcess(SYNCHRONIZE, 0, CallerPID); | |
if (!hCaller) | |
return 5; | |
if (!ReportReadiness(Cookie)) | |
return 6; | |
//Main loop: until the caller process quits | |
MSG msg; | |
while (MsgWaitForMultipleObjects(1, &hCaller, 0, INFINITE, QS_ALLEVENTS) == WAIT_OBJECT_0 + 1) | |
{ //Caller process being signaled is a quit condition | |
GetMessage(&msg, 0, 0, 0); | |
DispatchMessage(&msg); | |
} | |
CloseHandle(hCaller); | |
//Shutdown goes here... | |
} | |
CoUninitialize(); | |
return 0; | |
} | |
HRESULT STDMETHODCALLTYPE WorkerMain::QueryInterface(REFIID riid, void **ppv) | |
{ | |
if (InlineIsEqualGUID(riid, IID_IUnknown)) | |
*ppv = (IDispatch*)this; | |
else if (InlineIsEqualGUID(riid, IID_IDispatch)) | |
*ppv = (IDispatch*)this; | |
else | |
return E_NOINTERFACE; | |
return S_OK; | |
} | |
HRESULT STDMETHODCALLTYPE WorkerMain::Invoke( | |
DISPID dispid, REFIID, LCID, WORD, | |
DISPPARAMS *dp, VARIANT *Result, | |
EXCEPINFO*, UINT *) | |
{ | |
VARIANTARG *a = dp->rgvarg; | |
if (dispid == DISPID_HELLO) //One and only method - takes a string and a number | |
{ | |
if (dp->cArgs != 2) | |
return DISP_E_BADPARAMCOUNT; | |
LPCWSTR StringArg = V_BSTR(a + 0); | |
int IntArg = V_I4(a + 1); | |
//Random crashing! | |
if (rand() % 4 == 0) | |
*(int*)0 = 0; | |
//Some dummy processing | |
wchar_t s[200]; | |
swprintf_s(s, L"%s %d (in process %u)", StringArg, IntArg, GetCurrentProcessId()); | |
V_VT(Result) = VT_BSTR; | |
V_BSTR(Result) = SysAllocString(s); | |
return S_OK; | |
} | |
else | |
return DISP_E_MEMBERNOTFOUND; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is a companion Gist for this blog post: http://rathertech.blogspot.com/2017/11/abusing-com-for-tightly-coupled-process.html
It's a sample for a highly unusual use case of COM.
There is a gist with the same logic in C#.