We call CreateProcessAsUser()
and, after having checked the result, we start tracking (WMI) the process that might create other processes.
In 1 case, the first process is so fast that it creates another process and terminates before we can start tracking it.
I even tried to not check the result and start immediately to track after the call to CreateProcessAsUser()
, but it's not fast enough.
My idea is to start the process from a launcher.exe
so we can track all the generated processes.
Is there any other alternative solution? We have the PID of the terminated process.
if we start child process and want way when it and all the children processes terminate we can use job object. general steps
create new job object via CreateJobObjectW
set JobObjectAssociateCompletionPortInformation
with
SetInformationJobObject
create new process in suspended state (use CREATE_SUSPENDED
flag)
add process to job via AssignProcessToJobObject
resume new process with ResumeThread
now system will be send notifications to our completion port when new process will be started or exit. when no more process in job - will be JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO
message post to port - Indicates that the active process count has been decremented to 0
of course we need create I/O completion port too by CreateIoCompletionPort
and one or more (if only for this task - single thread more than enough) threads which will be call GetQueuedCompletionStatus
on port until end signal.
we can for example use lpCompletionKey as pointer to object with virtual functions, and every object know how handle action event. demo code:
struct __declspec(novtable) PortTask
{
virtual bool OnIoCompletion(OVERLAPPED* lpOverlapped, ULONG NumberOfBytesTransferred) = 0;
};
struct EndTask : public PortTask
{
virtual bool OnIoCompletion(OVERLAPPED* /*lpOverlapped*/, ULONG /*NumberOfBytesTransferred*/)
{
DbgPrint("%s<%p>\n", __FUNCTION__, this);
delete this;
return false;
}
};
struct IOPort
{
HANDLE CompletionPort;
LONG dwRefCount;
IOPort() : dwRefCount(1), CompletionPort(0) {
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}
~IOPort(){
if (CompletionPort) CloseHandle(CompletionPort);
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}
void AddRef(){
InterlockedIncrementNoFence(&dwRefCount);
}
void Release(){
if (!InterlockedDecrement(&dwRefCount)) {
delete this;
}
}
static ULONG WINAPI PortThread(PVOID This)
{
union {
ULONG_PTR CompletionKey;
PortTask* pTask;
};
ULONG NumberOfBytesTransferred;
OVERLAPPED* lpOverlapped;
HANDLE CompletionPort = reinterpret_cast<IOPort*>(This)->CompletionPort;
while (GetQueuedCompletionStatus(CompletionPort, &NumberOfBytesTransferred, &CompletionKey, &lpOverlapped, INFINITE) &&
pTask->OnIoCompletion(lpOverlapped, NumberOfBytesTransferred)) continue;
reinterpret_cast<IOPort*>(This)->Release();
return 0;
}
ULONG Create()
{
if (CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 1))
{
AddRef();
if (HANDLE hThread = CreateThread(0, 0, PortThread, this, 0, 0))
{
CloseHandle(hThread);
return NOERROR;
}
Release();
}
return GetLastError();
}
ULONG Stop()
{
if (EndTask* pTask = new EndTask)
{
if (!PostQueuedCompletionStatus(CompletionPort, 0, (ULONG_PTR)pTask, 0))
{
ULONG dwError = GetLastError();
delete pTask;
return dwError;
}
return NOERROR;
}
return ERROR_NO_SYSTEM_RESOURCES;
}
};
struct ActiveProcessZeroTask : public PortTask
{
//HWND hwnd; // in real code you send some message to hwnd instead thread
HANDLE _hJob;
ULONG _dwThreadId;
ActiveProcessZeroTask() : _hJob(0), _dwThreadId(GetCurrentThreadId()) { }
~ActiveProcessZeroTask() {
CloseHandle(_hJob);
PostThreadMessageW(_dwThreadId, WM_QUIT, 0, 0);
}
virtual bool OnIoCompletion(OVERLAPPED* dwProcessId, ULONG MessageId)
{
DbgPrint("%s<%p>(%x %p)\n", __FUNCTION__, this, MessageId, dwProcessId);
switch (MessageId)
{
case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO:
DbgPrint("%p - ACTIVE_PROCESS_ZERO\n", dwProcessId);
delete this;
break;
case JOB_OBJECT_MSG_NEW_PROCESS:
DbgPrint("%p - NEW_PROCESS\n", dwProcessId);
break;
case JOB_OBJECT_MSG_EXIT_PROCESS:
DbgPrint("%p - EXIT_PROCESS\n", dwProcessId);
break;
case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS:
DbgPrint("%p - ABNORMAL_EXIT_PROCESS\n", dwProcessId);
break;
}
return true;
}
ULONG Create(HANDLE CompletionPort, PCWSTR ApplicationName)
{
if (HANDLE hJob = CreateJobObjectW(0, 0))
{
_hJob = hJob;
JOBOBJECT_ASSOCIATE_COMPLETION_PORT jacp = { this, CompletionPort };
if (SetInformationJobObject(hJob, JobObjectAssociateCompletionPortInformation, &jacp, sizeof(jacp)))
{
STARTUPINFO si = { sizeof(si)};
PROCESS_INFORMATION pi;
if (CreateProcessW(ApplicationName, 0, 0, 0, 0, CREATE_SUSPENDED, 0, 0, &si, &pi))
{
ULONG dwError = NOERROR;
if (!AssignProcessToJobObject(hJob, pi.hProcess) ||
!ResumeThread(pi.hThread))
{
dwError = GetLastError();
TerminateProcess(pi.hProcess, 0);
}
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return dwError;
}
}
}
return GetLastError();
}
};
void demo()
{
if (IOPort* port = new IOPort)
{
if (port->Create() == NOERROR)
{
MessageBoxW(0, 0, L"just for demo #1", MB_ICONINFORMATION);
// exec cmd for demo
WCHAR ApplicationName[MAX_PATH];
if (GetEnvironmentVariableW(L"ComSpec", ApplicationName, RTL_NUMBER_OF(ApplicationName)))
{
if (ActiveProcessZeroTask* pTask = new ActiveProcessZeroTask)
{
if (pTask->Create(port->CompletionPort, ApplicationName) != NOERROR)
{
delete pTask;
}
}
}
// wait all childs exit
MessageBoxW(0, 0, L"Wait for MSG_ACTIVE_PROCESS_ZERO", MB_ICONINFORMATION);
// stop track thread
if (port->Stop() != NOERROR) __debugbreak();
}
port->Release();
}
{
MSG msg;
// remove Wm_QUIT
while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) continue;
MessageBoxW(0, 0, L"just for demo #2", MB_ICONINFORMATION);
}
}