Return Oriented Programming (ROP)
Document Details

Uploaded by SelfSufficientDjinn
Tags
Summary
The document explains Return Oriented Programming (ROP), a technique used to exploit software vulnerabilities by stringing together existing code snippets (gadgets) to execute arbitrary code. It also discusses Control Flow Integrity (CFI), a security mechanism designed to detect and prevent control-flow hijacking attacks such as ROP. Furthermore, it covers secure coding practices in C, and how to avoid implementation errors that could result in violations of memory safety.
Full Transcript
Return oriented programming (ROP) Cat and mouse Defense: Make stack/heap nonexecutable to prevent injection of code Attack response: Jump/return to libc Defense: Hide the address of desired libc code or return address using ASLR Att...
Return oriented programming (ROP) Cat and mouse Defense: Make stack/heap nonexecutable to prevent injection of code Attack response: Jump/return to libc Defense: Hide the address of desired libc code or return address using ASLR Attack response: Brute force search (for 32-bit systems) or information leak (format string vulnerability) Defense: Avoid using libc code entirely and use code in the program text instead Attack response: Construct needed functionality using return oriented programming (ROP) Return-oriented Programming Introduced by Hovav Shacham in 2007 The Geometry of Innocent Flesh on the Bone: Return- into-libc without Function Calls (on the x86), CCS’07 Idea: rather than use a single (libc) function to run your shellcode, string together pieces of existing code, called gadgets, to do it instead Challenges Find the gadgets you need String them together Approach Gadgets are instruction groups that end with ret Stack serves as the code %esp = program counter Gadgets invoked via ret instruction Gadgets get their arguments via pop, etc. - Also on the stack Simple example %eip (ret) 0x17f: pop %edx equivalent to ret mov %edx, 5 Gadget “program counter” %esp Text … 0x17f 5 next gadget %edx 5 “Instructions” 0x00 0xffffffff Code sequence %eip 0x17f: mov %eax, [%esp] mov %ebx, [%esp+8] %eax 5 mov [%ebx], %eax %ebx 0x404 %esp Text 5 … 5 … 0x404 … … 0x00 0x404 0xffffffff Equivalent ROP sequence %eip 0x17f: pop %eax ret %eax 5 … 0x20d: pop %ebx ret %ebx 0x404 … 0x21a: mov [%ebx], %eax %esp Text 5 … 5 0x20d 0x404 0x21a … 0x00 0x404 0xffffffff Image by Dino Dai Zovi Whence the gadgets? How can we find gadgets to construct an exploit? Automate a search of the target binary for gadgets (look for ret instructions, work backwards) - Cf. https://github.com/0vercl0k/rp Are there sufficient gadgets to do anything interesting? Yes: Shacham found that for significant codebases (e.g., libc), gadgets are Turing complete - Especially true on x86’s dense instruction set Schwartz et al (USENIX Security ’11) have automated gadget shellcode creation, though not needing/ requiring Turing completeness Blind ROP Defense: Randomizing the location of the code (by compiling for position independence) on a 64-bit machine makes attacks very difficult Recent, published attacks are often for 32-bit versions of executables Attack response: Blind ROP If server restarts on a crash, but does not re-randomize: 1. Read the stack to leak canaries and a return address 2. Find gadgets (at run-time) to effect call to write 3. Dump binary to find gadgets for shellcode http://www.scs.stanford.edu/brop/ Defeat! The blind ROP team was able to completely automatically, only through remote interactions, develop a remote code exploit for nginx, a popular web server The exploit was carried out on a 64-bit executable with full stack canaries and randomization Conclusion: give an inch, and they take a mile? Put another way: Memory safety is really useful! Control Flow Integrity Behavior-based detection Stack canaries, non-executable data, and ASLR aim to complicate various steps in a standard attack But they still may not stop it Idea: observe the program’s behavior — is it doing what we expect it to? If not, might be compromised Challenges Define “expected behavior” Detect deviations from expectation efficiently Avoid compromise of the detector Control-flow Integrity (CFI) Define “expected behavior”: Control flow graph (CFG) Detect deviations from expectation efficiently In-line reference monitor (IRM) Avoid compromise of the detector Sufficient randomness, immutability Efficient? Classic CFI (2005) imposes 16% overhead on average, 45% in the worst case Works on arbitrary executables Not modular (no dynamically linked libraries) Modular CFI (2014) imposes 5% overhead on average, 12% in the worst case C only (part of LLVM) Modular, with separate compilation http://www.cse.lehigh.edu/~gtan/projects/upro/ Secure? MCFI can eliminate 95.75% of ROP gadgets on x86-64 versions of SPEC2006 benchmark suite By ruling their use non-compliant with the CFG Average Indirect-target Reduction (AIR) > 99% AIR is, in essence, the percentage of possible targets of indirect jumps that CFI rules out - For CFI: nearly all of them Call Graph bool lt(int x, int y) { sort2(int a[], int b[], int len) return xy; } } sort2 lt sort gt Which functions call other functions Control Flow Graph bool lt(int x, int y) { sort2(int a[], int b[], int len) return xy; } } sort2 lt sort gt Break into basic blocks Distinguish calls from returns CFI: Compliance with CFG Compute the call/return CFG in advance During compilation, or from the binary Monitor the control flow of the program and ensure that it only follows paths allowed by the CFG Observation: Direct calls need not be monitored Assuming the code is immutable, the target address cannot be changed Therefore: monitor only indirect calls jmp, call, ret with non-constant targets Control Flow Graph bool lt(int x, int y) { sort2(int a[], int b[], int len) return xy; } } sort2 lt sort gt Direct calls (always the same target) Control Flow Graph bool lt(int x, int y) { sort2(int a[], int b[], int len) return xy; } } sort2 lt sort gt Indirect transfer (call via register, or ret) In-line Monitor Implement the monitor in-line, as a program transformation Insert a label just before the target address of an indirect transfer Insert code to check the label of the target at each indirect transfer Abort if the label does not match The labels are determined by the CFG Simplest labeling label lt L sort2 sort label L label L label L label gt L Use the same label at all targets Simplest labeling label lt L sort2 sort label L label L label L label gt L ok… system Use the same label at all targets Blocks return to the start of direct-only call targets but not incorrect ones Detailed labeling label lt M sort2 sort label L label N label L label gt M ok… Constraints: return sites from calls to sort must share a label (L) call targets gt and lt must share a label (M) remaining label unconstrained (N) Still permits call from site A to return to site B Classic CFI instrumentation Check target label Check target label Can we defeat CFI? Inject code that has a legal label Won’t work because we assume non-executable data Modify code labels to allow the desired control flow Won’t work because the code is immutable Modify stack during a check, to make it seem to succeed Won’t work because adversary cannot change registers into which we load relevant data - No time-of-check, time-of-use bug (TOCTOU) CFI Assurances CFI defeats control flow-modifying attacks Remote code injection, ROP/return-to-libc, etc. But not manipulation of control-flow that is allowed by the labels/graph Called mimicry attacks The simple, single-label CFG is susceptible to these Nor data leaks or corruptions void func(char *arg1) { Heartbleed would not be prevented int authenticated = 0; char buffer; Nor the authenticated overflow strcpy(buffer, str); - Control modification is allowed by graph if(authenticated) { … } Secure Coding Secure coding in C Since the language provides few guarantees, developers must use discipline Good reference guide: CERT C Coding Standard https://www.securecoding.cert.org/confluence/display/ seccode/CERT+C+Coding+Standard - Similar guides for other languages (e.g., Java) See also: David Wheeler: http://www.dwheeler.com/secure- programs/Secure-Programs-HOWTO/internals.html And Matt Bishop: http://nob.cs.ucdavis.edu/bishop/ secprog/robust.html Combine with advanced code review and testing — will consider in depth later in the course Design vs. Implementation In general, we strive to follow principles and rules A principle is a design goal with many possible manifestations. A rule is a specific practice that is consonant with sound design principles. The difference between these can sometimes be fuzzy Here we look at rules for good C coding In particular, to avoid implementation errors that could result in violations of memory safety In a future course module we consider principles and rules more broadly Rule: Enforce input compliance int main() { char buf, *p; Recall earlier int i, len; example while (1) { p = fgets(buf,sizeof(buf),stdin); if (p == NULL) return 0; len = atoi(p); p = fgets(buf,sizeof(buf),stdin); } Read integer if (p == NULL) return 0; len for = MIN(len,strlen(buf)); (i=0; i 9) possible return ‘?’; overflow } Unfounded trust in received input is a recurring source of vulnerabilities We will see many more examples in the course General Principle: Robust coding Like defensive driving Avoid depending on anyone else around you If someone does something unexpected, you won’t crash (or worse) It’s about minimizing trust Each module pessimistically checks its assumed preconditions (on outside callers) Even if you “know” clients will not send a NULL pointer … Better to throw an exception (or even exit) than run malicious code http://nob.cs.ucdavis.edu/bishop/secprog/robust.html Rule: Use safe string functions Traditional string library routines assume target buffers have sufficient length char str; char buf = “fine”; strcpy(str,”hello”); // overflows str strcat(buf,”day to you”); // overflows buf Safe versions check the destination length char str; char buf = “fine”; strlcpy(str,”hello”,sizeof(str)); //fails strlcat(buf,”day to you”,sizeof(buf));//fails Replacements … for string-oriented functions strcat ⟹ strlcat strcpy ⟹ strlcpy strncat ⟹ strlcat strncpy ⟹ strlcpy sprintf ⟹ snprintf vsprintf ⟹ vsnprintf gets ⟹ fgets Microsoft versions different strcpy_s, strcat_s, … Rule: Don’t forget NUL terminator Strings require one additional character to store the NUL terminator. Forgetting that could lead to overflows char str; strcpy(str,”bye”); // write overflow int x = strlen(str); // read overflow Using safe string library calls will catch this mistake char str; strlcpy(str,”bye”,3); // blocked int x = strlen(str); // returns 2 Rule: Understand pointer arithmetic sizeof() returns number of bytes, but pointer arithmetic multiplies by the sizeof the type int buf[SIZE] = { … }; int *buf_ptr = buf; while (!done() && buf_ptr < (buf + sizeof(buf))) { *buf_ptr++ = getnext(); // will overflow } So, use the right units while (!done() && buf_ptr < (buf + SIZE)) { *buf_ptr++ = getnext(); // stays in bounds } Defend dangling pointers int x = 5; int *p = malloc(sizeof(int)); free(p); int **q = malloc(sizeof(int*)); //reuses p’s space *q = &x; *p = 5; **q = 3; //crash (or worse)! x: 5 p: 5 ? q: Stack Heap Rule: Use NULL after free int x = 5; int *p = malloc(sizeof(int)); free(p); p = NULL; //defend against bad deref int **q = malloc(sizeof(int*)); //reuses p’s space *q = &x; *p = 5; //(good) crash **q = 3; x: 5 p: 0 ? q: Stack Heap Manage memory properly int foo(int arg1, int arg2) { struct foo *pf1, *pf2; Rule: Use goto int retc = -1; chains to avoid pf1 = malloc(sizeof(struct foo)); duplicated or if (!isok(arg1)) goto DONE; missed code … Encodes try/ pf2 = malloc(sizeof(struct foo)); if (!isok(arg2)) goto FAIL_ARG2; finally in … languages like retc = 0; Java FAIL_ARG2: free(pf2); //fallthru Confirm your logic! DONE: free(pf1); Gotofail bug return retc; } (Better) Rule: Use safe string library Libraries designed to ensure strings used safely Safety first, despite some performance loss Example: Very Secure FTP (vsftp) string library struct mystr; // impl hidden http://vsftpd.beasts.org/ void str_alloc_text(struct mystr* p_str, const char* p_src); void str_append_str(struct mystr* p_str, const struct mystr* p_other); int str_equal(const struct mystr* p_str1, const struct mystr* p_str2); int str_contains_space(const struct mystr* p_str); … Another example: C++ std::string safe string library Rule: Favor safe libraries Libraries encapsulate well-thought-out design. Take advantage! Smart pointers Pointers with only safe operations Lifetimes managed appropriately First in the Boost library, now a C++11 standard Networking: Google protocol buffers, Apache Thrift For dealing with network-transmitted data Ensures input validation, parsing, etc. Efficient Rule: Use a safe allocator ASLR challenges exploits by making the base address of libraries unpredictable Challenge heap-based overflows by making the addresses returned by malloc unpredictable Can have some negative performance impact Example implementations: Windows Fault-Tolerant Heap - http://msdn.microsoft.com/en-us/library/windows/desktop/ dd744764(v=vs.85).aspx DieHard (on which fault-tolerant heap is based) - http://plasma.cs.umass.edu/emery/diehard.html