The following article in part 2, will discuss “Dynamic imports methodology used by a malicious driver”.
This article will take the reader through a partial analysis of an actual piece of malware found in the wild. The aim is to show how the malware author attempts to find the base address of the Windows Kernel module and how the malware tries to hide its intended purpose. This is interesting because it calls the address of ‘legitimate’ functions in order to calculate the address of the intended target, making the malware appear benign at first glance. Finally, a description is given as to how the malware uses custom checksums calculated from exported function names and compares them against hard-coded ones to retrieve kernel functions.
This article will focus on the way the driver’s author attempts to locate the base address of the Windows Kernel module and on the algorithm that was used to produce custom checksums from the names of its exported functions, inorder to locate the kernel functions that the malware author is interested in. For the sake of convenience, this article calls the function dedicated to the process above ‘ChecksumExportLocator’.
Before actually calling the ‘ChecksumExportLocator’ function, the code goes through a loop in order to locate the base address of the Windows Kernel module. This is done because knowing the kernel module’s base address makes retrieving the table of exported functions trivial (see code comments in Figure 3).
The figure above shows the driver’s initialization routine. The alert reader will notice some code code obfuscation in the above fragment. Rather than directly call the function at 10380h, it loads another constant which when incremented returns the desired address to call. The red highlighted constant is put in the EAX register which will then be used inside the next function examined in this article.
The code fragment in Figure 2 shows further obfuscation using the technique of dynamically calculating values that are used as constants by the code. The ‘|’ operator in the comments demonstrates the calculation steps and should be read from right to left.
The fragment shown in Figure 2 is actually one of the more interesting parts. The code is using the address of a ‘legitimate’ function exported from the kernel module, in order to calculate the base address of it. So, once EAX = Ntoskrnl.MmMapLockedPagesSpecifyCache, it will zero out the lower 16-bits of the AX register and it will keep subtracting 1000h until if finds the magic number – 5A4Dh (MZ). At that point the malware assumes that it has found the base address of the kernel module in memory. The 1000h is subtracted from EAX for each loop iteration because this is the default section alignment in PE files in memory in Windows OS, and is actually the default smallest size for memory allocation – 4KB.
Once it has gone through the above process, it will move a constant (9C09DD08h) to EBX (again this is done in two steps), which is actually a DWORD representing the result of a custom checksum algorithm. This will be seen later on inside the ChecksumExportLocator routine in the next section.
It should now be clear how the author of this driver attempts to locate the base address of ntoskrnl.exe in memory, namely by importing a function from it that doesn’t look suspicious from the malware operation point of view. In other words, this driver would not look immediately malicious in the eyes of a malware researcher who has just gone through its import table.
Furthermore, the code author is also playing around with some constants in order to calculate the one that is going to use in the end, which makes the analysis that bit more involved.
This function is the most interesting one with regard to the topic of this article. It contains the code dedicated to the parsing of the Export Table of ntoskrnl and to the dynamic calculation of the address of the selected exported functions based on the custom checksum of their name.
In essence, this function goes through the list of names of the exported functions and calculates a custom checksum for each one, which will be compared with the hardcoded checksum placed in EBX before this function is entered.
If the checksum calculated matches the one placed in EBX, it will calculate the VA (Virtual Address) of the corresponding function by going through the list of the RVAs (Relative Virtual Addresses) of the functions and adding the one it is looking for to the Ntoskrnl.imagebase, storing the result in EAX.
At this point the VA of the function exported by ntoskrnl that it was looking for is stored in EAX, the next step is to save it somewhere for later use.
The first API located through this method is the Ntoskrnl.IoBuildDeviceIoControlRequest, once again one sees obfuscation in the instructions used in order to store its address. Although the most direct way would be to move the value from EAX into [10F0Ah], the code author has opted to push it onto the stack and then pop it to that location (Figure 4).
Following this is another call to a function, named ‘GetDynamicImports’ for convenience, which will use the rest of the hardcoded checksums along with the same algorithm to retrieve and store the remaining kernel functions the driver is interested in.
At this point one might ask, “how do you know that those checksums correspond to the names of those Ntoskrnl functions?”. In fact, the writing of this article was motivated by the answer to that very question. Of course, there is no way to know the answer, unless we code our own tool that will use the same custom checksum algorithm.
However, since we are just doing static analysis from user mode, we are not really interested in the addresses of those functions at this point. However, we are certainly interested to know their names in order to proceed with the analysis of the driver.
In the next part of this series we will demonstrate how to modify the code of the ChecksumExportLocator function in order to retrieve the names instead of the addresses and how to make a simple tool that will automate the whole process for all the dynamically imported APIs.
Did you miss the first article? You can find it here: