1 / 38

What makes the Rustocks tick

Introduction. This paper describes the evolution of Windows kernel based rootkit techniques in the Rustock malware family. The paper first presents a brief history of hooking techniques used by previous versions, i.e., Rustock.A and Rustock.B and then a detailed analysis of Rustock.C. Most of these techniques are well published as kernel vulnerabilities. But, this paper correlates real implementation of those techniques to various versions of Rustock. .

marc
Download Presentation

What makes the Rustocks tick

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


    1. What makes the Rustocks tick! Chandra Prakash Sunbelt Software, USA

    2. Introduction This paper describes the evolution of Windows kernel based rootkit techniques in the Rustock malware family. The paper first presents a brief history of hooking techniques used by previous versions, i.e., Rustock.A and Rustock.B and then a detailed analysis of Rustock.C. Most of these techniques are well published as kernel vulnerabilities. But, this paper correlates real implementation of those techniques to various versions of Rustock.

    3. Introduction, contd…. In regard to Rustock.C, this paper describes its step by step operational characteristics in kernel mode right from its startup and to the point when its bot dll is activated in user mode. Understanding operational characteristic of Rustock.C through static analysis is very cumbersome as it executes after several stages of unpacking. Also, right from its startup multiple threads are created that add to the difficulty of its dynamic analysis.

    4. Time frames of analyzed samples Here are the time frames of the samples that are analyzed: Rustock.A - March 2007. Rustock.B - August 2007. Rustock.C - June 2008. It is to be noted that there exist slight differences in operational behavior within each variant, i.e., Rustock.C just like A and B has sub-variants, with slightly differing behavior.

    5. Hook Techniques Used by Rustock.A Rustock.A hooks IofCallDriver which gets called from IoCallDriver, a well documented API used to send IRP requests to a target device object of a designated driver. 80529401 mov edx,dword ptr [esp+8] ; IRP 80529405 mov ecx,dword ptr [esp+4]; DeviceObject 80529409 call dword ptr [nt!pIofCallDriver] 8052940f ret 8 It hooks pIofCallDriver to hide its own files on disk from IRPs sent to NTFS driver.

    6. Typical IRP functions used to hide files

    7. Rustock.A – KiFastCallEntry Hook kiFastCallEntry routine of ntoskrnl that sets up call into System Service Descriptor Table (SSDT) to service a user mode system call, when the call transitions into kernel mode from user mode. For example, calls into ZwOpenFile, ZwTerminateProcess etc. in ntdll in user mode are handled in kernel mode via kiFastCallEntry. The virtual address referenced by MSR IA32_SYSENTER_EIP (0x176) is kiFastCallEntry.

    8. Rustock.A – KiFastCallEntry Hook, contd…. kiFastCallEntry hook uses a jmp instruction placed at a virtual address in the resource section of ntoskrnl containing string “FATAL_UNHANDLED_HARD_ERROR”. ; IA32_SYSENTER_EIP = 0x176 ; msr[176] = 806afd59 ; 806afd59 e9 ec 2e e6 77 4e 44 4c ....wNDL ; 806afd61 45 44 5f 48 41 52 44 5f ED_HARD_ ; 806afd69 45 52 52 4f 52 0d 0a 00 ERROR... 806afd59 e9ec2ee677 jmp rustockA+0x4c4a

    9. Hook techniques used by Rustock.B Rustock.B hooks IRP_MJ_CREATE dispatch routine of NTFS driver to hide its files. The IRP_MJ_CREATE dispatch routine of NTFS driver gets called when a file or directory is opened. It also hooks ZwCreateKey and ZwOpenKey that are used to hide its registry keys.

    10. ZwOpenkey hook example in Rustock.B 000105A4 call PsGetCurrentProcessId 000105A9 mov esi, [ebp+ObjectAttributes] 000105AC mov ebx, [ebp+KeyHandle] 000105AF push esi 000105B0 push [ebp+DesiredAccess] 000105B3 mov edi, eax; Save current process id in EDI. 000105B5 mov eax, OrigNtOpenKey 000105BA push ebx 000105BB call dword ptr [eax] ; Calling original NtOpenKey. 000105BD test eax, eax 000105BF mov [ebp+ObjectAttributes], eax 000105C2 jl short HookExit 000105C4 cmp edi, ServicesExePid ; Allow ZwCreateKey calls from services.exe. 000105CA jz short HookExit 000105CC mov edx, [esi+8] ; ObjectAttributes->ObjectName->Length 000105CF test edx, edx 000105D1 jz short HookExit . . .

    11. Rustock.C – Stage I unpacking In stage 1 it uses simple XOR to unpack to a designated region and after unpacking is done it transfers control to post stage 1 un-encrypted code. lea esp, [esp-4] mov dword ptr [esp], offset byte_13000 retn

    12. Rustock.C – Stage I unpacking, contd… A different sample of Rustock.C shows an anti-debugging trick when stage 1 unpacking is complete. f9af6065 popad f9af6066 sub esp,4 ; Increase current top of stack. f9af6069 mov dword ptr [esp],offset rustockC+0x3000 f9af6070 add esp,4 ; Decrease current top of stack. f9af6073 push dword ptr [esp-4] ; Access to a value beyond current top ; of stack. In a debugging session, this stack ; location may very well contain previous ; register eflags value stored by debug ; trace interrupt. As a result EIP after ‘ret’ ; can point to an invalid location. f9af6077 ret

    13. Locating base address of ntoskrnl After Stage 1 unpacking, it locates the load address of ntoskrnl by some pointer arithmetic on the interrupt descriptor table (IDT) by the following set of instructions: mov eax,dword ptr fs:[00000038h] ; Get IDT address. mov eax,dword ptr [eax+4] xor al,al find_ntos_base: sub eax,100h cmp word ptr [eax],5A4Dh jne find_ntos_base The base address of ntoskrnl is used to scan its export table for the following functions: ExAllocatePool ExFreePool ZwQuerySystemInformation _stricmp

    14. Stage 2 decompression and decryption In stage 2, it allocates a temporary buffer using ExAllocatePoolWithTag to decompress and decrypt data from stage 1. All memory allocation calls in kernel are made through this API with tag name as ‘Ddk ‘ (note the space character at the end). The decompression is done using apLib algorithm followed by decryption using RC4.

    15. Loading after stage 2 decompression and decryption The image is loaded with its image base as start address of the location from where it was originally unpacked, wiping out stage 1 decrypted data. Imports are fixed from ntoskrnl and hal.dll. The load information of these imported kernel modules is obtained through ZwQuerySystemInformation using SystemModuleInformation class. After the IAT fix up, the relocations are done in place. Once relocations are completed it zeroes out the MZ and PE signatures to obfuscate the loaded image to prevent its detection in kernel debuggers.

    16. DriverEntry of final unpacked driver image

    17. Initial activities of Thread1 Thread 1 created from DriverEntry. It starts creating a named event handle via ZwCreateEvent with name as \BaseNamedObjects\{C8453B23-1087-27d9-1394-CDBF03EC72D8}. Using \BaseNamedObjects directory indicates that this event object is also intended to be shared with user mode.

    18. Rustock.C connection to Rustock.A (Thread1) It starts by searching a NULL terminated ASCII string “FATAL_UNHANDLED_HARD_ERROR” in the resource section of ntoskrnl. __try { PMDL mdl; mdl = IoAllocateMDL( vaFatalHandledHardErrorStr, 0x1b, // NULL terminated length of str. 0, 0, 0); MmProbeAndLockPages( mdl, KernelMode, IoAccessRead); } __except(EXCEPTION_EXECUTE_HANDLER) { IoFreeMdl(Mdl); } The allocated MDL is not used anywhere. This code shows relation to Rustock.A that hooks the above region of string in the resource section to achieve kiFastCallEntry hook.

    19. Loading ntdll (Thread1) Reads ntdll.dll off disk. Then loads ntdll to obtain SSDT index of functions it hooks. After virtually mapped ntdll is ready, it stores it own load address, size and full driver path in designated memory locations for subsequent use. The self load information is actually used to map its own driver into user space as described later.

    20. Hooking registry (Thread1) Rustock.C hooks registry in a different way never seen before in its previous variants. It hooks registry key parse procedure in kernel that is registered by configuration manager with object manager. _OBJECT_TYPE_INITIALIZER +0x000 Length : 0x4c . . +0x030 OpenProcedure : (null) +0x034 CloseProcedure : 0x8056bf9e nt!CmpCloseKeyObject+0 +0x038 DeleteProcedure : 0x8056c072 nt!CmpDeleteKeyObject+0 +0x03c ParseProcedure : 0xf9b4fdd3 <-- Rustock.C address (normally nt!CmpParseKey). +0x040 SecurityProcedure : 0x8056bfd6 nt!CmpSecurityMethod+0 +0x044 QueryNameProcedure : 0x805a935e nt!CmpQueryKeyName+0 +0x048 OkayToCloseProcedure : (null) The parse procedure is employed to parse a registry path in registry related APIs. Some more functions ZwOpenKey and ZwCreateKey are also hooked.

    21. Hooking IRP_MJ_CREATE dispatch of NTFS driver (Thread1) It gets a handle to its own driver file on disk using ZwCreateFile and that handle is used in ObReferenceObjectByHandle to get a FILE_OBJECT pointer. It then calls IoGetRelatedDeviceObject on the DeviceObject field of file object to get the highest level device object in file system filter driver stack. Typically on machines supporting filter manager the highest level device object happens to be the device object of filter manager (FltMgr.sys) driver. Using highest level device object it walks down device stack until it finds the lowest level device object created by the NTFS driver. The device object of NTFS driver is used to hook its IRP_MJ_CREATE dispatch routine.

    22. Hooking NtTerminateProcess (Thread1) It then hooks ZwTerminateProcess and sets up a function dispatch table, which is used to serve the commands from bot dll in an obscure way. NTAPI NtTerminateProcess( IN HANDLE hProcess, IN NTSTATUS ExitCode ); 00012339 cmp dword ptr [ebp+0Ch], 0FCC7975Bh ; ExitCode parameter contains special ; encoded value for bot dll and driver ; communication. 00012340 jnz short OrigNtTerminateProcess . . OrigNtTerminateProcess: ; Normal process termination requests come ; here. 000123AF push dword ptr [ebp+0Ch] 000123B2 push dword ptr [ebp+8] 000123B5 mov eax, OrigNtTerminateProcess 000123BA call dword ptr [eax] The ExitCode parameter of ZwTerminateProcess is set to a specific value that indicates a message from bot dll to driver. The message parameters are encoded in the first hProcess parameter. Normal process termination requests are routed to original NtTerminateProcess routine address stored in memory as shown.

    23. Setting up bot dll (Mapping own driver image in Services.exe) services.exe is used as goat process for hosting bot dll and gets process id of services.exe process using SystemProcessAndThreadsInformation class in ZwQuerySystemInformation call. This process id is used to get the EPROCESS object associated with services.exe. The EPROCESS object is used in KeAttachProcess call to attach to the virtual address space of services.exe. Then it maps its own driver’s PE image into services.exe address space using IoAllocateMdl,MmBuildMdlForNonPagedPool, MmMapLockedPages sequence of calls. By mapping its own driver image in user space, it makes available to user mode process its code and data

    24. Setting up bot dll contd…. (Disabling DEP) NtSetInformationProcess is called on services.exe with PROCESS_INFORMAION_CLASS parameter as ProcessExecuteFlags(0x22) with mask value MEM_EXECUTE_OPTION_ENABLE(0x2). The purpose of this call is to disable no-execute (NX) bit for DEP data pages in this process.

    25. Setting up bot dll, contd… (Identifying target threads for APC1) Gets information of all threads of services.exe using SystemProcessAndThreadsInformation class in ZwQuerySystemInformation. To each of thread of services.exe, it sends an asynchronous procedure call (APC1). The APC mechanism is designed to execute a function in the context of a target thread.

    26. Setting up bot dll, contd… (APC1: Setting up import function table) APC1 call is to setup import address table for a list of functions LoadLibraryA GetProcAddress SetEvent Init CreateThread SleepEx The virtual addresses of these functions are resolved using the load address of kernel32.dll from dll load information stored in process environment block (PEB). The address of PEB is obtained using FS:[30] register expression. Init function is address of bot dll resolved by APC2.

    27. Setting up bot dll, contd… (Creating more alertable threads for prompt APC delivery) APC1 also creates a new thread in user mode. ThreadStartRoutine: 009f4281 push 1 009f4283 push 0FFFFFFFFh 009f4285 call dword ptr [esp+0Ch] ; SleepEx(INFINITE, TRUE) 009f4289 jmp ThreadStartRoutine This thread seems to doing nothing but sleeping forever! DWORD SleepEx( DWORD dwMilliseconds, BOOL bAlertable ) The real purpose of this sleep is to put thread in an alertable state with bAlertable parameter set to TRUE so that future APCs can promptly executed. If the thread is not in altertable state, APCs are not executed promptly; they are queued.

    28. Setting up bot dll, contd… (Read own driver file by direct access to NTFS driver) It first creates an empty file object using ObCreateObject and sets the file name in the file object referring to own driver file. It then gets the device object of lowest file system driver, i.e., NTFS driver, and using the new file object and device object generates own IRP_MJ_CREATE to read own driver file. The file is read in two steps. First the file size is obtained using IRP_MJ_QUERY_INFORMATION using FILE_INFORMATION_CLASS as FileStandardInformation. In the second step IRP_MJ_READ is sent in a buffer allocated from ExAllocatePoolWithTag.

    29. Setting up bot dll, contd… (Bypassing filter drivers) Typically IRP_MJ_CREATE, IRP_MJ_CLEANUP and IRP_MJ_CLOSE are implicitly generated by I/O Manager inside the Windows kernel, and Rustock.C by rolling out these IRPs on its own shows the sophistication of its authors. Making own IRP_MJ_CREATE the right way is a non-trivial task as it has several intricate steps to it, especially relating to setting parameters for caller’s security context. Due to rolling out its own IRP_MJ_CREATE, Rustock.C driver is able to send direct read (IRP_MJ_READ) and write (IRP_MJ_WRITE) requests to NTFS driver. This allows the malware to bypass any filter drivers that are typically used by security vendors to provide kernel based on access security against malicious files.

    30. Setting up bot dll, contd… (Layout of botdll in driver)

    31. Setting up bot dll, contd… (decrypting/uncompressing bot dll, sending APC2) The bot dll is stored encrypted and compressed in the original driver file The encryption consists of simple XOR and the compression algorithm used is aPLib. APC2 is sent that consists of code that does fixups of imports of bot dll. The imports are fixed up using LoadLibraryA and GetProcAddress APIs that are already setup via APC1.

    32. Activities of kernel Thread2 (Revival Strategy) The Thread2 is created from Thread1 and writes its own driver file to disk every five seconds in a loop. This is most likely its revival strategy against any deletes of its on disk driver file. Its own driver file is saved in memory during startup phase. Similar to reading of its driver file, it performs its write also by direct access to NTFS driver, bypassing file system filter device stack.

    33. Activities of kernel Thread3 Thread3 is created conditionally from process create notify routine. This thread does the same work as done towards the end of Thread1, which involves reading own driver, sending APC1, decompressing bot dll and sending APC2. In the notify routine it checks for process create only notifications of services.exe.

    34. Dispatch Routines The Rustock.C driver really has no driver dispatch routines set up in its DRIVER_OBJECT, as done in the DriverEntry routine of a typical device driver. But, it accomplishes the similar objective by an array of 11 functions setup functions in memory. For example, Dispatch function 0 frees up current driver in memory and reads own disk driver afresh and subsequently sends APC1 and APC2 as described earlier. Dispatch function 1 writes a new driver using IRP_MJ_WRITE. This can potentially be used to activate a completely new driver downloaded from bot dll. Dispatch function 2 deletes a disk file, using IRP_MJ_SET_INFORMATION and FileInformationClass as FileDispositionInformation.

    35. Dispatch Routines input/output parameters Each of these functions is called through hooked ZwTerminateProcess API, by setting a function index along with the corresponding input output parameters. The layout of input/output structure is described below. struct ZwTermProcDispatchIOParam { +0x0 FunctionIndex // Index into function array. +0x4 InputBuffer // Input buffer, if applicable. +0x8 InputBufferSize // Input buffer size, if applicable. +0xC OutputBuffer // Output buffer, if applicable. +0x10 OutputBufferSize // Output buffer size, if applicable. } The address of this structure is passed in as first parameter to ZwTerminateProcess API and the second parameter (ExitCode) consist of the special encoded value. NTAPI ZwTerminateProcess( IN HANDLE hProcess, IN NTSTATUS ExitCode );

    36. Removal All the above Rustock variants researched in this paper were removed by deleting its driver service registry keys under HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\<Rustock_Driver_Name> followed by a system reboot and delete of its driver file. But first the registry hiding mechanism employed by it needs to be defeated to get access to its driver service registry keys.

    37. Conclusion Each newer version the Rustock malware employs a real implementation of some unused technique that has been presented elsewhere as a Windows kernel vulnerability. Rustock’s success on 32-bit operating systems is primarily due to Windows kernel’s vulnerability to allow patching in designated locations. Windows 64-bit operating systems overcome this vulnerability through enforcement of patch guard and driver signing. There appears to be a trend shift in PC industry from 32-bit to 64-bit operating systems It remains to be seen if Rustock can sustain its notoriety on 64-bit operating systems as it did on 32-bit, by constant improvements on its stealth techniques.

    38. References IA32_SYSENTER_EIP. http://uninformed.org/index.cgi?v=8&a=2&p=12. Nt vs. Zw - Clearing Confusion On The Native API. http://www.osronline.com/article.cfm?id=257 Lukasz Kwiatek, Stanislaw Litawa, Yet Another Rustock Analysis ..., Virus Bulletin Magazine, August 2008. Kwiatek, L. Rustock.C – kernel mode protector. http://www.eset.com/threat-center/blog/?p=127 Bypassing Windows Hardware-enforced Data Execution Prevention. www.uninformed.org/?v=2&a=4. Asynchronous Procedure Calls. http://msdn.microsoft.com/en-us/library/ms681951.aspx. Stealth Rootkit Designed for Vista. http://news.softpedia.com/news/Stealth-Rootkit-Designed-for-Vista-30108.shtml Backdoor.Tidserv. http://www.symantec.com/business/security_response/writeup.jsp?docid=2008-091809-0911-99&tabid=2 Windows Vista blog. http://windowsvistablog.com/blogs/windowsvista/archive/2008/07/30/windows-vista-64-bit-today.aspx. Skape & Skywing, A Catalog of Local Windows Kernel-mode Backdoor Techniques. http://www.uninformed.org/?v=8&a=2&t=txt.

    39. Questions?

More Related