Ring3 / Ring0 Rootkit Hook Detection 2/2
This article was actually planned to be posted the day after the first, however; I’ve not had much sleep the past few weeks, then I got sick, so it was very delayed. I’m pleased with how popular the previous article was, so in the future I plan to write more like this.
In this part of the article i will be explaining some of the different ways to detect, bypass and remove hooks placed by malware.
If you haven’t read part 1 of this article, here it is: http://malwaretech.com/2013/09/ring3-ring0-rootkit-hook-detection-12.html## IAT Hooks
Description The Import Address Table (IAT) is a table of jumps “jmp dword ptr ds:[address]”. Because functions in dlls change address, instead of calling a dll function directly, the application will make a call to the relevant jmp in its own jump table. When the application is executed the loader will place a pointer to each required dll function at the appropriate address in the IAT. | | |—| | I’m not sure this actually helps |
if a rootkit were to inject itself inside the application and modify the addresses in the IAT, it would be able to receive control every time a target function is called.
Bypass Because the Export Address Table (EAT) of each dll remains intact, an application could easily bypass IAT hooks by just calling GetProcAddress to get the real address of each dll function. In order to prevent such a simple bypass, a rootkit would likely hook GetProcAddress/LdrGetProcedureAddress and use it to return fake addresses. These hooks can be bypassed by writing your own GetProcAddress implementation and using it to get the real function addresses.
Inline hooking is a method of receiving control when a function is called, but before the function has done its job. The flow of execution is redirected by modifying the first few (usually 5) bytes of a target function. A standard way to do this is to overwrite the first 5 bytes of the function with a jump to malicious code, the malicious code can then read the function arguments and do whatever it needs. If the malicious code requires results from the original function (the one it hooked): it may call the function by executing the 5 bytes that were overwritten then jump 5 bytes into the original function, which will miss the malicious jump/call to avoid infinite recursion/redirection. | | |—| | Example of an inline hook jumping to malicious code then executing the original function |
Bypass / Detection (Ring 3)
In usermode inline hooks are usually place inside functions that are exported by a dll. The most accurate way to detect and bypass these hooks would be to compare each dll against the original code. First a program would need to get a list of each dll that is loaded, find the original file and read it, align and load the sections into memory then perform base relocation. Once the new copy of the dll is loaded into memory, the application can walk the export address table and compare each function vs that in the original dll. In order to bypass hooks, an application can then either replace the overwritten code using the code from the newly loaded dll, alternatively, it could resolve imports in the newly loaded dll and use it instead (be aware that some dlls will not work if more than 1 instance is loaded).
This method of bypassing dll hooks practically involves writing your own implementation of LoadLibrary, it’s really not for the beginners or faint-hearted. As much as I would like to post the code to do this, I won’t, because it can (and will) be used by scriptkiddies to bypass usermode antivirus sandboxes or fight with other rootkits. (We can also use manual dll loading to detect / fix EAT hooks, I won’t go into this in detail as EAT hooks are very uncommon).
Bypass / Detection (Ring 0) In kernel mode, inter-modular jumps are a lot more rare. Hooks in ntoskrnl can usually be detected by disassembling each instruction in each function, then looking for jumps or calls that point outside of ntoskrnl (into driver bodies, etc). It is also possible to use the same method explained for usermode hook detection: a driver could read each ntoskrnl module from disk, load it into memory and compare the instructions against the original.
For inline hooks within drivers, scanning for jmp / call instructions that point outside of the driver body is much more likely to result in false positives, however, non-standard drivers that are the target of jumps / calls inside standard kernel drivers should raise a red flag. It is also possible to read drivers from disk. As drivers generally do not export many functions and IRP major function pointers are only initialized at runtime, it would probably be required that you compare the entire code section of the original and new driver. It is important to note that relative calls / jumps are susceptible to changes during relocation, this means that there will naturally be some differences between the original and new driver, however both relative calls / jumps should point to the same place.
The System Serivce Dispatch Table (SSDT) is a table of pointers for various Zw/Nt functions, that are callable from usermode. A malicious application can replace pointers in the SSDT with pointers to its own code. | | |—| | Example call paths for Nt/Zw functions |
Detection (Ring 0)
All pointers in the SSDT should point to code within ntoskrnl, if any pointer is pointing outside of ntsokrnl it is likely hooked. It’s possible a rootkit could modify ntoskrnl.exe (or one of the related modules) in memory and slip some code into an empty space, in which case the pointer would still point to within ntoskrnl. As far as I’m aware, functions starting with “Zw” are intercepted by SSDT hooks, but those beginning with “Nt” are not, therefore an application should be able to detect SSDT hooks by comparing Nt* function addresses with the equivalent pointer in the SSDT.
A simple way to bypass SSDT hooks would be by calling only Nt* functions instead of the Zw* equivalent. It is also possible to find the original SSDT by loading ntoskrnl.exe (this can be done easily with LoadLibraryEx in usermode) then finding the export “KeServiceDescriptorTable” and using it to calculate the offset of KiServiceTable within the disk image (Usermode applications can use NtQuerySystemInformation to get the kernel base address) , a kernel driver is required to replace the SSDT.
SYSENTER_EIP points to the code to be executed when the SYSENTER instruction is used. Usermode applications use SYSENTER to transition into kernel mode and call a kernel function (Those beginning with Nt/Zw), usually it would point to KiFastCallEntry, but can be replaced in order to hook all usermode calls to kernel functions. Detection / Bypass
SYSENTER_EIP hooking does not effect kernel mode drivers, and cannot be bypassed from usermode. In order to allow usermode applications to bypass this hook, a kernel driver must set SYSENTER_EIP to its original value (KiFastCallEntry), this can be done using the WRMSR instruction, however because KiFastCallEntry is not exported by ntoskrnl, getting the address could be tricky.
IRP Major Function Hook
The driver object of each driver contains a table of 28 function pointer, these pointer are to be called by other drivers via IoCallDriver or alternative means, the pointers correspond to operations such as read/write (IRP_MJ_READ/IRP_MJ_WRITE). These pointers can easily be replace by another driver. Detection
Generally all IRP major function pointers for a driver should point to code within the driver’s address space, this is not always the case, but is a good start to identifying malicious drivers which have redirected the IRP major functions of legitimate drivers to their own code.
Due to IRP major function pointers being initialized from withing the driver entry point (during runtime), it’s not really possible to get the original values by reading the original driver from disk, there are also issues with loading a new copy of the driver due to collisions. The only way I can think of for bypassing these sorts of hooks would be calling the lower driver (Drivers are generally stacked and the top driver passes the data to the driver below and so on, if the lowest driver isn’t hooked, an application could just send the request directly to the lowest driver).