Malware Analysis Tutorial 21: Hijack Disk Driver

Learning Goals:

  1. Use WinDbg for kernel debugging
  2. Understand basic inner working of disk driver
  3. Understand virtual hidden drive creation
  4. Reverse engineering Max++ driver infection technique

Applicable to:

  1. Operating Systems
  2. Assembly Language
  3. Operating System Security

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.

In WinDbg let’s set two breakpoints:
kd> bp _+3743
kd> bp _+3749
kd> g

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 esp
fafb39c0  81184008 0000005c 81139f38 e1533360
fafb39d0  00000000 dd841000 fafb3a18 812e6f80
fafb39e0  8054b6b8 81184076 e14fd07e 81184000
fafb39f0  805702de e14fd008 e14fd000 e14fd080
fafb3a00  00000001 00000000 00000080 00000020
fafb3a10  81184076 80557ee0 81184000 80566ca2
fafb3a20  e14fd07e 805a65ee 805a669a 81184000
fafb3a30  e1bc9908 00000000 e14fd008 e14fd010

Now press F11 in WinDbg into the function _+3743 you will have the following:
kd> t
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:

kd> db 0x81184008
81184008  5c 00 52 00 45 00 47 00-49 00 53 00 54 00 52 00  .R.E.G.I.S.T.R.
81184018  59 00 5c 00 4d 00 41 00-43 00 48 00 49 00 4e 00  Y..M.A.C.H.I.N.
81184028  45 00 5c 00 53 00 59 00-53 00 54 00 45 00 4d 00  E..S.Y.S.T.E.M.
81184038  5c 00 43 00 6f 00 6e 00-74 00 72 00 6f 00 6c 00  .C.o.n.t.r.o.l.
81184048  53 00 65 00 74 00 30 00-30 00 31 00 5c 00 53 00  S.e.t.0.0.1..S.
81184058  65 00 72 00 76 00 69 00-63 00 65 00 73 00 5c 00  e.r.v.i.c.e.s..
81184068  2e 00 6d 00 72 00 78 00-73 00 6d 00 62 00 00 00  ..m.r.x.s.m.b
81184078  da 01 0c 00 00 00 00 00-ff ff ff ff 18 00 00 00  …………….

Look at the above data dump you can immediately infer that the string is “REGISTRYMACHINESYSTEMControlSet001Services.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 “.”

So what’s the purpose of the code?

fae6775f 66833b2e        cmp     word ptr [ebx],2Eh

kd> db ebx
81184068  2e 00 6d 00 72 00 78 00-73 00 6d 00 62 00 00 00  ..m.r.x.s.m.b…
81184078  da 01 0c 00 00 00 00 00-ff ff ff ff 18 00 00 00  …………….
81184088  00 10 00 00 01 00 00 00-00 00 00 00 d9 01 00 00  …………….
81184098  00 00 00 00 58 4d 4c 03-00 00 00 00 d9 01 0c 00  ….XML………
811840a8  00 00 00 00 ff ff ff ff-18 00 00 00 00 10 00 00  …………….
811840b8  01 00 00 00 00 00 00 00-30 00 00 00 00 00 00 00  ……..0…….
811840c8  4c 51 4c 03 00 00 00 00-30 00 0c 00 00 00 00 00  LQL…..0…….
811840d8  ff ff ff ff 18 00 00 00-00 10 00 00 01 00 00 00  …………….

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

NTSTATUS IoCreateDevice(
__in      PDRIVER_OBJECT DriverObject, //"Driver.mrxsmb"
__in      ULONG DeviceExtensionSize,
__in_opt  PUNICODE_STRING DeviceName, //""??C2CAD972#4079#4fd3#A68D#AD34CC121074"

__in      DEVICE_TYPE DeviceType,
__in      ULONG DeviceCharacteristics,
__in      BOOLEAN Exclusive,
__out     PDEVICE_OBJECT *DeviceObject

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

kd> dd esp
fafaf8f4  fafaf940 81138bb8 812f2000 812f2068
fafaf904  fafaf927 0073005c 00730079 00650074
fafaf914  0072006d 006f006f 005c0074 00790073
fafaf924  00740073 006d0065 00320033 0063005c
fafaf934  006e006f 00690066 005c0067 00320031
fafaf944  00340033 00360035 00380037 0073002e
fafaf954  00760061 00000000 00000000 00000000
fafaf964  00000000 00000000 00000000 00000000
kd> db fafaf940
fafaf940  31 00 32 00 33 00 34 00-35 00 36 00 37 00 38 00
fafaf950  2e 00 73 00 61 00 76 00-00 00 00 00 00 00 00 00  ..s.a.v………
fafaf960  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  …………….
fafaf970  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  …………….
fafaf980  00 00 00 00 00 00 00 00-23 00 00 00 c1 c8 4d 80  ……..#…..M.
fafaf990  08 00 00 00 10 8b 13 81-10 8b 13 81 cc fd fa fa  …………….
fafaf9a0  30 00 00 00 10 8b 13 81-00 20 2f 81 68 20 2f 81  0…….. /.h /.
fafaf9b0  7c fc fa fa 00 00 00 00-7c fc fa fa e3 76 e0 fa  |…….|….v..

kd> p
fae0717c 53              push    ebx
kd> db fafaf940
fafaf940  79 00 6b 00 6e 00 75 00-65 00 65 00 6e 00 66 00  y.k.n.u.e.e.n.f.
fafaf950  2e 00 73 00 61 00 76 00-00 00 00 00 00 00 00 00  ..s.a.v.……..
fafaf960  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  …………….
fafaf970  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  …………….
fafaf980  00 00 00 00 00 00 00 00-23 00 00 00 c1 c8 4d 80  ……..#…..M.
fafaf990  08 00 00 00 10 8b 13 81-10 8b 13 81 cc fd fa fa  …………….
fafaf9a0  30 00 00 00 10 8b 13 81-00 20 2f 81 68 20 2f 81  0…….. /.h /.
fafaf9b0  7c fc fa fa 00 00 00 00-7c fc fa fa e3 76 e0 fa  |…….|….v..

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 “FileSystemsr“), 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 “DriverDisk” 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:

NTSTATUS IoEnumerateDeviceObjectList(
__in   PDRIVER_OBJECT DriverObject,
__out  PDEVICE_OBJECT *DeviceObjectList,
__in   ULONG DeviceObjectListSize,
__out  PULONG ActualNumberDeviceObjects

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 _+2cd8
kd> g
fae36cd8 ff15d480e3fa    call    dword ptr [_+0x40d4 (fae380d4)]
kd> dd esp
fafab5a0  81299670 fafab5b4 00000400 fafab9bc

kd> dt _DRIVER_OBJECT 81299670
   +0x018 DriverExtension  : 0x81299718 _DRIVER_EXTENSION
   +0x01c DriverName       : _UNICODE_STRING “DriverDisk

By examining the contents of _DRIVER_OBJECT using the WinDbg dt command, we are able to know that the driver is “DriverDisk“. 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
fae36cde 395dfc          cmp     dword ptr [ebp-4],ebx
kd> dd fafab9bc
fafab9bc  00000002 fafabc7c fae377af ffb3a6e8

kd> dd fafab5b4
fafab5b4  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].

kd> dt _DEVICE_OBJECT 812fb810
   +0x000 Type             : 0n3
   +0x002 Size             : 0x368
   +0x004 ReferenceCount   : 0n0

   +0x0ac SectorSize       : 0x200
   +0x0ae Spare1           : 0
   +0x0b0 DeviceObjectExtension : 0x812fbb78 _DEVOBJ_EXTENSION
   +0x0b4 Reserved         : (null)
kd> dt _DEVICE_OBJECT 812fc030
   +0x000 Type             : 0n3
   +0x002 Size             : 0x518
   +0x004 ReferenceCount   : 0n0
   +0x0ac SectorSize       : 0
   +0x0ae Spare1           : 1
   +0x0b0 DeviceObjectExtension : 0x812fc548 _DEVOBJ_EXTENSION
   +0x0b4 Reserved         : (null)

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?

kd> bp _+2CF6
kd> g
Sat Mar 17 10:40:52.406 2012 (UTC – 4:00): Breakpoint 1 hit
fae36cf6 8b4728          mov     eax,dword ptr [edi+28h]
kd> r edi

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 ffb3a6e8
   +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. Microsoft, MSDN, “IoCreateDevice”, available at
2. Mcrosoft MSDN, “IoEnumerateDeviceObjectList”, available at
3. Microsoft MSDN, “Device Object”, available at

By admin