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:
- https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugger-markup-language-commands
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