Strengthening Diversification Defenses by Means of a Non-Readable Code
Total Page:16
File Type:pdf, Size:1020Kb
1 Strengthening diversification defenses by means of a non-readable code segment Sebastian Österlund Department of Computer Science Vrije Universiteit, Amsterdam, Netherlands Supervised By: H. Bos & C. Giuffrida Abstract—In this paper we present a new defense against Just- hardware segmentation, this approach should theoretically have In-Time return-oriented-programming attacks. By making pro- no run-time performance overhead for Intel x86 architecture, gram code non-readable, the assembly of Just-In-Time gadgets by once it has been set up. Both position dependent executables scanning the memory is effectively blocked. Using segmentation and position independent executables are covered. Furthermore on Intel x86 hardware, the implementation of execute-only code an approach to implement a similar defense on x86_64 using can be achieved. We discuss two different ways of implementing the new MPX [7] instructions is presented. The defense such a defense for 32-bit Intel architecture: one for position dependent executables, and one for position independent executa- mechanism we present in this paper is of interest, mainly, bles. The first implementation works by splitting the address- for use in security of network connected applications (such space into two mirrored segments. The second implementation as servers or web-browsers), since these applications are often creates an execute-only memory-section at the top of the address- the main targets of remote code execution exploits. space, making it possible to still use the whole address-space. By relying on hardware segmentation the run-time performance II. RETURN-ORIENTED-PROGRAMMING ATTACKS overhead of these defenses is minimal. Remote code execution by means of buffer overflows has Keywords—ROP, segmentation, XnR, buffer overflow, memory been a problem for a long time. In this section we give an disclosure. overview of such remote code execution exploits. By overwriting a function return address, code at an arbitrary I. INTRODUCTION location can be executed, thereby making remote code exe- cution possible. The following code demonstrates a common In recent years traditional remote code execution exploits, exploitable bug in programs: making use of buffer-overflows, have become obsolete due to the introduction of data execution prevention (DEP) tech- void vulnerable_function( char ∗ user_input) niques. Techniques such as Intel NX [1] enforce a policy { of making writable data non-executable. On the other hand, char buffer[12]; attacks such as return-to-libc and other Return-Oriented- strcpy(buffer , user_input); Programming attacks overcome such techniques by executing } (snippets of) existing code which necessarily has to be exe- cutable [2], creating a so-called gadget chain. An overview of void hacked ( ) how these attacks work will be given in Sec. II. { A common defense against ROP-exploits is Address-space p r i n t f ( " I have been hacked!!!!"); layout randomization (ASLR). ASLR mitigates many of these } attacks by randomizing the layout of a program loaded in memory. However, there are several known weaknesses in the i n t main ( i n t argc , char ∗ argv [ ] ) currently used ASLR implementations [3]. { For example a leaked pointer disclosure may give away vulnerable_function(argv[1]); the base-address of executable code (e.g. the base address of return 0 ; libc), thus making it possible to simply add an offset in the } pre-assembled gadget chain. Fine-grained ASLR makes this In the example above, the length of the string user_input is harder [4][5], since the code is split up into smaller blocks unknown. Since strcpy copies characters until it reaches a end- and shuffled around. It has however been shown in [6] that, of-string character (i.e. NULL) 1, the program may overwrite thanks to memory disclosure vulnerabilities, these gadgets can memory beyond the buffer. be assembled for a specific target by scanning the memory Using this buffer overflow, an attacker may overwrite the for code snippets. By creating such a just-in-time assembled return address of the function vulnerable_function. By pointing exploit, the defense provided by ASLR is circumvented. The contribution made by this paper is a segmentation-based 1Some compilers actually do object-size checking on strcpy, preventing defense against memory disclosure attacks. By relying on some common buffer-overflows. 2 ... point in a procedure) in the return address. Usually this would not be critical, since the existing code in a program is limited attacker inserted value return address in scope. However, an attacker may also choose to execute code from a library, giving the attacker much more available vulnerable_function() Local variables executable code to work with. It is possible to chain together library functions by putting char *buffer the address of the next function in the return address of the called function. By assembling such a chain, a so-called gadget main() chain is created. Furthermore, it has been shown in [2] that the GNU C library is Turing complete, thus any arbitrary program ... can be constructed by chaining together functions from libc. Fig. 1: Example of buffer overflow attack. strcpy overwrites III. NON-READABLE CODE the return address by an address entered by the attacker, When gadget chains are assembled, the memory is scanned executing code at a location chosen by the attacker. for pieces of executable code that can be "glued" together. For example an attacker may scan the memory for a POP- instruction followed by a return. By scanning the memory for such gadgets, a very targeted attack can be assembled this address to another function the execution of the program Just-in-time, without knowing the exact location of executable may be altered (See Fig. 2). Furthermore, an attacker may put code. If it, however, was prohibited to scan the memory, these arbitrary code into memory and point the return address to the attacks would be rendered useless. By enforcing a policy entered code. of making executable code non-readable (the abbreviation Luckily, data execution prevention techniques such as W ⊕ XnR will henceforth be used for this primitive) it becomes X2, making executable data non-writable, prohibit the exe- impossible to scan the memory for such gadgets. XnR together cution of malicious code entered in this manner. When the with ASLR, would make it very hard (or rather impossible) processor tries to execute data on a page that is marked NX to determine where certain snippets of code are located, thus (non-executable) the processor throws an exception, terminat- creating a strong defense against JIT-ROP attacks. ing the offending program. Sadly, implementing such a policy is not achieved by just marking executable pages non-readable. For Intel x86, memory ... that is executable implies that it is readable, thus XnR has to be implemented in another way. The obvious approach would arg to system() Fill in: /bin/sh be to emulate execute-only functionality on a per-page basis in software. Such an implementation is presented in [8] and return address of system() will be discussed further in Sec. IX. In short: the problem with this approach is that software emulation adds some overhead, address of libc system() function return address which is not necessarily required. In the next section a novel approach of achieving non-readable text without software- vulnerable_function() Local variables based page access emulation is presented. char *buffer IV. SEGMENTATION-BASED ACCESS POLICY main() A. Segmentation on Intel x86 For the 8086 processor, Intel introduced segmentation. This ... memory-translation feature allows memory to be split into separate disjoint logical entities called segments. Segmentation Fig. 2: Example return-to-libc attack. If the process runs as allows programmers to create programs consisting of multiple root, an attacker may gain access to a shell with root privileges. segments located at different addresses in physical memory, When the command system("/bin/sh") is executed the attacker without having to worry about at which address in physical has gained root access to the system. memory certain parts of a program is loaded. The historical idea behind segmentation was to allow programmers to split the program into logically structured separated parts. There Despite this protection it is still possible for an attacker to are, thus, two kinds of segment types: data and code. Data execute existing code. Since program code necessarily needs segments can only be used for reading/ writing memory, while to be executable (why else call it a program...), it is possible code segments allow the memory-contents to be executed. For to execute existing functions by placing its address (or any a more comprehensive description of segmentation see chapter 3.7 of [9]. 2Introduced by OpenBSD in 2003 (http://marc.info/?l=openbsd- When a valid segment descriptor has been loaded into a miscm=105056000801065) segment register, all access to memory is relative to the base 3 C. Loading text in a separate segment Fig. 3: The layout of a standard ELF program in Linux with a flat address-space. The Code Segment (CS) and Data The main idea behind protection we propose is quite novel: Segment(DS) both cover the same area, namely 0x00000000 load text into an address outside the limits of the user data - 0xffffffff segment, but inside a new code segment. When a read is performed on an address outside the user data segment, the ... processor will throw a protection fault, while allowing an stack instruction fetch from that same address. A problem with splitting the memory in two segments is that compilers and linkers for Linux assume a flat memory model. When the Kernel loads the code, it assumes one contiguous mmap region piece of memory, where the access policy is determined by the page handler. Thus care should be given to load the correct Code Segment (CS) Data Segment (DS) section at the right memory address.