Just Let It Flow

July 18, 2011

Hookers – Underneath the Sheets

Filed under: Code,Windows — adeyblue @ 7:12 am


We all need ideas. Whether you’ve just finished something, or are getting a little bit bored with your current project you can’t help but let your mind drift to the next cool thing you’ll create. Sometimes the ideas come thick and fast, other times they’re like gold dust. When I’m in the second camp, and reading the various boards I read, I will quite happily steal other peoples.

One such board is Sysinternals’. They do winternals, I do winternals, they have a suggestion section and I want ideas. It’s a perfect fit. On a previous visit, one of the suggestions was a program that could list active hooks. Given my previous excursions into user/win32k territory, it didn’t seem like it’d be too hard. And apart from the digging around assembly listings for the structure offsets, it wasn’t, and that was more time-intensive than difficult. At any rate, I am now the owner of 14 versions of win32k.sys’ symbols. I don’t even have 14 games on my computer!

Rather than just dumping a download link (here) and what it does (list active hooks), I thought I’d deconstruct the hows and why’s of the kernel side of the query. Needless to say, much of what follows is discussion of undocumented things. I am aware this makes Raymond Chen cry. Sorry fella.

Finding hooks

Compared to other operations, enumerating the installed hooks is quite easy.

Thread specific hooks are recorded in a win32k per-thread data structure tagged, rather imaginatively, as THREADINFO[1]. This is essentially an ETHREAD/TEB like structure but one tailored specifically for user and gdi information. One of its members (aphkStart) is a 16 element array of pointers, individually they either point to NULL, or the head of a linked list of HOOK structures. Enumerating the hooks is simply a measure of walking down those chains.

For convenience, and probably so that iteration isn’t required to see if any hooks are set, the THREADINFO contains another member, fsHooks, which is a bitfield. If a bit is on, the corresponding index in the hook array is valid. Instead of 33 comparisons (16 for NULL and 17 for a for-loop), telling if there are hooks requires just one, nifty!

Global hooks, which are per desktop[2], are also stored in a per-object structure, also imaginatively named (DESKTOPINFO), and are also stored in an array with an accompanying bitfield. Bridging the two is pDeskInfo, a member of THREADINFO which points to its owning DESKTOPINFO.

Despite the bellyaching in the intro, working with all these undocumented structures isn’t actually too hard in practice. The Windows 7 symbols for win32k.sys include their layouts, which is nice. The symbols for the Vista/Server 2008 era don’t though, this is where the assembly studying comes and saves the day.

Knowing what these structures look like is one thing, getting at them is another…

Finding Desktops

You may know that at the top of Windows UI architecture is the humble windowstation[3]. Not much is known about these creatures (read as: nobody needs to care) except that they are a glorified container of desktops. It is these delilahs of display that contain the actual user objects such as windows, hooks, menus etc. As mentioned, threads are bound to one desktop and which one can be determined by following a pointer. Desktop objects are stored in a singly linked list, so by following another pointer in the desktop structure rpDeskNext, you can enumerate desktops under a windowstation.

Like threads, each desktop has a pointer to its owning windowstation which is also part of a singularly linked list. Everything’s fine and dandy, except that there are no guarantees the results of thread->desktop or desktop->windowstation find the heads of their respective lists. After all, there can only be one head before everybody has to fall into line.

One method, and the one I used in the Desktop Heap Monitor update is to query the \Windows\Windowstations and \Sessions\\Windows\Windowstations object directories via NtOpenDirectoryObject and NtQueryDirectoryObject. The reward for this is a directory handle and an object name which can be redeemed against ObOpenObjectByName for an object handle, which can be finally be exchanged for a pointer with ObReferenceObjectByHandle. It’s relatively lots of work, but mostly documented.

The alternative method which I only stumbled upon recently is much easier and much more hacky. One of win32k.sys’s globals is named grpWinStaList, which just happens to be the head of the windowstation list. As windowstation objects (named as tagWINDOWSTATION) contain a pointer to the head of the desktop list, iterating through them all is a piece of cake.

Finding Threads

Finding threads takes the finesse approach of the desktops and throws it down the kitchen sink. Mixed metaphors aside, there’s nothing more to it than using ZwQuerySystemInformation(SystemProcessInformation) to enumerate running processes. What MSDN conceals about the SYSTEM_PROCESS_INFORMATION structure is that at the end of it is an array of SYSTEM_THREAD_INFORMATION structures, one for each thread in that process.

The thread id contained in the struct is turned into an ETHREAD object by the above board PsLookupThreadByThreadId. Failure means the thread has probably exited, which is no biggie. Interrogation of the thread continues with PsGetThreadWin32Thread, a good cop who’ll smuggle out any THREADINFO pointer in the threads possession. Failure here isn’t a double cross, not all threads interact with the presentation side of Windows and those that don’t don’t have a value to return.

    const SYSTEM_THREAD_INFORMATION* pThreadInfo = process.Threads, *pEnd = pThreadInfo + process.ThreadCount;
    while(pThreadInfo != pEnd)
        PETHREAD pThread = NULL;
        if(NT_SUCCESS(PsLookupThreadByThreadId(pThreadInfo->ClientId.UniqueThread, &pThread)))
                PVOID pWin32Thread = PsGetThreadWin32Thread(pThread);
                    stat = getWin32ThreadHooks(pWin32Thread, listHead, pLookaside);
            // ensure reference is released no matter what happens

With the THREADINFO and offsets in hand, the hook information is a dereference away…

Hook Information

Having gotten our grubby mitts on them, we find HOOK structures record most of the relevant information themselves:

struct tagHOOK
    THRDESKHEAD head; // info about the creator
    struct tagHOOK* phkNext; // next entry in linked list
    int iHook; // WH_ hook type
    UINT_PTR offPfn; // RVA to hook function in ihmod library
    UINT flags; // HF_ flags (GLOBAL, ANSI)
    int ihmod;
    THREADINFO* ptiHooked; // the hooked thread
    PVOID rpDesk; // saved desktop pointer
    ULONG nTimeout :7;
    ULONG fLastHookHung :1;

Directly stored is the originating thread (in the head member), the hooked thread, and (via an offset) the hook function. Noticeable by its absence, for those hooks which specify one, is the lack of any instant dll identification.

Dll Name

By its’ name, ihmod hints at something to do with HMODULEs, and indeed it is. However, it’s nothing as simple as an index into an array of names or HMODULEs, nor can it be used by any function exported by the kernel or win32k. Undocumented is as undocumented does, ihmod is actually an index into another global of win32k.sys named aatomSysLoaded, an array of ATOMs. As mentioned, there are no functions exported that manipulate or query this array, no, more “dirty” methods are required. The dirtyness doesn’t stop once you’ve indexed the array (either by dereferencing a hardcoded offset or querying from the symbols).

So what purpose does this ATOM have? Much like you can use GlobalAddAtom or AddAtom in user mode to essentailly map a string to an ATOM, win32k does too in kernel mode with the filename of the HMODULE passed to SetWindowsHookEx. Remember I said there’s more hacking after the array indexing? It’s not strictly necessary. The user-mode function GetClipboardFormatName can turn that atom into a dll name[4]. MsgHookLister goes the whole hacky way though and uses the win32k function UserGetAtomName, just because well, what’s one extra bit of hackery in a thing built completely on top of it?

// abridged
WCHAR* GetDllName(PVOID pHeap, int atomIndex)
    // negative indexes are seen for LowLevel hooks
    // where there isn't an injected dll
    if(atomIndex < 0) return NULL;
    // turn the index to an ATOM
    ATOM actualAtom = aatomSysLoaded[atomIndex];
    WCHAR* pNameBuf = RtlAllocateHeap(pHeap, HEAP_ZERO_MEMORY, MAX_PATH * sizeof(WCHAR));
        ULONG charsNeeded = UserGetAtomName(actualAtom, pNameBuf, MAX_PATH);
        if(charsNeeded != 0)
            // this is just a normal atom that's been stringified ignore it
            if(pNameBuf[0] == L'#')
                RtlFreeHeap(pHeap, 0, pNameBuf);
                pNameBuf = NULL;
    return pNameBuf;

That’s almost everything, but thread ids can be made much more pallatable to the client app…

Process Name

The only other thing in the output not covered by the struct is the process name. Again, this can be gotten from user mode, and by looking in the PEB. However, in a rare bout of playing by the rules, MsgHookLister uses PsThreadToProcess to get an EPROCESS from an ETHREAD and PsGetProcessImageFileName to get the name string that is stored within it.

void CopyThreadProcessName(PETHREAD pThread, char* pNameBuffer, ULONG bufLen)
    // this doesn't need releasing
    PEPROCESS pProc = PsThreadToProcess(pThread);
    strncpy(pNameBuffer, (char*)PsGetProcessImageFileName(pProc), bufLen);
    pNameBuffer[bufLen - 1] = 0;
// call site
    HOOK* pHookIter = ...
    // the EThread offset is the same either way, 
    // so it's OK to leave this as THREADINFO7 even on Vista
    THREADINFO7* pSettingThread = static_cast<THREADINFO7*>(pHookIter->head.pti);
        hook.dwSettingThread = HandleToULong(PsGetThreadId(pSettingThread->pEThread));
        CopyThreadProcessName(pSettingThread->pEThread, hook.settingProcess, ARRAYSIZE(hook.settingProcess));

Copying to User Mode

After collating all the relevant hook data, it needs to be marshalled somewhere user mode can access it. Normal Windows API calls dealing with this have the caller pass a buffer and a size, with the minimum length returned if the buffer is insufficient. There’s nothing wrong with this method, but if the information takes a long time to gather or is volatile this isn’t the most efficient approach.

With that in mind, the approach taken by MsgHookLister is to always return this:

typedef struct WindowsHookInfo_
	ClientHookArray* pHookInfoArray;
} WindowsHookInfo;

Instead of having the caller call back with an enlarged buffer, the driver makes a new heap in the calling process, allocates all the memory it needs from it, and then passes the heap handle back to the user mode app. When it’s finished, the app cleans up by freeing the memory and destroying the heap.

A credible alternative to the heap is to allocate a glob of virtual memory and manage it yourself. It’s so credible I should’ve actually written it that way. To make the heap method work, it has to be created with the HEAP_NO_SERIALIZE flag because heaps created through the kernel use ERESOURCEs for locking, while user mode uses CRITICAL_SECTIONs, and they aren’t compatible. Oh well, some would say one last hack is a fitting ending to the call’s journey.

With that, the ride is over. The request has been serviced and the driver is ready to remorselessly go through the whole process again and again at the clients behest.


It did list queued window messages, now it lists active thread and global hooks too, it’s MsgHookLister. If this were Sysinternals it’d have a nice shiny gui but I’m not so it’s command line only. There are output samples and a video of it in action here. You can download the package here, it comes with x86 and x64 binaries and source code that puts the entirety of the above text into a ‘I could’ve figured that out’ category.

[1]: The Win32Thread value reported by WinDbg when you use !process or !thread is the address of the THREADINFO. With a Win7 target, ‘dt win32k!tagTHREADINFO [address]’ will display the structure’s contents.

[2] [3]: More information on windowstations and desktops can be found starting here

[4]: GetClipboardFormatName can also be used to get the name for a value returned from RegisterWindowMessage. Clipboard format names, window messages, window classes and these hook dll name atoms all share the same atom table. You cannot use (Global)GetAtomName, these have usermode only atom tables.

Powered by WordPress