Recently I got the idea to play around with bypassing bootkit disk filters from an email i received, which highlighted that my MBR spoofing code was able to get underneath the driver of a popular forensics tool, preventing it from reading the real disk sectors. Although I believe disk forensics should not be done on a live system, instead the disk should be mounted on a clean system and examined from there, I thought it would be fun to write a tool for bypassing various bootkit drivers and then post my research. Another email I received requested that I show how one would detect the presence of such filters from WinDbg, So I will try to cover both.
As I've shown in aprevious article
, disk filtering is usually done by hooking the IRP_MJ_SCSI field of the miniport driver's object. Another common method is hooking DriverStartIo; however, this field is only used in the old-style driver model and is set to NULL on most Vista+ systems. The drivers used depend on whether you use SCSI or ATA based hardware, but because all drivers follow the same model, I will simply use an ATA system in my examples.
Old Driver Model
Pre-Vista disk drivers would have a single ATA channel driver known as atapi.sys, which would provide the functionality of both a port and miniport. If a disk required a custom miniport, the vendor would have to write their own miniport + port driver, which is no small task. When a device receives a request such as IRP_MJ_SCSI, it queues it to the disk via IoStartPacket, which eventually calls the address held by the DriverStartIo field of the driver's object; thus hooking DriverStartIo would intercept any disk I/O requests, not just IRP_MJ_SCSI.
New Driver Model
The new driver model provides a Microsoft supplied port driver (ataport.sys) and miniport driver (atapi.sys), which work together to make up the channel interface. The port driver provides basic functionality, whilst the miniport provides hardware specific functionality; so, if a vendor needs a custom miniport driver, they could simply write their own miniport to interface with the Microsoft supplied port driver.
With the new model the IRP_MJ_SCSI field of atapi's driver object points to a function within ataport.sys (IdePortDispatch), which handles and queues requests using an internal mechanism instead of IoStartPacket, meaning bootkits hooking only IRP_MJ_SCSI and DriverStartIo can be bypassed using passthrough operations (even from usermode).
Although TDL is no longer active, I should mention that it hijacks kdcom.dll (the COM debugger extension) in such a way that it prevents it from starting. If you attempt to enable kernel debugging via COM on a TDL infected system, it will be completely bricked following reboot (even safemode won't load).
First things first you need to find which disk is your boot disk (it's up you you how you do this), but in most cases it will be \Device\Harddisk0\DR0. Once you've made sure WinDbg has the correct symbols loaded, use !devstack to display the device stack and find the bottom most device (the miniport).
Here is a normal output:
In the case of some TDL4 infections, the miniport driver object (\driver\atapi) will appear to be invalid (it's not), but it prevents the !devobj and !drvobj commands from working, so we'll have to get the driver object associated with the miniport by using dt _DEVICE_OBJECT on the lowest device's object.
Now we can examine the driver object (specifically the dispatch table) for major function pointer hooks. On a clean system all the dispatch routines should have addresses which resolve to symbols in either the miniport, port or ntoskrnl. On TDL4 infected systems the !drvobj command won't work, so you'll have to use dds (iv'e shown how to use both below).Major functions on a clean system shown with !drvobj
On an infected system (TDL4) we will see something similar to the below.
If the driver dispatch table appears to be clean, the next thing to do is disassemble the address pointed to by IRP_MJ_SCSI (IRP_MJ_INTERNAL_DEVICE_CONTROL), as this is the dispatch routine which handles disk read/write requests and could be inline hooked. In my case IRP_MJ_SCSI points to ataport!IdePortDispatch.
It may be difficult to detect inline hooks, especially if existing jump/calls are modified. One should compare the module in memory against its disk image, accounting for relocation and imports (the best way to do this would be to have a driver map the disk image into memory and relocate it to point to the original module, allowing you to simply compare the two).