Update 2012/08/26: Added support for Windows 7.
Recently, I had to fix a problem with Microsoft Office Word. The problem was that the normal key combination Alt-Shift for changing the input language was not working. After some time I discovered that the culprit was a rather old piece of software that had installed some Windows hooks targeted at the Microsoft Office Word, which (probably) stopped the propagation of the Alt-Shift key combination before it could actually change the language.
While I was investigating this issue, I noticed that there was no available utility that could detect and report installed Windows hooks. So here I present a Windows hooks detector. It is a command line application accompanied by a system driver, that scans, detects and reports installed Windows hooks.
Also you can download a demo hook application, that installs local and global hooks for testing purposes.
According to the MSDN documentation for Windows hooks:
a hook is a point in the system message-handling mechanism where an application can install a subroutine to monitor the message traffic in the system and process certain types of messages before they reach the target window procedure.
A Windows hook is installed by calling SetWindowsHookEx (source code) and removed by calling UnhookWindowsHookEx. It can either be installed globally or locally. When it is installed globally it intercepts messages for all threads belonging to the same desktop. When it is installed locally it intercepts messages directed to a specific thread.
The Windows API does not offer any way for an application to list the installed hooks. After a hook is installed, it is stored and managed inside kernel structures which cannot be accessed from the user mode. Thus a kernel driver is necessary to access these structures. Furthermore, the whole mechanism of installing, handling and removing hooks is undocumented, which means that there is need to reverse engineer the relevant code. So after spending some time with the IDA Pro Disassembler, SoftICE and a Windows XP image running on VMware the job was done.
An overview for detecting installed global hooks follows:
- Call PsGetCurrentThread and get the ETHREAD structure of the current thread. ETHREAD is an opaque data structure according to the MSDN documentation.
- Extract the THREADINFO structure by calling PsGetThreadWin32Thread. Both of them are undocumented.
- Extract the DESKTOPINFO.
- There you can a find all the globally installed hooks. They are organized in a array. Each element is a linked list and corresponds to a specific hook (WH_*).
An overview for detecting installed local hooks follows:
- Given a thread ID.
- Call PsLookupThreadByThreadId and get the ETHREAD structure of the specified thread.
- Extract the THREADINFO structure by calling PsGetThreadWin32Thread.
- There you can a find all the locally installed hooks for the specified thread. They are organized in a array. Each element is a linked list and corresponds to a specific hook (WH_*).
Each hook is represented by a HOOK structure, which is undocumented. The HOOK structure stores the virtual address of the handler function. If the handler resides inside a DLL, then the relative virtual address is stored and the DLL path is stored in an atom table. That’s because a DLL’s base address may be different across processes. Also, by storing the DLL path in the atom table, the system knows which DLL to load if the hook is triggered inside a new process. We could say that the hook DLL is lazily loaded into processes.
Unfortunately, this atom table is internal. Also, there is no exported function of the kernel that accesses it. So the only way to read its contents is to call directly non-exported and undocumented kernel function calls. In order to get the necessary addresses, the utility uses the Debug Help Library to load the PDB of win32k.sys. Two symbols are needed:
- UserGetAtomName: A function that reads the above mentioned internal atom table.
- aatomSysLoaded: An array containing the atoms of hook DLLs. The necessary offset in this array is stored in the HOOK structure.
The utility has been tested with the following versions of win32k.sys: 5.1.2600.2180, 5.1.2600.2770, 5.1.2600.5512, 6.1.7600.16385. These versions correspond to Windows XP SP2, SP3 and Windows 7. If you don’t have one of those versions and the utility crashes your system, please send me your win32k.sys for analysis. Otherwise, please inform me in order to update the supported versions.
There is one more way to get the DLL path for a hook, although it is less reliable, so I haven’t implemented it. Though, I am going to describe it. After loading a hook DLL the system stores in the PROCESSINFO structure the base address of the loaded DLL. So if the hook DLL is loaded in the inspected process, you can retrieve its base address and from the base address after traversing the module list of the process you can find the DLL path. If the hook DLL has not been loaded yet, then you are out of luck.