On Windows 7 (or Vista) I use
- Features of Modern Security Suites – Part 3
- Features of Modern Security Suites – Part 2
- Features of Modern Security Suites – Part 1
- KHOBE – 8.0 earthquake for Windows desktop security software
- Plague in (security) software drivers
- Introduction to Firewall Leak-testing
- Comparison of top five personal firewalls
- More about personal firewalls
- Design of ideal personal firewall
KHOBE – 8.0 earthquake for Windows desktop security software
Last updates: 2010/05/14 – Prevx 22.214.171.124 added to the table of vulnerable software
2010/05/13 – The attack's scope stressed out; added information about what kind of products and platforms are affected; related work updated
2010/05/10 – Information about related work added
2010/05/09 – ThreatFire 126.96.36.199 added to the table of vulnerable software
2010/05/07 – paragraph about which platforms are affected added to Final observations and notes
In September 2007, we have published an article about a great disease that affected tens of Windows security products. The article called Plague in (security) software drivers revealed awful quality of kernel mode drivers installed by all the major desktop security products for Windows. The revealed problems could cause random system crashes, freezes and in some cases more severe security issues.
Today, we reveal even more serious problem of the Windows desktop security products that can be exploited to bypass a big portion of security features implemented by the affected products. The protection implemented by kernel mode drivers of today's security products can be bypassed effectively by a code running on an unprivileged user account. If you ever heard of SSDT hooks or similar techniques to implement various security features such as products' self-defense, we will show you how to bypass the protection easily.
- The goal of this paper
- Original research
- Why are user mode hooks insecure
- SSDT hooking
- The problem
- Argument-switch attack to bypass checks of security software
- Combined attack
- Table of vulnerable software
- Final observations and notes
- Related work
The task of today's security software is to protect computers against malware and hacker attacks. This kind of application is generally very complex because it is trying to protect its users against threats of various kind. The security software use signatures and heuristic to detect known viruses, rootkits and trojan horses.
Malware writers are skilled enough to write malicious software that bypasses these detection techniques. To close this hole, modern security solutions implement HIPS and other forms of behavior control and blocking.
Because there were not many documented interfaces and APIs in the past which allowed to monitor and filter applications' activity in the system, many software vendors decided to make direct modifications to the user and kernel code and data structures. These modifications are often called as hooks. Today's security software often use tens of them to implement their security features.
Kernel mode hook implementation in security applications were subject of our research in 2007. This research showed that most of security software vendors implemented their kernel hooks very poorly and their applications were creating another holes into the operating system instead of protecting it. A special tool, BSODhook, was developed to find these vulnerabilities automatically. You can read more details about the research in Plague in (security) software drivers.
After we disclosed the results of our previous research, many vendors fixed the bogus implementation of their kernel mode hooks in reaction to the great publicity. This approach is required again because without the pressure from the media, it seems that many vendors are (again) absolutely uninterested in security holes in their software.
The goal of this paper
Most of security software perform their hooking on several well-known places. Some still base their protection mostly on user mode hooks which are, as reminded later in this document, faulty by design. Many vendors decided to alter the kernel part of system call mechanism implementation. They modify contents of System Service Descriptor Table (SSDT), which is often referred SSDT hooking. Other use different kind of kernel hooks for example by modifying the kernel code directly.
The main goal of this paper is to present an attack technique, called the argument-switch attack or KHOBE attack, that allows malicious code to bypass protection mechanisms of security applications. The attack is effective against user mode and kernel mode hooks. Because user mode hooks can be bypassed by simpler techniques, we focus on kernel mode hooks bypassing only. In the further text we demonstrate the attack techniques on SSDT hooks, which are the most common kernel hooks in today's security software. However, the attack techniques need no change to succeed against the other kinds of vulnerable kernel or user mode hooks.
For testing purposes, we have developed an engine called KHOBE (Kernel HOok Bypassing Engine) which simplifies writing of the exploits. With KHOBE we were able, in relatively short time, to verify that the presented vulnerability is an issue for many known security products on the market.
The research devoted to the problems we present in this article started in August 2008. Ten weeks later we finished it having a full documentation of the problem structured to three separate documents:
- The documentation of the problem and explanations of its aspects on examples of vulnerable implementations.
- The documentation of possible solutions – how to implement all the requested functionality safely.
- The documentation of KHOBE, the code library that can be used for fast and easy creation of exploits against the vulnerable implementations. This part was supported by various proof of concept exploits based on KHOBE.
The full results of the research were offered to our clients and other software vendors. In this article we use the parts of the first document to fully describe the problem. Other parts of the research will not be disclosed publicly and remain available for our business clients.
Why are user mode hooks insecure
Applications communicate with the operating system kernel through functions exported by various dynamic link libraries of Windows API, such as kernel32.dll, user32.dll, advapi32.dll and ntdll.dll. These routines usually transmit execution flow to the lower layer of the interface libraries. The layer on the bottom, represented by ntdll.dll (and partially user32.dll and gdi32.dll), uses special instruction, such as INT 0x2E, SYSENTER or SYSCALL, to call the kernel.
Various security applications place their hooks on the beginning of Windows API routines to stop malicious code working. However, all dynamic link libraries belonging to Windows API reside in the user mode portion of process' address space, hence the application code might avoid calling them which effectively bypasses hooks made by security software. If the application needs to communicate with the kernel, it can use the system call instruction directly. And this action cannot be caught or prevented by any type of user mode hooking.
System Service Descriptor Tables belong to the kernel mode part of system call interface implementation. They contain addresses of routines (referenced also as system services) that user mode code can invoke indirectly as a result of the special system call instruction. Through SSDTs, one can control every useful transition from user mode to kernel mode. That is why they seem to be so suitable for implementation of real time protection and self-defense.
Security software often modifies addresses stored in the tables to point to its own routines, called hook functions or hook handlers. These routines perform various checks to find out whether the call endangers the system or might be a threat to the security application itself. In these cases, the hook handler blocks the call or asks user what to do. If the hook routine considers the call as safe, it invokes original system service with same parameters it got from the user mode application. This must be done without changing the context of the call. Everything must be transparent to the original system service which expects no installed hooks.
The following code snippet shows a sample hook handler for NtLoadDriver system service. The handler attempts to block every attempt to load driver which service name is "SampleDriver".Example 1: Vulnerable NtLoadDriver hook handler.
Firstly, the hook handler routine determines the origin of the call (line 04). Filtering is applied only to calls coming from user mode. SSDT hooks are not very suitable for protection against kernel mode threats and hence passing calls that come from kernel mode is very common in hooking drivers. Then, the routine jumps to the try/except block where the verification of the parameter is made.
The hook routine uses ProbeForRead function (lines 0A and 0D) to verify that the address of the UNICODE_STRING parameter DriverServiceName, and the address specified in the Buffer member of the structure, are pointing to user mode memory. If the application passed kernel mode address to the NtLoadDriver system call, ProbeForRead raises exception which the hook handler catches by the try/except block and indicates by setting status code to STATUS_INVALID_PARAMETER on line 2D.
If no exception occurs, the hook handler copies the name of the driver service registry key to the kernel memory and compares it with the "SampleDriver" string, which stands for malicious driver service, that should be blocked from loading. The try/except block catches all exceptions that can be raised during copying of the DriverServiceName structure, which might happen when the application specified address in user mode which points to invalid memory region. You can read more about checking parameters of various types in Plague in (security) software drivers.
The comparison of unicode strings is made on line 27. If the handler discovers that the application is attempting to load the malicious "SampleDriver" driver, it blocks the call by setting status code to STATUS_ACCESS_DENIED on line 28.
If the call originates from kernel mode or the verification finishes successfully, the hook handler invokes the original system service and passes the original DriverServiceName pointer as its argument (line 37). Then it returns status code of the call (line 39). Passing original values of parameters to the system service is very important because its code will also check their validity using try/except block and ProbeForRead function. The hook handler must behave in a way that does not impact the execution flow of the original function.
The code of system services runs on IRQL PASSIVE_LEVEL which also applies to their hook handlers. Code running at this level can access pageable memory and, when the scheduler decides, might be preempted by another thread. And the scheduler plays the key role in the argument-switch attack.
The hook handler might be preempted at every moment of its execution. Imagine that the context switch occurs just before the line 37 is about to be executed. At this point, all security checks are done and the hook handler is about to invoke the original system service. Before the scheduler switches back to the hook handler, many threads might use the processor for some time, including other threads of the application that called NtLoadDriver.
If another thread of the application is scheduled onto the processor, it might theoretically change the driver service name passed to the NtLoadDriver call because the whole string lies in user mode portion of address space. The execution of hook handler had advanced too far to detect this modification and perform additional checks to stop malicious driver from loading. When the scheduler switch thread context back to the thread executing the hook handler, the original system service is invoked and might load driver which name was not subject of any security check made by the security software. And this is exactly how the argument-switch attack works.
Things get more simple on multiprocessor systems where two or more threads of the same application might run really simultaneously and the context switch at the crucial moment is not really needed. Today, multiprocessors or multi-core processors are very common hardware in desktop computers.
In the following sections, the thread that invokes the system service will be called the attacker thread and one that attempts to modify the parameters in order to bypass security checks will be referenced as the faker thread. Surprisingly, the attacking technique may be implemented even if the attacker thread runs in different process than the faker thread. In further text, however, we limit our thoughts to a single attacking process only.
Argument-switch attack to bypass checks of security software
As shown in the previous section, the idea of the argument-switch attack is quite simple. The attacker calls the system service with values of parameters that will certainly pass the checks made during the execution of services' hook handlers. When the faker thread gets its time slice, it tampers the contents of the parameters to values that would never pass the checks of the hook handler. If the tampering operation occurs after the security checks are done but before the original service, which does the main work, is called, the attack is successful.
So, one of the remaining questions is, which types of parameters can be faked and how to achieve it. Generally speaking, there are three types of arguments passed to system services:
- integer values, boolean values, bit masks (access mask, flags etc.) and other unstructured arguments;
- pointers to flat buffers or more complex structures such as UNICODE_STRING or OBJECT_ATTRIBUTES;
- handles to kernel objects.
Parameters of the first type cannot be tampered by the attack – during the system call, they are copied to the kernel stack, which is not accessible from user mode.
The idea of faking pointers was described for the example code of NewNtLoadDriver function in the previous sections. The faker thread cannot modify the value of the pointer, because it is copied to the kernel stack, hence not accessible from user space. However, the pointer refers to a region in the user memory and contents of this region can be changed. If the change occurs in the right time (after the security check but before the execution of the original system service), the hook is bypassed.
The situation with handles is more difficult. Again, the value of the handle itself can not be changed because it is copied to the kernel stack. What we can do, however, is to change the actual meaning of the handle value. We will describe the attack on the example of sample NtTerminateProcess hook. The handle type can be understood as a special type of pointer (handles refer to kernel objects like pointers do to memory regions), so one can conclude that the attack on handle arguments will have much in common with tampering pointers.
Consider the following implementation of NtTerminateProcess hook handler. We assume that the security software allows us to get handle with PROCESS_TERMINATE flag to the protected process and that the level of defense lies only in this NtTerminateProcess hook which blocks attempts to terminate process with PID saved in protected_process_id variable.Example 2: Vulnerable NtTerminateProcess hook handler.
Unlike the NtLoadDriver hook example, validating of parameters does not require any probing or copying, actions that must be performed within try/except block. There is no need to check ExitStatus argument, because every value is valid as the process exit code, and the most of the required handle checking (ProcessHandle parameter) is performed in ZwQueryInformationProcess call. The hook handler only checks the origin of the call and whether the handle could be valid or does not point directly to current process. This validation is performed on line 04.
If the handle seems to be valid and the call comes from user mode, the hook handler routine tries to find out which process the handle belongs to. Basic information, which includes PID of the target process, is retrieved on line 07 via call to ZwQueryInformationProcess. This routine filters out handles of bad type or entirely invalid.
The final check is made on line 0B where the PID of the target process is compared to PID of the process protected by the security software. In case of equality, the return status code is changed to "access denied" (line 0C) and the original handler is not invoked. Otherwise, the original system service is called (line 11). It is left for the original service code to verify that the handle was opened with PROCESS_TERMINATE access right, which is required for terminating the process via this service call.
Similarly to the NtLoadDriver example, the success of the attack depends whether the faker thread is able to alter the meaning of the ProcessHandle parameter when the attacker thread is executing lines from 08 to 10, i.e. after the check of PID, but before the execution of the original code. It seems that there is not much space for the attack, but in fact even the first instructions of the function called on line 11 can be executed. All instructions in the original function prior the handle value passed in ProcessHandle is dereferenced can be executed without affecting the result of the attack. Moreover, it should be noted that unlike our sample hook handler, hook handlers in security products are usually very complex and there are many instructions to be executed between the security check and the original function call.
The faker thread can not change the value of the ProcessHandle parameter, because it is pushed onto the kernel stack before the hook handler is executed. However, it can change the meaning. This is analogous to pointers – it is not possible to change their values but it is possible to change the memory region they point to. Changing meaning of a handle is just a bit more difficult than changing the contents of memory region.
The faker thread must destroy the handle passed by the attacker to the system call. This operation must be performed after the hook handler successfully returns from ZwQueryInformationProcess call, but before the original NtTerminateProcess is executed. Then the faker thread opens a handle to the protected process with PROCESS_TERMINATE access right (which will be allowed by security software due to our assumptions made in the beginning). If we are lucky, the new handle will have the same value as the closed one. In case the new handle is created before the attack thread reaches line 11 and invokes original NtTerminateProcess, the attack succeeds and the protected process is terminated. This sounds like a very unlikely thing to happen, but with a good implementation, such as KHOBE, it is possible to effectively, i.e. in real time, attack pointer arguments as well as handle arguments.
The previous sections illustrate how to bypass a single SSDT hook like NtLoadDriver or NtTerminateProcess. However, some security products implement multi-level protection – for example, they hook NtOpenProcess, where all attempts to get handle with PROCESS_TERMINATE access right are blocked. Additionally, NtTerminateProcess is intercepted to block termination attempts of those, who somehow obtained the handle with sufficient access rights.
However, even two-level protection is not enough and can be bypassed by slightly modified variant of the argument-switch attack. We will describe its semantics on the protection scheme presented in the previous paragraph. We need one attacker and two faker threads. During the initialization phase, a handle to the current process is opened with access rights that prevent its use for process termination. Then the attacker thread invokes NtTerminateProcess with this handle. This call is intercepted by the NtTerminateProcess hook handler. The hook handler finds out that the handle does not belong to any of the protected processes. Hence, the termination operation is allowed and the original system service is about to be executed.
Now, we need to use the standard argument-switch attack to bypass the hook of NtOpenProcess. If this sub-attack finishes before the attacker is scheduled on the processor again, the attack succeeds and the protected process is terminated.
At this moment, the attack needs scheduler to switch to the first faker thread which closes the handle to our process opened during the initialization phase and tries to call NtOpenProcess with PID of the current process again. The task of the second faker thread is to change the CLIENT_ID argument to point to the protected process just after the NtOpenProcess hook handler invokes the original system service. So the scheme of the sub-attack is similar to the attacks described in previous sections – the first faker thread plays role of the attacker thread and the second one acts as the faker thread.
If the sub-attack successfully finishes before the NtTerminateProcess system service begins its execution and if the handle to the protected process has the same value as the handle opened during the initialization, the combined attack succeeds and the protected process is terminated.
If the single argument-switch attack was seemed to be a very unlikely thing to happen, the combined version seems to be a miracle. Using KHOBE, however, we have proved again that on multiprocessor hardware the combined attack can be performed again in real time, the combined attack needs only few seconds to succeed.
Table of vulnerable software
We have performed tests with today's most Windows desktop security products. The results are presented in table below. The results can be summarized in one sentence: If a product uses SSDT hooks or other kind of kernel mode hooks on similar level to implement security features it is vulnerable. In other words, 100 % of the tested products were found vulnerable. It should be understood, however, that KHOBE research was focused only on products that do use kernel patching or hooking in SSDT or on similar levels. Only products that hook may be vulnerable to the attack and appear in the following table. The only reason there are not more products in the following table is our time limitation. Otherwise, the list would be endless.
|Product name and version||Result|
|3D EQSecure Professional Edition 4.2||VULNERABLE|
|avast! Internet Security 5.0.462||VULNERABLE|
|AVG Internet Security 9.0.791||VULNERABLE|
|Avira Premium Security Suite 10.0.0.536||VULNERABLE|
|BitDefender Total Security 2010 188.8.131.527||VULNERABLE|
|Blink Professional 4.6.1||VULNERABLE|
|CA Internet Security Suite Plus 2010 184.108.40.2062||VULNERABLE|
|Comodo Internet Security Free 4.0.138377.779||VULNERABLE|
|DefenseWall Personal Firewall 3.00||VULNERABLE|
|Dr.Web Security Space Pro 6.0.0.03100||VULNERABLE|
|ESET Smart Security 220.127.116.11||VULNERABLE|
|F-Secure Internet Security 2010 10.00 build 246||VULNERABLE|
|G DATA TotalCare 2010||VULNERABLE|
|Kaspersky Internet Security 2010 18.104.22.1686||VULNERABLE|
|KingSoft Personal Firewall 9 Plus 2009.05.07.70||VULNERABLE|
|Malware Defender 2.6.0||VULNERABLE|
|McAfee Total Protection 2010 10.0.580||VULNERABLE|
|Norman Security Suite PRO 8.0||VULNERABLE|
|Norton Internet Security 2010 22.214.171.124||VULNERABLE|
|Online Armor Premium 126.96.36.199||VULNERABLE|
|Online Solutions Security Suite 1.5.14905.0||VULNERABLE|
|Outpost Security Suite Pro 188.8.131.5263.452.0726||VULNERABLE|
|Outpost Security Suite Pro 7.0.3330.505.1221 BETA VERSION||VULNERABLE|
|Panda Internet Security 2010 15.01.00||VULNERABLE|
|PC Tools Firewall Plus 184.108.40.206||VULNERABLE|
|Security Shield 2010 220.127.116.113||VULNERABLE|
|Sophos Endpoint Security and Control 9.0.5||VULNERABLE|
|Trend Micro Internet Security Pro 2010 17.50.1647.0000||VULNERABLE|
|Vba32 Personal 18.104.22.168||VULNERABLE|
|VIPRE Antivirus Premium 4.0.3272||VULNERABLE|
|VirusBuster Internet Security Suite 3.2||VULNERABLE|
|Webroot Internet Security Essentials 22.214.171.124||VULNERABLE|
|ZoneAlarm Extreme Security 9.1.507.000||VULNERABLE|
Final observations and notes
The argument-switch attack requires specific behavior from system scheduler which is impossible to ensure from user mode. However, this fact does not affect the strength of the attack too much. If the goal of the attack is to perform the action that the security software always blocks, the attacker and faker threads might try the method many times till they succeed. Our experiments show that in most cases, the attack succeeds after few attempts and sometimes even the first attempt is successful. The number of attempts needed can be radically lowered using smart manipulation of the threads' priorities.
The attack pattern described in this document does not, in general, use any feature present only on privileged user accounts. This means, that successful attack might come even from processes running under restricted user account.
We showed how the argument-switch attack works on the example of SSDT hooks. However, this method might be used to exploit other types of hooks as well. It only needs several conditions, which hold in most common implementations of hooks. The attack might function also on hooks, which:
- run on low IRQL with scheduler enabled, this condition theoretically is not needed to be true on multiprocessor systems, where the attacker and the faker threads can run on different processors simultaneously;
- accept handles to kernel objects, or pointers referencing memory in user mode portion of address space;
- perform validity and security checks;
- call original routines with unchanged values of the important arguments.
During our research we also focused on how to defend against the argument-switch attack. We realized, that securing kernel hooks might be quite complicated task for security software vendors, particularly for those which software uses huge amounts of SSDT and other types of hooks. This paper does not disclose our solutions of the problem.
One might think that installing two security applications will protect against the argument-switch attack. This makes, however, the situation even worse. Imagine that two security applications, A and B, hooked the NtOpenProcess system service. When application tries to open a process, the execution flow is intercepted by the hook handler installed by driver of application B (this application hooked the service when it was hooked already by application A).
If the hook handler of B is not implemented securely against the argument-switch attack, it gives no additional security to hook routine of A. It might even create additional security risk for A. The hook handler of B might call the hook handler of A, but it does not have to; if B calls original system service instead, the function of operating system is not affected (except for performance), but A does not have a chance to perform its security checks.
On the other hand, if B wants to preserve the functionality of the A's hook handler and calls it with original arguments then B's hook handler is vulnerable to the argument-switch attack regardless the implementation of A's hook handler. So, unless A and B are implemented perfectly and can rely on each other's perfection (which is a very strong assumption similar to cooperation of two applications written by competitive vendors), A or B will be vulnerable to the argument-switch attack. This is why installing more than one security product on a single machine is a risk today even if their vendors claim the products to be compatible.
The research was done on Windows XP Service Pack 3 and Windows Vista Service Pack 1 on 32-bit hardware. However, it is valid for all Windows versions including Windows 7. Even the 64-bit platform is not a limitation for the attack. It will work there against all user mode hooks and it will also work against the kernel mode hooks if they are installed, for example after disabling the PatchGuard.
Many security products do not use hooks or kernel patches at all. Obviously, such products can not be attacked by the argument-switch attack. The most affected products are security suites that implement host protection features, various HIPS and products that implement self-defense mechanisms. And only the security features that are implemented by hooks may be vulnerable to the presented techniques. Antivirus products may be affected if they implement any kind of behavior control, self-defense mechanism or other additional security features based on hooks. Many antivirus products, however, do not contain any such features, or do not use hooks to implement them.
It is not uncommon to see a single product to implement security features using hooks on one version of Windows while on other versions of the operating system the vulnerable hooks are replaced with other, usually well documented and supported, methods. This corresponds with the development of the operating system itself, which support of third party security products is better with almost every new version. Some products combine the documented methods with hooking, which are then used for features that can not be easily implemented by any documented methods. On the other hand, some products keep their hooks even in the latest versions of Windows and even for the features that can be implemented using documented methods. For various features of security products, it is also possible to implement kernel hooks deeper in the kernel where no user mode data appear anymore. Such hooks, unlike SSDT hooks and hooks on similar level, are also safe.
Checking for Race Conditions in File Accesses by Matt Bishop and Michael Dilger was the first paper that discussed the idea of the attack described in our paper. The authors named the technique TOCTTOU flaw, which stands for time-of-check-to-time-of-use flaw. Later, Andrey Kolishak wrote a paper called TOCTOU with NT System Service Hooking in which he firstly applied the attacking technique against kernel hooks on Windows NT systems. Various papers were published discussing TOCTTOU flaws in applications for UNIX systems. In 2007 on 24th Chaos Communication Congress, twiz and sgrakkyu presented From Ring Zero to UID Zero, in which they discussed TOCTTOU variants pointed against SSDT hooks on Windows.
KHOBE was developed independently to TOCTTOU research and any other related work. TOCTTOU papers had minimal media attention and were completely unknown to us prior our disclosure of KHOBE research. And not only to us. TOCTTOU research was unknown to all security software vendors that we have been in contact with, in relation to KHOBE research, which is the obvious reason of why today's Windows security products are still vulnerable to the problem firstly invented in 1996. This means the KHOBE research does not come with the new idea, yet still it is an original research and plays a very important role because the presented techniques are now in the interest of top security companies for the first time. Vendors of Windows security software now attempt to fix the vulnerable hooks.
This paper presents attack pattern called the argument-switch attack which shows that common implementations of kernel mode hooks are not secure. This attack represents serious threat because many security software vendors base their security features on hooking. We tested the most widely used security applications and found out that all of them are vulnerable. Today's most popular security solutions simply do not work.