Everybody lies: reaching after the truth while searching for rootkits.
- Common ways to reach the truth
- The alternative
The article is also available in Russian
A rootkit hunter’s main goal – hunter being a human or a machine – boils down to retrieving genuine information about a possibly compromised system, as a ground for subsequent judging on its health state. But, because a rootkit’s main goal – that is, hiding the real state of things – is quite opposite to that by design, a rootkit hunter is bound to presume falsity of all kinds of information underlying his/her/its judgements, and to constantly strain after sources of information which may be considered trusted in an obscure environment of a possibly compromised system.
To put it short, choosing the right source of information is the cornerstone of the Rootkits Detection Quest. And it is also an ever-developing process, because what ever used to be a right source of information turns into a liar after some time and some steps of rootkits evolution.
In the following article we will introduce a new technique aiming to retrieve true information about a possibly compromised system, a technique which we consider an easier and healthier alternative to existing techniques with the same purpose. We will first briefly discuss advantages and limitations of known approaches to gathering system information, which are widely used in common antivirus and anti-rootkit solutions. After that we will proceed to the suggested alternative technique details, its pros and cons, known ways to defeat the technique, and a modest reference to its implementation in a real anti-rootkit utility.
Common ways to reach the truth
Existing antivirus and anti-rootkit solutions implement various approaches to rootkit detection, such as matching system information output from different sources (‘cross-view’), or code/structures integrity checking either by comparing them to a trusted model or by searching for generic anomalies.
Regardless of a specific rootkit detection approach implemented in a protective software, their developer’s options for a mechanism of retrieving genuine information about current system state are quite limited. In fact, there exist two mechanisms that allow reaching the truth from inside a system, detouring possibly subverted system structures.
- Restoring kernel code in locations suspected of being modified by a rootkit, system-wide (that is, global unhooking) prior to gathering system information.
- Gathering information from a deeper level of system architecture than the assumed deepest level of a rootkit’s residence.
Global unhooking is the way to neutralize rootkit activity system-wide. Usually, this includes restoration of the SDT, of some code at the beginning of kernel functions pointed from the SDT, of IRP handlers, and, generally, of any system structures suspected of a modification causing output data forgery (see red code on Fig.1.).
Fig.1. Rootkit modification of kernel
During system code restoration, a developer faces three difficult points.
Splicing: inline modification of a function code causing execution flow redirection (usually a jmp or a call)
- Locating or calculating veritable pointers to system calls, to IRP handlers etc., which are necessary for restoration of original execution paths. Likewise, locating original system executables necessary for restoration of possibly spliced system code at specific locations.
- Telling bad hooks that need to be removed, 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 writing of data to kernel executable regions which are constantly in use by system may result in BSOD in certain conditions.
The only advantage of global unhooking is that, if performed safely and successfully, it allows complete neutralization of some rootkits, restituting to all applications their ability to obtain true information.
At the same time, global unhooking approach has a number of significant limitations.
- Unsafe. Global unhooking implies to manipulations with pointers in kernel code which is invoked system-wide, which is a risky operation by design.
- Unreliable. A rootkit may re-install its global hooks at any time.
- Unsystematical. Specific code locations to be restored must be indicated, which enables a rootkit developer to exploit locations unforeseen by anti-rootkit.
- Tedious. Finding original pointers to functions, and telling bad hooks from good hooks, is not a simple quest.
Given the limitations, we can assert that global unhooking is more of a primitive reaction to known threats than of a truth seeker’s universal solution. Also because of insecurity, this approach is never implemented in earnest antivirus solutions.
Because 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 detour modified system structures forging information by invoking more profound system mechanisms than those possibly compromised.
This approach, being definitely much safer and far more universal than the previous, is limited in other significant ways.
- Labour-intensive. When getting deeper, it is necessary to implement all the data abstractions and conversions normally provided by higher level mechanisms.
- Strategically ineffective. Because getting deeper is rather avoidance than a solution, it only motivates rootkit developers to get deeper too, dealing with which will then require even more labour-intensive solutions from a protection developer .
Here is an example of a typical armaments drive: rootkit hooking system calls to hide files –anti-rootkit invoking file system driver – rootkit hooking file system driver IRPs – anti-rootkit invoking disk driver, and so on.
The resulting situation now is that a protection developer wants to emulate the whole operating system to succeed in skirting a rootkit.
Among all the untrustworthy sources of information, a self is probably the least untrustworthy. So we are going to make an anti-rootkit perform system calls on its own, by providing an anti-rootkit with its own clean kernel copy. In general, this is a low-cost way to retrieve truth by detouring possibly compromised system mechanisms without risking system safety.
Running your own kernel, obtained and established properly, will allow reliable detection of majority of modern kernel malware. More exactly, a light-weight implementation of kernel copy (which is described in this article) will allow detection of hidden objects caused by SDT-hooking and kernel code splicing rootkits, while a more complex implementation (sophisticating up to maintaining copies of a file system and network driver stacks) may allow detection of almost any known kernel malware type.
While sounds fearfully complex, basic implementation of a working kernel copy in Windows is fairly easy. General steps to achieve this are as follows.
- Find necessary executable files.
For a minimal working kernel, take the main kernel file (ntoskrnl.exe in majority of cases) and hal.dll. The most reliable way to locate kernel files is provided by hardware configuration analysis, detailed below.
- Load the files into kernel memory.
Remember that best practice for reading a kernel file is provided by direct reading from the disk, to ensure file authenticity.
- Relocate properly all the calls and data accessing code inside the kernel mapping.
Normally, all global variables in the kernel copy should be re-initialized 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. Remaining variables can be retrieved from the real kernel.
- Disable system notifications (both system-wide and locally) to ensure that no hidden data slips via legitimate callback mechanisms. This can be done by patching ExReferenceCallBackBlock so that it always returned 0.
- Redirect kernel calls from your own driver to the local kernel copy.
Fig.2. Duplicated kernel execution flow
PAE = Physical Address Extension
Because we should assume that straightforward sources for the main kernel file path/name (such as boot.ini file or HKLM\System\CurrentControlSet\Control\SystemStartOptions “KERNEL” registry key) might be easily spoofed by a rootkit, it is suggested to use a smarter algorithm including hardware configuration analysis for locating the main kernel filename. The latter is defined by two system parameters: number of processors installed, and PAE support.
|Kernel Name||PAE support||Multiprocessor support|
- Some hiding malware can not be detected this way by design.
The list includes malware that implements IRP hooks and filter drivers to hide.
- There is no trivial way to immediately remove kernel copy on process exit, because it may be still 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 in case the anti-rootkit is loaded again, it will not litter kernel memory.
- It is not reliable to remove hidden files and registry keys while rootkit body and hooks are still present in memory, since hidden objects can be restored by a rootkit. The suggested solution is to initiate immediate reboot (more exactly, a hard reset) after deleting hidden objects.
Ways to defeat the suggested technique boil down to either falsification or blocking of external information sources which we rely upon. That is, of files used to build a kernel copy. How could a rootkit possibly do that?
- A rootkit might push a patched ntoskrnl.exe upon file reading request for this file.
Solution: checking a kernel file’s Microsoft signature will ensure code integrity.
- Spoofing filenames instead of content.
Solution: retrieval of path/names via analyzing hardware configuration, described earlier.
- Blocking access to kernel files.
Hardly possible, because this will derange certain legitimate software.
- Safety. Manipulating a kernel copy before it is set into operation is as safe as own driver manipulations. At the same time, manipulating 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 a public establishment.
- Purity. Kernel code which is manually retrieved from the disk and then installed and invoked locally with proper foresight can be guaranteed of integrity. Thus, any data retrieval performed via a kernel copy, will output clean data unless the very data source is modified.
Not presenting another unsound panacea, the approach suggested in this article provides a cheap and safe way to detect rootkit activity caused by kernel code modification – the latter implying to prevalent majority of existing rootkits.
To prove practical usability of the suggested technique, we developed a freeware anti-rootkit tool based on it entirely. The tool is currently adjusted to detection of the TDSS rootkit, though is a general anti-rootkit by design. The tool is named Rootkit.Win32.TDSS remover and is available for download online.
In spite of the fact that effective realization of the suggested approach is quite easy, it is never implemented in existing ant-malware solutions. At least, we have never heard of such. If you know of any software using the technique described in this article, please let us know.