0

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.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
marco
  • 1,686
  • 1
  • 25
  • 33
  • 2
    What are you trying to accomplish with all this? – Lightness Races in Orbit Jan 15 '20 at 13:04
  • Shortly: when the main process starts, we have to disable a button. When all the children processes terminate, we can re-enable the button. – marco Jan 15 '20 at 13:08
  • for what you need track process ? and anyway wmi always very bad solution. faster can use job object for track – RbMm Jan 15 '20 at 13:17
  • @RbMm I didn't even know about the existence of JobObjects. Thanks for the hint, – marco Jan 15 '20 at 13:40
  • really very easy write code for this, use `SetInformationJobObject(hJob, JobObjectAssociateCompletionPortInformation..` and what for `JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO` – RbMm Jan 15 '20 at 13:41
  • @RbMm Sounds better than my 2nd idea: track our self so we can track all the children :) – marco Jan 15 '20 at 13:56
  • @marco, yes, i paste you code some late today – RbMm Jan 15 '20 at 14:01
  • What about `CreateProcessAsUser()` with creation flags `CREATE_SUSPENDED` and, after started tracking, `ResumeThread()` ? – marco Jan 15 '20 at 14:09
  • @marco - yes, of course you need use `CREATE_SUSPENDED` and `ResumeThread` after `AssignProcessToJobObject` call, as part of solution – RbMm Jan 15 '20 at 14:16
  • `CreateProcessAsUser()` with `CREATE_SUSPENDED` and `ResumeThread()` was enough to make it work. const auto processInfo = impersonator->startProcessAsUser(startInformation, static_cast(callingProcessId)); trackProcessChain(processInfo.dwProcessId, startInformation.executablePath); ::ResumeThread(processInfo.hThread); ::CloseHandle(processInfo.hThread); ::CloseHandle(processInfo.hProcess); – marco Jan 17 '20 at 10:47

2 Answers2

1

if we start child process and want way when it and all the children processes terminate we can use job object. general steps

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);
    }
}
Community
  • 1
  • 1
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • Thanks @RbMm, I will work on it and, in case, accept the answer. – marco Jan 17 '20 at 10:10
  • It should be noted that this will cause `JOB_OBJECT_MSG_NEW_PROCESS` and `JOB_OBJECT_MSG_EXIT_PROCESS` to trigger for all processes launched inside initial one. – iljau Nov 01 '21 at 01:43
0

You need to implement an explicit synchronization mechanism (semaphore). Bellow an algorithm :

In the parent process :

semaphore my_semaphore = CreateSemaphore ("my_semphaore");  
args my_arguments ("my_semphaore");  
CreateProcessAsUser (my_arguments); 
Create_Asynchronous_Thread { releasesemaphore (my_semaphore );} // unblock shild process}
waitforSingleObject (shild_process_PID)

In the shild process :

// do something ...
semaphore my_semaphore = CreateSemaphore ("my_semphaore");  
// do something (parent is blocked)
waitforsingleobject (my_semaphore);
Landstalker
  • 1,368
  • 8
  • 9
  • for what all this ? – RbMm Jan 15 '20 at 13:33
  • @RbMm It is his need. I don't know what he wants to do with all this – Landstalker Jan 15 '20 at 13:36
  • i not view from question that this is need – RbMm Jan 15 '20 at 13:38
  • I cannot modify the child process. It's a 3rd party program. – marco Jan 15 '20 at 13:39
  • @marco there is a better solution. you can Create a Child Process with Redirected Input and Output. In this case, you will not depends from "child process speed". See: https://learn.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output – Landstalker Jan 15 '20 at 14:20
  • all this too bad and not solution i think – RbMm Jan 15 '20 at 14:58
  • @RbMm what is your arguments ? or you say this just like that ? – Landstalker Jan 15 '20 at 15:20
  • how this help track all the children processes ? and *In the child process* - we cannot modify the child process - so this is not solution already – RbMm Jan 15 '20 at 15:21
  • @rbmm I say **algorithm**. It not a code or pseudo code. You must first read the answer before answering. – Landstalker Jan 15 '20 at 15:31
  • i read - and your algorithm is **wrong**. what is *CreateSemaphore* and *releasesemaphore* - are you know how windows semaphores work ? what is intial value and limit ? why you decide that this must work ? are you know when semaphore in signal state ? look like no. this algorithm not working at all – RbMm Jan 15 '20 at 15:37
  • @RbMm Read this https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsemaphorea you will understend how semaphore works. you will find how CreateSemaphore, ReleaseSemaphore, ... works.To response to your questions : initiale value=0 (to block Parent immediatly). and in my example i suppose that we have two process so your question about "limit" has no sens. And finaly, i don't decide that my solution work. I juste gave some ideas – Landstalker Jan 15 '20 at 15:50
  • after **first** call to *ReleaseSemaphore* - it go to the signal state. so you will be wait here not for **all** children but for **any** children. because only this - not solution at all – RbMm Jan 15 '20 at 15:52
  • ask self - when will be Semaphore in signaled state - (when waitforsingleobject return). and may be you understand after this.. – RbMm Jan 15 '20 at 15:55
  • @RbMm You begin to understand that this algorithm is applicable for a context with two processes and that is unfortunately not the case apparently for this environment. Your remarks regarding the syntax of the "CreateSemaphore" and "ReleaseSemaphore" functions do not help matters. – Landstalker Jan 15 '20 at 16:00
  • no, i all understand from begin. this is you not understand. this is wrong algorithm. if we have 2 child process - are *waitforsingleobject* will be wait for both ? :) after which count of calls *releasesemaphore* - *waitforsingleobject* return ? :) – RbMm Jan 15 '20 at 16:04
  • *this algorithm is applicable for a context with two processes* - for this context - it *senseless* at all, because in this case we need direct wait on process object and not need any semaphore. algorithm is full wrong. not work/have no sense, usage of semaphore objects is wrong here by sense – RbMm Jan 15 '20 at 16:07
  • @RbMm the waitforsingleobject must be put on the side of the process that one wishes to "make wait" therefore, on the side of the child process if you like. After the semaphore synchronization, the waitforsingleobject will come to synchronize with the process. I can even make it more complicated, an asynchronous thread is launched to release the semaphore so that in the waitforSingleObject (shild_process_PID) to be sure that the wait for the child process is already started. – Landstalker Jan 15 '20 at 16:23
  • @RbMm I just told you that I reasoned on a context with two processes. If you insist on a different context (multiple porcess, without possibility to modifie child process code) it is clear that it is not suitable. – Landstalker Jan 15 '20 at 16:27
  • all your "code" is wrong by design. not want or can not understand this - your task. my last comment here - semaphore not design for wait until we counter become zero. it design for opposite - wait when counter became not zero. so you try use absolute wrong object from begin. and in case 2 process - how i say - this senseless at all. simply wait for process object – RbMm Jan 15 '20 at 16:29
  • @RbMm Try to create semaphore with "0" as intiale value _CreateSemaphore(NULL, **0**, 10, NULL)_ and try to call **WaitForSingleObject** on this semaphore, you will discover another use of semaphores. – Landstalker Jan 15 '20 at 16:50
  • sorry, no any sense continue – RbMm Jan 15 '20 at 16:51
  • Thanks for the time spent @Landstalker but your code would not work because `CreateProcessAsUser` will start the new process in another thread. – marco Jan 17 '20 at 10:14
  • @marco No problems. You find finaly the solution ? – Landstalker Jan 20 '20 at 12:50
  • @Landstalker: yea and forgot to accept @RbMm answer. But I used something simpler: Just added the flag `CREATE_SUSPENDED` to `CreateProcessAsUser()` and, after tracking, `ResumeThread()`. But the answer is a better solution because makes use of more modern APIs. – marco Jan 21 '20 at 13:12