CreateFileA
at ❶. The filename pushed onto the stack is stored in EDI at ❷. (Not pictured is the EDI being loaded with the string \\.\FileWriterDevice
, which is the name of the object created by the driver for the user-space application to access.)The driver object is stored at address 0x827e3698
at ❶. Once we have the address for the driver object, we can look at its structure using the dt
command, as shown in .
u
command. In this case they do, but if they did not, it could mean that we made an error in the address calculation.ZwCreateFile
and ZwWriteFile
to write to a file from kernel space.Example 10-10. Code listing for IRP_MJ_DEVICE_CONTROL
function
F7B0DCB1 push offset aDosdevicesCSec ; "\\DosDevices\\C:\\secretfile.txt" F7B0DCB6 lea eax, [ebp-54h] F7B0DCB9 push eax ; DestinationString F7B0DCBA call ❶ds:RtlInitUnicodeString F7B0DCC0 mov dword ptr [ebp-74h], 18h F7B0DCC7 mov [ebp-70h], ebx F7B0DCCA mov dword ptr [ebp-68h], 200h F7B0DCD1 lea eax, [ebp-54h] F7B0DCD4 mov [ebp-6Ch], eax F7B0DCD7 mov [ebp-64h], ebx F7B0DCDA mov [ebp-60h], ebx F7B0DCDD push ebx ; EaLength F7B0DCDE push ebx ; EaBuffer F7B0DCDF push 40h ; CreateOptions F7B0DCE1 push 5 ; CreateDisposition F7B0DCE3 push ebx ; ShareAccess F7B0DCE4 push 80h ; FileAttributes F7B0DCE9 push ebx ; AllocationSize F7B0DCEA lea eax, [ebp-5Ch] F7B0DCED push eax ; IoStatusBlock F7B0DCEE lea eax, [ebp-74h] F7B0DCF1 push eax ; ObjectAttributes F7B0DCF2 push 1F01FFh ; DesiredAccess F7B0DCF7 push offset FileHandle ; FileHandle F7B0DCFC call ds:ZwCreateFile F7B0DD02 push ebx ; Key F7B0DD03 lea eax, [ebp-4Ch] F7B0DD06 push eax ; ByteOffset F7B0DD07 push dword ptr [ebp-24h] ; Length F7B0DD0A push esi ; Buffer F7B0DD0B lea eax, [ebp-5Ch] F7B0DD0E push eax ; IoStatusBlock F7B0DD0F push ebx ; ApcContext F7B0DD10 push ebx ; ApcRoutine F7B0DD11 push ebx ; Event F7B0DD12 push FileHandle ; FileHandle F7B0DD18 call ds:ZwWriteFile
The Windows kernel uses a UNICODE_STRING
structure, which is different from the wide character strings in user space. The RtlInitUnicodeString
function at ❶ is used to create kernel strings. The second parameter to the function is a NULL-terminated wide character string of the UNICODE_STRING
being created.
The filename for the ZwCreateFile
function is \DosDevices\C:\secretfile.txt. To create a file from within the kernel, you must specify a fully qualified object name that identifies the root device involved. For most devices, this is the familiar object name preceded by \DosDevices.
DeviceIoControl
is not the only function that can send data from user space to kernel drivers. CreateFile
, ReadFile
, WriteFile
, and other functions can also do this. For example, if a user-mode application calls ReadFile
on a handle to a device, the IRP_MJ_READ
function is called. In our example, we found the function for DeviceIoControl
by adding 0xe*4
to the beginning of the major function table because IRP_MJ_DEVICE_CONTROL
has a value of 0xe
. To find the function for read requests, we add 0x3*4
to the beginning of the major function table instead of 0xe*4
because the value of IRP_MJ_READ
is 0x3
.
In the previous example, we saw that a driver was loaded in kernel space when we ran our malware, and we assumed that it was the infected driver. Sometimes the driver object will be more difficult to find, but there are tools that can help. To understand how these tools work, recall that applications interact with devices, not drivers. From the user-space application, you can identify the device object and then use the device object to find the driver object. You can use the !devobj
command to get device object information by using the name of the device specified by the CreateFile
call from the user-space code.
kd> !devobj FileWriterDevice Device object (826eb030) is for: Rootkit \Driver\FileWriter DriverObject 827e3698 Current Irp 00000000 RefCount 1 Type 00000022 Flags 00000040 Dacl e13deedc DevExt 00000000 DevObjExt 828eb0e8 ExtensionFlags (0000000000) Device queue is not busy.
The device object provides a pointer to the driver object, and once you have the address for the driver object, you can find the major function table.
After you’ve identified the malicious driver, you might still need to figure out which application is using it. One of the outputs of the !devobj
command that we just ran is a handle for the device object. You can use that handle with the !devhandles
command to obtain a list of all user-space applications that have a handle to that device. This command iterates through every handle table for every process, which takes a long time. The following is the abbreviated output for the !devhandles
command, which reveals that the FileWriterApp.exe application was using the malicious driver in this case.
kd>!devhandles 826eb030 ... Checking handle table for process 0x829001f0 Handle table at e1d09000 with 32 Entries in use Checking handle table for process 0x8258d548 Handle table at e1cfa000 with 114 Entries in use Checking handle table for process 0x82752da0 Handle table at e1045000 with 18 Entries in use PROCESS 82752da0 SessionId: 0 Cid: 0410 Peb: 7ffd5000 ParentCid: 075c DirBase: 09180240 ObjectTable: e1da0180 HandleCount: 18. Image: FileWriterApp.exe 07b8: Object: 826eb0e8 GrantedAccess: 0012019f
Now that we know which application is affected, we can find it in user space and analyze it using the techniques discussed throughout this book.
We have covered the basics of analyzing malicious kernel drivers. Next, we’ll turn to techniques for analyzing rootkits, which are usually implemented as a kernel driver.