710 likes | 846 Views
Memory Corruption. James Walden Northern Kentucky University. Topics. Buffer Overflows Stack Overflows Heap Overflows Use after Free Memory Corruption Mitigations Return Oriented Programming Writing Secure Code. Memory Corruption.
E N D
Memory Corruption James Walden Northern Kentucky University CSC 666: Secure Software Engineering
Topics • Buffer Overflows • Stack Overflows • Heap Overflows • Use after Free • Memory Corruption Mitigations • Return Oriented Programming • Writing Secure Code CSC 666: Secure Software Engineering
Memory Corruption Memory corruption occurs when contents of a memory location are modified in a way unintended by the programmer. Modifications can result from • Buffer overflows • Format string attacks (%n) • Use after free Memory corruption vulnerabilities can result in • Execution of attacker-supplied code. • Attacker hijacking of program control flow. • Alteration of program data. • Information leak. CSC 666: Secure Software Engineering
Buffer Overflows buffer: limited contiguously allocated set of memory. • static: char buffer[32] • dynamic: malloc(), new What happens when you attempt to access an element beyond the end of the buffer? • Bounds checking prevents such accesses in most languages like Python, Ruby, and Java. • But in C, C++, Objective C, Forth, and assembly large inputs can overflow the buffer, overwriting adjacent data in memory. CSC 666: Secure Software Engineering
C Strings • C strings terminated with \0 character. • Many operating systems and software components are written in C • Interfaces inherit semantic “strings end with \0”. • Some components don’t handle \0 embedded in string gracefully, even if programming language can. • Note that UTF-16/UTF-32 include many byte 0s. • Note that \0 takes space – account for it! • Overwriting can create string doesn’t end. • Formal name is NUL character
An Example Buffer Overflow char A[8]; short B=3; • gets(A); CSC 666: Secure Software Engineering
Out-of-Bounds Read What’s the mistake in this program? int main() { int array[5] = {1, 2, 3, 4, 5}; printf("%d\n", array[5]); } Program output: > gcc -o buffer buffer.c > ./buffer 7077876 CSC 666: Secure Software Engineering
Out of Bounds Write Writing beyond the buffer: int main() { int array[5] = {1, 2, 3, 4, 5}; inti; for( i=0; i <= 255; ++i ) array[i] = 41; } Program output: > gcc -o bufferwbufferw.c > ./bufferw Segmentation fault (core dumped) CSC 666: Secure Software Engineering
What happens when a buffer overflows? What happened to our buffer overflow? • Overwrote memory beyond buffer with 41. • Program crashed with Segmentation fault. • Directly or indirectly accessed unmapped memory. • Creates a page fault exception. • OS does not find mapping on page table. • OS sends segmentation fault signal to process. Do overflows always produce a crash? • Unintentional overflows usually do, but • Attackers will restrict writes to mapped pages. CSC 666: Secure Software Engineering
History of Overflows • 1972: Computer Security Technology Planning Study. • 1988: Morris Worm spreads with fingerd overflow. CERT formed. • 1989: CERT published CA-1989-01 for BSD 4.3 passwd overflow. • 1995: Thomas Lopatic posts info on httpd overflow to Bugtraq. • 1996: Aleph One publishes “Smashing the Stack for Fun and Profit” • 1997: Solar Designer describes return-to-libc technique. • 1999: Compiler-based mitigations proposed. • 2001: Code Red worm exploits overflow in IIS. • 2003: Slammer worm infects every vulnerable server in an hour. • 2004: AMD and Intel add non-executable bit in page tables. • 2008: Twilight hack unlocks Wii consoles. • 2010: Android 2.3 adds support for non-exec stack. • 2013: Unicode SSIDs crash iPhones. • 2015: GHOST overflow impacts glibc. CSC 666: Secure Software Engineering
Process Memory Map Lower-numbered addresses Text (compiled program code) Often read- only This diagram shows how stacks grow on Intel x86s & others; some grow other way. Multi-threaded programs have multiple stacks Initialized global “data” Set on code load Used for global constants & variables Uninitialized global “data” Heap grows, e.g., due to “new” or malloc() Warning: Some diagrams elsewhere show lower-numbered addresses at the bottom Heap (dynamically allocated) Heap pointer Stack (procedure/ method calls) Stack pointer (SP) (current top of stack) Higher-numbered addresses Stack grows, e.g., due to procedure call
Memory Layout Example /* data segment: initialized global data */ int a[] = { 1, 2, 3, 4, 5 }; /* bss segment: uninitialized global data */ int b; /* text segment: contains program code */ int main(int argc, char **argv) /* ptr to argv */ { /* stack: local variables */ int *c; /* heap: dynamic allocation by new or malloc */ c = (int *)malloc(5 * sizeof(int)); } CSC 666: Secure Software Engineering
Abstract data type “Stack” “Stack”: Abstract CS concept • “A stack of objects has the property that the last object placed on the stack will be the first object removed. This property is commonly referred to as last in, first out queue” (LIFO). Minimum stack operations: • PUSH: Add an element to the top of the stack • POP: Removes the last element at the top of the stack (returning it) and reduces stack size by one. CSC 666: Secure Software Engineering
“Stack” in process memory map Memory area set aside to implement calls to a procedure/function/method/subroutine For now we’ll use these terms interchangeably In C the term is “function” Stack is used to implement control flow When you call a procedure, where it “came from” is pushed on stack When a procedure returns, the “where I came from” is popped from stack; system starts running code there Stack also used for other data (in many cases) Parameters passed to procedures Procedure local variables Return values from procedure CSC 666: Secure Software Engineering
Call Stack Layout Low Memory b() { … } a() { b(); } main() { a(); } High Memory CSC 666: Secure Software Engineering
What is a Stack Frame? Block of stack data for one procedure call. Frame pointer (FP) points to frame: • Use offsets to find local variables. • SP continually moves with push/pops. • FP only moves on function call/return. • Intel CPUs use %ebp register for FP. CSC 666: Secure Software Engineering
Why use stacks for function calls? First compiled languages (e.g., FORTRAN) did not use stacks Call data stored with fn where program “came from” Result: Procedures could not call themselves, directly or indirectly, as that would overwrite stored information. If functions can arbitrarily call other functions Need to store old state so can return back Need dynamic allocation for call (frame) sequences Stack is flexible & efficient CSC 666: Secure Software Engineering
CPUs tracktwo stack values Stack pointer (SP): Value of “top” of stack • Where last data was stored on stack, possibly +/- 1 depending on architecture conventions • Modified when data pushed/popped • May even be modified during expression calculation Frame pointer (FP): Value of “this frame” • Simplifies accessing parameters & local variables • Points inside stack to where “this procedure” starts • Modified on entry/exit of a procedure CSC 666: Secure Software Engineering
Accessing the Stack Pushing an item onto the stack. • Copy 4 bytes of data to stack. • Decrement SP by 4. Example:pushl $12 Popping data from the stack. • Copy 4 bytes of data from stack. • Increment SP by 4. Example: popl %eax Retrieve data without pop: movl %esp, %eax CSC 666: Secure Software Engineering
Calling a procedure Given this C program: void main() { f(1,2,3); } The invocation of f() might generate assembly: pushl $3 ; constant 3 pushl $2 ; Most C compilers push in reverse order by default pushl $1 call f “call” instruction pushes instruction pointer (IP) on stack • In this case, the position in “main()” just after f(…) • Saved IP named the return address (RET) • CPU then jumps to start of “function” CSC 666: Secure Software Engineering
Stack: After push of value 3 Lower-numbered addresses Higher-numbered addresses Stack pointer (SP) (current top of stack) 3
Stack: After push of value 2 Lower-numbered addresses Stack pointer (SP) (current top of stack) 2 Higher-numbered addresses 3 Stack grows, e.g., due to procedure call
Stack: After push of value 1 Lower-numbered addresses Stack pointer (SP) (current top of stack) 1 2 Higher-numbered addresses 3 Stack grows, e.g., due to procedure call
Stack: Immediately after call Lower-numbered addresses Stack pointer (SP) (current top of stack) Return address in main() 1 2 Higher-numbered addresses 3 Stack grows, e.g., due to procedure call
Function prologue Imagine f() has local variables, e.g. in C: void f(int a, int b, int c) { char buffer1[5]; char buffer2[10]; strcpy(buffer2, "This is a very long string!!!!!!!"); } Typical x86-32 assembly on entry of f() (“prologue”): pushl %ebp ; Push old frame pointer (FP) movl %esp,%ebp ; New FP is old SP subl $20,%esp ; New SP is after local vars ; “$20” is calculated to be >= local var space In the assembly above, “;” introduces a comment to end of line CSC 666: Secure Software Engineering
Stack: Immediately after call Lower-numbered addresses Stack pointer (SP) (current top of stack) Return address in main() 1 2 Higher-numbered addresses 3 Stack grows, e.g., due to procedure call
Stack: After prologue Lower-numbered addresses Stack pointer (SP) (current top of stack) Local array “buffer2” Local array “buffer1” Frame pointer (FP) – use this to access local variables & parameters Saved (old) frame pointer Return address in main() 1 2 Higher-numbered addresses 3 Stack grows, e.g., due to procedure call
Stack: Overflowing buffer2 Lower-numbered addresses Stack pointer (SP) (current top of stack) Local array “buffer2” Local array “buffer1” Frame pointer (FP) – use this to access local variables & parameters Overwrite Saved (old) frame pointer Return address in main() 1 2 Higher-numbered addresses 3 Stack grows, e.g., due to procedure call
Effect of Overflowing Buffer2 Overwrites whatever is past buffer2! • As you go further, overwrite higher addresses Impact depends on system details In our example, can overwrite: • Local values (buffer1) • Saved frame pointer • Return value (changing what we return to) • Parameters to function • Previous frames CSC 666: Secure Software Engineering
Common buffer overflow attack • Send too large data as user input. • Overlarge data overwrites buffer • Modifies return value, to point to something the attacker wants us to run • Maybe with different parameters, too • On return, runs attacker-selected code • But it gets worse… CSC 666: Secure Software Engineering
Shellcode Injection • Attacker can also include machine code that they want us to run. • If they can set the “return” value to point to this malicious code, on return the victim will run that code • Unless something else is done • Significant portion of “Smashing the Stack” paper describes how to insert such code. CSC 666: Secure Software Engineering
Stack: Overflow with Shellcode Lower-numbered addresses Stack pointer (SP) (current top of stack) Local array “buffer2” Malicious code Local array “buffer1” Frame pointer (FP) – use this to access local variables & parameters Saved (old) frame pointer Return address in main() Ptr to malicious code 1 2 Higher-numbered addresses 3 Stack grows, e.g., due to procedure call
Stack: Shellcode + NOP Sled Lower-numbered addresses Stack pointer (SP) (current top of stack) Local array “buffer2” NOP sled: \x90\x90\x90\x90\x90…. NOP sleds let attacker jump anywhere to attack; real ones often more complex (to evade detection) Shellcode: \xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh Local array “buffer1” Frame pointer (FP) – use this to access local variables & parameters Saved (old) frame pointer Shellcode often has odd constraints, e.g., no byte 0 Return address in main() Ptr to malicious code 1 2 Higher-numbered addresses 3 Stack grows, e.g., due to procedure call
Shellcode • Shellcode is a small piece of machine code inserted into a program by exploiting a vulnerability. • Called shellcode since it is often used to start a command shell under control of attacker. • Example shellcode • Remote shell (like ssh) • Reverse shell (opens connection to attacker server) • Remote desktop (RDP, VNC, etc.) • Downloader (installs remote control tools) CSC 666: Secure Software Engineering
Off-by-One Overflows Even 1 byte overflow can allow attacker to control execution by overwriting low order byte of FP. CSC 666: Secure Software Engineering
Heap Overflows Programs allocate memory on heap via • C: malloc() and free() • C++: new and delete To run shellcode, attack overwrites function pointer • Function pointers in C. • C++ methods implemented as function pointers. • C++ exceptions result in following a function pointer. CSC 666: Secure Software Engineering
Memory Allocation • Memory allocators typically • Allocate memory in chunks. • Chunks stored in a linked list of bins. • Chunks contain header, pointers, and data. • Each bin contains chunks of a specific size. • Final chunk contains free space information. • Memory corruption attacks target • Header information, and • Pointers. CSC 666: Secure Software Engineering
Malloc Data Structure CSC 666: Secure Software Engineering
Malloc Example char *a, *b, *c; a = malloc(32) b = malloc(32); c = malloc(32); … free(c) free(b) free(a) CSC 666: Secure Software Engineering
Malloc Example Memory Layout CSC 666: Secure Software Engineering
Malloc Layout after Free CSC 666: Secure Software Engineering
Overflowing Dynamic Buffers CSC 666: Secure Software Engineering
Overwriting vtable Pointers CSC 666: Secure Software Engineering
free() vulnerabilities • To de-allocate memory, free(void *) must • Use pointer to find chunk to de-allocate. • Modify pointers in chunk header to remove chunk from the list of in-use chunks. • Loop: if adjacent chunk is free, combine chunks to avoid memory fragmentation. • To exploit free(), an attacker can • Overwrite adjacent chunk metadata. • Cause free() to overwrite a specific word in memory with attacker specified data. • Attacker will choice location to be that of a pointer (lib function, vtableptr, ret addr, etc.) to control execution. CSC 666: Secure Software Engineering
Double free() vulnerability • Program free()s chunk twice, so chunk appears twice in the free list. • Program allocates memory, reusing part or all of chunk. • malloc() interprets first bytes as free list header. • Program interprets first bytes as data. • Write to allocated chunk, changing first bytes. • Program allocates, attempts to reuse chunk. • malloc() writes to location specified in first bytes, treating bytes as free list pointer, but those bytes now point to any location specified by attacker. CSC 666: Secure Software Engineering
Non-executable Stack • Memory protection prevents code on the stack from being executed. • Adds NX permission bit to page tables. • Required CPU and OS modifications. • Limitations • Some applications need to execute code on the stack (JIT compilers for Java, JavaScript). • Attackers can target other areas of memory. • Buffer overflows can result in remote code execution without running attacker generated shellcode.
Address Space Randomization • ASLR randomly changes code/data locations to prevent memory exploits. • Problems • Code must be compiled as Position Independent Executables (PIE) to relocate code segment at cost of a 10% performance penalty. • Memory corruption can result in information leakage of address layout. • Attackers can brute force 32-bit ASLR with many copies of shellcode (heap-spraying). CSC 666: Secure Software Engineering
Canary Defenses Compiler changes calling convention in two ways: • Adds a canary word to the stack (“canary in a coal mine”) • Verifies presence of canary word before executing the ret instruction to return to address on stack. Protects against stack smashing since • Overflow would have to overwrite canary to reach return value. • If canary is chosen randomly, attacker cannot know what to overwrite to that memory location. • Does not protect against indirect memory writes. CSC 666: Secure Software Engineering
Canary Stack Layout Frame Pointer Stack Pointer CSC 666: Secure Software Engineering
Return-Oriented Programming ROP is an exploit technique in which the attacker gains control of the call stack and uses it to execute small pieces of code, called “gadgets.” • Attacker uses existing code. • Bypasses NX defense, since no new code executed. Gadgets are typically found in shared libraries • Gadgets can be entire functions. • Gadgets can be fragments of code that end in a ret instruction (even unintentional instructions.) • Attacker controls order of execution and parameters by placing data on call stack. CSC 666: Secure Software Engineering