2009-08-01
Abstract
Alisa Shevchenko and Dmitry Oleksiuk introduce a new method for retrieving information from a possibly compromised system.
Copyright © 2009 Virus Bulletin
The main goal of a rootkit hunter (whether a human or a machine) boils down to retrieving information about a possibly compromised system in order to make a judgment about its state of health. But because the main goal of a rootkit is to conceal the real state of the system, the hunter is likely to make many false assumptions, and to constantly strain after the few obscure sources of information which can be considered trustworthy in a possibly compromised system.
To put it another way, choosing the right source of information is the cornerstone of the rootkit detection quest. This is also a challenge with a moving target, because what started out as a good source of information might become a bad source once a few steps of rootkit evolution have taken place.
In this article we will introduce a new method for retrieving information about a possibly compromised system – a technique which we consider to be an easier and safer alternative to existing techniques. First, we will discuss the advantages and limitations of known approaches to gathering system information, which are widely used in anti-virus and anti-rootkit solutions. Next, we will outline the proposed alternative technique, its pros and cons, known ways in which the technique can be defeated, and finally we will make a modest reference to its implementation in a real anti-rootkit utility.
Existing anti-virus and anti-rootkit solutions implement various approaches to rootkit detection, such as matching system information obtained from different sources (‘cross-view’) and checking the integrity of code/structures, either by comparing them to a trusted model or by searching for generic anomalies.
Regardless of the approach taken, the options available for the retrieval of valid information about the current state of the system are quite limited. In fact, there exist two mechanisms that allow possibly subverted system structures to be avoided:
Prior to gathering system information, kernel code is restored system-wide (global unhooking) in locations that are suspected of having been modified by a rootkit.
Information is gathered from a lower level of system architecture than the assumed lowest level of a rootkit’s residence.
Global unhooking is the way in which rootkit activity can be neutralized system-wide. Usually, this includes restoration of the SDT, of some code at the beginning of the kernel functions pointed to from the SDT, of IRP handlers, and generally, of any system structures suspected of having a modification that would cause output data forgery (see code displayed in red in Figure 1).
During system code restoration, a developer faces three difficult challenges:
Locating or calculating correct pointers to system calls, IRP handlers etc., which are necessary for the restoration of original execution paths. Likewise, locating the original system executables that are necessary for the restoration of possibly spliced system code at specific locations. (Splicing: inline modification of a function code causing execution flow redirection (usually a jmp or a call).)
Identifying bad hooks that need to be removed, and distinguishing them from legitimate hooks installed by system applications such as firewalls.
Safe writing of data found at pt.1, to locations found at pt.2.
The problem here is that the writing of data to kernel executable regions which are constantly in use by the system can cause BSOD (the blue screen of death) under certain conditions.
The advantage of global unhooking is that, if performed safely and successfully, it allows complete neutralization of some rootkits, restoring to all applications their ability to obtain true information.
The global unhooking approach has a number of significant limitations:
It is unsafe: global unhooking requires the manipulation of pointers in kernel code which is invoked system-wide. This is a risky operation.
It is unreliable: a rootkit could reinstall its global hooks at any time.
It is unsystematic: the specific code locations to be restored must be indicated. This enables a rootkit developer to exploit locations unforeseen by the anti-rootkit.
It is laborious: finding the original pointers to functions, and distinguishing bad hooks from good hooks, are not simple tasks.
Given the limitations, we can say that global unhooking is more of a primitive reaction to known threats than a universal solution. This approach is not widely implemented in anti-virus solutions due to its insecurity.
Because the Windows architecture is layered, information can be gathered from multiple points of a call chain. Thus, an anti-rootkit that wants to request system information can avoid modified system structures, gathering information by invoking more profound system mechanisms than those that may be compromised.
This approach, which is much safer and far more universal than the previous one, is limited in other significant ways:
It is labour-intensive: when going lower, it is necessary to implement all the data abstractions and conversions that are normally provided by higher-level mechanisms.
It is strategically ineffective: because getting lower is more of an avoidance tactic than a solution, it only motivates rootkit developers to get lower too, which will then require even more labour-intensive solutions.
The following is an example of a typical arms race:
Rootkit hooks system calls to hide files.
Anti-rootkit invokes file system driver.
Rootkit hooks file system driver IRPs.
Anti-rootkit invokes disk driver.
And so on.
The result is that the protection developer needs to emulate the whole operating system to successfully skirt around a rootkit.
Among all the untrustworthy sources of information, oneself is probably the least untrustworthy. So we propose an anti-rootkit device that performs its own system calls, by providing it with its own clean kernel copy. This is a low-cost way in which genuine information can be obtained by avoiding possibly compromised system mechanisms without risking system safety.
Running your own kernel, if obtained and established properly, will enable reliable detection of the majority of modern kernel malware. More precisely, a lightweight implementation of the kernel copy (described in this article) will allow detection of hidden objects caused by SDT hooking and kernel code splicing rootkits, while a more complex implementation (maintaining copies of a file system and network driver stacks) may allow detection of almost any known kernel malware type.
While it sounds fearfully complex, the basic implementation of a working kernel copy in Windows is fairly easy. The basic steps to achieve this are as follows:
Find necessary executable files. For a minimal working kernel, take the main kernel file (ntoskrnl.exe in the majority of cases) and hal.dll. The most reliable way to locate kernel files is provided by hardware configuration analysis, as detailed below.
Load the files into kernel memory. Remember that the best practice for reading a kernel file is to read it directly from the disk, to ensure file authenticity.
Correctly relocate all the calls and data accessing code inside the kernel mapping. Normally, all global variables in the kernel copy should be reinitialized manually. However, for a minimal kernel copy implementation, manual initialization is crucial only to certain variables, such as pIofCallDriver and pIofCompleteRequest, which are likely to be pointing to malicious code in the real kernel. The remaining variables can be retrieved from the real kernel.
Disable system notifications (both system-wide and locally) to ensure that no hidden data slips through via legitimate callback mechanisms. This can be done by temporary patching of ExReferenceCallBackBlock so that it returns 0 during the scanning process.
Redirect kernel calls from your own driver to the local kernel copy.
Because we should assume that straightforward sources for the main kernel file path/name (such as the boot.ini file or HKLM\System\CurrentControlSet\Control\SystemStartOptions ‘KERNEL’ registry key) could easily be spoofed by a rootkit, it is suggested that a smarter algorithm is used including hardware configuration analysis for locating the main kernel filename. The latter is defined by two system parameters: the number of processors installed, and PAE support (PAE = Physical Address Extension).
Some hiding malware cannot be detected this way. The list includes malware that implements IRP hooks and filter drivers in order to conceal itself.
There is no trivial way in which the kernel copy can be removed immediately on process exit, because it may still be in use by some system threads.
The suggested solution is to drop kernel code in memory after saving its address in the registry, so that if the anti-rootkit is loaded again, it will not litter the kernel memory.
Hidden files and registry keys cannot reliably be removed while the rootkit body and hooks are still present in memory, since hidden objects can be restored by a rootkit.
The suggested solution is to initiate an immediate reboot (more exactly, a hard reset) after deleting hidden objects.
Ways in which the suggested technique can be defeated boil down to either falsification or blocking of the external information sources upon which we rely. That is, of the files used to build a kernel copy. How could a rootkit do that?
A rootkit might push a patched ntoskrnl.exe upon a file-reading request for this file.
Solution: checking a kernel file’s Microsoft signature will ensure code integrity.
A rootkit might spoof filenames instead of content.
Solution: retrieval of path/names via analysis of the hardware configuration, as described earlier.
A rootkit might block access to kernel files.
This is unlikely, because it would affect certain legitimate software.
Advantages of the technique include the following:
Safety: manipulating a kernel copy before it starts being used is as safe as performing manipulations on one’s own driver, whereas manipulating a system kernel which is already in use is an extremely risky operation regardless of precautions.
Reliability: a rootkit will never install/restore hooks in a local kernel, since it is not public.
Purity: the integrity of kernel code which is retrieved manually from the disk and then installed and invoked locally with proper foresight can be guaranteed. Thus, any data retrieval performed via a kernel copy will output clean data unless the very data source is modified.
Without presenting another unsound panacea, the approach suggested in this article provides an inexpensive and safe way to detect kernel code modification caused by rootkit activity.
To demonstrate the usability of the suggested technique, we have developed a freeware anti-rootkit tool based on it. The tool is currently specialized for detection of the TDSS rootkit [1], though it is a generic anti-rootkit by design. The tool is named ‘Rootkit.Win32.TDSS remover’ and is available for download online [2].
In spite of the fact that effective realization of the suggested approach is quite easy, it has never (to our knowledge) been implemented in existing anti-malware solutions. We would be pleased to hear about any software using the technique described in this article.
[1] Shevchenko, A. Case study: the TDSS rootkit. Virus Bulletin, May 2009, p.10. http://www.virusbtn.com/pdf/magazine/2009/200905.pdf.