Kernel Debugging - Malware Analyst’s Cookbook and DVD: Tools and Techniques for Fighting Malicious Code (2011)

Malware Analyst’s Cookbook and DVD: Tools and Techniques for Fighting Malicious Code (2011)

Chapter 13. Kernel Debugging

Using a kernel debugger can provide powerful insight into the capabilities of low-level rootkits. Malware could introduce code into the kernel by loading a driver, patching existing drivers on disk, exploiting vulnerabilities, and writing to kernel memory from user mode withZwSystemDebugControl or by mapping the \Device\PhysicalMemory object. Regardless of how malware enters the kernel, if you are incapable of following it, you will quickly become lost, and your analysis will come to an abrupt halt.

This chapter provides an introduction to kernel debugging techniques and shows some practical examples of unpacking and reverse-engineering malicious kernel drivers. However, you can use a kernel debugger for more than just debugging drivers. You’ll commonly need to debug drivers and processes simultanously. For example, malware may have multiple components—a driver that runs in kernel mode and a process that runs in user mode. To fully understand how the components interact, you can use a kernel debugger to “watch” both sides of the conversation.

Remote Kernel Debugging

A typical kernel debugging session involves two separate systems—the target (the system being debugged) and the debugger (the system used to control the target). Figure 14-1 shows the basic idea for this type of setup. You need a separate machine to control the target because code cannot execute in the kernel while it is stopped in a debugger.

Figure 14-1: Remote kernel debugging requires two computers.

f1401.eps

To connect the two systems in a remote debugging scenario, you can use a serial cable, USB cable, network connection, or virtual hardware (if you’re using virtual machines). The examples in this chapter are based on using virtual machines to perform your debugging tasks.

Local Kernel Debugging

In a local kernel-debugging scenario, shown in Figure 14-2, the debugger application runs on the same system as the one that is being debugged. This type of setup limits your ability to control the target, and essentially, you can only perform read operations. In other words, you can list processes and drivers, dump kernel memory, and locate kernel symbols and things of that nature, but you cannot set breakpoints, step through code, or change the contents of registers or memory.

Figure 14-2: Local kernel debugging is limited in power.

f1402.eps

Software Requirements

The boxes representing the debugging system in Figures 14-1 and 14-2 contain the abbreviation WDK, which stands for Windows Driver Kit. The WDK contains Microsoft’s kernel debuggers, such as KD (a command-line version) and WinDbg (a GUI version). If you never plan to write your own drivers, then you can just install the Debugging Tools for Windows kit, which includes KD and WinDbg, but not the entire development environment. Depending on which package you install, the debugger applications will exist in different locations on your system. If you get them from Debugging Tools for Windows, then the path is probably C:\Program Files\Microsoft\Debugging Tools For Windows. If you get them from the WDK, the default path is C:\WINDDK\<Version>\Debuggers.

Additionally, you should install the symbols for your target operating system. Although you can download symbols from Microsoft at the time of your debugging session, it is always nice to have a local copy just in case network access isn’t available. Symbol files contain the names and addresses of functions, local and global variables, and type information for data structures, so they are critical to your ability to orient yourself in the kernel. The debuggers and symbols are freely available on Microsoft’s website at http://www.microsoft.com/whdc/devtools/default.mspx.

Recipe 14-1: Local Debugging with LiveKd

The LiveKd1 utility by Mark Russinovich lets you run Microsoft’s KD or WinDbg locally on a machine. As previously mentioned, this setup is limited in the amount of control you can exercise with your debugger (read operations only). However, sometimes if you’re just investigating small issues or “poking” around in the kernel, read access is all you need. To get started, follow these steps:

1. Make sure that you have installed the Microsoft debuggers and then download LiveKd from the link in the beginning of this recipe.

2. Extract livekd.exe from the archive and place it in the same directory as the Microsoft debuggers.

3. By default, when you launch livekd.exe, it starts the KD command-line debugger. If you would rather use WinDbg instead, then pass the –w flag to livekd.exe when executing it. You will need to answer a few questions related to setting up symbols, but in most cases, you can accept the defaults.

C:\>cd C:\WINDDK\7600.16385.0\Debuggers

C:\WINDDK\7600.16385.0\Debuggers>livekd.exe

LiveKd v3.14 - Execute kd/windbg on a live system

Sysinternals - www.sysinternals.com

Copyright (C) 2000-2010 Mark Russinovich

Symbols are not configured. Would you like LiveKd to set the

_NT_SYMBOL_PATH directory to reference the Microsoft symbol

server so that symbols can be obtained automatically? (y/n) y

Enter the folder to which symbols download (default is c:\symbols):

Launching C:\WINDDK\7600.16385.0\Debuggers\kd.exe:

Microsoft (R) Windows Debugger Version 6.11.0001.404 X86

Copyright (c) Microsoft Corporation. All rights reserved.

Loading Dump File [C:\WINDOWS\livekd.dmp]

Kernel Complete Dump File: Full address space is available

Comment: 'LiveKD live system view'

Symbol search path is:

srv*c:\Symbols*http://msdl.microsoft.com/download/symbols

Executable search path is:

Windows XP Kernel Version 2600 (Service Pack 3) Free x86 compatible

Product: WinNt, suite: TerminalServer SingleUserTS

Built by: 2600.xpsp_sp3_gdr.090804-1435

Machine Name:

Kernel base = 0x804d7000 PsLoadedModuleList = 0x80554040

Debug session time: Sat Feb 12 22:34:57.897 17420 (GMT-4)

System Uptime: 0 days 1:39:35.562

Loading Kernel Symbols

...............................................................

.............................................................

Loading User Symbols

...........

Loading unloaded module list

..............

kd> type your commands here...

4. You can now skip to Recipe 14-5 to begin using the debugger, but keep in mind that you can only execute read/view operations because you’re debugging the kernel locally.

Note You can actually use KD and WinDbg on a system without LiveKd. To do this, pass the –kl parameters (for kernel, local) to kd.exe or windbg.exe when starting them. In this case, however, you will need to set up symbols and the debugging environment on your own.

1 http://technet.microsoft.com/en-us/sysinternals/bb897415.aspx

Recipe 14-2: Enabling the Kernel’s Debug Boot Switch

You can remotely debug the kernel of any Windows system without installing special software onto the target. However, you do need to let the target kernel know that it should accept and respond to debugger connections. To do this, you must enable the /debug boot switch as described in this recipe.

Windows XP and Server 2003 Targets

Microsoft’s recommended way to make the required changes is to use bootcfg.exe.2 This tool validates your syntax for boot options and rejects invalid entries. You can also modify C:\boot.ini directly, but if you make a careless mistake when manually editing boot.ini, then you may not be able to boot your system again. To use bootcfg.exe, follow these steps:

1. List the existing configuration like this:

C:\>bootcfg

Boot Loader Settings

--------------------

timeout: 30

default: multi(0)disk(0)rdisk(0)partition(1)\WINDOWS

Boot Entries

------------

Boot entry ID: 1

Friendly Name: "Microsoft Windows XP Professional"

Path: multi(0)disk(0)rdisk(0)partition(1)\WINDOWS

OS Load Options: /noexecute=optin /fastdetect

2. Create a copy of the boot entry (ID 1 in this case) and give it a meaningful name. Verify your changes by typing bootcfg again, without any arguments.

C:\>bootcfg /Copy /D "XP Professional with Debug" /ID 1

SUCCESS: Made a copy of the boot entry "1".

C:\>bootcfg

[...]

Boot entry ID: 2

Friendly Name: "Microsoft Windows XP Professional - Debug"

Path: multi(0)disk(0)rdisk(0)partition(1)\WINDOWS

OS Load Options: /noexecute=optin /fastdetect

3. Enable the debug switch on the new boot entry (ID 2) and configure the port and baud. This particular setup uses the COM1 serial port, which you need to remember when adding a virtual serial device to your virtual machines.

C:\>bootcfg /Debug ON /ID 2 /PORT COM1 /BAUD 115200

SUCCESS: Changed the switches in OS entry "2" in the BOOT.INI.

4. Verify your changes by typing bootcfg again, without any arguments.

C:\>bootcfg

[...]

Boot entry ID: 2

Friendly Name: "Microsoft Windows XP Professional - Debug"

Path: multi(0)disk(0)rdisk(0)partition(1)\WINDOWS

OS Load Options: /noexecute=optin /fastdetect /debug /debugport=com1

/baudrate=115200

Windows Vista and Windows 7 Targets

Starting with Vista, Windows no longer uses boot.ini for boot settings. To enable the debug switch on these systems, you can use bcdedit.exe3 instead as shown in the following steps:

1. Launch a command shell with administrator privileges and type bcdedit to print the current boot loader configuration.

C:\>bcdedit

Windows Boot Manager

--------------------

identifier {bootmgr}

device partition=\Device\HarddiskVolume1

description Windows Boot Manager

locale en-US

inherit {globalsettings}

default {current}

resumeobject {d121a616-887e-11de-be3f-9b9b7d346734}

displayorder {current}

toolsdisplayorder {memdiag}

timeout 30

Windows Boot Loader

-------------------

identifier {current}

device partition=C:

path \Windows\system32\winload.exe

description Windows 7

locale en-US

inherit {bootloadersettings}

recoverysequence {d121a618-887e-11de-be3f-9b9b7d346734}

recoveryenabled Yes

osdevice partition=C:

systemroot \Windows

resumeobject {d121a616-887e-11de-be3f-9b9b7d346734}

nx OptIn

2. Create a copy of the configuration with identifier {current}, like this:

C:\>bcdedit /copy {current} /d "Windows 7 with Debug"

The entry was successfully copied to

{d121a61a-887e-11de-be3f-9b9b7d346734}.

3. Enable the debug boot switch for the newly created identifier.

C:\>bcdedit /debug {d121a61a-887e-11de-be3f-9b9b7d346734} ON

The operation completed successfully.

4. Type bcdedit again, without any parameters, to check if the system accepted your changes.

C:\>bcdedit

Windows Boot Loader

-------------------

identifier {d121a61a-887e-11de-be3f-9b9b7d346734}

device partition=C:

path \Windows\system32\winload.exe

description Windows 7 with Debug

locale en-US

inherit {bootloadersettings}

recoverysequence {d121a618-887e-11de-be3f-9b9b7d346734}

recoveryenabled Yes

osdevice partition=C:

systemroot \Windows

resumeobject {d121a616-887e-11de-be3f-9b9b7d346734}

nx OptIn

debug Yes

Booting into Debug Mode

At the next power-on, select the debugger-enabled operating system. Everything will proceed as normal until you connect to the system with a debugger. Figure 14-3 shows what you should see, depending on what you named your entries.

Figure 14-3: Booting into debugger-enabled mode

f1403.tif

2 http://support.microsoft.com/kb/317521

3 http://www.microsoft.com/whdc/driver/tips/Debug_Vista.mspx

Recipe 14-3: Debug a VMware Workstation Guest (on Windows)

This recipe assumes that you run VMware Workstation on a Windows host operating system (the debugger), and you want to explore the kernel of one of your VMware guests (the target). Here are the steps to getting your machines configured properly:

1. On your Windows host, install the Microsoft debuggers and the symbol package for your target’s operating system.

2. Enable the debug boot switch on your target, as described in Recipe 14-2. After the changes, shut down the target.

3. With the target powered down, you can add a new virtual serial device. Follow these steps:

a. Click Edit virtual machine configuration.

b. On the Hardware tab, click Add.

c. Select Serial Port and click Next.

d. Select Output to named pipe and click Next.

e. Enter a name for the pipe, or accept the default of \\.\pipe\com_1.

f. Select This end is the server.

g. Select The other end is an application.

h. Place a check in the Connect at power on box.

i. Place a check in the Yield on CPU poll box.

j. Verify your settings with Figure 14-4.

Figure 14-4: Adding a virtual serial port in VMware

f1404.eps

4. Power on the target, and choose the debugger-enabled operating system, as described in Recipe 14-2.

5. Launch WinDbg from your Windows host operating system using the following syntax:

C:\WinDDK\7600~\Debuggers> windbg –k com:pipe,port=\\.\pipe\com_1

6. Once you see the WinDbg application, press Ctrl+Break, or click Debug ⇒ Break on the menu. You should see the welcome screen, as shown in Figure 14-5.

Figure 14-5: The debugger’s welcome screen

f1405.tif

You can now skip to Recipe 14-5 to begin using the debugger.

Recipe 14-4: Debug a Parallels Guest (on Mac OS X)

Debugging between two virtual machines requires a few extra steps compared with Recipe 14-3. In this recipe, you’ll learn how to set up a remote debugging connection between guests using Parallels on Mac OS X. To start, you need two virtual machines running Windows.

1. Dedicate one of your virtual machines as the debugger and one as the target. You might want to rename the target “Windows—Debug Target” or something similar so you don’t get them mixed up.

2. On the debugging system, install the Microsoft debuggers and symbols for the target’s operating system.

3. Enable the debug boot switch on your target, as described in Recipe 14-2.

4. Power down both virtual machines.

5. Add a serial device to the target by following these steps:

a. Click Configure to bring up the virtual machine’s configuration.

b. Click the + icon to add hardware.

c. Choose Serial Port and click Continue.

d. Choose Socket and click Continue.

e. Enter a name for the Socket (/tmp/com_1 by default, which is fine).

f. Make sure the Mode is Server and click Add Device.

6. Add a serial device to the debugging system. To do this, follow the same steps as you did for the target, but for step f, make sure the Mode is Client and click Add Device. Verify that your target’s configuration appears like Figure 14-6 and that your debugging system’s configuration appears similar, but with Client selected instead of Server.

Figure 14-6: Adding a virtual serial port in Parallels

f1406.eps

7. Power on the target, and choose the debugger-enabled operating system, as described in Recipe 14-1.

8. Launch WinDbg from your debugging system using the following syntax:

C:\WinDDK\7600.16385.0\Debuggers> windbg –k

9. Once you see the WinDbg application, press Ctrl+Break, or click Debug ⇒ Break on the menu. You should see the welcome screen, as shown in Figure 14-5.

You can now continue to Recipe 14-5 to begin using the debugger.

Recipe 14-5: Introduction to WinDbg Commands And Controls

This recipe introduces you to some of the common WinDbg commands and things you need to know before beginning a debugging session.

Configuring Symbols

You should always configure symbols at the start of your debugging session. If you installed the symbol packages for your target’s operating system onto your debugging system, then you’ll need to know the path to where you put them (default is C:\symbols or C:\windows\symbols). Then issue the following command:

kd> .sympath c:\windows\symbols

Otherwise, you can download symbols as needed by pointing WinDbg to Microsoft’s online symbol server.

kd> .sympath "SRV* http://msdl.microsoft.com/download/symbols"

When you’re done, reload the symbols so WinDbg can access them.

kd> .reload

Creating Log Files

You can create log files of your commands and the corresponding output. Log files are useful because a single command can generate hundreds of lines of output. Additionally, months from now, you might not always remember exactly what you typed. The following commands show you how to enable logging for your debugging session:

kd> .logopen c:\test.log

Opened log file 'c:\test.log'

[... type your commands here ...]

kd> .logclose

Closing open log file c:\test.log

Locating Functions and Variables

You can use the x (examine symbols) command to locate symbols, such as functions exported by kernel drivers, functions exported by user-mode DLLs, and global variables. The syntax is x [module]![symbol] and you can use asterisks as wildcards. The following example searches the nt module (the name of the kernel executive) for functions related to mutexes:

kd> x nt!*mutex*

804d7690 nt!_imp_ExReleaseFastMutex = <no type information>

8055f900 nt!MmSectionBasedMutex = <no type information>

8055a160 nt!KiGenericCallDpcMutex = <no type information>

8055f920 nt!MmSectionCommitMutex = <no type information>

[...]

The following command looks in any loaded kernel module for functions related to notification events:

kd> x *!*notify*

8058a950 nt!NtNotifyChangeDirectoryFile = <no type information>

80612b0a nt!FsRtlNotifyCompletion = <no type information>

80561500 nt!PspCreateProcessNotifyRoutineCount = <no type information>

80554a04 nt!SepRmNotifyMutex = <no type information>

8068eb38 nt!PsImageNotifyEnabled = <no type information>

[...]

b2f04dc7 tcpip!AddrChangeNotifyRequest = <no type information>

b2f2eef6 tcpip!TcpSynAttackNotifyCcb = <no type information>

b2f08eb3 tcpip!IPNotifyClientsIPEvent = <no type information>

[...]

bf8c1ad2 win32k!NtUserNotifyProcessCreate = <no type information>

bf8bfc08 win32k!xxxUserNotifyProcessCreate = <no type information>

bf8acfbf win32k!DeviceCDROMNotify = <no type information>

You can also perform reverse lookups on an address to see if any symbols exist at the address or if any symbols exist at nearby addresses. For example, in the output that follows, 8062d880 is an address between PsSetCreateProcessNotifyRoutine and PsSetCreateThreadNotifyRoutine in the nt module:

kd> ln 8062d880

(8062d7b6) nt!PsSetCreateProcessNotifyRoutine+0xca

(8062d88d) nt!PsSetCreateThreadNotifyRoutine

Printing Objects/Structures

You can use the dt (display type) command to display type information for data structures and kernel objects. If you know the address in memory where a given structure or object exists, then you can have WinDbg parse the structure’s members accordingly. If you pass the -r switch, then dt will recursively parse any nested structures. The following commands show the format of a PEB structure and then apply it to a particular process’s PEB.

kd> dt _PEB

ntdll!_PEB

+0x000 InheritedAddressSpace : UChar

+0x001 ReadImageFileExecOptions : UChar

+0x002 BeingDebugged : UChar

+0x003 SpareBool : UChar

+0x004 Mutant : Ptr32 Void

+0x008 ImageBaseAddress : Ptr32 Void

[...]

kd> !process 0 0

PROCESS 820ddda0 SessionId: 0 Cid: 0e30 Peb: 7ffde000

ParentCid: 02a8 DirBase: 1710d000 ObjectTable: e1b809a8

HandleCount: 16.

Image: logon.scr

[...]

kd> .process /r /p 820ddda0

kd> dt _PEB 7ffde000

ntdll!_PEB

+0x000 InheritedAddressSpace : 0 ''

+0x001 ReadImageFileExecOptions : 0 ''

+0x002 BeingDebugged : 0 ''

+0x003 SpareBool : 0 ''

+0x004 Mutant : 0xffffffff

+0x008 ImageBaseAddress : 0x01000000

[...]

Here are a few structures and data types that you should become familiar with before an in-depth kernel debugging session. You will frequently run into functions that read or write these data types, so it’s important to get familiar with them ahead of time. To view them in WinDbg, use the dt command followed by their name, as shown in Table 14-1.

Table 14-1: Common dt Commands

Command

Description

_EPROCESS

The executive process block

_ETHREAD

The executive thread block

_PEB

The process environment block

_TEB

The thread environment block

_UNICODE_STRING

Structure for wide character strings

_DRIVER_OBJECT

Structure for drivers

_LIST_ENTRY

The linking component in doubly linked lists

_LARGE_INTEGER

Structure for 64-bit numbers

_CLIENT_ID

Structure for process ID and thread ID pairs

_POOL_HEADER

Structure that describes kernel pool allocations

_OBJECT_HEADER

Structure that describes kernel objects

_FILE_OBJECT

Structure for file objects

_CONTEXT

Structure that describes a thread’s state and registers

Formatting Data

You can print the data you find in memory using various formats. For example, the db command displays data as hex bytes and ASCII characters, the dd command displays data as double-word values, and the da/du commands display ASCII and Unicode strings, respectively. Here is an example dump using the address of the PEB from the preceding output:

kd> dd 7ffde000

7ffde000 00000000 ffffffff 01000000 00181e90

7ffde010 00020000 00000000 00080000 7c980600

7ffde020 7c901000 7c9010e0 00000001 7e412970

7ffde030 00000000 00000000 00000000 00000000

7ffde040 7c9805c0 000003ff 00000000 7f6f0000

7ffde050 7f6f0000 7f6f0688 7ffb0000 7ffc1000

7ffde060 7ffd2000 00000001 00000000 00000000

7ffde070 079b8000 ffffe86d 00100000 00002000

Assuming you only want to print the ImageBase value of the PEB, you can add the appropriate offset to the PEB base address and use the L parameter to control how many elements to display:

kd> dd 7ffde000+8 L1

7ffde008 01000000

The following example shows you how to display a hex + ASCII dump for a string. You can see that the string contains a \x00 byte between each character, which indicates it is a Unicode string.

kd> x nt!*sz*

805cc7cc nt!szDaylightBias = <no type information>

805cc7b0 nt!szDaylightName = <no type information>

kd> db nt!szDaylightBias

805cc7cc 4400610079006c00-6900670068007400 D.a.y.l.i.g.h.t.

805cc7dc 4200690061007300-000000002a535953 B.i.a.s.....*SYS

805cc7ec 54454d2a00000000-00000000e7030000 TEM*............

kd> du nt!szDaylightBias

805cc7cc "DaylightBias"

Printing Registers

You can print all registers at once with the r (registers) command, or specify an individual register such as r eax.

kd> r

eax=00000001 ebx=001f3475 ecx=80551fac edx=000003f8 esi=0000004a

edi=65f73b22

eip=804e3592 esp=f861f84c ebp=f861f85c iopl=0 nv up ei pl nz na po nc

cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000202

kd> r eax

eax=00000001

The following command shows the contents of the zero flag:

kd> r zf

zf=0

You can modify the contents of registers by simply assigning them a new value, like this:

kd> r eax=2

kd> r eax

eax=00000002

Searching Memory

You can search for a pattern of bytes in kernel or user-mode memory by using the s (search memory) command. The following example shows you how to locate potentially embedded executables by searching for the MZ header within a suspicious kernel driver.

kd> lm n

start end module name

804d7000 806ed700 nt ntoskrnl.exe

806ee000 8070e300 hal halaacpi.dll

b1ff1000 b2016880 windev_11a2_5d2d windev-11a2-5d2d.sys

b2180000 b21c0a80 HTTP HTTP.sys

b25a9000 b25fa880 srv srv.sys

kd> s -d b1ff1000 Lb2016880-b1ff1000 0x00905a4d

b1ff1000 00905a4d 00000003 00000004 0000ffff MZ..............

b1ff2340 00905a4d 00000003 00000004 0000ffff MZ..............

The first command determined the start and end address of a kernel driver named windev-11a2-5d2d.sys. The second command used the search function to find a double-word (-d) sized value of 0x00905a4d (MZ\x90\x00) anywhere in the driver’s memory. It found one occurrence at b1ff1000, which is the base of the driver—an expected result. It found a second occurrence at b1ff2340, which is not expected—it indicates the driver has another PE file embedded in its body. For more information about finding executable images and extracting them with WinDbg, see Cody Pierce’s MindshaRE4 blog entry.

You can search for ASCII strings with the -a flag or Unicode strings with the -u flag. In these cases, the strings in memory do not have to be NULL-terminated to match. Here’s an example of searching for the term “Windows” anywhere in the suspicious driver:

kd> s -a b1ff1000 Lb2016880-b1ff1000 "Windows"

b200ad9f 57696e646f77735c-495453746f726167 Windows\ITStorag

b200e278 57696e646f77734e-5420332e35310000 WindowsNT 3.51..

b200e288 57696e646f777320-3935000057696e64 Windows 95..Wind

b200e294 57696e646f777320-4e5420342e300000 Windows NT 4.0..

b200e2a4 57696e646f777320-3938000057696e64 Windows 98..Wind

b200e2b0 57696e646f777320-4d65000057696e25 Windows Me..Win%

b200e2d0 57696e646f777320-3230303000000000 Windows 2000....

b200e2e0 57696e646f777320-5850000057696e64 Windows XP..Wind

b200e2ec 57696e646f777320-3230303300000000 Windows 2003....

b200e2fc 57696e646f777320-5669737461000000 Windows Vista...

You can also extract ASCII or Unicode strings by using the s-sa or s-su commands, respectively. The following command lists all ASCII strings in the driver that are at least six characters long. The value in brackets specifies the length—it is a lowercase L followed by the number 6.

kd> s -[l6]sa b1ff1000 Lb2016880-b1ff1000

b1ff104d "!This program cannot be run in D"

b1ff106d "OS mode."

b1ff135f "'.rdata"

b1ff1387 "@.data"

b1ff13d8 ".reloc"

b1fF1414 "EventListener is EXITED, %d"

b1ff238d "!This program cannot be run in D"

b200adc8 "config"

b200add0 "\windev-peers.ini"

b200ade4 "[blacklist]"

b200e0e4 "contract@"

b200e0f8 "anyone@"

b200e100 "update"

b200e110 "f-secur"

b200e118 "rating@"

b200e120 "@microsoft"

b200e620 "Content-Type: application/x-www-"

b200e640 "form-urlencoded"

b200e814 "FORMAT"

b200e81c "COLLECTION"

[...]

Note If you plan to repeatedly search memory for the same terms, or if your WinDbg search is too slow or malware prevents your debugger from attaching, then you might be better off dumping memory and scanning it with a Volatility plug-in (see Recipe 16-6).

Controlling the Debugger

Table 14-2 shows commands that can assist you in controlling the execution of a program or kernel driver.

Table 14-2: Commands that Control Program Execution

Command

Description

g [breakaddress]

Go. Starts executing a current process or thread until the program ends, the optional [breakaddress] instruction is reached, or another event causes execution to stop.

p [count]

Step. Executes [count] instructions (or one instruction if [count] is not specified). If subroutines are encountered, this command treats the call as a single instruction and essentially steps over them.

pa <stopaddress>

Step to address

pt

Step to next return

t [count]

Trace. Executes [count] instructions (or one instruction if [count] is not specified). If subroutines are encountered, this command traces each instruction in the subroutine.

ta <stopaddress>

Trace to address

tt

Trace to next return

u [address]

Unassemble instructions at address (or starting at EIP if no address is specified)

uf [address]

Unassemble all instructions in a given function (uf shows a disassembly of the current function where EIP points)

bp <location>, bu <location>, bm <location>

Set a software breakpoint. The location parameter can be an absolute address (0x400020), an address relative to a register (eip+800), or a symbol (nt!ZwClose).

bl

List breakpoints

bc [number]

Clear a breakpoint

For a more comprehensive list of commands and their arguments, see one of the following resources:

· WinDbg From A to Z5

· WinDbg Thematically Grouped Command Sheet6

· The debugger.chm file distributed with Microsoft’s debuggers or Windows Driver Kit

4 http://dvlabs.tippingpoint.com/blog/2008/11/06/mindshare-finding-executable-images-in-windbg

5 http://windbg.info/doc/2-windbg-a-z.html

6 http://windbg.info/doc/1-common-cmds.html

Recipe 14-6: Exploring Processes and Process Contexts

As previously mentioned, you’ll rarely use a kernel debugger to only debug kernel drivers. In most cases, you’ll be switching back and forth between drivers and processes to understand how components in user mode interact with components in kernel mode. This recipe shows some techniques for investigating processes.

Listing Active Processes

You can use the !process command to print information about active processes. As the first parameter, you can specify the address of an EPROCESS structure to print a single process, or zero to print all processes. The second parameter indicates the level of detail you want about the process. The following command prints the smallest amount of detail about all processes:

kd> !process 0 0

**** NT ACTIVE PROCESS DUMP ****

PROCESS 823c8830 SessionId: none Cid: 0004 Peb: 00000000

ParentCid: 0000

DirBase: 00039000 ObjectTable: e1000cf8 HandleCount: 442.

Image: System

PROCESS 823823e0 SessionId: none Cid: 0260 Peb: 7ffde000

ParentCid: 0004

DirBase: 0a85d000 ObjectTable: e100d098 HandleCount: 19.

Image: smss.exe

PROCESS 8222b1b0 SessionId: 0 Cid: 0290 Peb: 7ffde000

ParentCid: 0260

DirBase: 0c973000 ObjectTable: e15c5af0 HandleCount: 375.

Image: csrss.exe

[...]

In the output, you can see the following fields:

· Cid: The process ID

· Peb: The address of the Process Environment Block

· ParentCid: The process ID of the process’s parent

· DirBase: The directory table (used for translation between virtual and physical addresses)

· ObjectTable: The handle table (see upcoming section on listing handles)

If you wanted to get the extended details about the csrss.exe process, you could specify the address of its EPROCESS block and increase the level of information like this:

kd> !process 8222b1b0 1

PROCESS 8222b1b0 SessionId: 0 Cid: 0290 Peb: 7ffde000

ParentCid: 0260

DirBase: 0c973000 ObjectTable: e15c5af0 HandleCount: 375.

Image: csrss.exe

VadRoot 820d5940 Vads 109 Clone 0 Private 293. Modified 959.

Locked 0.

DeviceMap e1004470

Token e14c9478

ElapsedTime 09:10:13.437

UserTime 00:00:00.265

KernelTime 00:00:00.718

[...]

Because the kernel organizes process objects in a linked list, you can create your own version of !process using the generic !list command. For example, let’s say you want to print the name and process ID for each process on the system. First, you’ll need to determine the offsets for the linked list, process ID, and file name fields in the EPROCESS block:

kd> dt _EPROCESS

ntdll!_EPROCESS

+0x000 Pcb : _KPROCESS

+0x06c ProcessLock : _EX_PUSH_LOCK

+0x070 CreateTime : _LARGE_INTEGER

+0x078 ExitTime : _LARGE_INTEGER

+0x080 RundownProtect : _EX_RUNDOWN_REF

+0x084 UniqueProcessId : Ptr32 Void

+0x088 ActiveProcessLinks : _LIST_ENTRY

[...]

+0x174 ImageFileName : [16] UChar

Once you know the offsets, you can use them in a command like this:

kd> !list "-t ntdll!_LIST_ENTRY.Flink -x \"db /c 8 @$extret-88+174 L16;

dd @$extret-88+84 L1\" nt!PsActiveProcessHead"

823c89a4 53 79 73 74 65 6d 00 00 System.. ; ImageFileName

823c89ac 00 00 00 00 00 00 00 00 ........

823c89b4 00 00 00 00 00 00 ......

823c88b4 00000004 ; UniqueProcessId

82382554 73 6d 73 73 2e 65 78 65 smss.exe ; ImageFileName

8238255c 00 00 00 00 00 00 00 00 ........

82382564 00 00 00 00 00 00 ...... ; UniqueProcessId

82382464 00000260

8222b324 63 73 72 73 73 2e 65 78 csrss.ex ; ImageFileName

8222b32c 65 00 00 00 00 00 00 00 e.......

8222b334 00 00 00 00 00 00 ......

8222b234 00000290 ; UniqueProcessId

[...]

The parameters for !list tell the command to start walking a linked list starting at nt!PsActiveProcessHead (a symbol in the nt module that points to the start of the process list). The command will iterate until it wraps back around to the beginning of the list or when it reaches a NULL entry. We have also indicated that it should use db to print the process name and dd to print the process ID. The @$extret variable contains the address of the list entry for each member of the list. Because the list entry starts at offset 88 within the EPROCESS block, you have to subtract 88 from @$extret to find the EPROCESS base. Then, to find the process ID and name fields, you add 84 and 174, respectively.

Switching Process Contexts

As you may know, each process has a unique “view” of user mode memory. Therefore, commands like dd 401000 are ambiguous, and you must first switch into the context of the process whose memory you want to view. Otherwise, you’ll see the data at 401000 (or just the question mark (?) characters if the address isn’t valid) in a different process than you expect. For example, consider the following commands, which print the same address in different process contexts:

kd> .process /r /p 82216c08

Implicit process is now 82216c08

.cache forcedecodeuser done

kd> dd 401000 L4

00401000 77dd7cc9 77dd7cb8 77dd7305 77dd819e

kd> .process /r /p 820ddda0

Implicit process is now 820ddda0

.cache forcedecodeuser done

kd> dd 401000 L4

00401000 ???????? ???????? ???????? ????????

As you can see, 401000 is valid in the context of one process, but not the other.

Listing Loaded DLLs

Once you switch to the correct process context, you can list the loaded DLLs using the !peb or !dlls commands. Because the list of loaded DLLs exists in the PEB, either command will work, but they show slightly different information. If you want to enumerate DLLs and then find a particular exported function, you could do something like this:

kd> !process 0 0

[...]

PROCESS 820eada0 SessionId: 0 Cid: 02e0 Peb: 7ffde000

ParentCid: 02a8

DirBase: 0d270000 ObjectTable: e15e20d0 HandleCount: 421.

Image: lsass.exe

kd> .process /r /p 820eada0

Implicit process is now 820eada0

.cache forcedecodeuser done

kd> !peb

PEB at 7ffde000

InheritedAddressSpace: No

ReadImageFileExecOptions: No

BeingDebugged: No

ImageBaseAddress: 01000000

Ldr 00191e90

Ldr.Initialized: Yes

Ldr.InInitializationOrderModuleList: 00191f28 . 00194350

Ldr.InLoadOrderModuleList: 00191ec0 . 00194340

Ldr.InMemoryOrderModuleList: 00191ec8 . 00194348

Base TimeStamp Module

1000000 48025186 Apr 13 2008 C:\WINDOWS\system32\lsass.exe

7c900000 49901d48 Feb 09 2009 C:\WINDOWS\system32\ntdll.dll

7c800000 49c4f482 Mar 21 2009 C:\WINDOWS\system32\kernel32.dll

77dd0000 49901d48 Feb 09 2009 C:\WINDOWS\system32\ADVAPI32.dll

77e70000 49e5f46d Apr 15 2009 C:\WINDOWS\system32\RPCRT4.dll

77fe0000 4988a20b Feb 03 2009 C:\WINDOWS\system32\Secur32.dll

75730000 49901d48 Feb 09 2009 C:\WINDOWS\system32\LSASRV.dll

[...]

kd> x lsasrv!*crypt*

757bcb33 LSASRV!LsaICryptProtectData (<no parameter info>)

757bcc91 LSASRV!LsaICryptUnprotectData (<no parameter info>)

The commands locate the address, in the memory of lsass.exe, for any functions in LSASRV.dll that contain the term “crypt.”

Viewing Process Memory Map

Virtual Address Descriptors (VAD) contain information about allocated memory segments in a process. As Chapter 16 discusses in greater detail, the VAD can help you locate hidden or injected code. To find a process’s VadRoot, use the !process command. Then pass the VadRoot value to !vad, like this:

kd> !process 823823e0 1

PROCESS 823823e0 SessionId: none Cid: 0260 Peb: 7ffde000

ParentCid: 0004

DirBase: 0a85d000 ObjectTable: e100d098 HandleCount: 19.

Image: smss.exe

VadRoot 8220e590 Vads 16 Clone 0 Private 29. Modified 9. Locked 0.

[...]

kd> !vad 8220e590

VAD level start end commit

822eb210 ( 1) 0 ff 0 Private READWRITE

822ec270 ( 2) 100 100 1 Private READWRITE

822fbd18 ( 3) 110 110 1 Private READWRITE

822feae0 ( 4) 120 15f 4 Private READWRITE

822ec0a8 ( 5) 160 25f 6 Private READWRITE

823008e8 ( 6) 260 26f 6 Private READWRITE

82302b58 ( 7) 270 2af 4 Private READWRITE

8237b038 ( 8) 2b0 2ef 4 Private READWRITE

822fb590 ( 9) 2f0 2f0 1 Private READWRITE

8220e590 ( 0) 48580 4858e 2 Mapped Exe EXECUTE_WRITECOPY

8220da58 ( 1) 7c900 7c9b1 5 Mapped Exe EXECUTE_WRITECOPY

822c0a18 ( 2) 7ffb0 7ffd3 0 Mapped READONLY

8229c008 ( 6) 7ffdb 7ffdb 1 Private READWRITE

8229d990 ( 5) 7ffdc 7ffdc 1 Private READWRITE

822b9838 ( 4) 7ffdd 7ffdd 1 Private READWRITE

822b7aa8 ( 3) 7ffde 7ffde 1 Private READWRITE

Total VADs: 16 average level: 5 maximum depth: 9

To calculate the virtual address for each VAD node, you need to multiply the start and end values by 0x1000. Thus, the VAD node at 8220da58 describes the memory at 7c900000–7c9b1000 inside the smss.exe process. According to the output, this memory contains a mapped executable, but it doesn’t show exactly which executable. In that case, you can leverage the lm command (vt is for verbose mode with timestamps) and determine that ntdll.dll exists in that space.

kd> lm vt a 7c900000

start end module name

7c900000 7c9b2000 ntdll

Loaded symbol image file: ntdll.dll

Mapped memory image file:

c:\windows\symbols\ntdll.dll\49901D48b2000\ntdll.dll

Image path: C:\WINDOWS\system32\ntdll.dll

Image name: ntdll.dll

Timestamp: Mon Feb 09 07:10:48 2009 (49901D48)

CheckSum: 000BC674

ImageSize: 000B2000

Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4

Viewing Process Handles

You can list information about a process’s open handles using the !handle command. The first argument to !handle is the handle value (or zero to list all handles) and the second argument is the level of information requested (zero displays the least information and 0xf displays the most information). The following command lists the least information for all handles in the current process context:

kd> !handle 0 0

processor number 0, process 823823e0

PROCESS 823823e0 SessionId: none Cid: 0260 Peb: 7ffde000

ParentCid: 0004

DirBase: 0a85d000 ObjectTable: e100d098 HandleCount: 19.

Image: smss.exe

Handle table at e13e9000 with 19 Entries in use

0004: Object: e1005448 GrantedAccess: 000f0003

0008: Object: 822e0d68 GrantedAccess: 00100020 (Inherit)

000c: Object: e17b73c0 GrantedAccess: 001f0001

0010: Object: e161ee80 GrantedAccess: 001f0001

0014: Object: e10044d0 GrantedAccess: 000f000f

0018: Object: e1645030 GrantedAccess: 000f000f

001c: Object: 822396b8 GrantedAccess: 00100001

0020: Object: e163d148 GrantedAccess: 000f0001

0024: Object: e17ac030 GrantedAccess: 000f000f

0028: Object: 8222dbe8 GrantedAccess: 001f0003

002c: Object: 82285480 GrantedAccess: 001f0003

0030: Object: 8222b1b0 GrantedAccess: 001f0fff

0034: Object: 8222b1b0 GrantedAccess: 00000400

0038: Object: e16095f0 GrantedAccess: 001f0001

003c: Object: e1805298 GrantedAccess: 001f0001

0040: Object: e1609820 GrantedAccess: 001f0001

0044: Object: e1fb6eb0 GrantedAccess: 001f0001

0048: Object: 82136800 GrantedAccess: 001f0fff

004c: Object: 821d2a70 GrantedAccess: 00000400

Each line in the output shows the handle value, the object’s address, and an access mask that describes the level of access granted for the object. As with any handle, the most important facts you’ll want to know are the object type (file object, mutex object, and so on) and the object name, if there is one. To find this out, specify a handle value this time when calling !handle and increase the level of information to the maximum:

kd> !handle 48 f

0048: Object: 82136800 GrantedAccess: 001f0fff Entry: e13e9090

Object: 82136800 Type: (823c8e70) Process

ObjectHeader: 821367e8 (old version)

HandleCount: 15 PointerCount: 336

Now you can tell that handle 48 is for a process object. This means you can find an EPROCESS object at 82136800. Therefore, you should be able to identify the process with the following command:

kd> !process 82136800 0

PROCESS 82136800 SessionId: 0 Cid: 02a8 Peb: 7ffdb000

ParentCid: 0260

DirBase: 0cf38000 ObjectTable: e15a1570 HandleCount: 577.

Image: winlogon.exe

At this point, you’ve identified that handle 48 in smss.exe is a handle to the winlogon.exe process. As shown in Figure 14-7, the handle value and interpretation is the same value you would see using a tool such as Process Hacker to examine smss.exe.

Figure 14-7: Process Hacker confirms that handle 48 is for a process named winlogon.exe.

f1407.eps

Recipe 14-7: Exploring Kernel Memory

This recipe introduces you to some of the WinDbg commands that you’ll likely execute when exploring kernel drivers and kernel memory.

Listing Loaded Modules

You can use the lm (list modules) command to list loaded modules, along with their start and end addresses in kernel memory and the file name on disk. To receive more information about the PE header values for the loaded module, you can pass the module’s base address to !dh or !lmi.

kd> lm f

start end module name

804d7000 806ed700 nt ntoskrnl.exe

806ee000 8070e300 hal halaacpi.dll

b22c8000 b2308a80 HTTP \SystemRoot\System32\Drivers\HTTP.sys

b2651000 b26a2880 srv \SystemRoot\system32\DRIVERS\srv.sys

[...]

kd> !dh b22c8000

File Type: EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

7 number of sections

480256BC time date stamp Sun Apr 13 14:53:48 2008

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic #

7.10 linker version

34500 size of code

C280 size of initialized data

0 size of uninitialized data

3B757 address of entry point

[...]

Viewing Pool Usage

When drivers allocate memory in the kernel, many of them use the ExAllocatePoolWithTag API function. The drivers can specify the size of the memory block, the type of memory (paged, non-paged, and so on), and a 4-byte ASCII tag to be associated with the memory. Here is a description of the function’s parameters:

PVOID ExAllocatePoolWithTag(

IN POOL_TYPE PoolType,

IN SIZE_T NumberOfBytes,

IN ULONG Tag

);

Parameters:

PoolType

The type of pool memory to allocate (PagedPool, NonPagedPool, etc)

NumberOfBytes

The number of bytes to allocate.

Tag

The 4-byte ASCII tag to be associated with the allocated memory.

Microsoft allows driver-defined tags to be associated with memory blocks to simplify debugging tasks, such as finding the source of a memory leak (for more information, see Who’s Using the Pool?7). It’s easy to find a memory-hogging application in user mode because monitoring programs show per-process memory usage. On the other hand, kernel drivers share the same memory pools, so it’s difficult to isolate the one driver that repeatedly fails to free memory.

Before you can benefit from pool tagging, you have to enable the tagging feature in the kernel (which takes effect after the next reboot). Then you can print statistics on how much memory is being tied up with each tag, and then hunt down which driver allocates memory with the suspect tags.

You can enable pool tagging on a target system in several ways:

· Use the global flags editor (glags.exe), which is distributed with the WDK.

· Use the !gflag WinDbg extension, like this:

kd> !gflag + ptg

Current NtGlobalFlag contents: 0x00000400

ptg - Enable pool tagging

· Use the Pooltag.exe program, which is distributed with the Windows Driver Kit (see Figure 14-8).

Figure 14-8: PoolTag enables pool tagging in the kernel.

f1408.tif

Regardless of how you choose to enable pool tagging, once it’s done, you can print statistics about the system’s pool usage. Figure 14-9 shows the Pooltag.exe application sorted by bytes used (highest to lowest). You can see that memory associated with the tag Gh05 is taking up the most memory.

Figure 14-9: Pools tagged with Gh05 are taking up the most memory.

f1409.tif

You can print similar statistics using the !poolused extension for WinDbg. Here is an example of how to print the pools in alphabetical order by tag, including a description of the tag’s purpose and source driver. The debugger reads descriptions from a plain text file named pooltag.txt with the format <pooltag> - <driver> - <description> so you can add to the known list of pool tags on your own.

kd> !poolused

Sorting by Tag

Pool Used:

NonPaged Paged

Tag Allocs Used Allocs Used

8042 4 3944 0 0 PS/2 kb and mouse,

Binary: i8042prt.sys

AcdN 2 1072 0 0 TDI AcdObjectInfoG

AcpA 3 192 1 504 ACPI arbiter data,

Binary: acpi.sys

AcpB 0 0 4 832 ACPI buffer data,

Binary: acpi.sys

[...]

Gh04 0 0 22 8368 GDITAG_HMGR_SPRITE_TYPE,

Binary: win32k.sys

Gh05 0 0 332 3488008 GDITAG_HMGR_SPRITE_TYPE,

Binary: win32k.sys

Gh08 0 0 8 8016 GDITAG_HMGR_SPRITE_TYPE,

Binary: win32k.sys

Gh09 0 0 1 616 GDITAG_HMGR_SPRITE_TYPE,

Binary: win32k.sys

Gh0< 0 0 105 3360 GDITAG_HMGR_SPRITE_TYPE,

Binary: win32k.sys

[...]

Proc 27 17280 0 0 Process objects,

Binary: nt!ps

PsQb 9 648 0 0 Process quota block,

Binary: nt!ps

The preceding output identified that the Gh05 tags are associated with memory owned by win32k.sys—which means they probably contain GDI objects. Based on pool tagging, you can also see that process objects (with tag Proc) are abundant in non-paged memory.

Finding Pool Allocations

Once you know the tag for an interesting (or suspicious) pool, you can use the !poolfind WinDbg extension to locate the addresses of all the memory blocks associated with the tag. For example, the following command shows pools with a Proc tag. If a rootkit calls ExAllocatePoolWithTag with a tag such as l33t, then you can use a similar command to hunt down all the kernel memory allocated by the rootkit.

kd> !poolfind Proc 0

Scanning large pool allocation table for Tag: Proc (823ec000 : 823f8000)

Searching NonPaged pool (81337000 : 82400000) for Tag: Proc

81f99d80 size: 8 previous size: 38 (Free) Pro.

81fbebc0 size: 280 previous size: 278 (Allocated) Proc (Protected)

81fc3680 size: 280 previous size: 30 (Allocated) Proc (Protected)

81fc9d80 size: 280 previous size: 98 (Free) Pro.

81fd5588 size: 280 previous size: 108 (Allocated) Proc (Protected)

81ff0930 size: 8 previous size: 40 (Free) Pro.

81ffd688 size: 280 previous size: 8 (Allocated) Proc (Protected)

82000770 size: 280 previous size: 40 (Allocated) Proc (Protected)

[...]

The output shows that !poolfind located several allocations with the Proc tag. Some are free (perhaps previously used for process objects that terminated) and some are allocated and protected (probably containing process objects for active processes). Because you know the structure for a process object (i.e., _EPROCESS), you can use that to get detailed information about each allocation. The following command shows how to determine the process name for the allocation at 81fbebc0:

kd> dt _EPROCESS 81fbebc0 + 8 + 18

nt!_EPROCESS

+0x000 Pcb : _KPROCESS

+0x06c ProcessLock : _EX_PUSH_LOCK

+0x070 CreateTime : _LARGE_INTEGER 0x1cada55'd9ffb16e

+0x078 ExitTime : _LARGE_INTEGER 0x0

+0x080 RundownProtect : _EX_RUNDOWN_REF

+0x084 UniqueProcessId : 0x00000120

[...]

+0x168 Filler : 0

+0x170 Session : 0xf8a94000

+0x174 ImageFileName : [16] "sqlservr.exe"

+0x184 JobLinks : _LIST_ENTRY [ 0x0 - 0x0 ]

+0x18c LockedPagesList : (null)

Why did we add 8 and 18 bytes (hex) to the pool allocation? It’s because each pool begins with a _POOL_HEADER structure, which is 8 bytes on the XP system that we used for testing. In the case of process objects, the pool header is then followed by an _OBJECT_HEADER, which is 18 bytes. After that, the _EPROCESS structure begins.

Finding the Pool Tag for an Address

You can use the !pool command to perform a reverse lookup on an address. If you have an address and don’t know its purpose, you can query for the associated tag, like this:

kd> !pool 81f4b270

Pool page 81f4b270 region is Nonpaged pool

81f4b000 size: 1d0 previous size: 0 (Free) Irp

81f4b1d0 size: 30 previous size: 1d0 (Allocated) Even (Protected)

81f4b200 size: 10 previous size: 30 (Free) Irp

81f4b210 size: 30 previous size: 10 (Allocated) Vad

81f4b240 size: 30 previous size: 30 (Allocated) Vad

*81f4b270 size: 10 previous size: 30 (Free) *File

Pooltag File : File objects

81f4b280 size: 98 previous size: 10 (Allocated) File (Protected)

81f4b318 size: 40 previous size: 98 (Allocated) Vadl

Now that you’ve determined the address 81f4b270 to be within a memory pool marked with the File tag, you can bet it’s a pool that contains a _FILE_OBJECT structure.

Additional Information

You should note the following points about pool tagging:

· The default pooltag.txt contains descriptions for tags used by most of the Microsoft drivers, but not for all third-party drivers, much less rootkits. One way you can hunt down the associated driver on disk, assuming it isn’t packed, is by searching your system32\drivers directory for .sys files that contain the 4-byte ASCII pool tag (see How to find pool tags used by third-party drivers8).

· The kernel does not prevent a rootkit from calling ExAllocatePoolWithTag with a tag used for a legitimate purpose. For example, a rootkit could allocate memory from the non-paged pool with the tag Proc and use it to store a list of command and control servers. You could catch these attempts by performing sanity checks on the content—something memory forensics frameworks do to reduce false positives when scanning for objects. For example, you could check if the process ID is valid, based on the maximum number of processes your system supports (see Pushing the Limits of Windows: Processes and Threads9). If the claimed process ID is something like 0xF7175511, then the memory you found in a pool marked with a Proc tag either contains an old, partially overwritten process object, or it never contained a process object in the first place. Also, be aware that rootkits can allocate memory using ExAllocatePool, which does not assign tags at all.

· For more information on pool headers and object headers, see Andreas Schuster’s Searching for processes and threads in Microsoft Windows memory dumps.10 If you don’t know the object’s structure, or if the memory doesn’t contain an object at all, then you can just explore it with commands such as db and dd.

7 http://www.microsoft.com/whdc/driver/tips/PoolMem.mspx

8 http://support.microsoft.com/kb/298102

9 http://blogs.technet.com/markrussinovich/archive/2009/07/08/3261309.aspx

10 http://www.dfrws.org/2006/proceedings/2-Schuster.pdf

Recipe 14-8: Catching Breakpoints on Driver Load

dvd1.eps

You can find supporting material for this recipe on the companion DVD.

The best place to start debugging a rootkit driver is at its entry point address. Why? Well, for the same reason that you typically debug processes starting with their entry points. If you allow any instructions to execute before your debugger gets control, then the malware could disable your debugger or complete installation before you even get the chance to analyze it.

One of the issues with catching a breakpoint on a driver’s entry point address is that you won’t know where to set the breakpoint until the driver loads. You can’t add the ImageBase and AddressOfEntryPoint values in the driver’s PE header and determine the address of the first instruction as you can for executable (.exe) Win32 programs. This is because executables are first to load in their own private address space, so there shouldn’t be any address conflicts. Drivers, on the other hand, share the same address space with all other drivers and will need to be re-based.

Before you get started, let’s review some of the methods that malware can use to load a driver. The techniques you use to catch breakpoints will depend on how the driver was loaded.

· ZwLoadDriver: Malware can load drivers by calling this API function, which exists on XP and later systems.

· Services: Malware can load drivers by installing them as a service and then starting the service.

· ZwSetSystemInformation: Malware can load drivers by calling this API function with the SystemLoadAndCallImage class.

Table 14-3 contains a summary of the different techniques discussed in this recipe, along with their primary advantages and disadvantages.

Table 14-3: Methods of Catching Breakpoints on Driver Load

Method

Advantage

Disadvantage

Deferred BP

Works for all loading methods

Requires prior knowledge of driver’s name and entry point address

Hard-coded BP

Not WinDbg-specific, works for all loading methods

Requires CRC update, will not work on signed drivers, and must have access to the driver’s file on disk before it loads

Loading a test driver

Not WinDbg-specific

Requires a separate breakpoint for different loading methods, may require recompiling the test driver for your target platform

Event exceptions

Does not require prior knowledge of driver name or prior access to driver’s file on disk, works for all loading methods

Requires a few additional commands after catching the exception

In the following discussions, you will need to know how to load a driver for the purposes of analyzing it. Here are a few techniques you can use:

· Use the sc.exe command11 to create a service for the driver.

· Use Process Hacker (click Tools⇒ Create Service).

· Use the DLoad12 utility from Code Project—this is a GUI tool that lets you load a driver using ZwLoadDriver, ZwSetSystemInformation, or by using Services.

· Double-click malware that installs the driver you want to analyze.

Deferred Breakpoints

You can set deferred breakpoints with the bu command (the u stands for unresolved, which is interchangeable with deferred in this case). The significance of these breakpoints is that WinDbg allows you to set them even if the target driver has not loaded yet. In the future, whenever a new driver loads, WinDbg checks if the driver contains the routine for which you set a deferred breakpoint. If so, WinDbg converts the routine to an address and sets the breakpoint.

The following command shows you how to use deferred breakpoints, assuming your driver is named mydriver.sys and it contains a function named DriverEntry. When you use the bl (breakpoint list) command to list the breakpoints, you’ll see parentheses around the routine name, which indicates that WinDbg was not able to resolve the routine in any currently loaded driver (as expected).

kd> bu mydriver!DriverEntry

kd> bl

0 eu 0001 (0001) (mydriver!DriverEntry)

At this point, you can use the g (go) command to let the target system execute. On the target system, load mydriver.sys. Your breakpoint should trigger like this:

kd> g

Breakpoint 0 hit

mydriver!DriverEntry:

f8c534b0 8bff mov edi,edi

One weakness with deferred breakpoints is that drivers aren’t required to export a function named DriverEntry—they can have any name the programmer desires. Thus, in many cases, your deferred breakpoint, based on locating DriverEntry, will fail and the driver will execute beyond your control.

To avoid this unwanted execution, you could look up the AddressOfEntryPoint value in the driver’s PE header and use that as a relative offset from the driver name when setting a breakpoint. This would take care of issues regarding function names. Assuming the driver’s AddressOfEntryPoint is 0x605, you could use the following command:

kd> bu mydriver+605

kd> bl

0 eu 0001 (0001) (mydriver+605)

In this case, you must at least know the driver’s name ahead of time. In addition, you need the AddressOfEntryPoint value, which requires that you parse the driver’s PE header before it loads. If you’re dealing with malware that drops a randomly named driver each time, or tries to prevent other programs from accessing its driver on disk, then you might need to use an anti-rootkit tool such as GMER to locate and extract the driver first.

Hard-coding Breakpoints

By hard-coding a breakpoint into the driver’s file on disk, you can be sure to catch it when the driver loads. This eliminates the need to set special breakpoints in your debugger, but it requires that you make a modification to the driver on disk. Specifically, you would look up the driver’s AddressOfEntryPoint value and replace the first byte of the function with 0xCC (an INT 3software breakpoint). The following commands show you how to make the required changes with pefile and then update the CRC checksum (otherwise some versions of Windows will reject the driver entirely). Make sure you save the original byte that you overwrite because you’ll need to replace it once the driver loads.

$ python

>>> import pefile

>>> pe = pefile.PE("mydriver.sys")

>>> orig_byte = pe.get_data(pe.OPTIONAL_HEADER.AddressOfEntryPoint, 1)

>>> print "Original: %x" % ord(orig_byte)

Original: 8b

>>> pe.set_bytes_at_rva(pe.OPTIONAL_HEADER.AddressOfEntryPoint,

chr(0xCC))

True

>>> pe.OPTIONAL_HEADER.CheckSum = pe.generate_checksum()

>>> pe.write("output.sys")

After applying the patch, regardless of how the driver is loaded, you should catch a breakpoint on its entry point function. Use the eb (edit byte) command in WinDbg to replace the original byte that you overwrote with 0xCC, and then you can continue debugging the driver.

kd> g

Break instruction exception - code 80000003 (first chance)

output+0x605:

bfaf1605 cc int 3

kd> u eip

output+0x605:

bfaf1605 cc int 3

bfaf1606 ff558b call dword ptr [ebp-75h]

bfaf1609 ec in al,dx

bfaf160a a18415afbf mov eax,dword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eax,eax

bfaf1611 b940bb0000 mov ecx,0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eax,ecx

kd> eb bfaf1605 8b

kd> u eip

output+0x605:

bfaf1605 8bff mov edi,edi

bfaf1607 55 push ebp

bfaf1608 8bec mov ebp,esp

bfaf160a a18415afbf mov eax,dword ptr [output+0x584 (bfaF1484)]

bfaf160f 85c0 test eax,eax

bfaf1611 b940bb0000 mov ecx,0BB40h

bfaf1616 7404 je output+0x61c (bfaf161c)

bfaf1618 3bc1 cmp eax,ecx

The disadvantage to hard-coding breakpoints is that you need access to the driver’s file on disk prior to loading it. If you’re analyzing malware that drops a driver on the fly and then loads it, you may need to recover the driver first. Furthermore, this technique won’t work for drivers that are cryptographically signed.

Loading a Test Driver

This method involves loading a test driver on your target system before executing malware. When the test driver loads, it looks on the stack to determine which instruction called the driver’s entry point—which you can then use as your breakpoint address. If the malware loads a malicious driver using the same technique as you used to load the test driver, your breakpoint will trigger at the right time—immediately before the malicious driver’s entry point is called.

The following is the source code for the test driver, named DriverEntryFinder, which you can find on the DVD.

#include "ntddk.h"

#include <stdio.h>

NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)

{

return 0;

}

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObj,

IN PUNICODE_STRING DriverReg)

{

int RETADDR;

// look on the stack to see who called us...

// the return address for the caller should

// be at +12 bytes relative to the ESP register

__asm {

push edx

mov edx, [esp+12]

mov [RETADDR], edx

pop edx

};

DbgPrint("The BP address depends on your load method:\n");

DbgPrint(" 1 - ZwLoadDriver\n");

DbgPrint(" 2 - Services\n");

DbgPrint(" 3 - ZwSystemSystemInformation\n");

DbgPrint("BP address if you used 1 or 2: 0x%x\n", RETADDR-3);

DbgPrint("BP address if you used 3: 0x%x\n", RETADDR-2);

DriverObj->DriverUnload = DriverUnload;

return STATUS_SUCCESS;

}

To use DriverEntryFinder, simply load it on your target system using the desired method (ZwLoadDriver, ZwSetSystemInformation, or Services). As described in Table 14-3, the breakpoint address will differ depending on how the driver is loaded. If you use ZwLoadDriver or the Services method, the breakpoint address will be inside a function named nt!IopLoadDriver. If you usent!ZwSetSystemInformation, the breakpoint address will be inside nt!ZwSetSystemInformation. Therefore, you should use DriverEntryFinder to locate all possible breakpoint addresses—unless you already know which method your malware sample uses.

If you’re already attached to your target with WinDbg, then you’ll see the DriverEntryFinder’s output in your WinDbg window. Otherwise, you can see the output with DebugView.

kd> g

The BP address depends on your load method:

1 - ZwLoadDriver

2 - Services

3 - ZwSystemSystemInformation

BP address if you used 1 or 2: 0x805a39aa

BP address if you used 3: 0x805a39ab

kd> ln 0x805a39aa

(805a35a9) nt!IopLoadDriver+0x66a

kd> u 0x805a39aa

nt!IopLoadDriver+0x66a:

805a39aa ff572c call dword ptr [edi+2Ch]

kd> bp nt!IopLoadDriver+0x66a

The output from the program prints two BP addresses. It is up to you to pick the right one based on how you loaded the driver. For example, if you used ZwLoadDriver (method 1), then the correct BP address is 0x805a39aa. The call instruction that you see at this address leads to the driver’s entry point!

Event Exceptions

You can configure how WinDbg handles events, including how the debugger reacts when new drivers load, new processes start, new threads start, and so on. This is probably the most straightforward way to catch a breakpoint on loading drivers. To view how WinDbg currently handles particular events, use the sx (set exception) command, like this:

kd> sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - ignore

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output – output

[...]

As you can see, WinDbg currently ignores the load module event (module is a synonym for driver in this case, but can also refer to user mode DLLs). If you want to gain control whenever a new module loads, you can reconfigure it like this:

kd> sxe ld

kd> sx

ct - Create thread - ignore

et - Exit thread - ignore

cpr - Create process - ignore

epr - Exit process - ignore

ld - Load module - break

ud - Unload module - ignore

ser - System error - ignore

ibp - Initial breakpoint - ignore

iml - Initial module load - ignore

out - Debuggee output – output

[...]

Most of the events can accept arguments so that WinDbg doesn’t break when any driver loads or when any process starts—you can tailor it by name. However, assuming you don’t know the name of the driver to be loaded, you can just use the sxe ld command and it will cause WinDbg to break for all drivers. Once that is set, you can execute the malware that loads a driver, and you should see something like this:

kd> g

nt!DebugService2+0x10:

80506d3e cc int 3

Now, find the newly loaded driver and set a normal breakpoint at its entry point address.

kd> lm n

start end module name

804d7000 806ed700 nt ntoskrnl.exe

806ee000 8070e300 hal halaacpi.dll

b21cd000 b220da80 HTTP HTTP.sys

bfaf3000 bfaf3780 mydriver mydriver.sys

[...]

kd> !dh -a bfaf3000

File Type: EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (i386)

5 number of sections

4AA83235 time date stamp Wed Sep 09 18:54:45 2009

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

10E characteristics

Executable

Line numbers stripped

Symbols stripped

32 bit word machine

OPTIONAL HEADER VALUES

10B magic #

7.10 linker version

180 size of code

180 size of initialized data

0 size of uninitialized data

605 address of entry point

[...]

kd> bp mydriver+605

kd> bl

0 e bfaf3605 0001 (0001) mydriver+0x605

kd> g

Breakpoint 0 hit

mydriver+0x605:

bfaf3605 8bff mov edi,edi

The address bfaf3605 is the entry point address for mydriver.sys. On any given system, there may be hundreds of drivers loaded, and if you’re not familiar with their names, it will be difficult to spot the one new driver that triggered your breakpoint. In this case, you can use .logopen as discussed in Recipe 14-5 to save the output of lm n before you execute malware. When your breakpoint triggers, re-run lm n and use diff on the log file to identify which driver is new.

11 http://support.microsoft.com/kb/251192

12 http://www.codeproject.com/KB/system/DLoad.aspx

Recipe 14-9: Unpacking Drivers to OEP

Assuming you’ve followed the instructions in the previous recipe, you can execute malware on a target system and expect to catch the breakpoint when a new driver loads. This gives you the ability to inspect the driver’s load parameters, unpack the driver, and understand its run-time behavior via debugging. It’s worth mentioning that if you get really lucky and run into a packed driver that doesn’t make any API calls during its unpacking routine, you might be able to unpack it with a user mode debugger (see the inReverse blog13). The example we use for this recipe is a variant of the Tibs malware—which you can find more about on ThreatExpert’s website.14

Investigating the Driver Object

First, make sure the target system is running by typing g for go. Then execute the malware on your target system. Assuming the driver was loaded with ZwLoadDriver or via Services, you’ll see something like this:

kd> g

Breakpoint 0 hit

nt!IopLoadDriver+0x66a:

805a39aa ff572c call dword ptr [edi+2Ch]

Before moving further, you may want to pause and gather some information about the loading driver. The value in the edi register is a pointer to the loading driver’s _DRIVER_OBJECT structure. Why does the instruction in IopLoadDriver call the member at 2Ch of this structure? Well, let’s see:

kd> dt _DRIVER_OBJECT [edi]

nt!_DRIVER_OBJECT

+0x000 Type : 4

+0x002 Size : 168

+0x004 DeviceObject : (null)

+0x008 Flags : 2

+0x00c DriverStart : 0xb2034000

+0x010 DriverSize : 0x25880

+0x014 DriverSection : 0x820e2da0

+0x018 DriverExtension : 0x8205e2f0 _DRIVER_EXTENSION

+0x01c DriverName : _UNICODE_STRING

"\Driver\windev-6ec4-1ec9"

+0x024 HardwareDatabase : 0x8068fa90 _UNICODE_STRING

"\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"

+0x028 FastIoDispatch : (null)

+0x02c DriverInit : 0xb2058a00

+0x030 DriverStartIo : (null)

+0x034 DriverUnload : (null)

+0x038 MajorFunction : [28] 0x804fa87e

nt!IopInvalidDeviceRequest+0

The preceding output shows that the driver’s DriverInit (entry point function) value exists at offset 2Ch of the _DRIVER_OBJECT structure—that’s why IopLoadDriver calls it. You can also see the following information about the driver:

· DeviceObject: This member is currently NULL, which means the driver has not yet initialized any devices (for example, through the use of IoCreateDevice or IoCreateDeviceSecure). If a driver creates any devices at all, it typically does so in the DriverEntry function, which hasn’t executed yet, which is why it is currently NULL.

· DriverStart: This member specifies the driver’s load address in kernel memory.

· DriverSize: This member specifies the size in bytes of the driver’s binary in memory (as per the SizeOfImage field in the PE header).

· DriverName: This member specifies the driver’s name.

· DriverInit: This member specifies the address of the driver’s entry point function.

· DriverUnload: This member specifies the virtual address of a function to be called when the driver unloads. In this case, the value is NULL because the driver hasn’t been allowed to execute long enough to set its unload function yet.

· MajorFunction: This is an array of 28 IRP (Input/Output Request Packet) handlers that are currently all initialized to the default nt!IopInvalidDeviceRequest.

To get to the driver’s entry point function from your breakpoint in IopLoadDriver, you just need to execute a single instruction (call dword ptr [edi+2Ch]). When you type the t (trace) command, it executes a single instruction and then prints the location and disassembly of the next instruction, like this:

kd> t

windev_6ec4_1ec9+0x24a00:

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

The output shows that the new driver’s name is windev_6ec4_1ec9.sys. Also, notice how the next instruction is at b2058a00, which is the same value you saw in the DriverInit member of the _DRIVER_OBJECT structure. This verifies that you’ve reached the driver’s entry point function. However, this isn’t necessarily the original entry point function (i.e., before being packed).

Unpacking Stage One

Microsoft defined the driver entry point function as follows:

NTSTATUS DriverEntry(

IN PDRIVER_OBJECT DriverObject,

IN PUNICODE_STRING RegistryPath

);

The important part to remember is that a pointer to the driver’s own _DRIVER_OBJECT is passed as its first parameter. You can print a disassembly of the entire entry point function, like this:

kd> uf .

windev_6ec4_1ec9+0x24a00:

b2058a00 e81c000000 call windev_6ec4_1ec9+0x24a21 (b2058a21)

b2058a05 60 pushad

b2058a06 b97c040000 mov ecx,47Ch ; this is the loop counter

windev_6ec4_1ec9+0x24a0b:

b2058a0b 812a7338483f sub dword ptr [edx],3F483873h ; unpack key

b2058a11 83c204 add edx,4 ; scan to next 4 bytes

b2058a14 83e904 sub ecx,4 ; subtract 4 from the loop counter

b2058a17 85c9 test ecx,ecx ; is the counter zero?

b2058a19 75f0 jne windev_6ec4_1ec9+0x24a0b (b2058a0b)

windev_6ec4_1ec9+0x24a1b:

b2058a1b 61 popad

b2058a1c 83c208 add edx,8

b2058a1f ffe2 jmp edx ; jump to unpacked code

The entry point calls a function at b2058a21 so you can explore that function as well:

kd> uf b2058a21

windev_6ec4_1ec9+0x24a21:

; moves the DriverObject into edx

b2058a21 8b542408 mov edx,dword ptr [esp+8]

; moves the DriverObject->DriverStart into edx

b2058a25 8b520c mov edx,dword ptr [edx+0Ch]

b2058a28 81c280530200 add edx,25380h

b2058a2e b835580200 mov eax,25835h

b2058a33 c3 ret

According to the disassemblies, the purpose of the function at b2058a21 is to copy the driver’s load address (DriverObject->DriverStart) into the edx register, add 25380 to the value, and then return. The entry point function then initializes a loop counter to 47c and subtracts 3F483873 from each 4 bytes starting at the value pointed to by edx (which presumably is the start of the packed code) until the loop counter reaches 0. Once the simple round of decoding is complete, the driver jumps to edx+8, which is either the program’s original entry point (OEP) or the next layer of packing.

The following command steps over the function at b2058a21 because you know what it does now:

kd> p

windev_6ec4_1ec9+0x24a05:

b2058a05 60 pushad

At this time, the edx register should contain a pointer to the packed code. You can verify by printing a hexdump and disassembly. Notice how the disassembly contains instructions such as aas and les that you don’t typically see—that’s a sign that the code is packed, which makes sense because you haven’t unpacked it yet.

kd> r edx

edx=b2059380

kd> db edx

b2059380 7338483f7338483f-c88b9e96c420493f s8H?s8H?..... I?

b2059390 7338a5c0602b607f-7320dc4173384907 s8..'+'.s .As8I.

b20593a0 fe38d1c40053883f-fcc5f559b3384bcc .8...S.?...Y.8K.

b20593b0 1453883ffcc5155a-b338d3fc4853883f .S.?...Z.8..HS.?

b20593c0 76f5f559b338d5f4-7e54883f2c6d483f v..Y.8..~T.?,mH?

b20593d0 732bedccf833647f-73c3e5ec8d78483e s+...3d.s....xH>

b20593e0 28ea627f7337fee4-8d7848a974889b27 (.b.s7...xH.t..'

b20593f0 003b483ffebde159-b338cdffe74f983e .;H?...Y.8...O.>

kd> u edx

windev_6ec4_1ec9+0x25380:

b2059380 7338 jae windev_6ec4_1ec9+0x253ba (b20593ba)

b2059382 48 dec eax

b2059383 3f aas

b2059384 7338 jae windev_6ec4_1ec9+0x253be (b20593be)

b2059386 48 dec eax

b2059387 3f aas

b2059388 c88b9e96 enter 9E8Bh,96h

b205938c c420 les esp,fword ptr [eax]

You can let the driver unpack itself by allow it to execute until it reaches the jmp edx instruction at b20581af, like this:

kd> g b2058a1f

windev_6ec4_1ec9+0x24a1f:

b2058a1f ffe2 jmp edx

Did it work? If so, you should see an entirely new set of bytes at the same addresses as before.

kd> db edx

b2059388 5553565751e80000-00005d81edf21740 USVWQ.....]....@

b2059398 00e89302000001c8-8b0089858d1a4000 ..............@.

b20593a8 898dad1a4000038d-a11a4000898dcd1a ....@.....@.....

b20593b8 40008bbdd51a4000-03bdad1a40008db5 @.....@.....@...

b20593c8 0b1c4000b9340000-00f3a48d85fb1b40 ..@..4.........@

b20593d8 008b9dad1a4000ff-b5b11a4000ffb5a5 .....@.....@....

b20593e8 1a40006a015053e8-8d0200008b85991a .@.j.PS.........

b20593f8 400085c0741750ff-b5c51a4000ffb5ad @...t.P....@....

kd> u edx

windev_6ec4_1ec9+0x25388:

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp,4017F2h

Great! The data has been decoded in memory and now represents valid instructions. Now you can use the t command to execute the jmp instruction, which will take you to b2059388. Then disassemble the entire function revealed by the first layer of packing.

kd> t

windev_6ec4_1ec9+0x25388:

b2059388 55 push ebp

kd> uf .

windev_6ec4_1ec9+0x25388:

b2059388 55 push ebp

b2059389 53 push ebx

b205938a 56 push esi

b205938b 57 push edi

b205938c 51 push ecx

b205938d e800000000 call windev_6ec4_1ec9+0x25392 (b2059392)

b2059392 5d pop ebp

b2059393 81edf2174000 sub ebp,4017F2h

b2059399 e893020000 call windev_6ec4_1ec9+0x25631 (b2059631)

b205939e 01c8 add eax,ecx

b20593a0 8b00 mov eax,dword ptr [eax]

b20593a2 89858d1a4000 mov dword ptr [ebp+401A8Dh],eax

b20593a8 898dad1a4000 mov dword ptr [ebp+401AADh],ecx

b20593ae 038da11a4000 add ecx,dword ptr [ebp+401AA1h]

b20593b4 898dcd1a4000 mov dword ptr [ebp+401ACDh],ecx

b20593ba 8bbdd51a4000 mov edi,dword ptr [ebp+401AD5h]

b20593c0 03bdad1a4000 add edi,dword ptr [ebp+401AADh]

b20593c6 8db50b1c4000 lea esi,[ebp+401C0Bh]

b20593cc b934000000 mov ecx,34h

b20593d1 f3a4 rep movs byte ptr es:[edi],byte ptr [esi]

b20593d3 8d85fb1b4000 lea eax,[ebp+401BFBh]

b20593d9 8b9dad1a4000 mov ebx,dword ptr [ebp+401AADh]

b20593df ffb5b11a4000 push dword ptr [ebp+401AB1h]

b20593e5 ffb5a51a4000 push dword ptr [ebp+401AA5h]

b20593eb 6a01 push 1

b20593ed 50 push eax

b20593ee 53 push ebx

b20593ef e88d020000 call windev_6ec4_1ec9+0x25681 (b2059681)

b20593f4 8b85991a4000 mov eax,dword ptr [ebp+401A99h]

b20593fa 85c0 test eax,eax

b20593fc 7417 je windev_6ec4_1ec9+0x25415 (b2059415)

windev_6ec4_1ec9+0x253fe:

b20593fe 50 push eax

b20593ff ffb5c51a4000 push dword ptr [ebp+401AC5h]

b2059405 ffb5ad1a4000 push dword ptr [ebp+401AADh]

b205940b e835000000 call windev_6ec4_1ec9+0x25445 (b2059445)

b2059410 e812000000 call windev_6ec4_1ec9+0x25427 (b2059427)

windev_6ec4_1ec9+0x25415:

b2059415 e8b3000000 call windev_6ec4_1ec9+0x254cd (b20594cd)

b205941a 8b85cd1a4000 mov eax,dword ptr [ebp+401ACDh]

b2059420 59 pop ecx

b2059421 5f pop edi

b2059422 5e pop esi

b2059423 5b pop ebx

b2059424 5d pop ebp

b2059425 ffe0 jmp eax ; jump to unpacked code

The output shows calls to six subroutines (which, for the sake of brevity, we will not show here) and a similar-looking jump near the end. It is generally unsafe to simply play until you reach the final jump because the driver may execute anti-debugging code or complete installation in one of the six subroutines. Therefore, you should disassemble each subroutine to get an idea of what they do, and then determine the next steps. In this case, you’ll see that they only seem to contain more unpacking code. Therefore, you can, in fact, safely execute the driver until it reaches the jump near the end, and then follow the jump and see where you end up.

kd> g b2059425

windev_6ec4_1ec9+0x25425:

b2059425 ffe0 jmp eax

kd> t

windev_6ec4_1ec9+0x24b8c:

b2058b8c 8bff mov edi,edi

kd> uf .

windev_6ec4_1ec9+0x24aee:

b2058aee 8bff mov edi,edi

b2058af0 55 push ebp

b2058af1 8bec mov ebp,esp

b2058af3 56 push esi

b2058af4 ff750c push dword ptr [ebp+0Ch]

b2058af7 8b7508 mov esi,dword ptr [ebp+8] ; DriverObject

b2058afa 56 push esi

b2058afb e806ffffff call windev_6ec4_1ec9+0x24a06 (b2058a06)

b2058b00 85c0 test eax,eax

b2058b02 757e jne windev_6ec4_1ec9+0x24b82 (b2058b82)

windev_6ec4_1ec9+0x24b04:

b2058b04 b9464403b2 mov ecx,offset

windev_6ec4_1ec9+0x446 (b2034446)

; setting the 28 IRP handler functions

b2058b09 898ea4000000 mov dword ptr [esi+0A4h],ecx

b2058b0f 898ea0000000 mov dword ptr [esi+0A0h],ecx

b2058b15 898e9c000000 mov dword ptr [esi+9Ch],ecx

b2058b1b 898e98000000 mov dword ptr [esi+98h],ecx

b2058b21 898e94000000 mov dword ptr [esi+94h],ecx

b2058b27 898e90000000 mov dword ptr [esi+90h],ecx

b2058b2d 898e8c000000 mov dword ptr [esi+8Ch],ecx

b2058b33 898e88000000 mov dword ptr [esi+88h],ecx

b2058b39 898e84000000 mov dword ptr [esi+84h],ecx

b2058b3f 898e80000000 mov dword ptr [esi+80h],ecx

b2058b45 894e7c mov dword ptr [esi+7Ch],ecx

b2058b48 894e78 mov dword ptr [esi+78h],ecx

b2058b4b 894e74 mov dword ptr [esi+74h],ecx

b2058b4e 894e70 mov dword ptr [esi+70h],ecx

b2058b51 894e6c mov dword ptr [esi+6Ch],ecx

b2058b54 894e68 mov dword ptr [esi+68h],ecx

b2058b57 894e64 mov dword ptr [esi+64h],ecx

b2058b5a 894e60 mov dword ptr [esi+60h],ecx

b2058b5d 894e5c mov dword ptr [esi+5Ch],ecx

b2058b60 894e58 mov dword ptr [esi+58h],ecx

b2058b63 894e54 mov dword ptr [esi+54h],ecx

b2058b66 894e50 mov dword ptr [esi+50h],ecx

b2058b69 894e4c mov dword ptr [esi+4Ch],ecx

b2058b6c 894e48 mov dword ptr [esi+48h],ecx

b2058b6f 894e44 mov dword ptr [esi+44h],ecx

b2058b72 894e40 mov dword ptr [esi+40h],ecx

b2058b75 894e3c mov dword ptr [esi+3Ch],ecx

b2058b78 894e38 mov dword ptr [esi+38h],ecx

; setting DriverObject->DriverUnload

b2058b7b c74634744403b2 mov dword ptr [esi+34h],offset

windev_6ec4_1ec9+0x474 (b2034474)

[...]

This time, when you print the disassembly of the function you’ve reached, you’ll see some code that you typically see in an (unpacked) driver’s entry point. In particular, the function sets the driver’s unload action and initializes the table of 28 IRP handlers. You can see it move [ebp+8], which is the function’s first argument (a pointer to the driver’s _DRIVER_OBJECT) into the esiregister. Then it moves the address of a subroutine at b2034446 into the ecx register—this is presumably the default IRP handler or I/O dispatcher. It moves the subroutine’s address into all 28 slots of the MajorFunction table. How do you know all those offsets from esi are slots in the MajorFunction table? If you look at the beginning of this recipe where it shows the format of a_DRIVER_OBJECT, you’ll see that the DriverUnload function exists at offset 34h and the MajorFunction table begins at 38h. Therefore, [esi+38h] is MajorFunction[0], [esi+3Ch] is MajorFunction[1], and so on.

13 http://www.inreverse.net/?p=327

14 http://www.threatexpert.com/reports.aspx?page=1&find=windev

Recipe 14-10: Dumping and Rebuilding Drivers

dvd1.eps

You can find supporting materials for this recipe on the companion DVD.

The tools we introduced in the unpacking section of Chapter 12 (such as LordPE, ProcDump, and Import REConstructor) don’t operate in kernel mode. If you need to extract a driver, or code from an arbitrary pool in kernel memory, one option is to use Volatility and the associated plug-ins (see Recipe 16-9). This recipe shows an alternate method, which involves using WinDbg to dump the driver. Then you can open the dumped file in IDA Pro for more in-depth static analysis.

Dumping the Driver

First, you’ll need to determine the memory range you want to dump. There are a few ways that you can go about finding that information:

· If you’ve unpacked the driver to OEP, as shown in the previous recipe, or if you were able to spot the malicious driver by using anti-rootkit tools (see Recipe 10-6), then you know the name and/or base address of the driver.

· If you know the starting address of a thread created by a malicious driver, you can dump memory at the thread’s start address and search backwards in memory to find the corresponding MZ header (if there is one).

· If you search kernel memory for any MZ headers that aren’t in the list of loaded modules per the lm command, then you might have found a rootkit hiding.

The technique you use to find a suspicious memory range will vary between cases. In this example, we’ll continue using the driver from the previous recipe that you unpacked to OEP. The following command identifies its start and end address:

kd> lm n

start end module name

804d7000 806ed700 nt ntoskrnl.exe

806ee000 8070e300 hal halaacpi.dll

b2034000 b2059880 windev_6ec4_1ec9 windev-6ec4-1ec9.sys

[...]

The following command dumps a copy of the driver’s memory to disk. When you do this, the dumped copy is saved to your debugging machine (the one on which you run WinDbg) and not the target. You specify the output file name, starting address, and number of bytes to read from the starting address like this:

kd> .writemem c:\unpacked.sys b2034000 Lb2059880-b2034000

Writing 25880 bytes.........................

Repairing the Driver

If you plan to analyze the dumped driver in IDA, you need to take a few additional steps.

1. Repair the PE header. The dumped driver contains the original PE header, so it reflects the default ImageBase rather than the driver’s real load address. Furthermore, in this case it reflects the packed driver’s AddressOfEntryPoint value rather than the unpacked driver’s entry point (OEP). The real load address is b2034000—the same as what you typed to dump the driver. The OEP address is shown in Recipe 14-9, but here it is again as a refresher:

kd> uf .

windev_6ec4_1ec9+0x24aee:

b2058aee 8bff mov edi,edi

b2058af0 55 push ebp

b2058af1 8bec mov ebp,esp

[...]

You can apply the changes using any PE editor, or you can do it on the command line with pefile. Remember that the AddressOfEntryPoint is relative to the ImageBase, not the absolute address.

$ python

>>> import pefile

>>> pe = pefile.PE("unpacked.sys")

>>> orig_ImageBase = pe.OPTIONAL_HEADER.ImageBase

>>> orig_AddressOfEntryPoint = pe.OPTIONAL_HEADER.AddressOfEntryPoint

>>> pe.OPTIONAL_HEADER.ImageBase = 0xb2034000

>>> pe.OPTIONAL_HEADER.AddressOfEntryPoint = (0xb2058aee - 0xb2034000)

>>> pe.write("unpacked.sys")

>>> print "Old Base: %x\nNew Base: %x\nOld EP: %x\nNew EP: %x\n" % (

orig_ImageBase,

newpe.OPTIONAL_HEADER.ImageBase,

orig_AddressOfEntryPoint,

newpe.OPTIONAL_HEADER.AddressOfEntryPoint)

Old Base: 10000

New Base: b2034000

Old EP: 24a00

New EP: 24aee

2. Load the driver in IDA. Because the file type is a kernel driver, IDA automatically labels the entry point function as DriverEntry and labels its parameters accordingly. Figure 14-10 shows how this should appear.

Figure 14-10: The unpacked driver loaded into IDA Pro

f1410.tif

3. Examine the code. You’ll notice if you browse other functions in the driver that the Import Address Table (IAT) is not properly rebuilt. This is the same problem you will run into when unpacking user mode programs (see Recipe 12-10) and when extracting processes and drivers from memory dumps (see Recipe 16-8). Figure 14-11 shows you how the unrepaired disassembly appears in IDA Pro. Instead of API function names, you can only see calls to addresses.

Figure 14-11: TWithout repairing the IAT, you can’t see API function names.

f1410.tif

4. Find the IAT. To do this, find an IAT entry in WinDbg or in the IDA Pro disassembly. Figure 14-11 shows two—dword_B2035230 and dword_B203522C. For this purpose, you’ll want to use the lowest address because you’re looking for the start of the IAT. Depending on the size of the IAT, configure your command to show the entire IAT, like this:

kd> dps B203522C-34 L30

b20351f8 00000000

b20351fc 00000000

b2035200 804e3bf6 nt!IofCompleteRequest

b2035204 804dc1a0 nt!KeWaitForSingleObject

b2035208 804e3996 nt!KeSetEvent

b203520c 80505480 nt!IoDeleteDevice

b2035210 805c5ba9 nt!IoDeleteSymbolicLink

b2035214 804dc8b0 nt!ZwClose

b2035218 8057b03b nt!PsTerminateSystemThread

b203521c 804ff079 nt!DbgPrint

b2035220 804e68eb nt!KeResetEvent

b2035224 805b86b4 nt!IoCreateNotificationEvent

b2035228 804d92a7 nt!RtlInitUnicodeString

b203522c 80564be8 nt!ObReferenceObjectByHandle

b2035230 8057ae8f nt!PsCreateSystemThread

b2035234 8054cbe8 nt!NtBuildNumber

b2035238 805a9c9b nt!IoCreateSymbolicLink

b203523c 8059fa61 nt!IoCreateDevice

b2035240 804fcaf3 nt!wcsstr

b2035244 8054b587 nt!ExFreePoolWithTag

b2035248 8054b6c4 nt!ExAllocatePoolWithTag

b203524c 80591865 nt!IoGetDeviceObjectPointer

b2035250 804d9050 nt!ObfDereferenceObject

b2035254 805473ba nt!_wcslwr

b2035258 80501e33 nt!wcsncpy

b203525c 8057715c nt!PsLookupThreadByThreadId

b2035260 804e7748 nt!wcscmp

b2035264 804dd440 nt!ZwQuerySystemInformation

b2035268 804dc810 nt!ZwAllocateVirtualMemory

b203526c 804ea23a nt!KeDetachProcess

b2035270 804dd044 nt!ZwOpenProcess

b2035274 804ea2c4 nt!KeAttachProcess

b2035278 8057194e nt!PsLookupProcessByProcessId

b203527c 804e8784 nt!KeInitializeEvent

b2035280 8055a220 nt!KeServiceDescriptorTable

b2035284 804e5411 nt!KeInsertQueueApc

b2035288 804e5287 nt!KeInitializeApc

b203528c 80552000 nt!KeTickCount

b2035290 805337eb nt!KeBugCheckEx

b2035294 00000000

b2035298 0044005c

5. You can copy and paste all lines shown in bold and save it to a text file. This is the information you need to label the imported functions in the IDA database.

6. Use the windbg_to_ida.py script to convert the lines you pasted into a text file (info.txt in the example) into IDC code for IDA Pro.

$ python windbg_to_ida.py info.txt

MakeName(0xb2035200, "IofCompleteRequest");

MakeName(0xb2035204, "KeWaitForSingleObject");

MakeName(0xb2035208, "KeSetEvent");

MakeName(0xb203520c, "IoDeleteDevice");

MakeName(0xb2035210, "IoDeleteSymbolicLink");

MakeName(0xb2035214, "ZwClose");

MakeName(0xb2035218, "PsTerminateSystemThread");

MakeName(0xb203521c, "DbgPrint");

MakeName(0xb2035220, "KeResetEvent");

[...]

7. In IDA Pro, go to File ⇒ IDC Command (or Shift+F2) and paste in the output from windbg_to_ida.py. You should see a window similar to the one shown in Figure 14-12. When you click OK, the IDC statements will label the API calls throughout your dumped driver.

Figure 14-12: Entering IDC statements into IDA Pro

f1412.tif

8. In IDA Pro, click Options ⇒ General ⇒ Analysis ⇒ Reanalyze Program. This will cause IDA Pro to fix up the disassembly with types and variable names, now that it can recognize which API functions are being called. Figure 14-13 shows an updated view of the same code blocks that Figure 14-11 contained, but with the new labels applied.

Figure 14-13: The repaired driver in IDA Pro

f1413.tif

The addresses and exact commands you learned about in the past few recipes are specific to windev_6ec4_1ec9.sys. However, the tools, techniques, and reasons you entered particular commands are all generic—and you can use them to unpack and rebuild kernel drivers installed by other malware samples.

Recipe 14-11: Detecting Rootkits with WinDbg Scripts

dvd1.eps

You can find supporting material for this recipe on the companion DVD.

If you routinely type the same commands into WinDbg, you could save time by creating reusable scripts. Another advantage to writing scripts is that you can share them with the community. You can find several general-purpose scripts on Microsoft’s Debugging Toolbox blog15 and some security-related scripts on the Laboskopia website.16

Using the Laboskopia Scripts

The Laboskopia scripts are particularly relevant because you can use them to identify kernel-level rootkits. For example, the scripts are capable of listing the following information:

· Entries in the Interrupt Descriptor Table (IDT) to identify rootkits that hook interrupts

· Entries in the Global Descriptor Table (GDT) to identify rootkits that install call gates

· Model-specific registers (MSRs) to identify rootkits that hook SYSENTER on XP and later systems

· System service descriptor tables (SSDTs) to identify rootkits that hook kernel-mode API functions

Note If you’re looking for a concise, but informative explanation of the following rootkit techniques, see skape & Skywing’s “A Catalog of Windows Local Kernel-mode Backdoor Techniques” at http://uninformed.org/index.cgi?v=8&a=2.

WinDbg scripts are plain-text files that contain the same commands that you would normally type into the debugger. To install scripts, just copy them into a subdirectory relative to WinDbg.exe. The image in Figure 14-14 shows an example directory layout after unzipping the collection of scripts from Laboskopia.

The syntax for executing a script in WinDbg looks like this:

kd> $$><directory\filename.txt

kd> $$>a< "c:\directory\filename.txt" "argument1" "argument2"

Figure 14-14: Directory layout for installed WinDbg scripts

f1414.tif

WinDbg is strict about where you place spaces and quotations when calling external scripts, so be careful what you type. Once you’ve got the Laboskopia scripts installed, run the initialization script, which sets up aliases for the other commands. It will look like this:

kd> $$><script\\@@init_cmd.wdbg;

Labo Windbg Script : Ok :)

('al' for display all commands)

kd> al

Alias Value

------- -------

!!display_all_gdt $$><script\display_all_gdt.wdbg;

!!display_all_idt $$><script\display_all_idt.wdbg;

!!display_all_msrs $$><script\display_all_msrs.wdbg;

!!display_current_gdt $$><script\display_current_gdt.wdbg;

!!display_current_idt $$><script\display_current_idt.wdbg;

!!display_current_msrs $$><script\display_current_msrs.wdbg;

!!display_system_call $$><script\display_system_call.wdbg;

!!hide_current_process $$><script\hide_current_process.wdbg;

!!save_all_reports $$><script\save_all_reports.wdbg;

!!search_hidden_process $$><script\search_hidden_process.wdbg;

!@display_gdt $$><script\display_gdt.wdbg;

!@display_idt $$><script\display_idt.wdbg;

!@display_msrs $$><script\display_msrs.wdbg;

!@get_debug_mode $$><script\get_debug_mode.wdbg;

!@get_original_ntcall $$><script\get_original_ntcall.wdbg;

!@get_original_win32kcall $$><script\get_original_win32kcall.wdbg;

!@get_system_version $$><script\get_system_version.wdbg;

!@hide_process $$><script\hide_process.wdbg;

!@is_hidden_process $$><script\is_hidden_process.wdbg;

With WinDbg commands alone (i.e., not using scripts), you can print IDT and MSR addresses like this:

kd> !idt 2e

Dumping IDT:

2e: 804de631 nt!KiSystemService

kd> rdmsr 0x176

msr[176] = 00000000'804de6f0

kd> ln 804de6f0

(804de6f0) nt!KiFastCallEntry

The authors chose to display the 0x2E entry of the IDT and the 0x176 MSR, because those are popular values that rootkits overwrite. However, they are not the only values that rootkits can overwrite to perform malicious actions. Using the Laboskopia scripts, you can print more comprehensive listings. Here is an example showing the extra information provided for the IDT:

kd> !!display_all_idt

####################################

# Interrupt Descriptor Table (IDT) #

####################################

Processor 00

Base : 8003F400 Limit : 07FF

Int Type Sel : Offset Attrib Symbol/Owner

---- ------ ------------- ------ ------------

002A IntG32 0008:804DEB92 DPL=3 nt!KiGetTickCount (804deb92)

002B IntG32 0008:804DEC95 DPL=3 nt!KiCallbackReturn (804dec95)

002C IntG32 0008:804DEE34 DPL=3 nt!KiSetLowWaitHighThread (804dee34)

002D IntG32 0008:F8964F96 DPL=3 SDbgMsg+0xf96 (f8964f96)

002E IntG32 0008:804DE631 DPL=3 nt!KiSystemService (804de631)

002F IntG32 0008:804E197C DPL=0 nt!KiTrap0F (804e197c)

[...]

The following example shows you how to print the MSRs:

kd> !!display_all_msrs

###################################

# Model-Specific Registers (MSRs) #

###################################

Processor 00

IA32_P5_MC_ADDR msr[00000000] = 0

IA32_P5_MC_TYPE msr[00000001] = 0

IA32_MONITOR_FILTER_LINE_SIZE msr[00000006] = 0

IA32_TIME_STAMP_COUNTER *msr[00000010] = 000066ce'0366c49c

IA32_PLATFORM_ID *msr[00000017] = 21520000'00000000

IA32_APIC_BASE *msr[0000001B] = 00000000'fee00900

MSR_EBC_HARD_POWERON msr[0000002A] = 0

MSR_EBC_SOFT_POWERON msr[0000002B] = 0

MSR_EBC_FREQUENCY_ID msr[0000002C] = 0

IA32_BIOS_UPDT_TRIG msr[00000079] = 0

IA32_BIOS_SIGN_ID *msr[0000008B] = 00000008'00000000

IA32_MTRRCAP *msr[000000FE] = 00000000'00000508

IA32_SYSENTER_CS *msr[00000174] = 00000000'00000008

IA32_SYSENTER_ESP *msr[00000175] = 00000000'f8974000

IA32_SYSENTER_EIP *msr[00000176] = 00000000'804de6f0

nt!KiFastCallEntry (804de6f0)

[...]

The next example shows you how to print the SSDTs. This script actually displays which entries are hooked rather than just printing their addresses. The target machine is infected with a rootkit that hooks NtEnumerateValueKey and NtOpenProces for the purpose of hiding files and processes.

kd> !!display_system_call

*****************

* Current Table *

*****************

ServiceDescriptor n0

---------------------

ServiceTable : nt!KiServiceTable (804e26a8)

ParamTableBase : nt!KiArgumentTable (80510088)

NumberOfServices : 0000011c

Index Args Check System call

----- ---- ----- -----------

0000 0006 OK nt!NtAcceptConnectPort (8058fe01)

0001 0008 OK nt!NtAccessCheck (805790f1)

[...]

0049 0006 HOOK-> lanmandrv+0x884 (f8b0e884) ##### Original ->

nt!NtEnumerateValueKey (80590677)

004A 0002 OK nt!NtExtendSection (80625758)

004B 0006 OK nt!NtFilterToken (805b0b4e)

[...]

0079 000C OK nt!NtOpenObjectAuditAlarm (805953b5)

007A 0004 HOOK-> lanmandrv+0x53e (f8b0e53e) ##### Original ->

nt!NtOpenProcess (805717c7)

007B 0003 OK nt!NtOpenProcessToken (8056def5)

007C 0004 OK nt!NtOpenProcessTokenEx (8056e0ee)

[...]

A final thing you can do with the Laboskopia scripts is compile all the output from previously shown commands (and more) into a single text file for later analysis. To do this, use the !!save_all_reports commands and then look for the log file in the same directory as WinDbg.exe.

Writing Your Own Scripts

If you want to add scripts to the Laboskopia collection (or start building your own from scratch), then you can. The following WinDbg script checks for registered notification routines (for more information, see Recipe 17-9). You can find the full source file named WinDbgNotify.txt on the companion DVD.

$$

$$ Example WinDbg script

$$

r $t0 = poi(nt!PspCreateThreadNotifyRoutineCount);

r $t1 = poi(nt!PspCreateProcessNotifyRoutineCount);

r $t2 = poi(nt!PspLoadImageNotifyRoutineCount);

.printf "No. thread start callbacks: %x\n", @$t0;

r $t3 = 0;

.while (@$t3 < 8)

{

r $t4 = poi(nt!PspCreateThreadNotifyRoutine + (@$t3 * 4));

.if (@$t4 != 0) {

.printf "%x => %x\n", @$t3, @$t4;

}

r $t3 = @$t3 + 1;

}

.printf "No. process start callbacks: %x\n", @$t1;

r $t3 = 0;

.while (@$t3 < 8)

{

r $t4 = poi(nt!PspCreateProcessNotifyRoutine + (@$t3 * 4));

.if (@$t4 != 0) {

.printf "%x => %x\n", @$t3, @$t4;

}

r $t3 = @$t3 + 1;

}

.printf "No. image load callbacks: %x\n", @$t2;

r $t3 = 0;

.while (@$t3 < 8)

{

r $t4 = poi(nt!PspLoadImageNotifyRoutine + (@$t3 * 4));

.if (@$t4 != 0) {

.printf "%x => %x\n", @$t3, @$t4;

}

r $t3 = @$t3 + 1;

}

Assuming you place the WinDbgNotify.txt script in a directory named MyScript, you can then invoke it like this:

kd> $$><MyScript/WinDbgNotify.txt

No. thread start callbacks: 0

No. process start callbacks: 0

No. image load callbacks: 1

0 => e13cdb37

The output shows that the target system has one registered image load callback routine. The routine at e13cbd37 will therefore execute when processes load DLLs. You could take this script further by doing a reverse lookup on the address and printing the owning driver, or even disassembling the function.

15 http://blogs.msdn.com/debuggingtoolbox/default.aspx

16 http://www.laboskopia.com/download/SysecLabs-Windbg-Script.zip

Recipe 14-12: Kernel Debugging with IDA Pro

Recent versions of IDA Pro come with a WinDbg plug-in that gives you the best of both worlds—access to a remote kernel using WinDbg’s engine paired with IDA’s GUI, IDA’s scripting languages, and IDA’s plug-ins. This recipe walks you through setting up the WinDbg plug-in for IDA and shows how it can make your life much easier.

To get started, you’ll need to follow the instructions in Recipe 14-3 or 14-4 so that your debugging machine and target system are connected. You should also review the tutorial created by the Hex-Rays staff and a supplementary blog post on debugging a VMware kernel with IDA’s GDB debugger, both accessible on the Hex-Rays website.17

Establishing a Connection

1. Open IDA Pro. Select the WinDbg plug-in, as shown in Figure 14-15.

Figure 14-15: Selecting IDA Pro’s WinDbg plug-in

f1415.tif

2. Configure the debug options. In particular, modify the Connection string to the port or pipe that you set up on your virtual machine. Then enable Kernel mode debugging and enter the path to your Debugging tools folder (the directory that contains dbgeng.dll), as shown in Figure 14-16. If you plan on executing malware on the target system that loads a kernel driver, check the Stop on library load/unload option in the Debugger setup window.

Figure 14-16: Configuring the debug options

f1416.eps

3. Accept the connection. Upon successful connection to the target system, IDA displays the image shown in Figure 14-17—an option to attach to the remote kernel. Click OK to continue.

Figure 14-17: Accepting the kernel connection

f1417.tif

At this point, you can explore the kernel in a very intuitive manner. The image in Figure 14-18 shows critical information in every window.

Figure 14-18: Debugging a remote kernel with IDA Pro

f1418.eps

· The IDA View: Shows the main disassembly window—where you view code, set/remove breakpoints, name variables, and so on.

· Debugger controls: Lets you play, pause, stop, step-in, step-over, and so on (there are also keyboard shortcuts for all of the controls).

· Modules tab: Lists the loaded kernel drivers with their base addresses and sizes.

· Symbols tab: If you click any of the loaded kernel drivers in the Modules tab, a new tab opens like the one shown in the top right—where you can browse the symbols in your selected module.

· WinDbg shell: Provides full access to the WinDbg command shell.

Configuring Type Libraries

When you open a file in IDA Pro, the application typically loads type libraries, which contain preconfigured structures and enumerations. However, when you use IDA Pro to debug a kernel, you have to manually load the type libraries. Go to View ⇒ Open subviews ⇒ Type Libraries. Then press the Insert key or right-click in the empty window and select Load type library. At a minimum, you should add the following libraries:

· ntddk: MS Windows <ntddk.h>

· ntapi: MS Windows NT 4.0 Native API <ntapi.h><ntdll.h>

· wnet: MS Windows DDK <wnet/windows.h>

· mssdk: MS SDK (Windows XP)

Once the type libraries are loaded, you can use the Symbol tab to find IopLoadDriver—the function responsible for calling a loaded driver’s entry point (see Recipe 14-8). Then you can do a text search for “call *dword ptr*” and locate the exact instruction in IopLoadDriver that leads to a driver’s entry point. Because you know the instruction references a _DRIVER_OBJECT, and now you have imported the correct type libraries, you can begin to apply labels, as shown in Figure 14-19.

Figure 14-19: The instruction in IopLoadDriver that Calls a driver entry point

f1419.tif

Unpacking the Driver

The following example assumes that you’ve read Recipe 14-9 because it’s based on unpacking the same driver, except this time you’ll see it from the perspective of IDA’s GUI. On the target system, load the malicious driver and use IDA’s single-step key (F7) to get from the breakpoint in IopLoadDriver to the loaded driver’s entry point. You should recognize the entry point function where it performs the first round of unpacking.

To let the driver unpack and get to the next round of decoding, right-click the line with jmp edx and select Run to cursor. As you will remember from Recipe 14-9, you actually have to repeat this step once more for the next function because there are two packing layers. When you reach the driver’s unpacked entry point and apply names and labels, it should appear like the image in Figure 14-20.

Figure 14-20: The unpacked driver with labels

f1420.tif

17 http://www.hexblog.com/2009/02/advancedwindowskerneldebugg.html