1

I'm creating a console process and then trying to monitor it for events with SetWinEventHook. If I use the PID associated with the process I've created, I never catch any events. If I set pid/thread to 0/0 (all processes/threads) then I get plenty of results. It seems like something is wrong in the way I'm trying to hook to a specific process. Any ideas would be appreciated.

#include "stdafx.h"
#include <windows.h>
#include <Oleacc.h>


DWORD CreateChildProcess();
void InitializeMSAA(DWORD pid);
void ShutdownMSAA();
void SetConsoleBufferSize(DWORD processId, short columns, short rows);
void CALLBACK HandleWinEvent(HWINEVENTHOOK hook, DWORD event, HWND hwnd,
    LONG idObject, LONG idChild,
    DWORD dwEventThread, DWORD dwmsEventTime);

HWINEVENTHOOK g_hook;

int main()
{
    DWORD pid = CreateChildProcess();
    Sleep(5000);
    InitializeMSAA(pid);
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

// Initializes COM and sets up the event hook.
void InitializeMSAA(DWORD pid)
{
    // Initializes Component Object Model library
    CoInitialize(NULL);

    g_hook = SetWinEventHook(
        EVENT_MIN, EVENT_MAX,       // Range of events (Console).
        NULL,                                          // Handle to DLL.
        HandleWinEvent,                                // The callback.
        pid, 0,              // Process and thread IDs of interest (0 = all)
        WINEVENT_OUTOFCONTEXT); // Flags.

    //| WINEVENT_SKIPOWNPROCESS
}

// Unhooks the event and shuts down COM.
void ShutdownMSAA()
{
    UnhookWinEvent(g_hook);
    CoUninitialize();
}

// Callback function that handles events.
void CALLBACK HandleWinEvent(HWINEVENTHOOK hook, DWORD event, HWND hwnd,
    LONG idObject, LONG idChild,
    DWORD dwEventThread, DWORD dwmsEventTime)
{
    IAccessible * pAcc = NULL;
    VARIANT varChild;
    HRESULT hr = AccessibleObjectFromEvent(hwnd, idObject, idChild, &pAcc, &varChild);
    if ((hr == S_OK) && (pAcc != NULL))
    {
        BSTR bstrName;
        pAcc->get_accName(varChild, &bstrName);
        if (event == EVENT_SYSTEM_MENUSTART)
        {
            printf("Begin: ");
        }
        else if (event == EVENT_SYSTEM_MENUEND)
        {
            printf("End:   ");
        }
        printf("%S\n", bstrName);
        SysFreeString(bstrName);
        pAcc->Release();
    }
}

// Creates a bash child process with i/o to a given console screen buffer
DWORD CreateChildProcess()
{
    // In order to launch bash in System32, program must be built as x64
    LPCTSTR applicationAddress = L"C:\\Windows\\System32\\bash.exe";

    PROCESS_INFORMATION piProcInfo;
    STARTUPINFO siStartInfo;

    // Set up members of the PROCESS_INFORMATION structure.
    SecureZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));

    // Set up members of the STARTUPINFO structure.
    // This structure specifies the STDIN and STDOUT handles for redirection.
    SecureZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
    siStartInfo.cb = sizeof(STARTUPINFO);

    // NB: Initial window size settings don't work for some reason.
    auto minX = GetSystemMetrics(SM_CXMIN);
    auto minY = GetSystemMetrics(SM_CYMIN);
    siStartInfo.dwXSize = 200;
    siStartInfo.dwYSize = 200;
    //siStartInfo.dwXCountChars = 119;
    //siStartInfo.dwYCountChars = 9;

    //siStartInfo.wShowWindow = SW_HIDE;
    siStartInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESIZE;
    // | STARTF_USESHOWWINDOW | STARTF_USECOUNTCHARS 

    // Create the child process. 
    BOOL success = CreateProcess(
        applicationAddress, // absolute path to the application
        TEXT("-i"),         // command line 
        NULL,               // process security attributes 
        NULL,               // primary thread security attributes 
        TRUE,               // handles are inherited 
        CREATE_NEW_CONSOLE,               // creation flags 
        NULL,               // use parent's environment 
        NULL,               // use parent's current directory 
        &siStartInfo,       // STARTUPINFO pointer 
        &piProcInfo);       // receives PROCESS_INFORMATION 


    if (!success)
    {
        int lastError = GetLastError();
    }

    return piProcInfo.dwProcessId;
}
Catma
  • 39
  • 1
  • 9
  • Tried with notepad.exe..... it is all working good. – Pavan Chandaka Oct 27 '17 at 18:03
  • So it does. Looks like it's a problem with console apps, as bash.exe and cmd.exe both do not trigger any hooks! – Catma Oct 27 '17 at 18:14
  • possible........ – Pavan Chandaka Oct 27 '17 at 18:15
  • Note that the documentation has some caveats for console applications, "In some situations, even if you request WINEVENT_INCONTEXT events, the events will still be delivered out-of-context. These scenarios include events **from console windows** and events from processes that have a different bit-depth (64 bit versus 32 bits) than the caller." But that doesn't seem to apply here because you are expecting out-of-context. Still, it feels like a clue. – Adrian McCarthy Oct 27 '17 at 18:19
  • You are clearly calling the Unicode version of `CreateProcess()`, but you are passing a string literal to the `lpCommandLine` parameter, which is a big no-no ([read the documentation!](https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425.aspx)). You are also leaking the process and thread handles that `CreateProcess()` returns. – Remy Lebeau Oct 28 '17 at 01:41
  • @RemyLebeau thanks for the observations! I am not using CreateProcessW yet but probably will in the future, and definitely would have gotten stuck on that bug for a little while. I was aware of a possible issue with the handles but had postponed looking into it until I understood more about the function and about handles in general. – Catma Oct 30 '17 at 13:39
  • @Catma "*I am not using CreateProcessW yet*" - actually, you are. You are assigning a **wide** literal to `applicationAddress` (instead of using the `TEXT()` macro), which is declared as `LPCTSTR`. That would not compile unless you were compiling with Unicode enabled, which means `LPCTSTR` maps to `LPCWSTR` and `CreateProcess()` maps to `CreateProcessW()`. – Remy Lebeau Oct 30 '17 at 14:43

1 Answers1

2

If I use the PID associated with the process I've created, I never catch any events.

The reason for that propably is that a console process does not own the console window. The console window is associated with conhost.exe (csrss.exe in versions of Windows prior to Win 7). So the messages related to the console window will be processed by conhost.exe, not the process you created.

If I set pid/thread to 0/0 (all processes/threads) then I get plenty of results.

Try to set the PID to that of the conhost.exe process associated with the console application you launched. You should now receive the events only from the console window.

I don't think there is a direct API to find the associated conhost.exe process but you could try to enumerate all child processes of the new process until you have found "conhost.exe". You propably have to do this in a loop as conhost.exe won't be there immediately after CreateProcess() returned.

zett42
  • 25,437
  • 3
  • 35
  • 72
  • If you are trying to hook a process related to a specific window, you can use `FindWindow()` or `EnumWindows()` to find the window, or use `SetWindowsHookEx()`/`SetWinEventHook()` to monitor the system globally for new window creations, and then use `GetWindowThreadProcessId()` to get the process ID that owns the window. – Remy Lebeau Oct 28 '17 at 01:44
  • @RemyLebeau Unfortunately `GetWindowThreadProcessId` on the window handle of a console window returns the PID of the console process, which does not own the console window. – zett42 Oct 28 '17 at 11:24