Saturday, March 10, 2012

Malware Analysis Tutorial 20: Kernel Debugging - Intercepting Driver Loading

Learning Goals:
  1. Use WinDbg for kernel debugging
  2. Patch Debugger to Defend Malicious Actions by Malware
  3. Understand Driver Entry
  4. Interception of Driver Loading
Applicable to:
  1. Operating Systems
  2. Assembly Language
  3. Operating System Security
1. Introduction
We now explore the more challenging part of a reverse engineering process. In the past, all the malicious actions performed by Max++ have been at the application level (ring-3), which can be effectively traced by Immunity Debugger. After Max++ injects code into a randomly selected driver file, and loads it with zwLoadDriver(), the analysis becomes more difficult - Immunity Debugger is not able to analyze ring-0 code and we have to use WinDbg as a kernel debugger. Unfortunately, WinDbg does not have a convenient notes-taking support. In this case, we will duplicate the VBox instance to two. One used for taking notes (where we will have Immunity Debugger to take comments on code), and the other used for kernel debugging (and it will be controlled by a WinDbg from the host via COM port).

In this tutorial, we concentrate on the sophisticated setup of the lab environment and kick start the analysis of the driver entry function. Our job is to intercept the loading of the driver that Max++ infects.

In the following, we call the VBox instance with IMM the Win_Notes instance and the instance to be debugged the Win_Debug instance. We first begin the introduction of how to set up the lab environment, and then we start the kernel debugging process.

Let us assume the name of the infected driver is rasppoe_2.sys.Note that the version of the IMM we used for this tutorial is 1.8.3. The instructions listed in Section 2 is for this version only.

2. Lab Configuration
The lab configuration of this tutorial is sophisticated. It contains the following four steps:
(1) Take the infected driver.
(2) Make the duplicate copy of VBox instance.
(3) Start the Win_Notes environment.
(4) Start the Win_Debug instance.

2.1 Retrieve the Infected Driver
The first step is to retrieve the infected driver. You can simply follow the instructions in Section 2 of Tutorial 19 (Anatomy of Infected Driver) and save the drivers. Once the driver is taken, you have to email these files back to yourself, and restore the snapshot of the clean system (because it has been infected and Max++ has removed itself from disk). Then download these files from your email. From this point, we can duplicate the VBox instance.

2.2 Make the Duplicate Copy of VBox instance
Now in VirtualBox right click on theWinXP instance and select CLONE, and rename the new instance to Win_Notes and select the full clone and all snapshots. Up to now, you should have two VBox instances of the WinXP system. We use Win_Notes for taking comments only.

Hofstra students can pick up both images from my office.

2.3 Start the Win_Notes Environment
The purpose of the Win_Notes environment is to take the notes. As the malware will crash IMM for a certain reason, we have to change the control flow of IMM a bit to successfully take our notes. Follow the steps below to set up the environment:

(1) Start IMM.
(2) In the first instance of IMM, open the IMM exe file from c:\Program Files\Immunity Debugger\IMM.exe
(3) Set a SOFTWARE breakpoint at 0x004E6095 . This is some vulnerable part of the IMM debugger. It occasionally crashes given mal-formatted data. We now provide some brief explanation here. By no means, we are trying to reverse engineer the IMM debugger, but just want to correct some bugs and provide a minor fix on the current version of the debugger.
   At 0x004E6095 (as shown in Figure 1), IMM is trying to put a "\0" at the end of a string. Here ESI points to the beginning of a string and EDI contains its length. Now, if ESI and EDI are both set to NULL, what happens is the instruction will trigger a segmentation fault (it's then trying to access address 0xFFFFFFFF which leads to segmentation fault).
   To solve the problem, we have to skip this instruction when ESI/EDI value is not right.
Figure 1. Reset the EIP to Skip one struction
  Follow the instructions below:
   Now load the rapppoe_2.sys in the second IMM, and let it run. We will hit the breakpoint 0x004E6095 several times in the 1st IMM. Whenever you see the ESI/EDI pair is 0, launch the Python command window (see Figure 1, the 2nd button on the toolbar), and type the following command to readjust EIP.
   imm.setReg("EIP", 0x004E609A)

  But if the ESI/EDI is ok, don't do the above, just hit F9 and let it continue. You will repeat the above for several times until the .sys file is loaded. Note that, during the process, you will get a couple of warnings like some other modules are out of range, just click ok and let it go.

(4) Now in the second IMM, click View -> Executable Modules, and double click on rapppoe_2.sys, you will be able to jump to the starting address of the module. The module should start from 0x10001000 (PUSH ESI).  Figure 2 shows our analysis window, loaded with the comments (Hofstra students can get the .udd file from my office). 

Figure 2. The Driver File Dump
 (5) We will need an additional step to finish: Click View->Modules in the 2nd IMM, and record its entry address. No matter which driver file Max++ picks to infect,  the entry address (offset) is always 0x372B (as shown in Figure 3).
Figure 3. Entry Point of the Module raspppoe_2.ssys
(6) Now right click in the CPU pane -> Go To (0x1000372B), and you should be located at the entry point of the drive module. The Win_Notes environment is prepared and you can use it to take notes (placing comments on the code). Figure 4 shows you the first part of the malicious infected driver.
Figure 4. Entry Part of the Infected Driver

2.4 Trace the Win_Debug Instance
We now describe how to use the WinDbg on the host to perform the tracing of the driver module. Our purpose is to stop the system on the Driver_Entry function of raspppoe_2.sys. Note that the function can only be traced by WinDbg because IMM is a ring-3 debugger. Our purpose is to stop at the driver entry function. This is accomplished by the following steps.

(1) Start WinXP_DEBUG image in DEBUGGED mode. Now in your host system, start a windows command window and CD to "c:\Program Files\Debugging Tools for Windows (x86)" (where WinDBG is installed). Type "windbg -b -k com:pipe,port=\\.\pipe\com_12" (check the com port number in your VBox instance set up).

(2)  Now in your WinDbg window, type "bu _+372b", this is to set a breakpoint at offset 0x372b for module named "_". You might wonder where the "_" is coming from. Later, we will show you how we find out that after the zwLoadDriver() is called by Max++, t a module named "_" is added by Max++.

(3) Now type "g" twice to let the system go. In the following, we will run Max++ in a controlled way, until we load the driver.

(4) Now launch IMM in the WinXP instance, clear all breakpoints and hardware breakpoints in IMM (see View->Breakpoints and View->Hardware Breakpoints).

(5) Go to 0x4012DC and set a hardware breakpoint there. (why not software bp? Because that region will be self-extracted and overwritten and the software BP will be lost). Pay special attention that once you go to 0x4012DC, directly right click on the line to set hardware BP (currently it's gibberish code).

(6) PressF9 several times run to 0x4012DC. You will encounter several breakpoints before 0x4012DC. If you pay attention, they are actually caused by the int 2d tricks (explained in Tutorial 3 and 4, and 5). Simply ignore them and continue (using F9) until you hit 0x4012DC.

Figure 5 shows the code that you should be able to see. As you can see, this is right before the call of RtlAddVectoredException, where hardware BP is set to break the LdrLoadDll call (see Tutorial 11 for details).
Figure 5: code at 0x4012DC
(7) Now scroll down about 2 pages and set a SOFTWARE BREAKPOINT at 0x401417. This is right after the call of LdrLoadDll("lz32.dll"), where Max++ finishes the loading of lz32.dll. Then hit SHIFT+F9 several times until you reach 0x401417 (you will hit 0x7C90D500 twice, this is somwhere inside ntdll.zwMapViewSection which is being called by LdrLoadDll).

Figure 6: code at 0x401407

(8) Now we will set a breakpoint at 0x3C1B3E  .  As shown in Figure 7, Goto 0x3C1B3E  and set a SOFTWARE BREAKPOINT. Press SHIFT+F9 to run to 0x3C1B3E  . (You may see a warning that this is out range of the code segment, simply ignore the warning).
Figure 7: The Code Right Before zwLoadDriver
Figure 7 shows the code that you should be able to see at 0x3C1B3E  . This is right before the call zwLoadDriver.

*** Now we'll capture the driver loading event.  in WinDbg, pres "Ctrl+Break" to stop the program. click Debug-> Event Filters, select the LoadModule event and click "Add". Then "g" to continue ***

Now go back to the WinXP_Debug, press F8 twice and execute the zwLoadDriver function and you might see that your WinXP_Debug instance is frozen, because now the WinDbg hitsmodule load event first. If you do an "lm" command in WinDbg, you will see that the newly loaded module is "_". Type "g" again in WinDbg, you will now hit the breakpoint bu _ + 372b.

As shown by Figure 8, the instruction we are stopped at is located at fae3772b (this is offset 372b relative to the base address of module "_").

Figure 8: WinDbg DUMP
Press Alt+7 (or View->Disassembly), you can watch the current instructions (as shown in Figure 9). If you compare with Figure 1, you can verify that we are indeed in the Driver Entry function of raspppoe_2.sys.
Figure 9: Disassembly Dump

3. Driver Entry Function
We now proceed to the analysis of the Driver Entry function using WinDbg. According to [1], a driver entry function on Windows has the following prototype:

  __in  struct _DRIVER_OBJECT *DriverObject,
 __in  PUNICODE_STRING RegistryPath 

The first parameter is an object containing the information about the related driver object, and the second parameter is a unicode string containing the path to the corresponding registry key in the registry database. Our first goal of the analysis is to figure out the values of these two input parameters.

If you look at the first couple of instructions of the function (see Figure 9, from fae3772b to fae37738), they are to set up the stack frame (e.g., to preserve the previous EBP value, to readjust ESP value to create the stack frame and to preserve other relevant registers).

In WinDbg, Press F10 (single step) to run to 0xFAE37738 (see Figure 9), according to the C function call parameter passing convention, we have the 1st parameter (_DRIVER_OBJECT) located at EBP+8 and the 2nd parameter (PUNICODE_STRING) at EBP+C. Now that these two values are simply pointers. Let's first get these two pointers by typing "dd EBP", we have the memory dump below: [Note that the first column is the address for each row and each row has 4 computer words (32-bit each),  e.g., fafabc7c (1st word in 1st row) is the address for fafabd4c (first row), and fafabc8c (1st word of second row) is the address for 00000000). Here EBP value is fafabc7c.

kd> dd EBP
fafabc7c  fafabd4c 805a399d 811254d8 81184000
fafabc8c  00000000 f7a1dcf4 00000000 00000018
fafabc9c  00000000 fafabcc0 00000010 00000000
fafabcac  00000000 811254d8 fafabd70 811462f0
fafabcbc  81184000 00200020 81128f48 e101b000
fafabccc  811254d8 81143280 00000020 8000030c
fafabcdc  fae34000 00000308 00120010 e14fe090
fafabcec  0000007a 00060004 e1bcc5f8 00000000

Clearly, the 1st parameter _DRIVER_OBJECT value is 811254d8 (the entry address of the object), and the _UNICODE_STRING (2nd parameter) starts at 81184000.

We now display their values:
kd> dt _DRIVER_OBJECT 811254d8
   +0x000 Type             : 0n4
   +0x002 Size             : 0n168
   +0x004 DeviceObject     : (null)
   +0x008 Flags            : 2
   +0x00c DriverStart      : 0xfae34000 Void
   +0x010 DriverSize       : 0x8000
   +0x014 DriverSection    : 0x81143280 Void
   +0x018 DriverExtension  : 0x81125580 _DRIVER_EXTENSION
   +0x01c DriverName       : _UNICODE_STRING "\Driver\.NDProxy"
   +0x028 FastIoDispatch   : (null)
   +0x02c DriverInit       : 0xfae3772b     long  +0
   +0x030 DriverStartIo    : (null)
   +0x034 DriverUnload     : (null)
   +0x038 MajorFunction    : [28] 0x804fa87e     long  nt!IopInvalidDeviceRequest+0
kd> dt _UNICODE_STRING 81184000
   +0x000 Length           : 0x70
   +0x002 MaximumLength    : 0x70
   +0x004 Buffer           : 0x81184008  "\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\.NDProxy"

As you note, this matches the role of raspppoe.sys (which is used for remote internet connectoin).

Challenge of the Day: Analyze the call at _+3743, what are its parameters and what is it doing?

[1] Microsoft, "DriverEntry Routine", available at

No comments:

Post a Comment