Skip to content

Instantly share code, notes, and snippets.

@sevaa
Created November 2, 2017 14:50
Show Gist options
  • Save sevaa/bf47ad2dbaac26f4cff78cc2e37fdef2 to your computer and use it in GitHub Desktop.
Save sevaa/bf47ad2dbaac26f4cff78cc2e37fdef2 to your computer and use it in GitHub Desktop.
Native process isolation using COM and ROT
#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;
}
#pragma once
#define WORKER_STARTED_EVENT_NAME L"MyWorkerStarted"
#define WORKER_MONIKER L"MyProject/Worker/"
#define DISPID_HELLO 1
#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;
}
@sevaa
Copy link
Author

sevaa commented Nov 2, 2017

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#.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment