Academia.eduAcademia.edu

Hello rootKitty: A lightweight invariance-enforcing framework

2014, arXiv (Cornell University)

Hello rootKitty: A lightweight invariance-enforcing framework Francesco Gadaleta, Nick Nikiforakis, Yves Younan, and Wouter Joosen arXiv:1405.5651v1 [cs.OS] 22 May 2014 IBBT-DistriNet Katholieke Universiteit Leuven Celestijnenlaan 200A B3001 Leuven, Belgium [email protected] Abstract. In monolithic operating systems, the kernel is the piece of code that executes with the highest privileges and has control over all the software running on a host. A successful attack against an operating system’s kernel means a total and complete compromise of the running system. These attacks usually end with the installation of a rootkit, a stealthy piece of software running with kernel privileges. When a rootkit is present, no guarantees can be made about the correctness, privacy or isolation of the operating system. In this paper we present Hello rootKitty, an invariance-enforcing framework which takes advantage of current virtualization technology to protect a guest operating system against rootkits. Hello rootKitty uses the idea of invariance to detect maliciously modified kernel data structures and restore them to their original legitimate values. Our prototype has negligible performance and memory overhead while effectively protecting commodity operating systems from modern rootkits. Keywords: rootkits, virtualization, detection, invariance 1 Introduction Operating systems consist of trusted software that executes directly on top of a host’s hardware providing abstraction, arbitration, isolation and security to the rest of the software. Due to their prominent position, operating systems have been a common target of attackers who try to circumvent their protection mechanisms and modify them to their advantage. In the past, a program that allowed a user to elevate his access and become a system administrator (“root”) was called a rootkit. Today the meaning of rootkits has changed and is used to describe software that hides the attacker’s presence from the legitimate system’s administrator. Kernel-mode rootkits1 target the core of an operating system and thus they are the hardest to detect and remove. In extreme cases, a kernelmode rootkit may be introduced by a software bug in the kernel, triggered by a 1 Such rootkits appear very often in the form of device drivers (Microsoft Windows) or Loadable Kernel Modules (Linux kernel). 2 F. Gadaleta et al. malicious or a benign but-exploitable process. Regardless of the way the rootkit is introduced, the result is malicious code running with operating system privileges which can add and execute additional code or modify existent kernel code. The activities resulting from a successful attack can range from spamming and keylogging to stealing private user-data and disabling security software running on the host. In the past, rootkits have also been used to turn their targets into nodes of a botnet as with Storm Worm [9] or to perform massive bank frauding [13]. Even rootkits that do not introduce new code, but rather make use of existing fragments of code to fabricate their malicious functions, need to somehow have these fragments executed in the order of their choosing [10]. Changing the control flow of the kernel involves either changing specific kernel objects such as function pointers or overwriting existing fragments of code with new code. Using the idea of modified kernel-data structures as a sign of rootkits, security researchers have developed several approaches to mitigate rootkits. Unfortunately, many of these are affected by considerable overhead [8,16] or miss a fundamental security requirement such as isolation[32]. Isolation is needed to prevent a countermeasure in the target system from being disabled/crippled by a potential attack. Other countermeasures have been presented in which operating system kernels are protected against rootkits by executing only authenticated (or validated) kernel code [24,22,16]. The aforementioned rootkit [10] that doesn’t introduce new kernel code and re-uses fragments of authenticated code bypasses such countermeasures. In [19] a countermeasure to detect changes of the kernel’s control flow graph is presented; Anh et al. [14] uses virtualization technology and emulation to perform malware analysis and [29] protects kernel function pointers. Another interesting work is [23] which gives more attention to kernel rootkit profiling and reveals key aspects of the rootkit behavior by the analysis of compromised kernel objects. Determining which kernel objects are modied by a rootkit not only provides an overview of the damage inflicted on the target but is also an important step to design and implement systems to detect and prevent rootkits. A rising trend in security research is the use of virtualization technology for non-virtualization specific purposes [7,21,5,4]. The property that makes virtualization particularly attractive, from a security perspective, is that isolation is guaranteed by the current virtualization-enabled hardware. Using the appropriate instruction primitives of such hardware, makes it straightforward to fully separate and isolate the target from the monitor system. In this paper we present Hello rootKitty, a lightweight invariance-enforcing framework to mitigate kernel-mode rootkits in common operating system kernels. We start from the observation that many critical kernel data structures are invariant. Many data structures used by rootkits to change the control-flow of the kernel contain values that would normally stay unchanged for the lifetime of a running kernel. Our protection system consists of a monitor that checks the contents of data structures that need to be protected at regular times and detects whether their contents have changed. If a change is detected, our system warns the administrator of the exploited kernel and corrects the problem by restoring the Hello rootKitty: A lightweight invariance-enforcing framework 3 modified data structures to their original contents. Our monitor runs inside a hypervisor and protects operating systems that are being virtualized. Due to the hardware-guarantees of isolation that virtualization provides, an attacker has no way of disabling our monitor or tamper with the memory areas that our system uses. Hello rootKitty imposes negligible performance overhead on the virtualized system and it doesn’t require kernel-wide changes other than a trusted module to communicate the invariant data structures from the guest operating system to the hypervisor. Hello rootKitty can be integrated with existing invariance-inferencing engines and protect commodity operating systems running on virtualized environments. Alternatively, our system can be used directly by kernel developers to protect their invariant structures from rootkit attacks. The rest of the paper is structured as follows. Section 2 describes the problem of rootkits and presents our attacker model. Section 3 presents our solution. In Section 3.1 we present the architectural details of Hello rootKitty followed by its implementation in Section 3.2. We evaluate our prototype implementation in Section 4 and present its limitations in Section 5. We discuss related work on rootkit detection in Section 6 and conclude in Section 7. 2 Problem description In this section we describe common rootkit technology and we also present the model of the attacker that our system can detect and neutralize. 2.1 Rootkits Rootkits are pieces of software that attackers deploy in order to hide their presence from a system. Rootkits can be classified according to the privilege-level which they require to operate. The two most common rootkit classes are: a) user-mode and b) kernel-mode. User-mode rootkits run in the user-space of a system without the need of tampering with the kernel. In Windows, user-mode rootkits commonly modify the loaded copies of the Dynamic Link Libraries (DLL) that each application loads in its address space [28]. More specifically, an attacker can modify function pointers of specific Windows APIs and execute their own code before and/or after the execution of the legitimate API call. In Linux, user-mode rootkits hide themselves mainly by changing standard linux utilities, such as ps and ls. Depending on the privileges of the executing user, the rootkit can either modify the default executables or modify the user’s profile in a way that their executables will be called instead of the system ones (e.g.,by changing the PATH variable in the Bash shell). Kernel-mode rootkits run in the kernel-space of an operating system and are thus much stronger and much more capable. The downside, from an attacker’s perspective, is that the user must have enough privileges to introduce new code in the kernel-space of each operating system. In Windows, kernel-mode rootkits are loaded as device-drivers and target locations such as the call gate for 4 F. Gadaleta et al. interrupt handling or the System Service Descriptor Table (SSDT). The rootkits change these addresses (hooking) so that their code can execute before specific system calls. In Linux, rootkits can be loaded either as a Loadable Kernel Module (LKM) or written directly in the memory of the kernel through the /dev/mem and /dev/kmem file [20]. These rootkits target kernel-data structures in the same way that their Windows-counterparts do. Although this paper focuses on Linux kernel-mode rootkits, the concepts introduced apply equally well to Windows kernel-mode rootkits. An empirical observation is that kernel-mode rootkits need to corrupt specific kernel objects, in order to execute their own code and add malicious functionality to the victim kernel. Studies of common rootkits [17,19] show that most dangerous and insidious rootkits change function pointers in the system call table, interrupt descriptor table or in the file system, to point to malicious code. The attack is triggered by calling the relative system call from user space or by handling an exception or, in general, by calling the function whose function pointer has been compromised. We report a list of rootkits which compromise the target kernel in Table 1. Rootkit Adore, afhrm, Rkit, Rial, kbd, Allroot, THC, heroin, Synapsis, itf, kis SuckIT Adore-ng Knark Description Modify system call table Modify interrupt handler Hijack function pointers of fork(), write(), open(), close(), stat64(), lstat64() and getdents64() Add hooks to /proc file system Table 1. Hooking methods of common linux rootkits 2.2 Attacker Model In this work we first assume that the operating system which is being attacked is virtualized, i.e., it runs on top of a hypervisor which has more privileges than the operating system itself. Virtualization guarantees isolation thus we assume that the guest operating system cannot access the memory or code of the hypervisor. Our system detects the rootkit after it has been deployed, a fact which allows our model to include all possible ways of introducing a rootkit in a system. Thus, a rootkit can be introduced either by: – A privileged user loading the rootkit as a Loadable Kernel Module – A privileged user loading the rootkit by directly overwriting memory parts through the /dev/ memory interfaces – An unprivileged user exploiting a vulnerability in the kernel of the running operating system which will allow him to execute arbitrary code Hello rootKitty: A lightweight invariance-enforcing framework 5 Finally, our system doesn’t rely on secrecy so our model of the attacker includes him being aware of the protection system. 3 Hello rootKitty: protecting kernel data against rootkits In this section we describe our approach to detect rootkits that compromise function pointers or data structures residing in the kernel. 3.1 Approach By studying the most common rootkits and their hooking techniques one can realize that they share at least one common characteristic. In order to achieve execution of their malicious code, rootkits overwrite locations in kernel memory which are used to dictate, at some point, the control-flow inside the kernel. Most of these locations are very specific (see Table 1) and their values are normally invariant, i.e. they don’t change over the normal execution of the kernel. Since these objects are normally invariant, any sign of variance can be used to detect the presence of rootkits. We use the terminology of “critical kernel objects” to name objects that can be used by an attacker to change the control-flow of the kernel. The approach of Hello rootKitty is, given a list of invariant critical kernel objects, to periodically check them for signs of variance. When our countermeasure detects that the contents of an invariant critical kernel object have been modified, it will report an ongoing attack. Invariant critical kernel objects have been identified in several contributions, such as [29,2,3,6]. The methods to detect invariance differ depending on the type of critical kernel object and are the following: 1. Static kernel objects at addresses hardcoded and not dependent on kernel compilation 2. Static kernel objects dependent on kernel compilation (e.g., provided by /boot/System.map in a regular Linux kernel) 3. Dynamic kernel objects allocated on the heap by kmalloc, vmalloc and the rest of the kernel-specific memory allocation functions Identifying and protecting static kernel objects (type 1 and type 2) is straightforward. During the installation of the operating system to be monitored, a virtual machine installer would know in advance whether the guest is of Windows or Unix type. This is the minimal information required to detect kernel objects whose addresses have been hardcoded (type 1). Moreover, the Linux operating system provides System.map, where compilation-dependent addresses of critical kernel objects are stored (type 2). In contrast, identifying dynamic kernel objects (type 3) needs much more effort and depends on the invariance detection algorithm in place. Part of our countermeasure is a trusted module which operates in the guest operating system at boot time. Boot time is considered our root of 6 F. Gadaleta et al. trust. We are confident this to be a realistic assumption. 2 From this point on, the system is considered to operate in an untrusted environment and a regular integrity checking of the protected objects is necessary to preserve the system’s safety. Given a list of invariant kernel objects, the trusted module communicates this data (virtual address and size) of the kernel objects to observe after boot, and stores them in the guest’s address space. Then it will raise a hypercall in order to send the collected entries to the hypervisor. The hypervisor will checksum the contents mapped at the addresses provided by the trusted module and will store their hashes in its address space, which is not accessible to the guest. The trusted module is then forced to unload via a end-of-operation message sent by the hypervisor. Hello rootKitty doesn’t accept objects after the kernel has booted, in order to prevent a possible Denial-Of-Service attack launched by an attacker who is aware of the presence of our system. It is important to point out that Hello rootKitty is not a invariance-detection system for critical kernel objects and thus it must be provided with a list of kernel objects on which it will enforce invariance. This list can be either generated by invariance detection systems [29,2,3,6] or manually compiled by kernel and kernel-module developers. (1) guest kernel trusted module (2) shared memory (3) (5) hypervisor (4) private memory Fig. 1. High level view of trusted module-hypervisor interaction Although implementing countermeasures in a separated virtual machine or within the hypervisor increases the degree of security via isolation, it often leads to higher performance overhead than the equivalent implementation in the target system. A challenging task is that of checking integrity outside of the target 2 Boot time ends right before calling kernel thread which starts init, the first userspace application of the Linux kernel. At this stage the kernel is booted, initialized and all the required device drivers have been loaded. Hello rootKitty: A lightweight invariance-enforcing framework 7 operating system while limiting the performance overhead. We achieve this by exploiting the regular interaction of a Virtual Machine Monitor and the guest operating system. In a virtualized environment the guest’s software stack runs on a logical processor in VMX non-root operation [11]. This mode differs from the ordinary operation mode because certain instructions executed by the guest kernel may cause a VMExit. A VMExit, is a transition from VMX non-root mode to VMX root mode. After a VMExit the hypervisor will gain control of the CPU and will handle the exception. When the handler terminates the hypervisor performs a VMEntry and returns the control to the guest which will load the latest state of the logical processor and resume execution in VMX non-root mode. Our countermeasure performs integrity checks every time the guest kernel writes to a control register (MOV CR* event) which in-turn causes a VMExit. Trapping this event is strategic because when virtual addressing is enabled, the upper 20 bits of control register 3 (CR3) become the page directory base register (PDBR). This register is fundamental to locate the page directory and the page tables for the current task. Whenever the guest kernel schedules a new process (process switch) the guest CR3 is modified. Performing integrity checks on the MOV CR* events is a convenient way to keep detection time and performance overhead to a minimum while guaranteeing a high level of security on protected objects. Moreover, this choice allows a constraint relaxation to improve performance even more by paying a small cost in terms of detection time. We provide more details for our constraint relaxation in Section 3.2. Alternatively the hypervisor could check integrity randomly during the execution of the guest operating system. But this would not scale according to the guest system load as our current approach does. 3.2 Implementation In this section we discuss the implementation details of Hello rootKitty. We consider the choice of the hypervisor of critical importance in order to limit the overhead of the entire system. In fact, countermeasures implemented in virtualized environments are usually affected by considerable overhead which often prevents their deployment in actual production systems. We developed a prototype of our countermeasure in BitVisor, a tiny Type-I hypervisor [26] which exploits Intel VT and AMD-V instruction sets. Our target system runs a Linux kernel with version 2.6.35 and the trusted module has been implemented as a loadable kernel module for the Linux kernel. Our choice of BitVisor is mainly due to its memory address translation features. In BitVisor, the guest operating system and the Virtual Machine Monitor share the same physical address space. Thus, the VMM does not need any complex mechanism to provide translations from guest to host virtual addresses. The guest operating system will rely on the guest page table to perform translations from virtual to physical addresses. This considerably reduces the size of the hypervisor’s code and has a very low impact on the overall performance. Unfortunately, in this specific architecture, the VMM can not directly use the guest page table. Translations of guest virtual 8 F. Gadaleta et al. addresses to host virtual addresses are thus performed by the cooperation of the trusted module and the hypervisor, as explained later in this section. Figure 1 presents a high-level view of Hello rootKitty. Our system detects illegal modifications to invariant critical kernel objects in three phases which are described below. Communicating phase The trusted module executes in the guest’s address space and communicates the addresses and sizes of critical kernel objects to be protected. In order to test and benchmark our system in a realistic way, we created an artificial list of critical kernel objects by allocating synthetic kernel data. For each critical object the trusted module will retrieve its physical address by calling pa(virtual address), a macro of the Linux kernel. If the kernel object is stored in one physical frame the trusted module will immediately collect the start address and the size. If the kernel object is stored on more than one physical frames the trusted module will store the relative list of physical addresses. When the virtual addresses of all objects have been translated, a hypercall is raised which signals the hypervisor to start the integrity checking. Detection phase In order to detect changes the hypervisor needs to access the contents at the physical addresses collected by the trusted module. This is achieved by mapping the physical address and size of each object in its private memory and computing the signature of its actual contents. When all objects have been checksummed an end-of-operation flag is set in a memory area shared with the trusted module, which in turn will be unloaded. The checksum is performed by a procedure which implements MD5. This cryptographic hash function provides the integrity guarantees needed for our purposes. While stronger hash functions exist, we believe that the security and collision rate provided by MD5 are strong enough to adequately protect our approach from mimicry attacks. Repairing phase When the hypervisor detects that the signature of a protected object is different from the one computed the first time, two different behaviors are allowed: a) the system will report an ongoing attack or b) the system attempts to restore the contents of the compromised object if a copy has been provided by the trusted module. Since the hypervisor and the guest share the same physical address space, the hypervisor can restore the original content by mapping the physical address of the compromised object in its virtual space. The untampered value is then copied and control returns to the guest. The restoration of modified critical data structures means that, while the rootkit’s code is still present in the address space of the kernel, it is no longer reachable by the kernel control-flow and thus it is neutralized. Since switching from VMX-root to VMX-non-root causes a flush of the TLB, any code in the guest that was using the compromised object will perform the address translation and memory load again and will thus load the restored value. As previously mentioned, whenever task switching, the CR3 register’s contents are changed. Hello rootKitty traps the MOV CR* event and checks the integrity of the critical kernel objects. This checking occurs outside of the guest operating system and thus can’t be influenced by it. Since the number of kernel objects might be high, the hypervisor will perform the integrity checking of only a subset of objects. Control is then Hello rootKitty: A lightweight invariance-enforcing framework 9 returned to the guest kernel and another subset of critical kernel objects will be checked at the next MOV CR* event. While considerably improving the performance overhead, this relaxation obviously comes at a cost in terms of security and detection time. We do believe however, that the resulting detection ability of Hello rootKitty remains strong, a belief which we explore further in Section 5. 4 Evaluation We implemented Hello rootKitty in BitVisor (Ver 1.1) and the trusted module as a loadable kernel module of the Linux kernel. All experiments were performed on Intel Core 2 Duo 2 Ghz processor with 4GB of RAM. 4.1 Security Evaluation In order to evaluate whether Hello rootKitty would detect a real rootkit we downloaded and installed a minimal rootkit [15] which hijacks a system-call entry, specifically the setuid systemcall, from the system-call table. When the setuid system call is invoked with the number 31337 as an argument, the rootkit locates the kernel structure for the calling process and elevates its permissions to “root”. The way of hijacking entries in the system-call table is very common among rootkits (see Table 1) since it provides the rootkit a convenient and reliable control of sensitive system calls. The critical kernel object that the rootkit modifies is the system-call table which normally remains invariant throughout the lifetime of a specific kernel version. The Linux kernel developers have actually placed this table in read-only memory, however the rootkit circumvents this by remapping the underlining physical memory to new virtual memory pages with write permissions. Before installing the rootkit, we gave as input to our trusted module, the address of the invariant system-call table and its size. Since Hello rootKitty is an invariance-enforcing framework and not an invariance-discovering system, the invariant critical kernel-objects and their size must be provided to it from an external source. This source can either be automatic invariance-discovering systems or kernel programmers who wish to protect their data structures from malicious modifications. Once our system was booted we loaded the rootkit in the running kernel. When the next MOV to control-registered occured, the system trapped into the hypervisor and Hello rootKitty detected the change on the invariant system-call table. After reporting the attack, the system repaired the systemcall table by restoring the system-call entry with the original memory address. This means, that while the rootkit’s code is still loaded in kernel-memory it is no longer reachable by any statement and thus inactive. This shows, that Hello rootKitty can detect rootkits and repair the kernel provided that a) the kernel objects used by the rootkit to achieve control are invariant and b) the utilized kernel objects are included in the list of invariance that is given to our system. 10 F. Gadaleta et al. 4.2 Performance benchmarks According to slabtop, a Linux utility which displays kernel slab cache information, approximately 15,000 kernel objects are allocated during system’s lifetime, 75% of which smaller than 128 bytes. These numbers are never exceeded in other detection systems. Thus in order to measure the overhead introduced by our countermeasure we instrumented the trusted module to create 15,000 kernel objects each of 128 bytes and then performed different types of benchmarks. In order to avoid checking all 15,000 objects at each VMExit we check each time a different subset of the object set. This parameter is configurable and its value depends on the priorities of each installation (performance versus detection time). We measure real (wall-clock) timings in a virtualized environment to compensate for the inaccuracy of time measurements within the virtual machine (i.e. the guest’s timers are paused when the hypervisor is performing any other operation). We collected results from ApacheBench [1] sending requests on a local webserver running lighttpd (Table 3) and from SPECINT 2000 as macrobenchmarks to estimate the delay perceived by the user (Table 4). Lastly, we collected accurate timings of microbenchmarks from lmbench (Table 2). The macrobenchmarks show that our system imposes neglibible overhead on the SPEC applications (0.005%) allowing its widespread adoption as a security mechanism in virtualized systems. Microbenchmarks show a consistent overhead on process forking, as expected. In Table 2 we do not report measurements of context switching latencies because the numbers produced by this benchmark are inaccurate [27,12]. An improvement of local communication bandwidth is due to the slower context switching which has the side effect of slightly increasing the troughtput of file or mmap re-reading operations. 4.3 Memory overhead Memory overhead is proportional to the number of protected objects. The data structure needed to store information for integrity checking is 20 bytes long (64bit kernel object physical address, 32-bit kernel object size, 32-bit checksum, 32-bit support flags used by the hypervisor) 3 . Protecting 15,000 objects costs 193KB when the original content is not provided and 2168 KB otherwise. Moreover, every time a subset of the list of objects is checked the hypervisor needs to map each object from the guest physical space to its virtual space. In our proof of concept the hypervisor will map 100 objects of 128 bytes each every time a MOV CR event is trapped. This has an additional cost of 13KB. Thus the overall cost in terms of memory is approximately 206KB (2181KB if a copy of the original content is provided). Since the regular hypervisor allocates 128MB at system startup, the memory overhead is 1.5%. The trusted module needs the same amount of memory. But since it will free previously allocated memory after raising the hypercall, that memory would be regularly used by the kernel. 3 In order to repair the compromised object, the hypervisor needs to store the object’s original content too. This may increase the memory overhead. no counterm. Hello rootKitty overhead (%) no counterm. Hello rootkitty overhead (%) sh proc 16.K 18.K 12.5% Page fault 9.32010 9.84780 5.5% Mem write 698.7 697.8 0.12% Table 2. Performance overhead of Hello rootKitty in action on lmbench benchmarks Benchmark no counterm. Time 7.153 (sec) Requests per second 13981.10 (num/sec) Time per request 3.576 (ms) Time per concurrent request 0.072 (ms) Transfer rate 52534.36 (Kbytes/sec) Hello rootKitty 7.261 (sec) 13771.43 (num/sec) 3.631 (ms) 0.073 (ms) 51746.51 (Kbytes/sec) Perf.overh. 1.50% 1.52% 1.54% 1.4% 1.52% Hello rootKitty: A lightweight invariance-enforcing framework no counterm. Hello rootKitty overhead (%) Processes - times in microseconds - smaller is better open clos slct TCP sig inst sig hndl fork proc exec proc 16.6 3.08 0.48 2.41 1222 4082 16.5 3.09 0.48 2.47 1724 5547 0.6% 0.3% 0% 2.5% 41.0% 35.8% File and VM system latencies in microseconds - smaller is better 0K File create 0K File delete 10K File create 10K File delete Mmap latency Prot fault 26.0 21.5 99.9 28.2 62.2K 4.355 26.4 21.3 99.8 27.8 66.5K 4.444 1.53% -0.93% -0.1% -1.43% 6.9% 2.0% Local Communication bandwidth in MB/s - bigger is better TCP File reread Mmap reread Bcopy(libc) Bcopy (hand) Mem read 2401 313.0 4838.1 617.5 616.1 4836 2348 313.2 4885.0 619.7 618.8 4842 2.2% -0.06% -0.93% -0.32% -0.43% -0.12% Table 3. Results of ApacheBench sending 100000 requests, 50 concurrently on local lighttpd webserver 11 12 F. Gadaleta et al. Benchmark 164.gzip 175.vpr 176.gcc 181.mcf 197.parser 256.bzip2 300.twolf Average no counterm.(sec) 204 138 88.7 86.4 206 179 229 161.6 Hello rootKitty(sec) Perf. overh. 204 0% 142 2.8% 89.0 0.3% 86.7 0.34% 207 0.5% 179 0% 229 0% 162.4 0.005% Table 4. SPEC2000 benchmarks of Hello rootKitty in action 4.4 Detection Time Due to the relaxation of integrity checking introduced in the earlier sections, it is possible that the modified critical kernel object will not be in the current subset that Hello rootKitty checks. The current prototype of our system checks a subset of 100 objects at every change of a Control Register. For the total of 15,000 objects, this means that in the worst case scenario Hello rootKitty will detect the malicious modification 149 process switches later than the moment it happened. We found out that in a normally loaded system, this corresponds to approximately 6 seconds of wall-clock time. We believe that this is an acceptable security trade-off for the performance benefits that relaxation offers. 5 Limitations In this section we describe the limitations and possible weak points of our countermeasure. Since Hello rootKitty checks the critical kernel objects for invariance at every change of a Control Register (CR*), an attacker can possibly compromise the scheduler of the operating system and avoid task switching, thus avoiding changes of the CR3. The problem with this attack is that it effectively freezes the system, since the control can’t be returned back from the kernel to the running applications. A rootkit’s main goal is to hide itself from administrators thus any rootkit behaving this way will a) reveal that there is something wrong in the kernel of the running operating system and b) will never be able to intercept system calls of running processes. These facts suggest that while the attack is possible, it is not probable. Another attack might occur because of the relaxation explained in Section 3.2 which improves the performance overhead but comes with a cost in terms of detection time. Since at any MOV CR event the hypervisor will check the integrity of a subset of objects, several malicious processes in the guest might compromise the kernel and restore the original contents before the hypervisor performs the checking. We consider such an attack hard to accomplish because, although the hypervisor performs integrity checking in a deterministic fashion, the attacker has no information about the position of the compromised object in the hypervisor’s memory space. A possible mitigation for this kind of attack is the randomization of the sequence in which blocks are checked. Hello rootKitty: A lightweight invariance-enforcing framework 13 A third possible way to compromise the guest kernel would be by corrupting critical kernel objects whose values legitimately change during the kernel’s lifetime. Such objects are not invariant and thus can’t be included in the list of objects that Hello rootKitty checks since our system is unable to differentiate legitimate from non-legitimate changes. The majority though of existing kernel-mode rootkits modify invariant data structures thus our system reduces considerably the rootkit attack surface and prevents most rootkits from performing a successful attack. Lastly, Hello rootKitty depends on invariance inference engines to provide an accurate list of invariant critical kernel objects. Thus, if the inference engine used doesn’t provide all the invariant critical kernel objects (false negatives), Hello rootKitty will be unable to detect attacks that occur in the non-reported kernel objects. 6 Related work A number of efforts exist on detecting and preventing kernel malware. In this section we explore related work that attempts to protect a kernel using specialized hardware, virtualization, code integrity and profiling. Hardware-based countermeasures Copilot [18] is a kernel integrity monitor which detects illegal modications to a hosts kernel by fetching physical memory pages where kernel data and code is stored. Although the monitor has negligible overhead, it needs a separate PCI-card to fetch pages of the running kernel. Gibraltar [2] is a system to infer kernel data structure invariants by fetching snapshots of kernel memory in a way similar to Copilot, by using a PCI-card. The violation of the inferred invariants is reported as a potential attack. While detecting malicious behavior, those countermeasures are limited by the usage of special hardware. A wide deployment of such systems is harder to achieve. Kernel code integrity A countermeasure specifically designed to prevent the execution of unauthorized code is described in [24]. The system comes in the form of a tiny hypervisor which protects legacy OSes and ensures that only validated code of the guest can execute in kernel mode. Another rootkit prevention system is NICKLE [22], which prevents unauthorized kernel code execution via memory shadowing. The hypervisor maintains a shadow physical memory to store authenticated guest kernel code. At runtime it determines if the instruction fetch is for kernel or user mode. After verifying the code it will route the instruction fetch accordingly to shadow or standard memory. Kernel rootkit attacks would be detected and prevented since invalidated code would attempt to run in kernel mode. A recent attack to bypass countermeasures against code injection attacks, such as the Non-Executable stack countermeasure, is Return Oriented Programming (ROP) [25]. In ROP, the attacker, instead of injecting malicious code in the address space of a vulnerable process, crafts his malicious payload by combining fragments of existing code. This method of attacking has been used to create return-oriented rootkits which re-use fragments of authorized kernel code 14 F. Gadaleta et al. for malicious purposes [10]. Such rootkits can bypass countermeasures like the ones proposed by [22,24]. Yin et al. [32] protect kernel function pointers from being compromised by rootkits. The approach consists of an analysis and a detection subsystems. The analysis subsystem keeps track of function pointer propagation in kernel memory via a whole-system emulator and generates the policy for hook detection. The detection subsystem resides on the target machine and detects violations of the inferred policy. Although the described countermeasure is binary-centric and can generate a hook detection policy without modifying any guest source code, it can be disabled by a rootkit attack since the detection system resides in the target machine. A countermeasure which protects kernel hooks dynamically allocated from the heap is described in [29]. Since protection of kernel objects needs byte-level granularity, obviously finer than the page-level granularity provided by commodity hardware, this countermeasure relocates kernel hooks to be protected to a dedicated page and then exploits the regular page-level protection of the MMU. Although the overhead is negligible, rootkit attacks that compromise non-control data would not be prevented. Analysis and profiling systems Malware analysis can reveal important information about the way a rootkit compromises data structures or how private data are stolen, allowing researchers to understand rootkit’s behavior and design effective countermeasures. A virtual machine monitor designed for malware analysis as described in [14] extracts features like memory pages, system calls, disk and network accesses from the analyzed program running in the guest. Wang et al. [30] is an analysis tool against persistent rootkits, which compromise kernel hooks for hiding purposes. Those kernel hooks are first identified by monitoring the kernel-side execution path of system utility programs (e.g. ps, ls) and then reported as potential targets. Identification of kernel hooks and extraction of the hook implanting mechanisms via dynamic analysis is proposed in [31]. A whole-system emulator is used rather than a virtual machine monitor. A rootkit proler is described in [23]. A VMM is used to log the rootkit hooking behavior, to monitor targeted kernel objects, to extract kernel rootkit code and infer the potential impact on user-level programs. Hello rootKitty can be easily integrated with the systems described above in order to perform integrity checking and detect illegal changes to those kernel objects collected by the analysis tools. 7 Conclusion In this paper we demonstrated how the guaranteed isolation between a hypervisor and a guest operating system can be used to build a non-bypassable invariance-enforcing framework. We realized our idea by designing and implementing Hello rootKitty, a lightweight countermeasure to mitigate kernel-mode rootkits in common operating system kernels. Upon detection of a change of an invariant kernel object, Hello rootKitty alerts the administrator of the guest operating system and proceeds to repair the kernel by restoring the data structure Hello rootKitty: A lightweight invariance-enforcing framework 15 to its original values. Due to this change, the rootkit’s injected code is no longer reachable by any statements in the kernel and thus can no longer affect the running kernel’s operations. The evaluation of our prototype showed that Hello rootKitty can detect control-flow changes used by modern rootkits with negligible performance and memory overhead, making it a viable countermeasure for protecting operating systems in virtualized environments. Acknowledgements: This research is partially funded by the Interuniversity Attraction Poles Programme Belgian State, Belgian Science Policy, IBBT and the Research Fund K.U.Leuven. References 1. Apachebench: A complete benchmarking and regression testing suite. 2. Arati Baliga, Vinod Ganapathy, and Liviu Iftode. Detecting kernel-level rootkits using data structure invariants, 2010. 3. Martim Carbone, Wenke Lee, Weidong Cui, Marcus Peinado, Long Lu, and Xuxian Jiang. Mapping kernel objects to enable systematic integrity checking. In In ACM Conf. on Computer and Communications Security, 2009. 4. John Criswell, Andrew Lenharth, Dinakar Dhurjati, and Vikram Adve. Secure Virtual Architecture: A Safe Execution Environment for Commodity Operating Systems. In Proceedings of SOSP ’07. 5. Prashant Dewan, David Durham, Hormuzd Khosravi, Men Long, and Gayathri Nagabhushan. A hypervisor-based system for protecting software runtime memory and persistent storage. In Proceedings of SpringSim ’08, 2008. 6. Brendan Dolan-Gavitt, Abhinav Srivastava, Patrick Traynor, and Jonathon Giffin. Robust signatures for kernel data structures. In Proceedings of CCS ’09, 2009. 7. Francesco Gadaleta, Yves Younan, Bart Jacobs, Wouter Joosen, Erik De Neve, and Nils Beosier. Instruction-level countermeasures against stack-based buffer overflow attacks. In Eurosys, 2009. 8. Tal Garfinkel and Mendel Rosenblum. A virtual machine introspection based architecture for intrusion detection, 2003. 9. Thorsten Holz, Moritz Steiner, Frederic Dahl, Ernst Biersack, and Felix Freiling. Measurements and mitigation of peer-to-peer-based botnets: a case study on storm worm. In Proceedings of LEET’08, 2008. 10. Ralf Hund, Thorsten Holz, and Felix C. Freiling. Return-oriented rootkits: Bypassing kernel code integrity protection mechanisms. In SSYM’09: Proceedings of the 18th conference on USENIX security symposium, 2009. 11. Intel Corporation. Intel 64 and IA-32 Architectures Software Developer’s Manual - Volume 3B, 2007. 12. Open Kernel labs. Why lmbench is evil? http://www.ok-labs.com/blog/entry/ why-lmbench-is-evil/. 13. Mcaffee: 2010 threat predictions. http://mcafee.com/us/local_content/white_ papers/7985rpt_labs_threat_predict_1209_v2.pdf. 14. Anh M. Nguyen, Nabil Schear, HeeDong Jung, Apeksha Godiyal, Samuel T. King, and Hai D. Nguyen. Mavmm: Lightweight and purpose built vmm for malware analysis. Computer Security Applications Conference, Annual, 2009. 15. oblique. setuid rootkit. http://codenull.net/articles/kernel_mode_hooking. tar.gz. 16 F. Gadaleta et al. 16. Daniela Alvim Seabra de Oliveira and S. Felix Wu. Protecting kernel code and data with a virtualization-aware collaborative operating system. In Proceedings of ACSAC ’09, 2009. 17. PacketStorm. http://packetstormsecurity.org/UNIX/penetration/rootkits/. 18. Nick L. Petroni, Jr. Timothy, Fraser Jesus, Molina William, and A. Arbaugh. Copilot - a coprocessor-based kernel runtime integrity monitor. In In Proceedings of the 13th USENIX Security Symposium, 2004. 19. Nick L. Petroni, Jr. and Michael Hicks. Automated detection of persistent kernel control-flow attacks. In Proceedings of CCS ’07, 2007. 20. Linux on-the-fly kernel patching without LKM, by sd and devik. Phrack Issue 58. 21. QubesOS: Architecture Specification. http://qubes-os.org/files/doc/ arch-spec-0.3.pdf. 22. Ryan Riley, Xuxian Jiang, and Dongyan Xu. Guest-transparent prevention of kernel rootkits with vmm-based memory shadowing. In Proceedings of RAID ’08, 2008. 23. Ryan Riley, Xuxian Jiang, and Dongyan Xu. Multi-aspect profiling of kernel rootkit behavior. In Proceedings of Eurosys ’09, 2009. 24. Arvind Seshadri, Mark Luk, Ning Qu, and Adrian Perrig. Secvisor: a tiny hypervisor to provide lifetime kernel code integrity for commodity oses. In Proceedings of twenty-first ACM SIGOPS symposium on Operating systems principles, 2007. 25. Hovav Shacham. The geometry of innocent flesh on the bone: return-into-libc without function calls (on the x86). In Proceedings of CCS ’07, 2007. 26. Takahiro Shinagawa, Hideki Eiraku, Kouichi Tanimoto, Kazumasa Omote, Shoichi Hasegawa, Takashi Horie, Manabu Hirano, Kenichi Kourai, Yoshihiro Oyama, Eiji Kawai, Kenji Kono, Shigeru Chiba, Yasushi Shinjo, and Kazuhiko Kato. Bitvisor: a thin hypervisor for enforcing i/o device security. In Proceedings of VEE ’09, 2009. 27. Carl Staelin and Larry McVoy. lmbench manual page. 28. Symantec. Windows rootkit overview. http://www.symantec.com/avcenter/ reference/windows.rootkit.overview.pdf. 29. Zhi Wang, Xuxian Jiang, Weidong Cui, and Peng Ning. Countering kernel rootkits with lightweight hook protection. In Proceedings of CCS ’09, 2009. 30. Zhi Wang, Xuxian Jiang, Weidong Cui, and Xinyuan Wang. Countering persistent kernel rootkits through systematic hook discovery. In Proceeding of RAID ’08, 2008. 31. Heng Yin, Zhenkai Liang, and Dawn Song. Hookfinder: Identifying and understanding malware hooking behaviors. In NDSS, 2008. 32. Heng Yin, Pongsin Poosankam, Steve Hanna, and Dawn Song. Hookscout: Proactive binary-centric hook detection. In Detection of Intrusions and Malware, and Vulnerability Assessment, 2010.