Kernel Debugging in Practice

In this section, we’ll examine a program that writes to files from kernel space. For malware authors, the benefit of writing to files from kernel space is that it is more difficult to detect. This isn’t the stealthiest way to write to a file, but it will get past certain security products, and can mislead malware analysts who are looking for telltale calls in the user space to CreateFile or WriteFile functions. The normal Win32 functions are not easily accessible from kernel mode, which presents a challenge for malware authors, but there are similar functions that are used regularly in malware written from the kernel. Since the CreateFile and WriteFile functions are not available in the kernel mode, the NtCreateFile and NtWriteFile functions are used instead.

Looking at the User-Space Code

In our example, a user-space component creates a driver that will read and write the files in the kernel. First we look at our user-space code in IDA Pro to investigate what functions it calls to interact with a driver as shown in Example 10-4.

Example 10-4. Creating a service to load a kernel driver

04001B3D  push    esi             ; lpPassword
04001B3E  push    esi             ; lpServiceStartName
04001B3F  push    esi             ; lpDependencies
04001B40  push    esi             ; lpdwTagId
04001B41  push    esi             ; lpLoadOrderGroup
04001B42  push    [ebp+lpBinaryPathName] ; lpBinaryPathName
04001B45  push    1               ; dwErrorControl
04001B47  push    3               ; dwStartType
04001B49  push   1               ; dwServiceType
04001B4B  push    0F01FFh         ; dwDesiredAccess
04001B50  push    [ebp+lpDisplayName] ; lpDisplayName
04001B53  push    [ebp+lpDisplayName] ; lpServiceName
04001B56  push    [ebp+hSCManager] ; hSCManager
04001B59  call    ds:__imp__CreateServiceA@52

We see in the service manager routines that a driver is being created with the CreateService function. Note the parameter for dwService type is 0x01. This value indicates that this is a kernel driver.

Then we see in Example 10-5 that a file is being created to get a handle to a device with a call to 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.)

Example 10-5. Obtaining a handle to a device object

04001893                 xor     eax, eax
04001895                 push    eax             ; hTemplateFile
04001896                 push    80h             ; dwFlagsAndAttributes
0400189B                 push    2               ; dwCreationDisposition
0400189D                 push    eax             ; lpSecurityAttributes
0400189E                 push    eax             ; dwShareMode
0400189F                 push    ebx             ; dwDesiredAccess
040018A0                push    edi             ; lpFileName
040018A1                call    esi ; CreateFileA

Once the malware has a handle to the device, it uses the DeviceIoControl function at to send data to the driver as shown in Example 10-6.

Example 10-6. Using DeviceIoControl to communicate from user space to kernel space

04001910  push    0               ; lpOverlapped
04001912  sub     eax, ecx
04001914  lea     ecx, [ebp+BytesReturned]
0400191A  push    ecx             ; lpBytesReturned
0400191B  push    64h             ; nOutBufferSize
0400191D  push    edi             ; lpOutBuffer
0400191E  inc     eax
0400191F  push    eax             ; nInBufferSize
04001920  push    esi             ; lpInBuffer
04001921  push    9C402408h       ; dwIoControlCode
04001926  push    [ebp+hObject]   ; hDevice
0400192C  call    ds:DeviceIoControl

Looking at the Kernel-Mode Code

At this point, we’ll switch gears to look at the kernel-mode code. We will dynamically analyze the code that will be executed as a result of the DeviceIoControl call by debugging the kernel.

The first step is to find the driver in the kernel. If you’re running WinDbg with a kernel debugger attached and verbose output enabled, you will be alerted whenever a kernel module is loaded. Kernel modules are not loaded and unloaded often, so if you are debugging your malware and a kernel module is loaded, then you should be suspicious of the module.

Note

When using VMware for kernel debugging, you will see KMixer.sys frequently loaded and unloaded. This is normal and not associated with any malicious activity.

In the following example, we see that the FileWriter.sys driver has been loaded in the kernel debugging window. Likely, this is the malicious driver.

ModLoad: f7b0d000 f7b0e780   FileWriter.sys

To determine which code is called in the malicious driver, we need to find the driver object. Since we know the driver name, we can find the driver object with the !drvobj command. Example 10-7 shows example output:

Example 10-7. Viewing a driver object for a loaded driver

kd> !drvobj FileWriter
Driver object (827e3698) is for:
Loading symbols for f7b0d000   FileWriter.sys ->   FileWriter.sys
*** ERROR: Module load completed but symbols could not be loaded for FileWriter.sys
 DriverFileWriter
Driver Extension List: (id , addr)

Device Object list:
826eb030

Note

Sometimes the driver object will have a different name or !drvobj will fail. As an alternative, you can browse the driver objects with the !object Driver command. This command lists all the objects in the Driver namespace, which is one of the root namespaces discussed in Chapter 7.

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 Example 10-8.

Example 10-8. Viewing a device object in the kernel

kd>dt nt!_DRIVER_OBJECT 0x827e3698
nt!_DRIVER_OBJECT
   +0x000 Type             : 4
   +0x002 Size             : 168
   +0x004 DeviceObject     : 0x826eb030 _DEVICE_OBJECT
   +0x008 Flags            : 0x12
   +0x00c DriverStart      : 0xf7b0d000
   +0x010 DriverSize       : 0x1780
   +0x014 DriverSection    : 0x828006a8
   +0x018 DriverExtension  : 0x827e3740 _DRIVER_EXTENSION
   +0x01c DriverName       : _UNICODE_STRING "DriverFileWriter"
   +0x024 HardwareDatabase : 0x8066ecd8 _UNICODE_STRING "REGISTRYMACHINE
                             HARDWAREDESCRIPTIONSYSTEM"
   +0x028 FastIoDispatch   : (null)
   +0x02c DriverInit       : 0xf7b0dfcd     long  +0
   +0x030 DriverStartIo    : (null)
   +0x034 DriverUnload     : 0xf7b0da2a     void  +0
   +0x038 MajorFunction    : [28] 0xf7b0da06     long  +0

The entry for MajorFunction in this structure is a pointer to the first entry of the major function table. The major function table tells us what is executed when the malicious driver is called from user space. The table has different functions at each index. Each index represents a different type of request, and the indices are found in the file wdm.h and start with IRP_MJ_. For example, if we want to find out which offset in the table is called when a user-space application calls DeviceIoControl, we would look for the index of IRP_MJ_DEVICE_CONTROL. In this case, IRP_MJ_DEVICE_CONTROL has a value of 0xe, and the major function table starts at an offset of 0x038 from the beginning of the driver object. To find the function that will be called to handle the DeviceIoControl request, use the command dd 827e3698+0x38+e*4 L1. The 0x038 is the offset to the beginning of the table, 0xe is the index of the IRP_MJ_DEVICE_CONTROL, and it’s multiplied by 4 because each pointer is 4 bytes. The L1 argument specifies that we want to see only one DWORD of output.

The preceding command shows that the function called in the kernel is at 0xf7b0da66, as shown in Example 10-9. We can check to see if the instructions at that address look valid by using the u command. In this case they do, but if they did not, it could mean that we made an error in the address calculation.

Example 10-9. Locating the function for IRP_MJ_DEVICE_CONTROL in a driver object

kd> dd 827e3698+0x38+e*4 L1
827e3708  f7b0da66
kd> u f7b0da66
FileWriter+0xa66:
f7b0da66 6a68            push    68h
f7b0da68 6838d9b0f7      push    offset FileWriter+0x938 (f7b0d938)
f7b0da6d e822faffff      call    FileWriter+0x494 (f7b0d494)

Now that we have the address, we can either load the kernel driver into IDA Pro or set a breakpoint on that function and continue to analyze it within WinDbg. It’s usually easier to start by analyzing the function in IDA Pro and then use WinDbg if further analysis is needed. While scanning through the IDA Pro output of our malicious example driver, we found the code in Example 10-10, which calls 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 DosDevicesC: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.

Finding Driver Objects

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 DriverFileWriter 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.126.56