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:

NTSTATUS ObRegisterCallbacks(
    _In_ POB_CALLBACK_REGISTRATION CallBackRegistration,
    _Out_ PVOID *RegistrationHandle
);
typedef struct _OB_CALLBACK_REGISTRATION {
    USHORT Version;
    USHORT OperationRegistrationCount;
    UNICODE_STRING Altitude;
    PVOID RegistrationContext;
    OB_OPERATION_REGISTRATION *OperationRegistration;
} OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;
typedef struct _OB_OPERATION_REGISTRATION {
    POBJECT_TYPE *ObjectType;
    OB_OPERATION Operations;
    POB_PRE_OPERATION_CALLBACK PreOperation;
    POB_POST_OPERATION_CALLBACK PostOperation;
} OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;

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:

from pykd import *  
import sys  
  
#typedef ULONG OB_OPERATION;  
  
#typedef struct _OB_OPERATION_REGISTRATION {  
#  POBJECT_TYPE                *ObjectType;  
#  OB_OPERATION                Operations;  
#  POB_PRE_OPERATION_CALLBACK  PreOperation;  
#  POB_POST_OPERATION_CALLBACK PostOperation;  
#} OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;  
  
struct_ob_operation_registration = createStruct("_OB_OPERATION_REGISTRATION", 0)  
struct_ob_operation_registration.append("ObjectType", baseTypes.VoidPtr)  
struct_ob_operation_registration.append("Operations", baseTypes.ULong)  
struct_ob_operation_registration.append("PreOperation", baseTypes.VoidPtr)  
struct_ob_operation_registration.append("PostOperation", baseTypes.VoidPtr)  
  
#typedef struct _OB_CALLBACK_REGISTRATION {  
#  USHORT                    Version;  
#  USHORT                    OperationRegistrationCount;  
#  UNICODE_STRING            Altitude;  
#  PVOID                     RegistrationContext;  
#  OB_OPERATION_REGISTRATION *OperationRegistration;  
#} OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;  
  
struct_ob_callback_registration = createStruct("_OB_CALLBACK_REGISTRATION", 0)  
struct_ob_callback_registration.append("Version", baseTypes.UInt2B)  
struct_ob_callback_registration.append("OperationRegistrationCount", baseTypes.UInt2B)  
struct_ob_callback_registration.append("Altitude", typeInfo("nt!_UNICODE_STRING"))  
struct_ob_callback_registration.append("RegistrationContext", baseTypes.VoidPtr)  
struct_ob_callback_registration.append("OperationRegistration", struct_ob_operation_registration.ptrTo() )

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

typeInfo("nt!_UNICODE_STRING")

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.

def dump( address ):  
    data = typedVar( struct_ob_callback_registration, address )   
    dprintln( "\n" )  
    dprintln(str(data))  
    dprintln( "[+] Altitude ...\n\n" )  
    dprintln( dbgCommand("dt nt!_UNICODE_STRING %x" % data.Altitude.getAddress()) )  
    dprintln( "[+] OperationRegistration ...\n\n" )  
    for x in range(0, data.OperationRegistrationCount ):  
        dprintln( str( data.OperationRegistration[x] ) )

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:

if len(sys.argv) != 2:
    dprintln("%s pykd script for dumping _OB_CALLBACK_REGISTRATION\n" % sys.argv[0] )
    dprintln("Usage: %s \n" % sys.argv[0] )
    dprintln("Usage: %s [address] \n" % sys.argv[0] )
    dprintln("Usage: %s [@register]\n" % sys.argv[0] )
else:
    address = sys.argv[1]
    if address.startswith('@'):
        #register, strip @
        address = address[1:]
        address = int(reg(address.lower()))
    else:
        address = int(address, 16)
dump(address)

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

!py C:\scripts\dump.py @rcx

Output

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

0: kd> .load pykd
0: kd> !py c:\scripts\dump.py
c:\scripts\dump.py pykd script for dumping _OB_CALLBACK_REGISTRATION
Usage: c:\scripts\dump.py
Usage: c:\scripts\dump.py [address]
Usage: c:\scripts\dump.py [@register]
0: kd> !py c:\scripts\dump.py @rcx
struct/class: _OB_CALLBACK_REGISTRATION at 0xfffff581bfc06280
    +0000 Version : UInt2B 0x100 (256)
    +0002 OperationRegistrationCount: UInt2B 0x2 (2)
    +0008 Altitude : _UNICODE_STRING
    +0018 RegistrationContext : Void* 0x0 (0)
    +0020 OperationRegistration : _OB_OPERATION_REGISTRATION* 0xfffff581bfc062b0 (18446732536349483696)
[+] Altitude ...
"266210"
    +0x000 Length : 0xc
    +0x002 MaximumLength : 0xe
    +0x008 Buffer : 0xfffff80d`17f19250 "266210"
[+] OperationRegistration ...
struct/class: _OB_OPERATION_REGISTRATION at 0xfffff581bfc062b0
    +0000 ObjectType : Void* 0xfffff803b540c0d0 (18446735293542351056)
    +0008 Operations : ULong 0x3 (3)
    +0010 PreOperation : Void* 0xfffff80d17f0be70 (18446735333852757616)
    +0018 PostOperation : Void* 0xfffff80d17f0be60 (18446735333852757600)
struct/class: _OB_OPERATION_REGISTRATION at 0xfffff581bfc062d0
    +0000 ObjectType : Void* 0xfffff803b540c0f0 (18446735293542351088)
    +0008 Operations : ULong 0x3 (3)
    +0010 PreOperation : Void* 0xfffff80d17f0be70 (18446735333852757616)
    +0018 PostOperation : Void* 0xfffff80d17f0be60 (18446735333852757600)

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:

dprintln( text, dml = False)

Specifying True for the second parameter will output DML.

preaddr = data.OperationRegistration[x].PreOperation
postaddr = data.OperationRegistration[x].PostOperation
dprintln(‘<link cmd=”bp %x”>breakpoint pre operation function %d</link>’ % (preaddr, x), True)
dprintln(‘<link cmd=”bp %x”>breakpoint post operation function %d</link>’ % (postaddr, x), True)

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:

setByte( data.OperationRegistration[x].PostOperation , int("0xC3", 16))
setByte( data.OperationRegistration[x].PreOperation , int("0xC3", 16))

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