Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Upcoming SlideShare
Catálogo lentes oscuros
Next
Download to read offline and view in fullscreen.

2

Share

Download to read offline

BA1_Breitenfellner_RC4

Download to read offline

Related Audiobooks

Free with a 30 day trial from Scribd

See all

BA1_Breitenfellner_RC4

  1. 1. Implementation of an Android Kernel Rootkit for Unrooted Stock Images Marcel Breitenfellner B A C H E L O R A R B E I T Nr. S1310237008-A eingereicht am Fachhochschul-Bachelorstudiengang Mobile Computing in Hagenberg im Juni 2016
  2. 2. Diese Arbeit entstand im Rahmen des Gegenstands Sicherheit in mobilen Systemen im Sommersemester 2016 Betreuer: Dr. Erik Sonnleitner ii
  3. 3. Declaration I hereby declare and confirm that this thesis is entirely the result of my own original work. Where other sources of information have been used, they have been indicated as such and properly acknowledged. I further declare that this or similar work has not been submitted for credit elsewhere. Hagenberg, June 15, 2016 Marcel Breitenfellner iii
  4. 4. Contents Declaration iii Kurzfassung vii Abstract viii 1 Introduction 1 1.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 Challenges . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.3 Goals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.4 Outline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 2 Linux Kernel 3 2.1 Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 2.1.1 Kernel Space versus User Space . . . . . . . . . . . . . 4 2.1.2 Kernel Architectures . . . . . . . . . . . . . . . . . . . 7 2.1.3 Subsystems . . . . . . . . . . . . . . . . . . . . . . . . 7 2.1.4 Supported Architectures . . . . . . . . . . . . . . . . . 10 2.2 Kernel Security . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.2.1 Discretionary Access Control . . . . . . . . . . . . . . 11 2.2.2 Network Access Control . . . . . . . . . . . . . . . . . 12 2.2.3 Memory Protection . . . . . . . . . . . . . . . . . . . . 13 2.2.4 Linux Security Modules . . . . . . . . . . . . . . . . . 17 2.2.5 Kernel Capabilities . . . . . . . . . . . . . . . . . . . . 20 2.3 Loadable Kernel Modules . . . . . . . . . . . . . . . . . . . . 20 2.3.1 Structure . . . . . . . . . . . . . . . . . . . . . . . . . 21 2.3.2 Building . . . . . . . . . . . . . . . . . . . . . . . . . . 22 2.3.3 Managing . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.4 Kernel Rootkits . . . . . . . . . . . . . . . . . . . . . . . . . . 25 2.4.1 Kernel Modification Techniques . . . . . . . . . . . . . 26 2.4.2 Defense . . . . . . . . . . . . . . . . . . . . . . . . . . 29 2.5 Additional Concepts . . . . . . . . . . . . . . . . . . . . . . . 30 2.5.1 Process . . . . . . . . . . . . . . . . . . . . . . . . . . 30 2.5.2 Filesystems . . . . . . . . . . . . . . . . . . . . . . . . 30 iv
  5. 5. Contents v 2.5.3 Types . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 2.5.4 Networking . . . . . . . . . . . . . . . . . . . . . . . . 32 2.6 Futex Exploit . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 3 Android Characteristics 34 3.1 Android Open Source Project . . . . . . . . . . . . . . . . . . 35 3.2 Bionic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 3.3 Virtual Machines . . . . . . . . . . . . . . . . . . . . . . . . . 35 3.3.1 Dalvik Virtual Machine . . . . . . . . . . . . . . . . . 36 3.3.2 Android Runtime . . . . . . . . . . . . . . . . . . . . . 36 3.4 Unique Android Kernel Features . . . . . . . . . . . . . . . . 37 3.4.1 Android Shared Memory . . . . . . . . . . . . . . . . . 37 3.4.2 Binder . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 3.4.3 Process Memory Allocator . . . . . . . . . . . . . . . . 38 3.4.4 Logger . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 3.4.5 Wakelocks . . . . . . . . . . . . . . . . . . . . . . . . . 40 3.4.6 Out of Memory Handling . . . . . . . . . . . . . . . . 40 3.4.7 Alarm Timers . . . . . . . . . . . . . . . . . . . . . . . 40 3.4.8 Paranoid Networking . . . . . . . . . . . . . . . . . . . 41 3.4.9 Timed Output . . . . . . . . . . . . . . . . . . . . . . 41 3.4.10 RAM Console . . . . . . . . . . . . . . . . . . . . . . . 42 3.4.11 Other Changes . . . . . . . . . . . . . . . . . . . . . . 42 4 Related Work 43 4.1 SuckIt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 4.1.1 Features . . . . . . . . . . . . . . . . . . . . . . . . . . 44 4.1.2 Difficulties . . . . . . . . . . . . . . . . . . . . . . . . . 44 4.2 Suterusu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 4.2.1 Features . . . . . . . . . . . . . . . . . . . . . . . . . . 46 4.2.2 Difficulties . . . . . . . . . . . . . . . . . . . . . . . . . 47 4.3 Android Rootkit . . . . . . . . . . . . . . . . . . . . . . . . . 48 4.3.1 Obtaining System Call Table . . . . . . . . . . . . . . 48 4.3.2 Calculating System Call Table Size . . . . . . . . . . . 50 4.4 Android Modem Interception . . . . . . . . . . . . . . . . . . 51 5 Implementation 52 5.1 Concept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 5.1.1 System Architecture . . . . . . . . . . . . . . . . . . . 52 5.1.2 Kernel Rootkit . . . . . . . . . . . . . . . . . . . . . . 54 5.1.3 Root Exploit Tool . . . . . . . . . . . . . . . . . . . . 55 5.1.4 Infection via an APK . . . . . . . . . . . . . . . . . . 56 5.2 General Requirements . . . . . . . . . . . . . . . . . . . . . . 56 5.3 Building a Kernel . . . . . . . . . . . . . . . . . . . . . . . . . 57 5.3.1 Obtaining Sources . . . . . . . . . . . . . . . . . . . . 57
  6. 6. Contents vi 5.3.2 Compiling . . . . . . . . . . . . . . . . . . . . . . . . . 57 5.3.3 Packaging . . . . . . . . . . . . . . . . . . . . . . . . . 59 5.3.4 Execution . . . . . . . . . . . . . . . . . . . . . . . . . 59 5.4 Kernel Rootkit . . . . . . . . . . . . . . . . . . . . . . . . . . 61 5.4.1 Building . . . . . . . . . . . . . . . . . . . . . . . . . . 61 5.4.2 Core Features . . . . . . . . . . . . . . . . . . . . . . . 62 5.5 Exploit Binary . . . . . . . . . . . . . . . . . . . . . . . . . . 68 5.5.1 Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 5.5.2 Add Payload . . . . . . . . . . . . . . . . . . . . . . . 70 5.6 Infection APK . . . . . . . . . . . . . . . . . . . . . . . . . . 71 5.7 Infection Process . . . . . . . . . . . . . . . . . . . . . . . . . 72 5.8 Experienced Problems . . . . . . . . . . . . . . . . . . . . . . 72 5.8.1 Pack Kernel Image . . . . . . . . . . . . . . . . . . . . 73 5.8.2 Find Right Commit . . . . . . . . . . . . . . . . . . . 73 5.8.3 Disable SELinux from Kernel . . . . . . . . . . . . . . 73 5.8.4 Communication Device File Permissions . . . . . . . . 74 5.8.5 Modify Ramdisk . . . . . . . . . . . . . . . . . . . . . 74 6 Evaluation 75 6.1 Practical Use . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 6.2 Defense . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 6.3 Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 6.3.1 Root Permissions . . . . . . . . . . . . . . . . . . . . . 77 6.3.2 Android Fragmentation . . . . . . . . . . . . . . . . . 77 6.3.3 Special Security Features . . . . . . . . . . . . . . . . 78 6.3.4 Licensing Issues . . . . . . . . . . . . . . . . . . . . . . 78 6.4 Future Work . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 6.4.1 Patch Boot Image On-The-Fly . . . . . . . . . . . . . 79 6.4.2 Build on Demand . . . . . . . . . . . . . . . . . . . . . 79 6.4.3 Modify Buildstring . . . . . . . . . . . . . . . . . . . . 79 6.4.4 Dynamic IPs / DNS . . . . . . . . . . . . . . . . . . . 79 6.4.5 Dynamic System Call Table Allocation . . . . . . . . . 80 6.4.6 Intercept GSM Communication . . . . . . . . . . . . . 80 6.4.7 Change Root Exploit . . . . . . . . . . . . . . . . . . . 80 6.4.8 Infect via /dev/kmem . . . . . . . . . . . . . . . . . . 81 6.4.9 Anonymize Reverse Shell . . . . . . . . . . . . . . . . 81 6.4.10 Hide Files in Different Folders . . . . . . . . . . . . . . 81 6.4.11 Hijack other Apps . . . . . . . . . . . . . . . . . . . . 81 7 Conclusion 82 A DVD Content 83 References 90
  7. 7. Kurzfassung In dieser Arbeit werden wir ein Android Kernel Rootkit und den dazu- gehörenden Infektionsweg konzipieren und implementieren. Bevor es dazu kommt beschäftigen wir uns mit den Grundlagen des Linux Kernels, wie zum Beispiel den enthaltenen Sicherheitskonzepten, um abschätzen zu kön- nen wie ein Kernel Rootkit aufgebaut sein muss und welche Hindernisse uns erwarten. Des Weiteren nehmen wir bereits bestehende Linux- und Android Kernel Rootkits und Projekte zu diesem Thema unter die Lupe um einen Eindruck zu erlangen welche Lösungen zu diesem Problem bereits existieren. Basierend auf den Erkenntnissen aus den Grundladen des Linux Kernel und der näheren Begutachtungen ähnlicher Schadsoftware werden wir ein Konzept für ein funktionierendes Android Kernel Rootkit und den dazu- gehörigen Infektionsweg erstellen und im Anschluss daran umsetzen. Das Hauptaugenmerk bei der Infektion liegt dabei, dass diese für den Benut- zer möglichst unauffällig erfolgt. Des Weiteren sehen wir uns Ausschnitte des finalen Quellcodes der Schadsoftware im Detail an und erörtern wie die Kernfunktionalitäten implementiert wurden. Am Ende dieser Arbeit fassen wir das gewonnene Wissen zusammen und geben einen Ausblick auf zukünftige Arbeiten in diese Richtung. vii
  8. 8. Abstract In this thesis we will design an Android kernel rootkit and a way of infection from the scratch. Before the actual implementation process starts, we will take a look at how the Linux kernel in general works to find out which security features we have to take care of and where we could tweak the kernel internals in the implementation process in order to gain rootkit behaviour. In this work we will also take a closer look at similar kernel rootkits to understand how other developers implemented the malicious features. The core part is the design and implementation process of the rootkit and the way of infection. Based on the gained knowledge of the kernel basics and the dissection of similar malware we will design an Android kernel rootkit from the scratch. We will also take a look at how a device could be infected without the user knowing and how the rootkit can survive on an Android device. In this thesis we will also take a look at the actual source code in order to explain how we actually managed to implement the desired behaviour. At the end of this work we will conclude the gained knowledge and discuss ideas for future work gathered while working on this topic. viii
  9. 9. Chapter 1 Introduction 1.1 Motivation By now there are billions of active Android based devices in many different versions and with custom modifications [22]. But all are based on the same heart - the Linux kernel. With access to the kernel we have full control over the device. In this thesis we will look into what exactly a Linux kernel is, how the kernel, which is used in Android based devices, differs from that and what we can actually do by implementing a Linux kernel module for an Android based device and what limitations and difficulties there are. 1.2 Challenges While Android applications are usually developed in Java and executed in the dalvik virtual machine (DVM), the system beneath that is developed in plain C. In addition to that we are developing in kernel space, which means, beside the fact that all the libraries change, we have to be very careful. A single pointer to the wrong piece of memory could be enough to crash the kernel or even corrupt the file system. There is nothing which protects us from damaging the system. 1.3 Goals What we want is a running Linux kernel module on an Android device. To achieve that we maybe need to compile our own kernel for the particular testing device and get it running. Once we have a running kernel with the first hello world module we can take a deeper look into kernel space develop. For example purposes we will show up how to implement a simple Linux kernel rootkit. 1
  10. 10. 1. Introduction 2 1.4 Outline At first we will take a look at the Linux kernel, how everything works, what security mechanisms there are and what we need to know to build our own Linux kernel module with simple rootkit functions. Then we continue to a chapter where we will take a closer look at the differences between the basic general Linux kernel and the kernel shipped with every Android device. Since the Linux kernel source is published under the GNU General Public License (GPL) license we also take a look where to get the Android kernel source code from. To get a basic idea how a kernel rootkit works we take a look at some other popular rootkits, what functions they offer and most importantly how exactly they manage to do what they are doing, since kernel space programming can be very tricky. Then we will continue to build our own kernel module and expand it with the newly gained knowledge of how to actually implement a Linux kernel rootkit. At the end we sum it all up and draw a conclusion about what we have learned and what is open for improvements.
  11. 11. Chapter 2 Linux Kernel When we hear Linux some of us will think of a whole operating system, but in fact it’s only the same kernel that all of them share - the Linux kernel. Developed by Linus Torvalds in 1991, as a little private project without expecting to gain big attention, he created a lightweight POSIX like system which targeted the Intel 80386 microprocessor with an x86_64 architecture [59]. It happened to be a huge success as we know today. By now 486 of the Top 500 fastest super computers worldwide are running a Linux based system, mostly because it’s open source, which means that it can be easily adapted to the special needs of those machines [88]. As we know Android uses also an adapted Linux kernel for its operating system, which had a market share of 82.8% in 2015 [47]. On the server side there is a likelihood of 53.4% to communicate with a Linux server on the World Wide Web [88]. Due to its versatility operating systems based on the Linux kernel also had a 56.2% market share in embedded devices like TVs or routers [18]. The trend is also likely to continue due to the growing market for the Internet of Things, which is basically a bunch of small, energy saving embedded devices, a perfect environment for the Linux kernel. A few reasons why Linux is so successful are: GPL licensed the kernel source code is. [sic] This license allows free of charge use for private and commercial projects. But we have to con- sider that the source code has to be republished under GPL if the kernel source code was modified not for internal use only [33]. Fast updates are provided by the big community. If problems occur or ma- jor security risks are published, community patches are often available only a few hours later and added to the kernel source tree soon. Small and efficient design is a key aspect of the kernel. It is possible to fit the whole kernel image on an floppy disk (1.44MB) if needed [59]. Paid developers sponsored by big companies such as Google or Intel con- tribute over 85% of all commits to the Linux kernel source code [26]. 3
  12. 12. 2. Linux Kernel 4 Compatability is a big deal in Linux. At the beginning the kernel was only compatible to the i386 architecture, but nowadays it runs on a variety of architectures. Among them are x86_64, ARM, mips, and powerpc [56]. It can also directly mount filesystems used in other operating system like MS-DOS, Windows, OS X, OS/2, Solaris and many more [59]. 2.1 Structure So what actually is a kernel and what are its tasks to accomplish? If we take a look at the architecture of a modern operating system, the kernel is always the core which provides the basic functionalities with the highest privileges such as the low level hardware communication, process management, mem- ory management, file system access and the networking stack. 2.1.1 Kernel Space versus User Space An operating system always consists of a so called privileged and an unpriv- ileged space (or mode) - in Linux it’s the user and kernel space (see figure 2.1). This is needed to protect the system and security critical features as the memory management or the networking stack. In kernel mode there aren’t any access restrictions present, which means once a kernel module (see sec- tion 2.3) is loaded, it can modify the whole system. This is not required for most processes running on an computer, but the kernel needs these rights to manage the system resources and distribute them to all currently running user mode processes. Applications C Library System Call Interface Kernel Hardware User Space Kernel Space { { Figure 2.1: High-level Overview of the Structure of the Linux kernel.
  13. 13. 2. Linux Kernel 5 As mentioned before, all system relevant operations like memory or process management happen in kernel space. Also the low level device drivers are located there to provide an interface to user space to be used by applications. The only mechanism for controlled communication between these two modes is the system call interface (SCI), which provides certain low level kernel functionalities to user space applications [63]. System Call A system call in Linux is used when a user space program wants to call a kernel provided functionality in a controlled way. Such an action is every- thing hardware related like hard disk I/O or video card access. Also new processes can only be created with a special system call. A modern Linux kernel has about 300 different system calls. There are a few very similar calls due to downward compatibility reasons. Once a specific system call is implemented in a stable version it is unlikely to be removed again later, because user space programs, which used this particular system call earlier, could crash if there is no valid return value. On the technical side a system call in Linux on x86_64 is initiated by a software interrupt. This causes the system to switch from user into kernel mode and read the EAX register which contains the requested system call number. If there are any function parameters they are provided through the EBX to EDX, ESI and EDI registers. The kernel executes the requested call and sends the return value (errors are special values too) back to the calling function in user space. In table 2.1 we can see a list of well knows functions implemented as system calls. We can see which parameters are required in the registers for the system call to be executed successfully after issuing the software interrupt. Name EAX EBX ECX EDX ESI EBI sys_chdir 12 char* path - - - - sys_chmod 15 char* path mode_t perm - - - sys_getpid 20 - - - - - sys_mkdir 39 char* path - - - - Table 2.1: Example System Calls for x86_64 (see: syscalls.h and syscall_64.tbl) The collection of all system calls is called system call table (SCT), which is represented by a list of pointers in the system memory. Each one points to the specific function which is responsible for the particular action. Access to this table of pointers is very important for a kernel rootkit, to hook kernel functions in order to infect the system. This is why the SCT kernel symbol
  14. 14. 2. Linux Kernel 6 was only exported until kernel version 2.4. Now it’s much harder to find and modify this table, because it’s located in the read only section of the memory (see section 2.4 for in depth information). For example lets take a look at the sys_getpid system call. This system call returns the process identifier (PID) of the calling process. As we see in table 2.1 we only need to push the system call number (20) to the EAX register and then execute the software interrupt 0x80 (on x86_64 systems) as we can also see in figure 2.2. The system then fetches the pointer of the SCT at position 20 and calls it with the passed arguments stored in the registers. We can see the internal implementation of sys_getpid in listing 2.1. Listing 2.1: System Call Implementation 1 asmlinkage long sys_getpid(void) 2 { 3 return current->tgid; 4 } This very simple system call only returns the thread group identifier (TGID), which is in almost all cases identical to the PID [51]. User Application C Library Kernel System Call get_pid() Return Return system_call_table[EAX]() EAX = _NR_GET_PID Transition to Kernel Space 0x80 User Space Kernel Space Transition to User Space Figure 2.2: sys_getpid Call Trace [51]
  15. 15. 2. Linux Kernel 7 2.1.2 Kernel Architectures There are two main types into which every kernel can be categorized. There are monolithic and micro kernels. Micro kernels are, as the name implies, very minimalistic kernels, which only include the most fundamental features such as process and low level memory management in kernel space. Other functionalities like the networking stack are, if needed anyway, implemented as an user space process. This has the advantage of a very small modu- lar kernel but is sometimes slower then a monolithic kernel image due to communication overhead [28]. On the other hand in a monolithic kernel all needed core features run in kernel mode and are bundled into a single kernel image binary, like the Linux kernel does. Due to the optimizations provided at compile time a speed boost is possible here. To compete with the micro kernels flexibility, the Linux kernel also knows the concept of loadable ker- nel modules (LKMs), which we will discuss later in section 2.3. A LKM is a compiled binary specially built for each kernel to work with, which can be loaded and unloaded into the kernel at runtime. It provides miscellaneous functions like a device driver or - to sneak a bit on the dark side - a kernel rootkit. 2.1.3 Subsystems Now that we have understood the SCI we can take a look at the underlying subsystems in the Linux kernel. As we can see in figure 2.3 there are a few more core components that are essential for the kernels to fulfil its tasks. Process Management (PM) Virtual File System (VFS) Memory Management (MM) Network Stack Arch Device Drivers (DD) System Call Interface (SCI) Figure 2.3: Basic Kernel Architecture [50].
  16. 16. 2. Linux Kernel 8 Arch The kernel also delivers some platform-dependent code which is need- ed for the kernel to communicate on the assembly level with the un- derlying hardware. A detailed overview of the supported hardware ar- chitectures will be give in section 2.1.4. For each processor type which is supported by the kernel there is a specific subdirectory in the Linux kernel source code which targets its specific hardware features. That’s the only part of the kernel code which is platform-dependent [50]. Device Drivers: The majority of the over 19 millions line of code the Linux kernel consists of is provided by device drivers, which are responsible for the communication with all the connected hardware [26]. Exam- ples for device drivers which support special hardware are the USB or Bluetooth driver, which are usually taken for granted but in fact need special kernel implementation. Memory Management: Probably the most important part of the kernel is the memory management. It takes care of the separated kernel and user space memory as well as the assignment of new memory areas to processes or various buffers. It is also responsible for the abstraction of the physical memory to pages (on most architectures 4kB in size) also further higher level constructs are being tracked by the slab allocator. When a system is about to run out of memory this system has to decide which memory allocations to prioritize, which memory to swap out to the hard disk and, if everything fails, which process to terminate [42, 50]. Network Stack: The networking stack is needed to manage connections between endpoints. It follows the strict layer design of the OSI model, which separates for example the physical layer from the data link layer (Ethernet) the network layer (Internet Protocol) and the transport layer (mostly TCP or UDP). To user space the network stack is acces- sible though the SCI [50]. Process Management: This part takes care of the execution of processes. In kernel space they are called threads and in user space they are com- monly called processes. But in fact they are technically almost identical in the Linux kernel. From user space, processes can be managed using the SCI, which provides all needed functions. Important system calls are [45]: fork is used to spawn a new process. It creates a child process for the current process. The root of all processes has the PID 1 which is started at system boot [91]. exec executes a program. kill sends the passed signal to a process. exit terminates the current process.
  17. 17. 2. Linux Kernel 9 Scheduler: Since Linux is a multitasking system it can handle multiple processes at the same time. Technically only as many processes as (virtual) CPU cores can be executed simultaneously, so there has to be a sub system that takes care of this and splits the processing time in an as fairly as possible way to all processes. This system is called scheduling system, which is an important part of the process manage- ment subsystem. From user space it looks like every process is running simultaneously, but in fact the scheduler assigns a little processing time slice to every process every once and awhile. There are different ways, so called governors (see table 2.2), to determine which process to assign how much processing time. Governor Description performance CPU frequency set to maximum powersave CPU frequency set to minimum userspace Manually controlled from user space ondemand Sets the CPU frequency depending on the CPU load conservative Much like ondemand, but smaller frequency steps Table 2.2: Governors in the Linux Kernel [17] Virtual File System (VFS): To create and common API for all kinds of file systems the Linux kernel implements a virtual file system to provide abstract functions like open(), read() and write() [15, 50]. This abstraction API is again accessible via the SCI from user space as we can see in listing 2.4. write() sys_write() filesystem write function User Space VFS Filesystem Physical Media Figure 2.4: Example Function Call Trace [15] We will take a closer look at the virtual file system (VFS) and file systems in section 2.5.2.
  18. 18. 2. Linux Kernel 10 2.1.4 Supported Architectures It is NOT protable [sic] (uses 386 task switching etc), and it probably never will support anything other than AT-harddisks, as that’s all I have :-(. —Linus Torvalds, August 25, 1991 Initially Linus Torvalds developed the Linux kernel as a private project for the i386 architecture. The project gained much attention and soon others started developing an unofficial port of the kernel code to the Amiga, which uses a Motorola 68000 architecture. They replaced a huge amount of im- portant kernel code in order to run on the different architecture, which he couldn’t keep track of in the main source tree [28, 57]. In order to get rid of this problem and make it easier to add support for new hardware architectures Linus applied huge changes to the kernel code. From there on everybody could rather easily port the Linux kernel to a new architecture, which was soon accomplished with the first official kernel port to the DEC Alpha AXP platform [28]. The most used ports nowadays are shown in table 2.3. Name Word Size [bit] Designed by Misc i386 32 Intel - x86_64 32, 64 Intel Standard for PCs ARM 32, 64 ARM Holding Standard for smartphones and tablets mips 32, 64 MIPS Tech. Embedded systems, Sony Pl- aystation, PlayStation 2 and PlayStation Portable powerpc 32, 64 Apple IBM Motorola Apple PCs between 1994 and 2006, game console, embed- ded applications Table 2.3: Supported Architectures [15, 56] But there is also support for a great number of other niche platforms as we can see in table 2.4 [56].
  19. 19. 2. Linux Kernel 11 Name Word Size [bit] Designed by Misc avr32 32 Atmel - blackfin 16, 32 Analog Devices - cris 32 Axis - frv 32 Fujitsu Designed for parallel pro- cessing h8300 8, 16, 32 Renesas Tech. - m32r 32 Mitsubishi Mostly used in engine con- trol units m68k 32 Motorola - s390 32 IBM - sh 32 Hitachi - sh64 64 Hitachi - sparc 32 Oracle - sparc64 64 Oracle - v850 32 NEC - xtensa 32 Tensilica Designed for digital signal processing Table 2.4: Supported Architectures [15, 56] 2.2 Kernel Security By now we have discussed, that user and kernel space are separated for architectural and security reasons, but we haven’t encountered any other security mechanism. We now take a brief look at some security concepts which are used in the Linux kernel. 2.2.1 Discretionary Access Control One of the oldest security concepts in the Linux kernel is the discretionary access control (DAC), which is basically inherited from the old UNIX kernel it originates from. The basic concept is, only the owner of an object (e.g. file or directory) can manage its access. For example if we create a new file in the home directory, we can decide who can read and write to our new file. This is a simple form of access control lists (ACLs) which are further explained in section 2.2.1. Only the root user (superuser) is always allowed to violate these access controls [64]. Technically this security policy is implemented as permission bits added to the files inode on the file system, which can only be set by the owner of the file or the superuser [65].
  20. 20. 2. Linux Kernel 12 This mechanism doesn’t protect against buggy software or misconfigura- tions, also the root user is a permanent security risk. Some data flows such as network packets are not directly covered by this access control so there are also a few more security mechanisms [65]. Access Control Lists The portable operating system interface (POSIX) interface between the op- erating system and the user application and defines how each compliant application reacts on which case [85]. The Linux kernel complies to this standard, so it distinguishes between three different access types [55]: (r) read allows to read a file (w) write allows to write to a file (x) execute allows to execute a file Each file system object is also associated with a user identifier (UID), group identifier (GID) and the three sets of the previously mentioned access types for the file owner, a group member and everybody else [55]. When a process requests access to a file system object (see figure 2.5) it is classified to one of these three groups. Then it is checked which permissions are granted each groups and whether the requesting process is allowed to execute its action. Owner Group Other rwx rwx rwx File Class File Permission Bits Figure 2.5: The POSIX.1 File Permission Model [55]. 2.2.2 Network Access Control Since Linux has a well implemented network stack which features a variety of protocols and the previously mentioned DAC and ACL only manage file system access, there are also dedicated networking stack security features.
  21. 21. 2. Linux Kernel 13 Netfilter Netfilter is a framework on the network layer (Internet Protocol) which hooks every packet that passes through the networking stack [65]. It provides an API for LKMs, which lets them decide if a packet should be dropped or passed to the next layer in the networking stack. One famous example for the kernel modules (see section 2.3) is iptables. This module implements an IPv4 firewall where the corresponding user space program iptables manages a set of user defined rules, which every packet has to pass to be forwarded to the next layer of the networking stack [64, 65]. There are also modules available to filter on the link layer (ebtables) or ARP packets (arptables). The network stack also supports the secure implementation of the Internet Protocol IPsec. 2.2.3 Memory Protection All the memory protection mechanisms have basically only one goal: Make it as hard as possible for malware to take advantage of a buffer overflow. But in order to understand what we can do to improve the systems security, we need to understand what actually happens when a buffer overflow is utilized. Memory Layout In figure 2.6 we can see the memory layout of an user space process on a x86_64 architecture. It is divided into several parts. kernel virtual memory: This is the dedicated region for the kernel ad- dress space, it contains the kernel code and the whole stack and heap (kmalloc(), kfree()) user space process stack: Contains the return address to jump to, the local function variables and the function arguments. user space process heap: Contains the processes memory allocated dur- ing runtime (malloc()). DATA segment: Contains pre-initialized global and static variables as well as constants. BSS segment: Contains uninitialized global and static variables filled with zeros by default. CODE segment: Contains the binary image of the process. esp: Stack-Pointer - Points to the lower boundary of the stack. brk: Break-Pointer - Points to the upper boundary of the heap.
  22. 22. 2. Linux Kernel 14 Kernel Virtual Memory User Space Process Stack User Space Process Heap (malloc) DATA & BSS segment (read- and writable) CODE segment (read-only) 0x00000000 brk pointer esp pointer 0xc0000000 0xffffffff Invisible to user space code. Directly loaded from the executable program file. { { Figure 2.6: Process Memory Layout [80] Buffer Overflow Now that we know how the basic memory layout of a process looks like we can go further. When a process is launched the stack is prepared for it with the function arguments and the return address as we can see in figure 2.7. Parent Process Stack Return Address char* buffer; Local Variables ESP 0xc0000000 EBP Function Parameters Old EBP Figure 2.7: Stack Variables
  23. 23. 2. Linux Kernel 15 If an attacker finds an unchecked buffer it is possible for him to write to memory regions beyond the buffers borders. He will first overwrite all local variables and finally reach the return address of the function. There he can insert any address of prepared malicious code to compromise the system. Now lets take a look at a few techniques which won’t completely suppress this behavior, but make the attackers life much harder. Address Space Layout Randomization The idea here is to randomize all memory regions of a process to make it more difficult to use a buffer overflow to execute exploit code [62, 69, 80]. This is achieved by moving all process memory regions like DATA, BSS or HEAP at a random or at least for the intruder hard-to-guess memory offset [62]. The offset is stored in a dedicated CPU register, but in a 32-bit address space only a few bits can be used for randomization, which makes it vulnerable to probabilistic attacks [9]. So this a rather simple but very effective method of improving security on a Linux system, but there is no advantage without a disadvantage. In order to work on a full ASLR enabled system every executable binary file and every referenced library has to be compiled as position independent executable (GCC: -fpie and -fpic option) [62, 69, 80]. If ASLR is enabled in the kernel and which enforcement level is currently set can be determined by reading the corresponding /proc (see section 2.5.3) file as we can see in listing 2.2. Listing 2.2: Determine ASLR Status 1 cat /proc/sys/kernel/randomize_va_space Possible values are: 0 ASLR is disabled. The kernel was booted with the norandmaps pa- rameter [67]. 1 ASLR is partially enabled. Randomizes the positions of the stack, virtual dynamic shared object (VDSO) page, and shared memory re- gions. The base address of the data segment is located immediately after the end of the executable code segment [67]. 2 ASLR is fully enabled. Randomize the positions of the stack, VDSO page, shared memory regions, and the data segment. This is the default setting [67].
  24. 24. 2. Linux Kernel 16 Position Independent Executable Applications which are compiled PIE compliant can be loaded at a random address on start-up. Unfortunately usually only a small set of binaries used in the operating system are compiled with the PIE option, because there is a 5-10% performance penalty [69]. Android is the first Linux system which dropped the support for non-PIE binaries in 2015 with version 5 [53]. Traditionally compilers produce position dependent code which refers to actual memory addresses, but PIE executables use the fact, that modern system run processes in their own virtual address space and therefore lets the memory management unit take care of the offsets [80]. No Execute Bit The NX-Bit is also known as executable space protection (ESP) in Linux or data execution prevention (DEP) in Windows [86]. The idea is that memory areas can be marked as non-executable. These memory regions are defined in the ELF header of the loaded executable. If there are any violations the system terminates the process. This is also an effective way to prevent the execution of code which was injected by a buffer overflow [70, 80]. W⊕X This concept is similar to NX (see section 2.2.3) and in order to work also requires NX. The idea is to mark a memory page either as writeable (x)or executable, but never both at once. It works like an XOR (that’s why it’s called W⊕X or W^X) [69, 80].
  25. 25. 2. Linux Kernel 17 2.2.4 Linux Security Modules This project provides a general purpose access control framework in kernel space, where specially designed kernel modules (see section 2.3) are used to enforce security policies [78, 90]. DACs is useful for managing files and resources between users, but don’t protect the system from possible attacks [90]. The framework allows different access control models to be implemented as Linux kernel modules which we will take a look at later. To demonstrate how this all works take a look at figure 2.8. User Level Process System Call Open Lookup inode Error Checks DAC LSM Hook Complete Request inode Examine Context Does request pass policy?Yes or No User Space Kernel Space Figure 2.8: LSM Hook Architecture [90] When user space processes ask for access to a resource it has to be done through the SCI (see section 2.1.1). All requests first pass the kernels exist- ing security logic, which even includes the standard DAC. It is then passed to the linux security modules (LSM) framework which can decide whether to allow or disallow the access through its security policies [90]. Well known implementations for LSMs are Security-Enhanced Linux (SELinux), simpli- fied mandatory access control kernel (SMACK), TOMOYO and AppArmor. Now lets take a closer look at the best-known of these implementations, which is also used in Android.
  26. 26. 2. Linux Kernel 18 SELinux Was originally developed by the National Security Agency (NSA) and start- ed a kernel security design discussion which led to the LSM concept. The framework is implemented as a LSM and strictly separates between the policy enforcement and the decision-making code [78, 90]. The idea is to give a program the least privileges it needs to run, but nothing more, which is in general called mandatory access control (MAC). Another key aspect of SELinux is the role based access control (RBAC). The system defines roles for different permission levels similar to MAC groups, but one role can represent multiple users [49]. SELinux consists of four central components [80]: Object Manager: The object manager decides which actions to allow or disallow based on the security rules obtained by the AVC. Access Vector Cache (AVC): Here all previous decisions are cached to improve the systems performance. Security Server: If the decision isn’t found in the AVC, the security server is invoked to look for a matching decision in the binary policy file (see figure 2.9) [49]. Security Policy: The rules are compiled into the security policy binary file. Object Manager Subject (process) Object (file, dir, etc.) Security Server Security Policy Access Vector Cache (AVC) Linux Kernel Figure 2.9: SELinux Components [31]
  27. 27. 2. Linux Kernel 19 A security context (or label) is a string which consists of four parts delimited with colons. Username: Is similar to a user name or group of the MAC system [31]. Role: Users can be associated with one or more roles in order to adapt the RBAC [31]. Type: This is used to group processes in logical groups [31]. MLS security range: Can be used to classify into multiple levels of access. As we can see in listing 2.3 and 2.4 the security contexts of files or processes can be displayed by adding the -Z option to commands ls or ps. Listing 2.3: Security Context of Files 1 root@hammerhead:/ # ls -Z 2 drwxr-xr-x root root u:object_r:device:s0 dev 3 dr-xr-xr-x root root u:object_r:proc:s0 proc 4 -rw-r--r-- root root u:object_r:rootfs:s0 service_contexts 5 drwxr-xr-x root root u:object_r:storage_file:s0 storage 6 dr-xr-xr-x root root u:object_r:sysfs:s0 sys Listing 2.4: Security Context of Processes 1 root@hammerhead:/ # ps -Z 2 LABEL USER PID PPID NAME 3 u:r:init:s0 root 1 0 /init 4 u:r:kernel:s0 root 2 0 kthreadd 5 u:r:ueventd:s0 root 131 1 /sbin/ueventd 6 u:r:logd:s0 logd 148 1 /system/bin/logd 7 u:r:vold:s0 root 154 1 /system/bin/vold SMACK The SMACK module was developed as an easier to use version of SELinux which also includes a simple form of MAC and security labels. It is currently used in the Tizen ecosystem [65]. TOMOYO This approach also uses a MAC system but is path-based instead of security label based. Also this system learns about valid trees of process invocation and find out which actions are likely to be valid or invalid. By now this system hasn’t been adopted in any larger distribution [65].
  28. 28. 2. Linux Kernel 20 AppArmor This system uses a path based MAC scheme which is designed to be simple to manage, like the TOMOYO system. AppArmor also features a learning mode to automatically create a security profile of an application. This system is shipped with Ubuntu and OpenSUSE. 2.2.5 Kernel Capabilities Another security feature in the Linux kernel are kernel capabilities to offer a more granular set of privileges than root or not root. This POSIX capabilities are implemented as a four sets of bitmaps appended to the task_struct. This feature is not restricted to processes, but it is also used to reduce the risk caused by files setUID flag. So there are basically four sets of bitmap which manage the different capabilities [34]. effective: capabilities currently in use permitted: granted capabilities inheritable: capabilities inherited to child processes bset: capability bounding set Each set consists of a list of bits. Each bit represents a certain system capa- bility. If, for example, bit 26 is set a process can modify the systems clock [34]. 2.3 Loadable Kernel Modules Now that we have a rough overview on the overall kernel design and its subsystems we can start looking into the heart of the thesis - Linux Kernel Modules. As mentioned before, Linux has a monolithic kernel which includes everything needed to run in a single kernel image, but if we need some additional features it’s not practical to reconfigure, build and run a new kernel every time. This is the task of a kernel module, which is a piece of code that can be loaded and unloaded to extend the kernels functionalities during runtime. The module is a piece of compiled C code just like everything else in the kernel, but the implementation has to follow some predefined patterns. Lets take a look at a minimalistic hello world code example in listing 2.5.
  29. 29. 2. Linux Kernel 21 Listing 2.5: Hello World LKM 1 #include <linux/module.h> // Needed for the modules macros. 2 #include <linux/kernel.h> // KERN_INFO is defined here 3 4 static int __init module_start(void) 5 { 6 // Prints to the info stream in the kernel log at /proc/kmsg. 7 printk(KERN_INFO "Hello World!"); 8 return 0; // Indicate successful loading. 9 } 10 11 12 static void __exit module_stop(void) 13 { 14 printk(KERN_INFO "Bye World!"); 15 } 16 17 // Define init and exit function names. 18 module_init(module_start); 19 module_exit(module_stop); 20 21 // Add some infos to be displayed when using lsmod. 22 MODULE_LICENSE("GPL"); 23 MODULE_AUTHOR("Marcel Breitenfellner"); 24 MODULE_DESCRIPTION("BA1 - Hello World"); 2.3.1 Structure A basic LKM only consists of two functions which are called when the module is loaded and unloaded. These two functions are registered as the modules entry and exit point within the kernel through the function calls module_init and module_exit(). Actually these aren’t real function calls, but macros that define the (de-)initialization function for this module [59]. There are also a few useful macros that provide general information about our kernel module. A few self explanatory examples are: • MODULE_DESCRIPTION() • MODULE_AUTHOR() • MODULE_VERSION() • MODULE_LICENSE() If none or a non-GPL compatible license is used the kernel is be marked as tainted until the next reboot, as the kernel modules code is not open source and therefore can not be verified. This state shows up in all error logs and bug reports. In this state the kernel module may have not access to all kernel core functions and data structures [15].
  30. 30. 2. Linux Kernel 22 2.3.2 Building There are two different ways of building our kernel module. We can either directly integrate our module source code into the Linux kernel source tree or let it reside in a dedicated directory outside the source tree [59]. Integrating to the Source Tree If we are for example implementing a driver for a new device and plan to share our code with the community this is our way to go. First we have to locate the right directory for our module source code. Drivers are stored in the drivers/ directory of the source tree, which contains several subdirec- tories for different device classes like drivers/usb or drivers/block. Once the right class is found, we create a new directory for our modules source code. Then we add obj-m += yourmodule/ to the parent directories make- file [15, 59]. The kbuild system invokes the modules makefile when it needs to be built. Dedicated Directory But we can also create our module outside the Linux source tree. Therefore we need to create a new directory and set up our makefile (see listing) 2.6. Listing 2.6: Create Makefile (1) 1 obj-m := module_name.o 2 fishing-objs := module_main.o module_feature.o The source files module_main.c and module_feature.c are compiled into module_name.ko when the command shown in listing 2.7 is executed. Listing 2.7: Create Makefile (2) 1 make -C /path/of/the/kernels/source SUBDIRS=$PWD modules We will also take a more detailed look into building module when discussing the sample implementation of a LKM in section 5.4.1.
  31. 31. 2. Linux Kernel 23 2.3.3 Managing To load and manage the kernel module we have a few different commands on most Linux based systems. insmod This is the simplest way to load a module into a running kernel. What it does is basically just ask the kernel via the init_module system call to load the specified binary executable and linking format (ELF) image into the kernel, link all needed symbols and call its defined init function. Before loading, the kernel checks if the module was built for the current running version of the kernel by comparing the value of the vermagic, which contains the buildstring of the target kernel and some target CPU information. This method doesn’t perform any dependency checks (if the module needs other modules to be loaded beforehand) or advanced error checking [59]. To load a module named module.ko we need to execute the following command shown in listing 2.8 with root permissions. Listing 2.8: Load Kernel Module 1 insmod module.ko rmmod This is the simplest way of removing a kernel module again. Therefore this command only performs a delete_module system call which matches the module by its unique name, calls its exit function and finally unloads the module from the kernel. Using this command to unload the previously loaded kernel module we need to execute the following command shown in listing 2.9 with root permissions. Listing 2.9: Unload Kernel Module 1 rmmod module modprobe This is a more intelligent way to manage kernel modules which is also rec- ommended to use. When loading a module, all modules our module depends on are also be loaded into the kernel. Parameters can also be passed when loading the module. This tool can also unload a module and its dependencies as long as the usage count is zero [59]. To load a module called module.ko we need to execute the following com- mand shown in listing 2.10 with root permissions.
  32. 32. 2. Linux Kernel 24 Listing 2.10: Load Kernel Module 1 modprobe module.ko To unload a module called module.ko we need to execute the following com- mand shown in listing 2.11 with root permissions. Listing 2.11: Unload Kernel Module 1 modprobe -r module.ko modinfo In section 2.3.1 we looked into all the available macros when implementing a kernel module. With this command we can make use of the entered infor- mation to get some basic information about the selected kernel module as we can see in the example output below. To get the detailed information of module.ko just enter the command shown in listing 2.12. Listing 2.12: Get Detail Informations 1 modinfo module.ko If we print the detail information provided by our later implemented rootkit module we would see the output shown in listing 2.13. Listing 2.13: Example Kernel Module Detail Information 1 filename: ~/BA1/PRO/dev/kernel/module_proc/module.ko 2 description: BA1 Android LKM sample implementation. 3 author: Marcel Breitenfellner 4 license: GPL 5 srcversion: BE690CC13DF17BE156EC820 6 depends: 7 vermagic: 3.4.0-gadb2201 SMP preempt mod_unload ARMv7
  33. 33. 2. Linux Kernel 25 lsmod The last important command, when handling kernel modules, is used for displaying which modules are currently loaded into the kernel. To retrieve this list just enter lsmod which prints a list or obtain the information directly from the kernel through the /proc file system (see section 2.5.3) by executing the command cat /proc/modules directly in the shell. The exact output may vary between different Linux distributions, but in listing 2.14 we can see an example output from an Android device. Listing 2.14: Show Loaded Kernel Modules on Nexus 5 1 root@hammerhead:/ # lsmod 2 module 13846 0 - Live 0xbf000000 (O) The output contains the following information: module the name of the module 13846 memory size of the module in bytes 0 how many instances of the module are currently loaded, zero means unloaded - if the module depends on any other module it’s shown here Live the current status of the kernel module, possible values are Live, Loading and Unloading 0xbf000000 the memory offset of the module When executing lsmod the sixth column may only show 0x00000000 as the memory offset the loaded module. This is a security mechanism to hide kernel pointers from user space. If we want to disable this security feature just execute the command shown in listing 2.15 with root permissions. Listing 2.15: Show Loaded Kernel Modules 1 echo 0 > /proc/sys/kernel/kptr_restrict 2.4 Kernel Rootkits A rootkit in general is a piece of malicious software, which hides its pres- ence while having root access to the system. Once infected the rootkit can spy on the machines activities, load additional malicious code and modify everything. The rootkit can live in user space or in kernel space [54, 73]. A user space rootkit modifies binaries like ls or ps to hide its presence from the user. But from the kernel they are pretty easy to detect and even to remove [73].
  34. 34. 2. Linux Kernel 26 We will be looking into kernel space rootkits. Because this type of rootkit lives in kernel space it is pretty hard to detect and even harder to remove successfully. After it has managed to infect the system it usually loads its code into the kernel through a LKM (see section 2.3). Another option to modify the kernel and place a kernel rootkit is to directly modify it by ac- cessing the /dev/mem or the /dev/kmem devices, which grant direct memory access. Once loaded into the kernel there are different ways to modify the system, all of them have the target to hide its presence, load and hide other malicious software. There is no chance of detecting the kernel rootkit from user space, if implemented well [73]. 2.4.1 Kernel Modification Techniques Once the rootkit kernel module was loaded into the kernel, it has several ways of modifying to achieve its goals. Hook System Calls As we discussed in section 2.1.1 the SCT is a list of pointers to functions which are called when a system call is invoked from user space. As the only way of communicating between user space and kernel space is through the SCI, the SCT is a very effective way to go. The only challenge here is to find the address of the SCT as it is only exported as a kernel symbol until kernel version 2.4. Possible ways of finding the right address would be through the interrupt descriptor table (IDT) or the /proc/kallsyms file. The disadvantage of this method is, that a rootkit can be easily detected, because the address of the system call has obviously changed [54, 73]. Lets look at an example code snippet (see listing 2.16) of a kernel module which enables to exchange a pointer from the SCT with a custom function. In line three there is the method header of the sys_chmod function which is hooked and assigned later. When the module is loaded the __init function is called and the SCT is loaded from memory. In this example we already looked up the sys_call_table pointer from the System.map file, which contains the memory allocation of all predefined kernel symbols at compile time. In line 14 the pointer of the sys_chmod function is exchanged with our hooked_syscall_chmod and finally the original value is stored in the default_sys_chmod variable. When the module is unloaded the changes are undone, again with the xch function.
  35. 35. 2. Linux Kernel 27 Listing 2.16: Hook System Calls 1 unsigned long *sys_call_table; 2 3 asmlinkage long (*default_sys_chmod)(const char __user *filename, umode_t mode); 4 5 asmlinkage long hooked_syscall_chmod(char* path, umode_t permission){ 6 // Do something bad. 7 return -1; 8 } 9 10 static int __init module_init(void) 11 { 12 *(long*) &sys_call_table = 0xc0106e64; 13 14 default_sys_chmod = (void*)xchg(&sys_call_table[__NR_chmod], hooked_syscall_chmod); 15 16 return 0; 17 } 18 19 static void __exit module_exit(void) 20 xchg(&sys_call_table[__NR_chmod], default_sys_chmod); 21 } Actually the SCT is stored in a read only memory page since Linux kernel version 2.6 [11]. To address this problem we have to write directly into the CPU control register. Thanks to Jürgen Quade for the code snippet shown in listing 2.17 which demonstrates how to disable the memory write protection during runtime [72]. Listing 2.17: Disable Write Protection [72] 1 void disable_write_protection_cr0(void) 2 { 3 unsigned long value; 4 5 asm volatile("mov %%cr0,%0":"=r" (value)); 6 if(value & 0x00010000) 7 { 8 value &= ~0x00010000; 9 asm volatile("mov %0,%%cr0"::"r" (value)); 10 } 11 }
  36. 36. 2. Linux Kernel 28 Modifying Kernel Objects A different way to hide for example a kernel modules existence without modifying the SCT, is to directly modify internally used objects called kernel objects. These kernel objects have to be modified very carefully, because they are needed for the system to work properly. The difficulty here is to find the right list of kernel objects which holds all the loaded LKMs. Usually doubly linked lists are used here. We have to take care of two different lists where our to be hidden kernel module is listed. The first one is looked up when the ls command in user space is called, so we modify the output of /proc/module. In line two of the following code snippet in listing 2.18 we can see how the kernel object is removed from the list. The second list, which needs to be modified in the kernel. is responsible for the /sys/module and is treated in line three [11]. Listing 2.18: Hide Module 1 int module_init(void) { 2 list_del_init(&__this_module.list); 3 kobject_del(&THIS_MODULE->mkobj.kobj); 4 5 return 0; 6 } The two functions list_del_init and kobject_del basically just remove the kernel object from a passed doubly linked list. In a doubly linked list every node has a prev and a next pointer which point to the previous and the next object in the list. What the delete function does is to let our previous object point to our next object and vice versa. So the module isn’t referenced by any kernel object anymore but is still alive in the memory. When the lists are traversed for showing its contents, the module can’t be found anymore and it looks like the module isn’t loaded. This method can also be rather easily applied to hide processes from the task_struct and is pretty hard to detect, because these kernel objects aren’t directly referenced anymore and have to be located in a different way to detect the modification [73]. Hook Kernel Jump-Tables Another harder to detect approach of hooking functions is through various jump tables in the kernel. The difficulty here is also to find them in the memory and also to write to them [73]. Needless to say that there are already various methods of exploiting [19, 52].
  37. 37. 2. Linux Kernel 29 Patch Kernel Code The chance of a rootkit to be detected when changing a system call pointer in the SCT or any other kernel jump table is high [73]. So this approach doesn’t change the pointer itself, but the code in the genuine system call method. This is achieved by modifying the first bytes of a system call method which inserts a jump to the hooking function [19]. This method of changing a system calls behaviour is rather hard to detect and therefore a good way to go [73]. 2.4.2 Defense Unfortunately there aren’t many defense methods against kernel rootkits. Once an intruder gains root access to the machine he can usually just insert a kernel rootkit without any major problems. But there are two defense options worth mentioning. Disable Kernel Module Support Most kernel level rootkits are LKMs. A very effective way to prevent these rootkits from being loaded into the kernel, is to disable the kernel module support when configuring the kernel. In order to do so we need to add CONFIG_MODULES=n to the .config file before compiling a new kernel. With this kernel configuration we aren’t able to load modules into the kernel, so all needed drivers have to be compiled directly into to kernel and may increase the size and memory usage. For example the Linux kernel which runs on Android devices has disabled module support to prevent this type of malicious software. Disable Direct Memory Access It is also possible to modify the kernel through the /dev/mem and /dev/kmem devices. For example the SuckIT rootkit used this method (see section 4.1) We could disable or remove them, but some rather important software relies on them. For example the X or any rootkit detector software which needs direct memory access requires this interface.
  38. 38. 2. Linux Kernel 30 2.5 Additional Concepts We have now gained a rough overview about what the kernel is and how it works. We will take a sneak peak into another few basic concepts which are relevant for the implementation part later on. 2.5.1 Process A process is an active program which actually executes code. But beside that a process may also need file or network access. Managing this and assigning a virtual memory region to the process is the kernels responsibility. This running program can also have multiple execution threads. In the Linux kernel implementation isn’t a big difference between processes and threads. The kernels scheduling system is responsible to split all the memory and processing time in a fair way to all running processes [59]. Internally the kernel holds a doubly linked list of task_struct structures which contains all necessary information about the process. This structure is rather big, because the kernel needs to store a lot of information about the running process. On 32-bit system it’s about 1.7kB in size. This includes, beside the name and PID, also the assigned virtual memory regions and processor information, permissions, owner, parent process, process signals, opened files and ports and much more. All running processes are aligned in a tree. Every process has a parent and maybe siblings, except PID 1 which is the initial init process which is started after the boot process. To obtain detailed information about all running processes the kernel creates a virtual directory in the /proc filesystem for each process which contains miscellaneous information [59]. 2.5.2 Filesystems The Linux kernel features an innovative approach to provide an open and abstract interface for different files systems. This kernel subsystem is called VFS and offers an interface for file access across all supported file systems. This system defines a set of actions which should be supported by any file system and offers them through the SCI to user space applications. Sup- ported actions are for example open(), read() or write() [15].
  39. 39. 2. Linux Kernel 31 write() sys_write() filesystem write function User Space VFS Filesystem Physical Media Figure 2.10: Function Call Trace for a write() Call [15] As we can see in figure 2.10 a write() call in user space is relayed to the sys_write() function in the kernel to the implementation of the file system. This file system implementation also takes care of the type of the underlying physical media [15]. The VFS knows a few different types of objects: superblock: Represents a specific mounted filesystem [59]. inode: Represents a specific file [59]. dentry: Represents a directory entry [59]. file: Represents an opened file [59]. For our later implementation the file object is of special interest, because it features a set of functions like read() or write() and also readdir() (see listing 2.19). Listing 2.19: Function Prototype 1 |int (*readdir) (struct file *, void *, filldir_t); With the help of this and the passed filldir function it is possible to hide files from the file system, more details on this in section 5. 2.5.3 Types There is a huge number of different file systems. But they can be categorized in different groups. Disk-based filesystems This type of filesystems is used to store and manage data on hard disks and similar systems like flash drives. In table 2.5 we can see a small snipped of available filesystems and operating systems which they originated from [15].
  40. 40. 2. Linux Kernel 32 Name Original Operating System ext2 Linux ext3 Linux ext4 Linux hfs Apple OS X hfs+ Apple OS X sysv UNIX minix MINIX vfat Microsoft Windows ntfs Microsoft Windows Table 2.5: Disk-based Filesystems [15] Network filesystems These type of filesystems allow to access files which are stored on other computers in the network [15]. For example NFS, AFS or CIFS are supported network file system by the VFS. Special filesystems This third category covers filesystems which doesn’t fit in any of the men- tioned categories. The most important one for this thesis is the /proc system which provides an interface to the actual state of some kernel data struc- tures. As mentioned in section 2.5.1 this filesystem contains a list of all running processes with some detail informations such as memory status, ac- quired ports or files. Additionally we can also modify the kernels behavior by writing to special files in the /proc system [15, 59]. For example we could activate IPv4 forwarding by writing to the right file as we can see in listing 2.20. Listing 2.20: Activate Packet Forwarding 1 echo 1 > /proc/sys/net/ipv4/ip_forward 2.5.4 Networking To user space the only possible way to know which ports are currently opened in the transport layer is through the matching subfiles of /proc/net/ (e.g. proc/net/tcp4 for TCP connections over (most likely) IPv4). These files are so called sequence files where one row of a file describes one opened connection. Sequence files have four basic functions [1, 27, 79]:
  41. 41. 2. Linux Kernel 33 • start(): Initialize the file. • stop(): Close the file. • next(): Show the next entry of the file. • show(): Fill the file buffer with data. On the kernel side the tcp4_seq_show is responsible for this buffer filling task. So in order to let opened network connections disappear from user space view, we need to hook this particular function [79]. 2.6 Futex Exploit This vulnerability was discovered by comex (and later verified as CVE-2014- 3153) in May 2014 and effects most Linux kernel versions until 3.14.5. The hacker geohot extended this exploit by escalating the privileges to root. He used this exploit in his Android rooting toolkit TowelRoot, which allows easy one-click-rooting of Android devices until version 4.4.2. He obfuscated his implementation of the exploit, but there are also different implementations of the same scenario [32]. In order to use this exploit to gain root access to a kernel there are typically two seperate bugs involved. In simple words we can say that there is a list in the kernel that hold all tasks, which wait for a priority inheritance futex. As the call is usually blocking anyway, this list element is held in the kernel stack which is dangerous, because if the function exits without removing the element there is a null reference. With a special combination of kernel calls the previously mentioned behaviour can be triggered, which leads to a NULL reference from the previous list element. A few more tricks are used to allocate the memory region of the disposed element again and bring its content under control. Finally the manipulation of this waiting list leads to a situation where the exploit can write anywhere in memory, which means the credentials of a process can be set to 0. This elevates the processes permissions to root [32, 93–95].
  42. 42. Chapter 3 Android Characteristics When the mobile operating system Android was developed the Linux kernel was chosen to be the core of the system due to its proven driver model, good process- and memory management and the variety of existing drivers [21, 61]. Because the operating system targets mobile devices, some changes had to be made to the Linux kernel in order to improve the performance and battery life (see section 3.4) [16]. In addition to that the development team created their own C library and Java runtime to resolve licensing conflicts and optimize the system for the limited resources available on mobile device platforms [61]. In table 3.1 we can get a rough overview about which Linux kernel versions were used in the major Android releases [29]. Android Version Codename Linux Kernel Version 1.0 (no code name) 2.6.25 1.1 (no code name) 2.6.26 1.5 Cupcake 2.6.27 1.6 Donut 2.6.29 2.0 Eclair 2.6.29 2.2 Froyo 2.6.32 2.3 Gingerbread 2.6.35 3.0 Honeycomb 2.6.36 4.0 Ice Cream Sandwich 3.0.1 4.1 Jelly Bean 3.0.31 4.4 KitKat 3.10 5.0 Lollipop 3.16.1 6.0 Marshmallow 3.18.10 Table 3.1: Linux Kernel Versions vs. Android Releases [7, 8, 35–41, 89] 34
  43. 43. 3. Android Characteristics 35 3.1 Android Open Source Project Big parts of the Android system originate from open source projects (like the Linux kernel), which require to stay open source when used in a published project (GPL). The Android operating system consists of two main parts, a platform (KitKat, Lollipop,..) and the modified Linux kernel. Google hosts a set of git repositories (https://source.android.com) which contain the source code of the platform, the kernel, the native development kit (NDK) and the Android SDK. With the published source code OEMs are able to adapt the system to their new devices and the community is able to modify the system to their needs and provide updates for devices long after they reached their official end of life. 3.2 Bionic On most Linux distributions the glibc (GNU C library) is used to provide all library routines. Some developers think that its memory footprint is too big, that’s why Android uses a different implementation - the Bionic [61, 87]. The main focus of this project was to have fast execution paths, avoid edge cases, keep the memory footprint small and remain a simple structure [61]. The library also has to be rather small because it is loaded into memory for each process (~200kB Bionic vs. ~400kB glibc). Bionic has built in support for all Android specific features, but does not fully comply to all POSIX defined features like C++ exceptions or wide chars. All native code, which should run on an Android device, has to be compiled against Bionic and not glibc [21]. 3.3 Virtual Machines User applications in Android aren’t delivered as native binaries. At compile time the Java source files (*.java) are compiled into *.class files and then further translated into *.dex (Dalvik EXecutable) files, which contain the byte code that is needed to execute the program on the device. Originally there was the DVM, which compiled the *.dex files just before they needed to be executed into native machine code (just in time (JIT)), but nowadays the Android Runtime (ART) assumes this part. In order to save memory the *.dex file format uses mechanisms like constant pools, which store all constant values used within a class. This can result in significant memory savings as shown in table 3.2. The garbage collector also has to take care of the shared pools and keep track of all the references to not free any memory too early, this is implemented by using marking bits [30].
  44. 44. 3. Android Characteristics 36 Application Uncompressed JAR [bytes] Compressed JAR [bytes] Uncompressed DEX [bytes] Web Browser 470,312 (100%) 232,065 (49%) 209,248 (44%) Alarm Clock 119,200 (100%) 61,658 (52%) 53,020 (44%) Table 3.2: Constant Pool Memory Savings [30] 3.3.1 Dalvik Virtual Machine Until Android 5.0 the DVM was responsible to run the applications in the *.dex format on the devices. In order to do so the virtual machine uses a JIT compiler, which compiles the needed sources during runtime into native code. This has the advantage of a rather low overall CPU load while de- livering platform independent code because only the currently needed parts are compiled, but result in a constant higher memory usage of ~100kB per process caused by the JIT compiler. Unlike the common Java Virtual Ma- chine (JVM), which uses a stack based architecture, the JIT uses a register based architecture. Nowadays this is obsolete due to the increased computing power of modern smart phones (see 3.3.2) [10, 14, 77]. 3.3.2 Android Runtime In June 2014 with Android 4.4.4 the ART was introduced as a tech-demo, which could be enabled on every device through the developer menu. Only a few months later in November 2014 with Android 5.0 the ART replaced the DVM per default. The most significant internal change is that the JIT mechanism was replaced with an ahead in time (AOT) compiler. This means the complete application is compiled once, usually at install time, into native code. The obvious advantage is that there is no extra CPU load necessary at runtime to execute an application because everything is pre-compiled when installing the application. One downside is that there can’t be any on-the-fly optimizations of the code, because the code was already compiled [60].
  45. 45. 3. Android Characteristics 37 3.4 Unique Android Kernel Features Over time Google engineers constantly improved and modified the Linux kernel, some changes have made it into the mainline of the Linux kernel. So the set of changes between the Linux main source tree and the Android kernel source tree is not extremely large with 249 patches or 25.000 lines of code with kernel version 2.6.23 (2011). Those kernel patches target kernel bugs discovered when developing Android, add new hardware support, im- prove the power management on mobile devices, add some advanced error reporting and improve the overall system security and performance [6, 81]. 3.4.1 Android Shared Memory With ashmem a virtual memory region can be allocated by calling the pro- vided function ashmem_create_region() in order to share it between dif- ferent processes. If a physical piece of memory is needed, e.g. for hardware reasons, pmem (see section 3.4.3) is the way to go. The returned file de- scriptor now needs to be shared with the second process in order to gain access to the just allocated memory region. To do so we can’t simply share the pointer of the memory region because every process has its own virtual address space nor is it possible to create a new memory region with the same name as used at the first allocation. The common way to go is to share the file descriptor through the Binder mechanism to finally allow the second process to gain access this specific virtual memory region [6, 83]. Corresponding files in the kernel source tree: • $KSRC_ROOT/mm/ashmem.c 3.4.2 Binder The Binder is an extension to the existing inter-process communication (IPC) mechanisms in Linux. In the first beta releases of Android the Open- Binder by Be Inc. implementation was used, but in order to resolve licensing conflicts the Binder was completely rewritten by Google. This library pro- vides bindings of functions and data between different processes. There were already several systems in the Linux kernel which target this problem. Some examples are: [2, 46, 77]: • Signal • Pipe • Socket • Semaphore • Message Queue • Shared Memory
  46. 46. 3. Android Characteristics 38 The Binder improves this already existing IPC mechanisms by avoiding copies of the memory which needs to be shared by directly copying the writ- ten memory in the readers ring buffer. It also increases the lifespan of user space objects, which can be shared and passed between different processes, while the Binder library keeps track of all the references to the different ob- jects [82]. The Binder also contains some rather useful features like one- or two-way binding, which allows uni- or bidirectional communication between processes, weak references, which allow to keep track of another objects life- time without increasing their reference count or the link to death facility, which notifies a process when an observed object or process goes away [43]. Corresponding files in the kernel source tree: • $KSRC_ROOT/drivers/staging/android/binder.c • $KSRC_ROOT/drivers/staging/android/binder.h 3.4.3 Process Memory Allocator pmem is another custom driver which allocates memory, but in contrast to ashmem, which allocates virtual memory regions, the pmem allocates phys- ical memory regions usually between 1MB and 16MB in size. The allocated memory can be shared between user- and kernel space drivers. pmem re- quires the allocator to hold the file descriptor, generated while allocating the memory region, on the heap as long as there are references to the mem- ory region [6, 29]. Corresponding files in the kernel source tree: • $KSRC_ROOT/drivers/misc/pmem.c • $KSRC_ROOT/drivers/misc/uid_stat.c • $KSRC_ROOT/include/linux/android_pmem.h 3.4.4 Logger This is the kernel part of the integrated logcat logging mechanism. This system provides four different logging buffers: main: (user-)application log radio: radio and phone related log event: system event log system: system information log
  47. 47. 3. Android Characteristics 39 In figure 3.1 we can see how logcat is connected to the kernel logging sys- tem and user space logger. Usually an application calls the provided library method android.util.log with a priority level (VERB, DEBUG, INFO, WARN or ERROR) which is then passed through the liblog to the kernel implementation of the logger. The different log types are now available as device files at: • /dev/log/main • /dev/log/radio • /dev/log/event • /dev/log/system It’s also possible to read the logs directly through logcat or via adbd over a remote connection (USB, TCP) [2, 6, 29]. Java Program android.util.logNative Program liblog com.android.internal. osAndroidPrintStream logcat adbd main radioevent system /dev/log/ Kernel Space User Space System.out.err Device stdout stdout/stderr Host adb Server adb logcat Eclipse Figure 3.1: Android Logging System Architecture [29] Corresponding files in the kernel source tree: • $KSRC_ROOT/drivers/staging/android/logger.c • $KSRC_ROOT/drivers/staging/android/logger.h
  48. 48. 3. Android Characteristics 40 3.4.5 Wakelocks This added feature is used to prevent the system from entering sleep states to keep the system responsive and the delays low, e.g. when the screen is switched on or the user is currently typing. Wakelocks can be created from user- or kernel space [2, 6]. Corresponding files in the kernel source tree: • $KSRC_ROOT/include/linux/wakelock.h • $KSRC_ROOT/kernel/power/userwakelock.c • $KSRC_ROOT/kernel/power/wakelock.c 3.4.6 Out of Memory Handling Nomen est omen. This kernel module is initialized by the init.rc with default rules for the memory threshold, which define when a process is killed if a memory shortage occurs. In user space processes can refine those rules by adjusting the oom_adj value to extend a processes lifespan if needed. But as soon as these boundaries are about to be exceeded, the module kills processes until enough memory pages are free again. Caches are generally considered to be free until they’re not locked [2, 6]. Corresponding files in the kernel source tree: • $KSRC_ROOT/drivers/misc/lowmemorykiller.c • $KSRC_ROOT/security/lowmem.c 3.4.7 Alarm Timers Alarm Timers are used to tell the kernel from user space when it wants to be woken up again. The kernel takes care about the sleep state in the mean time and schedules a wakelock at the appropriate time. Corresponding files in the kernel source tree: • $KSRC_ROOT/drivers/rtc/alarm.c • $KSRC_ROOT/include/linux/android_alarm.h
  49. 49. 3. Android Characteristics 41 3.4.8 Paranoid Networking On Android every installed application has its dedicated UID and GID for security reasons. There are also groups for different permissions on the sys- tem as we can see in table 3.3. If an application is about to be launched, the zygote process spawns its process and set the correct UID and GID. On the kernel side, the paranoid networking extensions checks the GID of the calling process if it tries to access some restricted functionality (like networking); if the access is granted it proceeds execution [2, 6, 29]. AID / Name GID Capability AID_NET_BT_ADMIN 3001 Create Bluetooth sockets, diagnosis and manage connections AID_NET_BT 3002 Create SCO, RFCOMM or L2CAP Bluetooth sockets AID_INET 3003 Create AF_INET(6) sockets AID_NET_RAW 3004 Create RAW and PACKET sockets AID_NET_ADMIN 3005 CAP_NET_ADMIN capability, di- rect network interface access, manip- ulate routing tables and sockets Table 3.3: Networking Capability Groups [29] Corresponding files in the kernel source tree: • $KSRC_ROOT/include/linux/android_aid.h 3.4.9 Timed Output This modification provides an interface to user space where the general pur- pose input/outputs (GPIOs) can be set and reset after a given timeout which is used to control the vibrator code [2, 6]. Corresponding files in the kernel source tree: • $KSRC_ROOT/drivers/staging/android/timed_gpio.c • $KSRC_ROOT/drivers/staging/android/timed_gpio.h • $KSRC_ROOT/drivers/staging/android/timed_output.c • $KSRC_ROOT/drivers/staging/android/timed_output.h
  50. 50. 3. Android Characteristics 42 3.4.10 RAM Console This allows to store the kernel log (printk) to the RAM, so it can be viewed after the next reboot, when a kernel panic occurred, in /proc/last_kmsg/ [2, 6]. Corresponding files in the kernel source tree: • $KSRC_ROOT/drivers/staging/android/ram_console.c 3.4.11 Other Changes There is also a bunch of other small changes in the kernel [2, 6]: Goldfish: Virtual CPU used in the Android Emulator. YAFFS2: This was an open source flash memory file system which was used by Android until version 2.2 (Froyo). It wasn’t part of the standard Linux kernel, so Google added it to Android. Bluetooth: Fixed some Bluetooth headset bugs and added Bluetooth de- bugging and access control functions. Scheduler: Slight changes were made here to improve the process scheduler and timing. Android Debug Bridge (ADB): Is a protocol which connects to a de- veloper machine through USB or TCP/IP and allows to control the device for development purposes.
  51. 51. Chapter 4 Related Work In this chapter we will discuss other papers and releases that inspired and helped me while working on this topic. There is a lot of information about Linux kernel rootkits, but when it comes to Android specific kernel rootkits there isn’t that much work available, especially because Android restricts loading Linux kernel modules by default. We will take a look at two Linux kernel rootkits as well as two papers targeting Android kernel level rootkits in detail. For each project we will show up how their unique features were implemented. 4.1 SuckIt The SUperuser Control KIT is worth mentioning because it uses a rather different approach of infecting the system and is able to run on kernels even without LKM support or the System.map file. There isn’t much malware active which uses this way of infection so this is remarkable. The original source code was published for the i386 platform in Phrack article in 2001 by sd and devik. The infection is done via the /dev/kmem file, which needs root permissions to be read from and written to, but allows direct access to the systems memory. This rootkit won’t touch the original SCT, but copies it into a different memory location and exchanges a few (25) system calls with modified versions. The key is to change the SCT pointer in the interrupt table of the CPU to the new memory location. If a user space process invokes a system call with the software interrupt 0x80, the compromised pointer to the modified SCT is used to determine which modified system call is executed. The complete process of infecting the system is achieved only by using system calls and direct memory access (DMA), but it does not rely on the internal kernel data structure, which allows the rootkit to stay portable and compatible to different kernel versions [20, 58, 74]. 43
  52. 52. 4. Related Work 44 4.1.1 Features • Password protected reverse shell initiated by a spoofed packet to by- pass most firewalls. • The server side (rootkit itself) can run only using system calls, without the need of glibc or anything else. • No changes in the file system. • Full remote environment tty support, including windows size. • Hide processes. • Hide files. • Hide open ports. 4.1.2 Difficulties The SuckIt rootkit was pretty much the first of its kind and of course there were and still are difficulties in developing rootkits which are independent from LKM support. We will now take a look at the key features that make this type of rootkit possible. Accessing the System Call Table The first difference to the common approach is the memory access. In a typi- cal kernel module implementation it is possible to access the kernel memory region directly, but that’s not possible from user space. As described in section 2.4 the /dev/kmem device enables, given sufficient permissions, to directly read from and write to the system memory, including the kernel memory space [20]. Some Linux systems provide a System.map file, which contains a list of all generated kernel symbols at compile time with the corresponding memory addresses, which could be directly accessed via the kmem file, but not every system provides this file. The same information could be obtained through the /proc/ksyms file, but the returned memory addresses are usually hidden for security reasons [23]. So we have access to the whole memory but no idea how to locate the needed data segments in the memory. What we know is that all system calls are stored in a well documented and downward compatible list of pointers somewhere in memory, which is called when a 0x80 software interrupt occurs. So the SuckIt rootkit uses the IDT, which is held in a CPU register (IDTR) to obtain the memory address of the SCT as we can see in the short example in listing 4.1, the full version can be viewed in the original article [20, 23].
  53. 53. 4. Related Work 45 Listing 4.1: Obtain the SCT through IDTR [20] 1 // 1.) Read the IDTR. 2 asm("sidt %0" : "=m" (idtr)); 3 printf("idtr base at 0x%Xn", (int)idtr.base); 4 5 // 2.) Get IDT entry for interrupt 0x80 (system call). 6 readkmem(&idt, idtr.base + 8 * 0x80, sizeof(idt)); 7 sys_call_off = (idt.off2 << 16) | idt.off1; 8 9 // 3.) Get SCT address from indirect call. 10 readkmem(sc_asm, sys_call_off, CALLOFF); 11 p = (char*)memmem (sc_asm,CALLOFF, "xffx14x85", 3); 12 sct = *(unsigned*)(p + 3); Allocate Memory Now the rootkit is able to directly modify the SCT in memory, but we have no address to be pointed at yet. We can’t create a function in user space to replace a system call, because kernel functions need to be located in kernel memory space (usually above 0xc0000000). Typically a new piece of memory in the kernel is allocated by calling the provided library function kmalloc(sizeof(*foobar), GFP_KERNEL), but we don’t know the address of kmalloc or GFP_KERNEL nor do we have access to the required kernel symbols. In addition to that we wouldn’t even be able to call the function from user space [20]. The authors of this malware came up with a dirty but efficient way to solve this problem. Their rootkit goes through the text section of kernel, gathers all push calls, sorts them by occurrence. Now they have a big chance that the kmalloc function is on the top because it’s heavily used in the kernel. If we have managed to obtain the correct address, we still need to know the value of GFP_KERNEL to allocate a new piece of memory. Gladly the values usually don’t differ much in different kernel versions as we can see in table 4.1 [20]. Kernel Version GFP_VALUE 1.0.x - 2.4.5 0x0003 2.4.5 - 2.4.x 0x01f0 Table 4.1: Linux Kernel Versions versus GFP_VALUE [20]
  54. 54. 4. Related Work 46 Overwrite Finally SuckIt needs to make use of the newly gained ability to allocate memory in kernel space and replace system calls. As mentioned before, direct calls of kmalloc are not possible from user space. To avoid a direct call, the rootkit looks up the address of some random (hardly used) system call. It then builds a function which just calls kmalloc and returns the pointer to the newly allocated memory. The chosen system call content is subsequently overwritten with the generated function code and is executed through a 0x80 software interrupt. The original system call is then restored [23]. We now have the ability to find and access the SCT, allocate new memory in kernel space while calling from user space. That’s all we need to set up and exchange system calls [20, 23]. Mission accomplished. 4.2 Suterusu Suterusu is an open source LKM rootkit developed by Michael Coppola starting in 2013 as a hobby project. It’s not intended for practical use but to show how modern rootkits work. The compiled source code runs on ARM and x86_64 architectures and is intended to work from kernel version 2.6 until 3.x. Our implementation of an Android kernel rootkit is rather similar to this one because it already supports the ARM architecture, which is not common for low level malware [25]. 4.2.1 Features • Binary to control rootkit from user space. • Supports the x86_64 and ARM architecture. • Hide processes. • Hide files. • Hide open ports. • Drop to root shell. • Keylogger. • Wait for magic packet, download and execute file from remote server. • Control PROMISC network flag. • Dis-/Enable module loading. • Hides itself on the system.
  55. 55. 4. Related Work 47 4.2.2 Difficulties One difficulty in the development of Suterusu was that it uses a, by the time of development, not widely used mechanism to hijack function calls. Also the support for different kernel versions and architectures is remarkable [25]. Function Swapping The traditional way to redirect a function (e.g. a system call) is to replace its pointer. This is well known and easily detectable, so Suterusu uses a different method of function hooking. If a function needs to be replaced its prologue is overwritten with different data to redirect every function call from the original function to a hook. This works slightly different on x86_64 and ARM. In our example we will take a short look at the x86_64 implementation. Listing 4.2: Suterusu Function Hooking [25] 1 // Init new function prologue. 2 // push $addr; ret 3 memcpy(n_code, "x68x00x00x00x00xc3", HIJACK_SIZE); 4 *(unsigned long *)&n_code[1] = (unsigned long)newFunction; 5 6 // Backup old function prologue. 7 memcpy(o_code, targetFunction, HIJACK_SIZE); 8 9 // Disable write protection. 10 o_cr0 = disable_wp(); 11 12 // Exchange prologue. 13 memcpy(targetFunction, n_code, HIJACK_SIZE); 14 15 // Enable write protection. 16 restore_wp(o_cr0); As we can see in the example code in listing 4.2 first of all a shellcode buffer with the length HIJACK_SIZE, which holds the new hook, is initialized and loaded with the new function address. The original function code is now backed up and stored for the restore mechanism. On the x86_64 platform we also need to take care of the memory write protection mechanism (see below). Finally the new function prologue code is written to the hook and the write protection is restored.
  56. 56. 4. Related Work 48 Memory Write Protection This concept protects kernel text pages from being overwritten at runtime on x86_64, but on ARM no such mechanism is present. This is achieved by setting the bit number 16 on the control register (CR0) to zero. The example codes at listing 4.3 and 4.4 demonstrate how the write protection is dis- and enabled. This code is also designed to prevent race conditions by unlucky scheduling [11–13, 25, 48, 75]. Listing 4.3: Disable Write Protection on x84_x64 [25] 1 inline unsigned long disable_wp(void) 2 { 3 unsigned long cr0; 4 5 preempt_disable(); 6 barrier(); 7 8 cr0 = read_cr0(); 9 write_cr0(cr0 & ~X86_CR0_WP); 10 return cr0; 11 } Listing 4.4: Enable Write Protection x84_x64 [25] 1 inline void restore_wp(unsigned long cr0) 2 { 3 write_cr0(cr0); 4 5 barrier(); 6 preempt_enable_no_resched(); 7 } 4.3 Android Rootkit This nameless ARM rootkit was designed and implemented for a paper pub- lished in the Phrack magazine by Dong-Hoon You. This paper covers two traditional and two novel SCT hooking methods, based on the interrupt ser- vice routine (ISR) handler in the exception vector table (EVT). The paper itself only covers the methods used to find and modify the SCT, but there aren’t any further rootkit features implemented [92]. 4.3.1 Obtaining System Call Table Since the system_call_table symbol is not exported anymore (see section 2.1.1), a different way of obtaining its address must be found. In this paper two interesting ways are described.
  57. 57. 4. Related Work 49 Exception Vector Table If an interrupt or exception occurs on the ARM platform there is the EVT which holds an exception handler address for the occurred exception. The Android kernel uses a high vector table with addresses above 0xffff0000. With an offset of 0x08 there is a four byte instruction to call the software interrupt handler, which is stored with an offset of 0x420. As we can see in listing 4.5 the routine first finds the address of the vector_swi (software interrupt process execution handler) function of the high EVT in lines two to eight and then searches for the code that handles the SCT. With some assembly tricks, which can be looked up in detail in the paper, he figured out that we are looking for the opcode 0xe28f8*** to calculate the offset of the SCT as we can see in lines 11 and 12 in the listing 4.5 [92]. Listing 4.5: Obtain the SCT [92] 1 void get_sys_call_table(){ 2 void *swi_addr = (long *)0xffff0008; 3 unsigned long offset = 0; 4 unsigned long *vector_swi = 0; 5 unsigned long sys_call_table = 0; 6 7 offset = ((*(long *)swi_addr) & 0xfff) + 8; 8 vector_swi = *(unsigned long *)(swi_addr + offset); 9 10 while(vector_swi++){ 11 if(((*(unsigned long *)vector_swi) & 0xfffff000) == 0 xe28f8000){ 12 offset = ((*(unsigned long *)vector_swi) & 0xfff) + 8; 13 sys_call_table = (void *)vector_swi + offset; 14 break; 15 } 16 } 17 } sys_close The code above is rather complicated, that’s why he figured out a slightly easier way of getting the desired address. The sys_close symbol has a 0x06 offset to the sys_call_table symbol in memory. Apart from the modified while loop with the adapted code to search for the sys_close address, the code example in listing 4.6 is identical to the example above in listing 4.5. This method has the obvious disadvantage that the address for sys_close must be determined in advance [92].
  58. 58. 4. Related Work 50 Listing 4.6: Obtain the SCT [92] 1 void get_sys_call_table(){ 2 void *swi_addr = (long *)0xffff0008; 3 unsigned long offset = 0; 4 unsigned long *vector_swi = 0; 5 unsigned long sys_call_table = 0; 6 7 offset = ((*(long *)swi_addr) & 0xfff) + 8; 8 vector_swi = *(unsigned long *)(swi_addr + offset); 9 10 while(vector_swi++){ 11 if(*(unsigned long *)vector_swi == &sys_close){ 12 sys_call_table = (void *)vector_swi - (6 * 4); 13 break; 14 } 15 } 16 } 4.3.2 Calculating System Call Table Size In order to make use of the obtained SCT location we also need to know how big it is because usually we need a copy for further modifications to gain a better protection against rootkit detection. The size of the SCT varies between different kernel versions and platforms, so we need a way to identify its size at runtime. To achieve this the rootkit searches for a specific opcode (0x357****) pattern in the vector_swi that controls the size of the SCT. From this value the rootkit is able to calculate the total number of system calls and therefore the size of the SCT as we can see in listing 4.7. Listing 4.7: Calculate SCT Size [92] 1 while(vector_swi++){ 2 if(((*(unsigned long *)vector_swi) & 0xffff0000) == 0xe3570000 ){ 3 i = 0x10 - (((*(unsigned long *)vector_swi) & 0xff00) >> 8) 4 size = ((*(unsigned long *)vector_swi) & 0xff) << (2 * i); 5 break; 6 } 7 }
  59. 59. 4. Related Work 51 4.4 Android Modem Interception At DEFCON 18 in 2010 another interesting paper about Android kernel rootkits has been published. Beside the typical rootkit features like obtaining the SCT or hiding itself the system calls are used as a kind of a debugging system to see where we can get from there. The system calls sys_read, sys_write, sys_close and sys_open are intercepted and printed to the kernel debug log to obtain this logging be- haviour. He then scans the logs and finds out that the kernel communicates with the integrated mobile modem in plaintext through system calls as we can see below in the kernel log dump in listing 4.8 and 4.8. Listing 4.8: Incoming Call [68] 1 <6>sys_read: AT+CLCCc:13371585907841334111",129 Listing 4.9: Outgoing Call [68] 1 <4>sys_write: ATD+442073734841; This opens the possibility to intercept the modem communication from a kernel rootkit by modifying the AT buffer. Mobile devices are almost always on and connected to the internet which could be used to establish a reverse shell with parameters received via text message without even passing them to the operating system. Another scenario is to call premium rate numbers from the kernel, when the user is inactive on the phone, if we were about to do something evil [68].
  60. 60. Chapter 5 Implementation We now have a rough overview of which subsystems the Linux kernel consists of and how kernel modules work. We also took a look at the Android specific kernel features and also discussed how other rootkits manage to work as they are intended to. We will now work out step by step how to build and run a kernel with module support for a given device, how the implementation works and how we could infect a device by using a root exploit. At the end of this chapter we will discuss miscellaneous problems occurred while working on this project. The whole process from setting up the system, obtaining the kernel sources, building and executing the modified kernel and also the build and execution of the exploit and kernel rootkit are explained in depth on the provided DVD. 5.1 Concept The goal of this project is to design and implement a system that allows to inject a Linux kernel rootkit to a stock Google Nexus 5 device. This kernel module needs to offer the ability to hide PIDs, files and ports from user space. A nice to have is a feature to gain root permissions by just asking the rootkit for it. In this chapter we will discuss the architecture of this project. We will take a closer look at both the over-all structure of the whole setup and the core of the project - the kernel rootkit. 5.1.1 System Architecture On the hardware side there are two devices involved. First we have the Android victim device, which is in our case a Nexus 5. On the other hand we have a server, which will provide the relevant data to modify the kernel. They both communicate over a wireless network connection as we can see in figure 5.1. 52
  61. 61. 5. Implementation 53 Network Netcat Server Nexus 5 Load boot.img Figure 5.1: System Architecture The victim device is infected using a simple Android application package (APK) which has to be installed beforehand. First of all the user needs to run the provided APK as we can see in figure 5.2. The APK itself contains a root exploit for the running Linux kernel version, some binary tools and the kernel rootkit. To stay portable the modified kernel isn’t statically included, but fetched from the remote server during runtime. But more details on this later in section 5.1.4. Run APK Gain Root -> Exploit Unpack Helper Binaries Load boot.img from Server Reboot Prepare Startup Scripts Place RootkitFlash boot.img Kernel Module is Loaded Reverse Shell is Established Wait for Commands Figure 5.2: General Flow Chart After the environment is set up, the application will search for a server to download a modified boot.img from. When it is successfully retrieved it will then be written to the boot partition of the device to make the changes
  62. 62. 5. Implementation 54 persistent. Then a few initialization scripts will be placed to load the kernel module on boot and establish a reverse shell. At the next boot the rootkit will be loaded into the kernel and a re- verse root shell will be established to the server. From now on the device is persistently infected. The rootkit has now the chance to hide itself and the generated files from the users view before it starts waiting for instructions to execute. Without root permissions the user shouldn’t be able to spot the difference to a clean system. 5.1.2 Kernel Rootkit The kernel module is the core part of the whole project. We will use a similar technique as Suterusu (see section 4.2) to hijack the system. But first of all we need to develop a general idea of what to do. When a kernel module is loaded, a special function is called to set things up. As we can see in figure 5.3 we first need to prepare some hooks in the kernel to provide the functionality of hiding PIDs, files and ports. When the initialization is done, a communication interface needs to be created to send further instructions to the rootkit. Once the setup process is done the module will wait for further instructions sent through the communication interface or being unloaded. Create Communication Interface Load Prepare Hooks Wait for Commands Figure 5.3: Load Kernel Rootkit Flow Chart It is also possible to safely unload the module. Again a specially prepared function is executed which takes care of this scenario. As we can see in figure 5.4 the module will first find and remove active hooks. All hidden resources will be unhidden before removing the hooks and restore the original state of all kernel data structures. When this is done the communication interface to user space is removed and the module is safely exited. Remove Communication Interface Unload Remove Active Hooks Exit Figure 5.4: Unload Kernel Rootkit Flow Chart
  • nimaarek

    Jun. 2, 2021
  • zahernourredine

    Dec. 11, 2018

Views

Total views

283

On Slideshare

0

From embeds

0

Number of embeds

8

Actions

Downloads

13

Shares

0

Comments

0

Likes

2

×