WinDbg: using pykd to dump private symbols

We’ve recently been conducting some reverse engineering and vulnerability analysis on an Anti Virus (AV) product and wanted to attach Rohitab API Monitor to one of the AV’s running processes so that I could log the Windows API function calls in order to better understand how the AV was implemented.

The AV in question was protecting its user mode process by making use of Kernel callbacks in one of the device drivers. The callbacks were registered using the ObRegisterCallbacks function:

This is the post patch guard method of allowing the AV driver to intercept calls to ZwOpenProcess, amongst others, so that access can be denied to the process handle, or the returned handle can be given less access rights etc.

The OB_CALLBACK_REGISTRATION and _OB_OPERATION_REGISTRATION structures are not defined in the Microsoft public symbols, so the WinDbg command x nt!_OB* doesn’t help. Both structures are well documented on MSDN, so after intercepting the function call to ObRegisterCallbacks in WinDbg I started decoding the structures by calculating the offsets and dumping memory addresses.

After a couple of debugging runs, this soon becomes tedious; my heart sank when I thought I might have to write a WinDbg script to automate the process. If you’re like me and you don’t use WinDbg scripts that often then you will know how time consuming it can be to re-learn WinDbg scripting each time you need it.

Then I remembered pykd, a python scripting module for Python, which I had heard about but never tried.

Pykd installation

If you are using the x64 version of WinDbg then you also need to install a 64 bit version of python. I chose the 2.7.x version as I already have some build scripts written for Python 2.7.x. At the time of writing, the latest version was 2.7.14:

Once installed head over to the pykd repository. The home of pykd has recently moved to githomelab.ru:

Download the bootstrapper zip, which contains pykd.dll:

pykd.dll has to be copied into the WinDbg “winext” folder, which for me was in the following location:

  • C:\Program Files\Windows Kits\10\Debuggers\x64\winext

The pykd module need to be added to your Python installation. I used pip from a command prompt to achieve this:

Pykd can then be loaded into WinDbg by using the command .load pykd

Note that the pykd documentation is in Russian, however Google Translate did an excellent job of translating it to English. Documentation can be found here:

Dumping OB_CALLBACK_REGISTRATION using pykd

Pykd allows type information to be dynamically created using the typeInfo class. It is also possible to retrieve existing type information (similar to the dt WinDbg command). Using these two capabilities the _OB_CALLBACK_REGISTRATION structure can be defined:

Using the typeInfo function we can obtain the type information for UNICODE_STRING which is a public symbol:

We can create an instance of a type using the typeVar function which takes parameters typeInfo and address. In pykd an address is simply an integer.

Once we have a typeVar instance, we can dump the information in a similar way to using dt nt!_ _OB_CALLBACK_REGISTRATION <address> (if the symbol was public), by casting to a string. Dumping the data in this way doesn’t recursively dump the information, so the output can be improved by iterating over the OperationRegistration array and individually dumping each item.

The final step is to parse the script command line. I wanted to be able to use a register as a parameter as well as an address, so for this so I did some ghetto input parsing:

The script can be run in WinDbg as follows, using the rcx register as input:

Output

Executing the script, while on a breakpoint at nt!ObRegisterCallbacks gives the following output.

Ideas for enhancing the script

Breakpoint Links

It is possible to output debugger markup language (DML) from pykd. It would be quite simple to emit links from the script that allow breakpoints to be set.  DML is documented here:

The dprintln function takes an additional parameter that specified if DML should be emitted:

Specifying True for the second parameter will output DML.

Patching a ret into the callback functions

Using pykd it is also possible to manipulate memory, so the callback functions could be automatically patched to return immediately, and in fact this is what I did to bypass the process protection implemented by the AV:

Other WinDbg scripting options

If you don’t want to use python/pykd then other options are available. Here are two of the most recent ones.

WinDbg Preview (JavaScript)

The WinDbg preview version available here:

It has an updated modern UI and allows JavaScript to be used for scripting

LINQ Debugger Objects

WinDbg can also be queried with LINQ, if you are familiar with LINQ then this might be a good bet