1. Introduction
This tutorial continues the analysis presented in Tutorial 20. We reveal how Max++ performs another round of driver infection, and how it sets up and hides an infected driver. We will also study how to use hardware data breakpoint to trace the use of data and kernel data structures. Our analysis starts from _+37AF.

2. Lab Configuration
In general we will use the instructions of Section 2 of Tutorial 20. In the following we just remind you of several important steps in the configuration:
(1) You need a separate image named "Win_Notes" to record and comment the code. You don't really need to run the malware on this instance, but just to record all your observations using the .udd file. To do this, you have to modify the control flow of IMM so that it does not crash on .sys files. See Section 2 of Tutorial 20 for details. Jump to 0x100037AF to start the analysis.
(2) The second "Win_DEBUG" image has to be run in the DEBUG mode and there should be a WinDbg hooked from the host system using COM part -- so here, we are doing kernel debugging.
(3) Set a breakpoint "bu _+37af" in WinDbg to intercept the driver entry function.

3. Data Breakpoints and Tracing File Name
We now continue the analysis after Tutorial 21. We begin with _+37AF. Figure 1 shows the first couple of instructions. As shown in Figure 1, the first section of the code is to massage a collection of names.

Figure 1. Copy and Manipulate Strings

At 0x100037BF, it is copying string "\??\C2CAD...\snifer67" to the area pointed by EDI. Doing a data analysis in WinDbg yields the following. Clearly, EDI value (the starting address) of the string is 0xFAFAF9F8 (which is ESP+34 at this moment)

Similarly you can infer the second string generated by the swpringf at 0x100037DB (in Figure 1) is "\systemroot\system32\drivers\rasppoe" (this is the name of the randomly picked driver). The name could change in every run.

The the challenge to us is that if we look in the notes window, we are not able to infer where these two strings are used! We have to use WinDbg data breakpoints to figure out where these file/service names are used.

Let's take the second string as an example. By analyzing the input parameter of swprintf (as shown in Figure 1, 2nd highlighted area), we know that the second string "\systemroot\system32\drivers\rasppoe" is located at 0xFAFB7A78, as shown in following. Then we could set a data read breakpoint on it: ba r4 fafb7a78 (this means to watch for any reading on the 4 bytes starting at fafb7a78).

Now run the program we hit _+0x1b in RtlInitUnicodeString, at this time, if you run Kp (to show the stack contents) you might not be able to get the right sequence of frames in the stack (as shown in the following).

In this case, we want to step out of RtlInitUnicodeString. There is a command Step Out (shift+f11), however, not working here, because Max++ does not follow the conventional C conventions. We have to press F10 very patiently. After around 10 steps over (F10), we reached _+1a32, as shown below!

_+1a32 is a part of a function in Max++, which is responsible for constructing an instance of _OBJECT_ATTRIBUTES (where "\systemroot\system32\drivers\rasppoe" is served as the ObjectName).

Figure 2. The Function Which Calls RtlInitUnicodeString

Tracing again from _+1a32, we can find that the program flow jumps to _+23e9 (which reads the contents of the driver file and put it in a collection of locked virtual pages).

Challenge 1. Finish the above analysis and provide a detailed report on how the "\systemroot\system32\drivers\raspppoe" string is used.

4. Virtual Pages
We continue the analysis. At _+3803, Max++ calls another function located at _+23C8 (which reads the contents of a file and puts the contents in virtual pages). There are some interesting technical details here. Figure 3 shows its function body. Note the first highlighted area, it constructs an instance of _OBJECT_ATTRIBUTES that entails the file name "\systemroot\system32\drivers\raspppoe", as discussed in Section 3 (how to trace the use of data). Then Max++ opens the file and queries about the standard file information of the file. When all operations succeed, it proceeds to the creation of virtual pages.

Figure 3. First Part of _+23C8

We continue to the second part of Function _+23C8 (as shown in Figure 4). In driver implementation, in many cases you have to lock the physical pages for your virtual addresses (so that your contents in RAM will not be swapped into disk by OS). The intention of this part of code is pretty clear: it first requests virtual pages (see the first highlighted area), the virtual page descriptor is saved in a data structure named _MDL (stored at 8121c970). Once successful, it will ask the system to allocate the physical pages (see MmMapLockedPageSpecifyCache). Then Max++ reads the infected driver file into these pages (starting at address 0xf7649000). If you dump the data starts at 0xf7649000, you would find it's really a binary executable (i.e., see the magic 4D5A header info. for DOS header).

Now comes the interesting part (see the last highlighted area of Figure 4). Once the file contents of the infected driver are read, Max++ immediately released the physical pages (for virtual address 0xf7649000) immediately. This is quite counter-intuitive, wouldn't Max++ want to use these data later? It's your job to figure it out.

Challenge 3. Use the same trick for tracing the data, set two data breakpoints. One for the _MDL (e.g., in our case it's 0x8121c970) and one for the starting address of the infected driver executable data (e.g., in our case it's 0xf7649000). Try to figure out if these pages of malicious binary executable are really used or not. In summary, you have to answer the question: why does Max++ release the pages in Figure 4?

Challenge 4. Analyze the function of _+22C3.

5. Infection of Driver Again and the Use of Virtual Pages
At _+3889 Max++ calls function 2D9F. We now analyze its function (as shown in Figure 5). It is used to infect a driver file (the file name is given as the first parameter in its stack frame). The function first creates a section object on the file, then it performs a memcopy from a MDL descriptor to the file, and flushes the contents back to the file.

Figure 5. Infect Driver fips.sys

Challenge 5. use data tracing technique to analyze where is the malicious file content from?

6. Final Set Up of Malicious Disk Driver
In Tutorial 22, we showed you how a malicious disk driver is used to simulate the file requests on "\??\C2CAD..." using a file called "12345678.sav". In the following, we show how this driver is configured by copying attributes from the real disk driver.

Figure 6. Wiring and Copying of Driver Object

The first part (as shown in the first highlighted area in Figure 6), adjusts the DriverSection field of the infected object. It is actually a basic link list operation, which tries to remove the infected driver from the list of modules. Notice that the type of the DriverSection field (offset 0x14) is _LDR_DATA_TABLE_ENTRY. You can use WinDbg to verify.

Next in the second highlighted area of Figure 6, Max++ tries to copy all the attributes from the original \Driver\Disk object to the infected driver (in this case on the comments it's .serial, the name could change during sessions). There is only one attribute of the infected driver remains: the major function _+2bDE! Up to this point, Max++ has successfully set up the infected disk driver and it has hided it from the loaded module list.

1. Introduction
This tutorial continues the analysis presented in Tutorial 20. We reveal how Max++ uses a modified disk driver to handle I/O requests on the disk it created (its name is "\\?\C2CAD..."). Recall that in section 4.2.3 we showed you Max++ creates a new IO device and hooks it to the malicious driver object, so that whenever an IO request is raised on this device the request will be forwarded to driver object 8112d550, as shown below. Pay attention to the value of MajorFunction (0xfae36bde), this is where IO requests are handled. Obtaining the module base address, we can easily calculate its offset: _+2BDE.

To replicate the experiments of this tutorial, you have to follow the instructions in Section 2 of Tutorial 20. In this tutorial, we perform analysis on the code of raspppoe.sys from _+2BDE (0x10002BDE)

2. Lab Configuration
In general we will use the instructions of Section 2 of Tutorial 20. In the following we just remind you of several important steps in the configuration:
(1) You need a separate image named "Win_Notes" to record and comment the code. You don't really need to run the malware on this instance, but just to record all your observations using the .udd file. To do this, you have to modify the control flow of IMM so that it does not crash on .sys files. See Section 2 of Tutorial 20 for details. Jump to 0x10002BDE to start the analysis.
(2) The second "Win_DEBUG" image has to be run in the DEBUG mode and there should be a WinDbg hooked from the host system using COM part -- so here, we are doing kernel debugging.
(3) Set a breakpoint "bu _+2BDE" in WinDbg to intercept the driver entry function.

3. Background: Windows Driver Development
Opferman provides an excellent introduction and sample code in [1]. In the following, we summarize of the major points here.

(1) Each driver has a driver entry function, its prototype is shown below:

NTSTATUS DriverEntry(PDRIVER_OBJECT pDrv, PUNICODE_STRING reg)

Here pDrv is a pointer to _DRIVER_OBJECT, and reg is a string that represents the registry entry where the driver could store information.

As we shown earlier in Tutorial 20, the DriverEntry function is located at _+372b.

(2) Each driver may have a collection of 28 functions to handle different types of I/O requests (such as close handle, read, write etc.) The IRP Function code can be found at [2] (typical ones are IRP_MR_CREATE and IRP_MR_READ).

You might wonder, do we have to set breakpoints on all of the 28 functions? The answer is YES and NO. Look at the following dump (combined with the dump in section 1).

At offset 0x38 of the driver object (the starting of the major function array), all IRP handlers are set to one single function _+2BDE! The malware author tries to be lazy here, and it saves us a lot of job too. We can just concentrate on _+2BDE then!

Now before we move on, we should know that each IRP handler function has the following prototype:

NTSTATUS Handler(PDEVICE_OBJECT pDevice, PIRP pIRP)

Here, the first parameter is a device object, and the second parameter represents the IRP request to handle.

When we hit the _+2BDE handler, we could easily find out the contents of the two input parameters (device located at 8112d550 and irp located at 00070000) as below:

4. Anatomy of Infected Disk Driver
Figure 1 shows you the first part of the IRP handler function at _+2BDE.

Figure 1. Infected Disk Driver

As shown in Figure 1, the control flow is a very simple decision procedure. First it takes out the PDEVICE_OBJECT pointer from EBP+8 (1st parameter) and compare it with a global variable stored at 100061B0 (see highlighted area). Clearly, the global variables stores the newly created infected device (for \??\C2CAD...). If it is not a request to \??\C2CAD, the flow jumps to 10002BFD (second highlighted area), which calls PoCallDriver to relay the request to low level (real) drivers to do the work; otherwise it calls a self-defined function handleIRPForVirtualVolume which performs the real operation to simulate the virtual disk.

Challenge 1. Analyze the logic between 10002BFD and 10002C25 (highlighted area in Figure 1). Especially, explain the instructions at 0x10002C16 and 0x10002C19.

5. Simulating the Virtual Disk Operations
Now we will analyze the function handleIRPForVirtualVolume. It is located at _+292A. In this case, you need to set a breakpoint using "bp _+292A" in WinDbg. Figure 2 shows its major function body. Notice that you can easily infer from the context that EBX is an input parameter of the function, EBX points to the IRP request right now!

Figure 2. Function body of handleIRPForVirtualVolum

Now comes the interesting part. Look at Figure 2, at 0x1000293C EAX now has the "MajorFunction" of _IO_STACK_LOCATION (the value is one of the IRP_MJ_xxx types). Then there is a big switch case statement (see the highlighted area in Figure 2), which redirects the control flow to handle each of the different IRP requests such as READ, WRITE, etc.

Challenge 2. Argue that the statement about "0x1000293C EAX now has the "MajorFunction" (the value is one of the IRP_MJ_xxx types" is true. You may need to find out the definition of IRP_MJ_xyz values.

As an example of how Max++ simulates the disk volume operation, we show how it handles the IRP_MJ_READ request. Figure 3 shows the handler code.

Figure 3. Simulate the Disk Operation on File

First, let's look at the definition of _IO_STACK_LOCATION which represents an I/O operation task. Note that at this moment, ESI points to the current _IO_STACK_LOCATION, the following is its contents. You can easily infer that it's current Device Object is \??\C2CAD...

Now look at the first instruction LEA EAX, [ESI-24] in Figure 3. The purpose here is to move 0x24 bytes away (note the direction of stack) and the size of _IO_STACK_LOCATION (0x24). So EAX is now pointing to a new _IO_STACK_LOCATION instance. The next couple of instructions copy the first 9 words of the existing _IO_STACK_LOCATION to the new.

Then at 0x10002B10 (look at the highlighted area of Figure 3), it assigns the value of ECX (from global variable at DS:[1000614C]) to offset 0x18 of the new _IO_STACK_LOCATION. Notice that 0x18 is the FileObject attribute (see above dump of _IO_STACK_LOCATION!). The following is the dump of the File Object pointed by ECX:

Now it's pretty clear that the READ operation on the disk volume is actually achieved by CONSTRUCTING A NEW _IO_STACK_LOCATION task on the "*.sav" file created by Max++ earlier!

The last interesting point is at 0x10002B17: Max++ hooks up a function for the CompleteRoutine (offset 0x1c of _IO_STACK_LOCATION), the intention is pretty clear: the data stored on the *.sav file is encrypted, and Max++ now decodes it when reading it out.

We've finished a very challenging and interesting analysis of a portion of the infected disk driver. Now it's your job to finish the rest:

Challenge 3. What happens when FormatEx operation is performed on the virtual disk volume?

Challenge 4. Analyze all the other IRP_MJ_ operations supported by the infected disk driver (hint: this could take considerable efforts).

References
[1] T. Opferman, "Driver Development Introduction Part I", available at http://codeproject.com
[2] MSDN, "IRP Function Code", available at

1. Introduction
This tutorial shows you how to use WinDbg to analyze the malicious .sys (driver) file (let it be raspppoe.sys which is randomly infected by Max++). We will learn some basic analysis techniques using WinDbg.

Once the driver file is loaded, Max++ will establish a virtual hidden drive to store the malicious files it unpacked from itself or downloaded from the Internet. To achieve this goal, Max++ has to perform some low level disk operations. First, it will establish a file called "12345678.sav", which actually stores all the information of the hidden disk drive. Then it overwrites the disk/file driver so that all requests (read/write operations) to the hidden disk drive is actually performed on the file "12345678.sav".

To replicate the experiments of this tutorial, you have to follow the instructions in Section 2 of Tutorial 20.

2. Lab Configuration
In general we will use the instructions of Section 2 of Tutorial 20. In the following we just remind you of several important steps in the configuration:
(1) You need a separate image named "Win_Notes" to record and comment the code. You don't really need to run the malware on this instance, but just to record all your observations using the .udd file. To do this, you have to modify the control flow of IMM so that it does not crash on .sys files.
(2) The second "Win_DEBUG" image has to be run in the DEBUG mode and there should be a WinDbg hooked from the host system using COM part -- so here, we are doing kernel debugging.
(3) Set a breakpoint "bu _+372b" in WinDbg to intercept the driver entry function.

Once your Win_Notes environment is set up you should have the following dump at 0x1000372b.

Figure 1. Win_Notes Dump

Figure 2 shows the initial setting of WinDbg.

Figure 2. Win_Debug ScreenShot

In the following we use "_" to represent the base address of the driver file. In the Win_Notes, the base address is 0x10000000 and in the WinDbg, it is based on the run-time value, e.g., 0xfafe6400. Whenver we refer to an address we will use the form of _ + offset, e.g., _+372b (which is the entry address of the driver)

3. Basic WinDbg Skills for Inspecting Function Parameters
This section introduces several useful techniques you can use to analyze function calls when using WinDbg. We will take the function call at 0x10003743 (in Figure 1) as an example. Our objective is to analyze: what is the function and what are its parameters.

Since we might have to step into the function in WinDbg, let's first set a breakpoint at the next immediate instruction after 0x10003743, if you look at Figure 1 you will notice that the next address is _ + 3749.

You might notice that here we are using "bp" instead of "bu", this is because the module name "_" can be resolved by WinDbg now after the module is loaded.

Now let's examine the data around ESP. Typing "dd esp", we have the following. Clearly, currently ESP value is 0xfafb39c0 and the first parameter would be located at 0x81184008 and the second parameter would be 0x0000005c and so on.kd> dd espfafb39c0 81184008 0000005c 81139f38 e1533360fafb39d0 00000000 dd841000 fafb3a18 812e6f80fafb39e0 8054b6b8 81184076 e14fd07e 81184000fafb39f0 805702de e14fd008 e14fd000 e14fd080fafb3a00 00000001 00000000 00000080 00000020fafb3a10 81184076 80557ee0 81184000 80566ca2fafb3a20 e14fd07e 805a65ee 805a669a 81184000fafb3a30 e1bc9908 00000000 e14fd008 e14fd010

Now press F11 in WinDbg into the function _+3743 you will have the following:kd> tnt!wcsrchr:80506d93 8bff mov edi,edi

Notice that it shows that we are currently located in the function body of nt!wcsrchr. This allows us to identify the function name: wcsrchr. There could be an easier way to identify it using the "ln" command in WinDbg. However, interestingly, it does not work in our session and at this moment we do not know the cause.

Searching the documentation for wcsrcchr(str, c), we soon find that wcsrchr is the wide character version of strrchr which searches the last occurrence of a character "c" in a string "str". Here the wide character means that each character actually takes two bytes (for internationalization and localization purpose).

So far we could infer that 0x81184008 is the starting address of the string, and the character is 0x5c. Doing a data dump of 0x81184008, we have:

Look at the above data dump you can immediately infer that the string is "\REGISTRY\MACHINE\SYSTEM\ControlSet001\Services\.mrxsmb" and 0x5c is the "\" character. Clearly, Max++ is trying to get the service name associated with the driver. Keep in mind that the string (service name) is from the Driver_Object (the first parameter of the entire driver entry function)!

Now type "g" again in Windbg so that it hits _+3749. Then run the program by F10 (step over) several times, you will soon reach the code at _+375f (the 0x1000375f in Figure 1). You might notice that it is comparing the value located at [ebx] with 0x2E. Look at the dump you might notice that the ASCII value for 0x2E is "."

The purpose is to examine if the service name starts with ".". Recall that in Tutorial 18 (section 4), when Max++ infects the driver, it also modifies its registry entry (duplicates all the registry contents and creates a new entry name "."). This time, the code is verifying that it is indeed the infected driver!

If this is indeed the infected driver and now since it's "correctly" running. Max++ now has to hide its trace and properly set up the registry key. The next action it's going to perform is to remove the "." and "LEGACY_" registry entry. It's now your job to analyze the two function calls at _+3797 and _+379d (0x10003797 and 0x1000379d in Figure 1).

Challenge 1. Analyze the function call at 0x10003797 in Figure 1 (what is it trying to do and what are its function parameters?)Challenge 2. Analyze the function call at 0x1000379d in Figure 1 (what is it trying to do and what are its function parameters?)

4. Infecting File Driver
We now look at Max++'s behavior regarding disk driver. Right after modifying the registry entries, Max++ calls function _+36CA. [For your lab, simply go to 0x100036CA in your notes environment and "bp _+36CA" in WinDbg]. As shown in Figure 2, the function consists of two important steps: (1) to create a disk device "\??\C2CAD ..." and set up the 12345678.sav file for simulating all file operations on the disk. This is done by the function call at 0x100036DE; and (2) to infect all disk devices by setting up their IRP requests handlers properly; this is done at the JMP of 0x100036E4 (it jumps to 0x10002C95). We now proceed to these two important calls.

Figure 2. Infection of Disk Driver

4.1 Create Device "\??\C2CAD..." (_+3108)
At 0x100036DE, Max++ calls function createDeviceC2CAD (_+3108). Figure 3 displays the first part of function _+3108 (createDeviceC2CAD is the name we assigned to the function). It consists of two function calls: (1) at 0x1000312C, Max++ calls ntoskrnl.IoCreateDevice to create a device calls "\??\C2CAD..."; and then (2) at 0x10003140, Max++ calls ntoskrnl.ObMakeTemporaryObject to make the newly created device partially effective.

4.1.1 Create Disk Volume

Figure 3. Add Disk Device and Update IRP Handler

The following is the official declaration of IoCreateDevice() function from MSDN [1]. To analyze the contents, we could use the same technique introduced earlier and stop at _+312C to observe the stack. We listed the values of each of the attributes in the following as well (in the comments area).

In summary: its purpose is to create a new device named "\??\C2CAD972..." whose driver is ".mrxsmb".

Challenge 3: where is the resulting DEVICE_OBJECT located? (hint: look at the last parameter of IoCreateDevice).

The next function is to call ObMakeTemporary on the newly allocated DEVICE_OBJECT and call ObDereferenceObject again. These two function are reserved function of the OS and there is no official documentation. The purpose is to completely destroy the newly created device object! (so that it's not living in kernel any more!) You might wonder why?

This is because once the device object is deleted, you will not be able to find the device "\??\C2CAD..."anymore. But, the driver mechanism for handling request to "\??\C2CAD..." has somehow partly survived the deletion of device object. The next step of Max++ is to complete the construction of IO handling for "\??\C2CAD..." and simulate its actions using a single file called 12345678.sav.

4.1.2 Create Simulation File 12345678.sav

We now look at how the file 12345678.sav is created. As shown in Figure 4, it consists of three steps: (1) At 0x10003176 it encrypts 12345678.sav using the last AGP write time and generates a random file name such as yknueenef.sav; (2) it then at 0x100031B4 calls zwCreateFile to create the file, (3) it calls zwSetFileControlInfo to make sure that the file is not compressed.

Figure 4. Create File 12345678.sav

To see the trick of the encoding file name, look at the following WinDbg dump below (before and after the call at 0x10003177).

Challenge 4. Analyze the details of the function call at 0x10003177 (encrypt file name).Challenge 5. Analyze the details of the function call at 0x100031B4 (create file)Challenge 6. Analyze the details of the function call at 0x100031E5 (set file control information)

Figure 5. Set up Properties of 12345678.sav and Test Disk Drive

As shown in Figure 5, the next action Max++ performs is to set the end of file of 12345678.sav to 0x0100 0000 (because this is going to be a big file that holds a lot of stuff). It then gets the device handler of 12345678.sav (note that the current disk driver is "FileSystem\sr"), however, this handler will be never used (by the IoDeleteDevice at the end). Because at 0x1000329E, it directly jumps and skips the call of IoDeleteDevice call.

Challenge 7. Analyze the details of the function call at 0x100031FD (zwSetInformation).Challenge 8. Analyze the details of the function call at 0x1000322E (getDevice)Challenge 9. Analyze the details of the function call at 0x10003297 (which calls _+302E)

Note that function _+302E (in challenge 9) just tests the opening of "\??\C2CAD..." and it fails (no wonder, because the IRP handling has not been set up for the new disk driver yet).

4.2 Infect Disk Devices (_+2C95)
We now look at the function at 0x10002C95 (the jump is made from 0x100036E4). Figure 6 shows its function body. This function infects (modifies) all device objects hooked to the Disk driver. We now check the details in depth. As shown in the highlighted areas in Figure 6, the function performs to actions: (1) first it calls IoEnumerateDeviceObjectList to get the list of device objects registered with the "\Driver\Disk" driver - in our system, we get a list of two devices; (2) it then uses a loop to modify the devices related (creating a new device and making some copies as well as modifications). But what's the purpose of all these actions? We need to get more details and trace the execution.

Figure 6. Infect Devices Hooked to Disk Driver

4.2.1 Device Object List
We are interested in figuring out the device list involved. To achieve this goal, we need to first check out the parameters of IoEnumerateDeviceObjectList. According to [2], the function prototype of IoEnumerateDeviceList is listed below:

Clearly, the first parameter is the INPUT driver object (related to which we want to enumerate all devices), the second parameter is a pointer points to the place holder for a list of _DEVICE_OBJECTs, and the fourth parameter is another integer point which points to the integer variable that holds the number of device objects.

To examine the contents of the stack, we display the first four words starting from the ESP value, as shown below. Here, the first param (DriverObject) is 81299670 and the fourth param (ActualNumberDeviceObjects) is fafab9bc.

kd> bp _+2cd8kd> gfae36cd8 ff15d480e3fa call dword ptr [_+0x40d4 (fae380d4)]kd> dd espfafab5a0 81299670 fafab5b4 00000400 fafab9bckd> dt _DRIVER_OBJECT81299670 nt!_DRIVER_OBJECT ... +0x018 DriverExtension : 0x81299718 _DRIVER_EXTENSION +0x01c DriverName : _UNICODE_STRING "\Driver\Disk"
By examining the contents of _DRIVER_OBJECT using the WinDbg dt command, we are able to know that the driver is "\Driver\Disk". After stepping over the function call, we can identify that there are two Device Objects involved, and they are 812fb810 and 812fc030: . Details are shown below.

kd> p_+0x2cde:fae36cde 395dfc cmp dword ptr [ebp-4],ebxkd> dd fafab9bcfafab9bc 00000002 fafabc7c fae377af ffb3a6e8kd> dd fafab5b4fafab5b4 812fb810 812fc030 00000000 e1b63f30
We could display the contents of the two device object at as below. First note their difference in Sector size. What is your guess? You could infer that _DEVICE_OBJECT 812fc030 is not a volume! According to "If the device object does not represent a volume, this member is set to zero. If the device object represents a volume, this member specifies the volume's sector size, in bytes." in [3].

4.2.2 The Loop That Performs Infection
We now look at the very tricky actions of related to infection. The loop picture is shown below.

Figure 7: Part 1 of the Loop: check If this the module

At _+2CF6 (or 0x10002CF6), EDI is now pointing to the current _DEVICE_OBJECT being visited. You can verify the information below (it's now pointing to the second device at 812FC030). Note that the loop starts to process the LAST device in the list! Can you figure out why?

Challenge 10. Prove that the loop as shown in Figure 6 is processing the device objects from the end to the beginning.

Now, as shown in Figure 7, Max++ is performing a lot of checks on the device properties, mostly performed on the DeviceExtension structure. The device extension structure is device specific and we could not get more details here, but the 2nd device module has passed all checks and now Max++ performs the modification of the module.

4.2.3 Infection
The second part of the loop is shown in Figure 8.

Figure 8. Create New Device Object to Wrap Old Device Object

The first interesting action is the call of IoCreateDevice() at _+2D34. Its first parameter is a DRIVER_OBJECT. When we dump its contents we found something interesting:kd> dt _DRIVER_OBJECT ffb3a6e8nt!_DRIVER_OBJECT +0x000 Type : 0n4 ... +0x02c DriverInit : 0xfae3772b long +0 +0x030 DriverStartIo : (null) +0x034 DriverUnload : (null) +0x038 MajorFunction : [28] 0xfae36bde long +0

This is the infected driver (entry address 372b)! So Max++ is creating another IO device which is associated with the infected driver. Note that the DEVICE_OBJECT returned by the function will be stored at [EBP-8]. You can infer this from the IoCreateDevice() specification and the code.

Next, Max++ will perform some copy actions. For example, at _+2D3E, it is copying the StackSize attribute (located at offset 0x30) of _DEVICE_OBJECT from the old device object to the new one. Then, at _+2D4D, it is setting the first word (guess: AttachedDevice) of the DeviceExtension to the old device. If you look into details, it seems to be performing some standard linked list operations which modifies the DeviceExtension of the new device object in the following ways:

At DeviceExtension[0] it stores the OLD device object, at DeviceExtension [4] it stores the real attached low level hardware device (controled by ATAPI)! While the device itself is registered with the INFECTED driver!

Thus, it is very clear what Max++ wants to do: it inserts another layer of device into the IRP request handler chain such that any disk IRP request will go through the INFECTED DRIVER (entry function: 372b)!

At this point, we have a big picture: Max++ has completed the first part of wiring of the malicious driver to disk operations (a new wrapper disk device object is created). All IRP requests related to disk will be wired to the malicious driver first! But notice that the "\??\C2CAD..." device has not been completely set up. We'll continue to the second part in the next tutorial.

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.

(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 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 breakpointbu _ + 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:

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.