Ring3 / Ring0 Rootkit Hook Detection Part 1

Introduction

The cybercrime underworld hasn’t given me any exciting malware to reverse and I’m running out of ideas for new posts, so I’m going to do a 2 part article about the techniques used by rootkits to intercept function calls, and how to detect them. The first part will explain some hooking methods, the second part will explain how to detect them. As I haven’t done any kernel mode stuff on this blog, I will be looking at both user mode and kernel mode hooks on a x86 windows system.

Execution Flow

In order to get a better understanding of the attack surface, I’ve made a simplified flow chart of a call to the WriteFile function in kernel32.dll. This is just an example to highlight key points, I chose the WriteFile function as it makes for a nice example, and disk I/O is commonly intercepted by malware, however most of the stuff on this graph will apply to lots of functions.
 
(1)
  • WriteFile is just a simple wrapper for NtWriteFile.
  • Can be hooked with inline, IAT or EAT hooks.
  • Hooking this function will intercept all calls to WriteFile in whichever process the hooks are placed.
  • All paths used inside kernel32 are generally Dos Paths (C:file.txt).

(2)

  • NtWriteFile is a small stub that sets the EAX register to a 32bit value (I’ll explain this value later), then calls KiFastSystemCall.
  • Can be hooked with inline, IAT, or EAT hooks.
  • Hooking this function will intercept all calls to CreateFile, NtWriteFile or ZwWriteFile in whichever process the hooks are placed.
  • All paths used by ntdll file functions are generally NT Paths (??C:file.txt).
(2.1)
  • In order to call KiFastSystemCall NtWriteFile moves the address 0x7FFE0300 (KiFastSystemCall / KiFastSystemCall Pointer) into the EDX register, then it does “call edx” or “call dword ptr [edx]”
  • The rootkit could replace the address 0x7FFE0300 within the NtWriteFile function body in order to hook it.
  • Hooking this function will intercept all calls to CreateFile, NtWriteFile or ZwWriteFile in whichever process the hooks are placed.
  • All paths used by ntdll file functions are generally NT Paths (??C:file.txt).

(3)
  • KiFastSystemCall is a small stub that moves the stack pointer into the EDX register then executes the sysenter.
  • The stub is only 5 bytes in size and the last instruction (RETN) is pointed to by KiFastSystemCallRet, this only leaves 4 writable bytes (not enough space for a near call/jmp). Furthermore, the address is hard-coded which makes IAT or EAT hooks impossible.
  • Sometimes the KiFastSystemCall stub resides in KUSER_SHARED_DATA, in which case it is not writable from usermode.
  • By hooking this function, the rootkit gains the ability to intercept all user mode calls to kernel functions.

(4)
  • The SYSENTER instruction is what transfers execution from user mode to kernel mode, in order to execute an kernel function. when the instruction is executed, the CPU sets the code segment to the content  of the SYSENTER_CS register, the stack pointer to the content of the SYSENTER_ESP register, and the EIP to the content of the SYSENTER_EIP register. The SYSENTER_EIP register points to the KiFastCallEntry function ntoskrnl, as a result of this, the cpu will begin executing KiFastCallEntry.
  • These registers are known as MSRs (Model Specific Register), they are only readable by using the cpu instruction RDMSR (Read MSR) and writable using the WRMSR (Write MSR) instruction. These instructions are both privileged (can only be executed from ring 0) therefore, in order to hook, a kernel driver must be loaded.
  • By modifying the SYSENTER_EIP, the rootkit gains the ability to intercept all user mode calls to kernel functions, but we cannot intercept any kernel mode calls, because only user mode call use SYENTER.

(5)
  • KiFastCallEntry is responsible for taking the 32bit value from the EAX register (this is the value we mentioned in 2). The first 11 bits are the ordinal of the SSDT function to use (SSDT_Address+(Ordinal*4)), the 12th and 13th byte determine which SSDT to use, the rest of the bits are ignored. Once the function has worked out which SSDT to use, it calls the address at the given ordinal in the table.
  • Can be hooked with an inline hooks.
  • By hooking this function, the rootkit can intercept all user mode calls to kernel functions, as well as all kernel mode calls to functions starting with Zw, but not those starting with Nt.
(5.1)
  • Because the SSDT is a table of function pointers, it is also possible to hook calls by replacing the pointer within the SSDT. For every kernel function in ntdll, there is an equivalent pointer within the SSDT, therefore we can hook any function at will just by replacing the pointer. We are also able to hook all kernel mode calls to functions starting with Zw using this method, however, we cannot hook kernel mode calls to functions starting with Nt.

(6)
  • NtWriteFile…Again. We saw a call to NtWriteFile in 2, however that was just an ntdll.dll stub to enter into kernel mode, this is the actual NtWriteFile call pointed to by the address at the given SSDT ordinal.
  • NtWriteFile builds an IRP (I/O Request packet) and supplies it to IopSynchronousServiceTail, it also passes a device object associated with the file being written.
  • Can be hooked with an inline hook.
  • By hooking this function, the rootkit can intercept user mode and kernel mode calls to NtWriteFile and ZwWriteFile.

(7)
  • IopSynchronousServiceTail may only be used on certain versions of windows, it is just a simple wrapper for IofCallDriver, so I’ll skip this.

(8)
  • IofCallDriver takes a device object pointer (PDEVICE_OBJECT) and IRP pointer (PIRP) (both supplied by NtWriteFile). The device object contains a pointer to the driver object of the driver associated with that device (PDRIVER_OBJECT). The driver object contains a member called “MajorFunction”, this is an array of 28 driver defined function pointers (a bit like an EAT or the SSDT), ofCallDriver will call one of the IRP major functions, based on which one is specified by the “MajorFunction” member in the IO_STACK_LOCATION for the supplied IRP.In the case of file operations, the device object given by NtWriteFile will nearly always be filesystemntfs (aka ntfs.sys) or a filter device attached to FileSystemNtfs, because filter drivers pass on the call to the device below below them until it gets to FileSystemNtfs, we can assume the call will always end up at filesystemntfs unless one of the filter drivers cancels it. Here is a full list of IRP major function names:
    IRP_MJ_CREATE                   
    IRP_MJ_CREATE_NAMED_PIPE          
    IRP_MJ_CLOSE                     
    IRP_MJ_READ                      
    IRP_MJ_WRITE                       
    IRP_MJ_QUERY_INFORMATION           
    IRP_MJ_SET_INFORMATION           
    IRP_MJ_QUERY_EA                    
    IRP_MJ_SET_EA                      
    IRP_MJ_FLUSH_BUFFERS              
    IRP_MJ_QUERY_VOLUME_INFORMATION   
    IRP_MJ_SET_VOLUME_INFORMATION     
    IRP_MJ_DIRECTORY_CONTROL          
    IRP_MJ_FILE_SYSTEM_CONTROL        
    IRP_MJ_DEVICE_CONTROL             
    IRP_MJ_INTERNAL_DEVICE_CONTROL     
    IRP_MJ_SHUTDOWN                   
    IRP_MJ_LOCK_CONTROL               
    IRP_MJ_CLEANUP                    
    IRP_MJ_CREATE_MAILSLOT           
    IRP_MJ_QUERY_SECURITY             
    IRP_MJ_SET_SECURITY               
    IRP_MJ_POWER                      
    IRP_MJ_SYSTEM_CONTROL            
    IRP_MJ_DEVICE_CHANGE             
    IRP_MJ_QUERY_QUOTA                
    IRP_MJ_SET_QUOTA                 
    IRP_MJ_PNP
    • By hooking IofCallDriver, the rootkit can intercept practically any call to any driver. In order to only intercept calls to a certain driver, the rootkit can check the “DriverName” member pointed to by the driver object which is pointed to by the device object. Alternatively to intercept calls to a certain device, the rootkit could call ObQueryNameString on the device object (It is important to note that not all devices have names). The rootkit can also filter only specific IRP major function calls, this is done by calling “IoGetCurrentIrpStackLocation” on the IRP pointer, then checking the “MajorFunction” member of the returned IO_STACK_LOCATION.

    (9)
    • The IRP_MJ_WRITE function is responsible for writing files within the filesystem.
    • By attaching a filter device to the device stack of FileSystemNtfs or by replacing an IRP major function pointer with one of its own, the rootkit can intercept any call to FileSystemNtfs. In order to intercept NtWriteFile calls, the rootkit would need to inspect IRP_MJ_WRITE calls in the filter device, or replace the IRP_MJ_WRITE pointer in the driver object.

    (10)
    • This refers to the volume and partition drivers that are used by FileSystemNtfs, these are not normally targeted by rootkits, therefore i have left them out.
    • These drivers can be hooked in the same way as 9.

    (11)
    • The NTFS filesystem uses the IRP_MJ_WRITE major function of the class driver “DriverDisk” aka disk.sys, in order to write a disk. Because DriverDisk is much lower level than the NTFS filesystem driver, there are no file name, instead it is only possible to work with LBAs (Logical Block Addresses). Logical Block Addressing in a linear method of addressing the disk by sectors, each sector is usually 512, 1024, 2048, or 4096 bytes. The sector number starts at 0 (Master Boot Record) and goes up to whatever, depending on the size of the disk.
    • Hooking of drivers lower than ntfs.sys is usually only seen in kernel mode payload drivers used by bootkits. This is due to the fact that bootkits tend to only work with files outside of the NTFS filesystem, therefore not having to worry with translating file names to LBAs.

    (12)
    • The disk subsystem refers to any driver(s) below disk.sys, generally this a port/miniport driver, which is a hardware or protocol specific driver. In most cases this will be atapi.sys or scsiport.sys which are for ATA and SCSI complaint disk devices.
    •  At this level a new IRP major function is used, IRP_MJ_SCSI, which is an alias for IRP_MJ_INTERNAL_DEVICE_CONTROL. Here, the rootkit will have to work with SCSI_REQUEST_BLOCK parameters, which further complicates things compared to a disk.sys hook.
    • Any port/miniport hooks are usually only found in advanced kernel mode payload drivers used by bootkits.

Leave a Reply

Your email address will not be published. Required fields are marked *