Buffer overflow and stack smashing attacks Principles of application software security
Buffer overflows One of the most common vulnerabilities in software Particularly problematic when present in system libraries and other code that runs with high execution privileges.
How it works Application reserves adjacent memory locations (buffer) to store arguments to a function, or variable values. Attacker gives an argument too long to fit in the buffer. The application copies the whole argument, overflowing the buffer and overwriting memory space. If the conditions are “just right” this will enable to attacker to gain control over the program flow and execute arbitrary code, with the same privileges of the original application.
Stack smashing Function (sub- routine) calls results in an activation frame being pushed onto a memory area called the stack. function arguments return address previous frame pointer local variables local buffer variables Direction of stack growth
Memory management The stack, which contains activation frames, starts at the highest memory address allocated for the process, and grows downwards Variable-length data (e.g., strings) that are read dynamically, are kept in the heap, which grows upwards This arrangement maximizes flexibility of virtual memory management Stack Heap unitialized variables initialized variables code instructions Direction of stack growth Direction of heap growth
How to smash Give application a very long string with malicious code The string length, being much larger than the space allocated in the heap (buffer size declaration) causes the heap to overflow into the stack and overwrites the return address The return address now points to the beginning of the malicious code function arguments Return address (overwritten with entry address of malicious code) Previous frame pointer (overwritten w/ malicious code) local variables (overwritten w/ malicious code) local buffer variables (overwritten w/ malicious code) Direction of stack growth
(a)Basic stack overflow C code (b) Basic stack overflow example runs Figure 10.5 Basic Stack Overflow Example $ cc -g -o buffer2 buffer2.c $./buffer2 Enter value for name: Bill and Lawrie Hello your name is Bill and Lawrie buffer2 done $./buffer2 Enter value for name: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Segmentation fault (core dumped) $ perl -e 'print pack("H*", " fcffbf a4e4e4e4e0a");' |./buffer2 Enter value for name: Hello your Re?pyy]uEA is ABCDEFGHQRSTUVWXabcdefguyu Enter value for Kyyu: Hello your Kyyu is NNNN Segmentation fault (core dumped) void hello(char *tag) { char inp[16]; printf("Enter value for %s: ", tag); gets(inp); printf("Hello your %s is %s\n", tag, inp); }
Memory Before After Contains Address gets(inp) gets(inp) Value of bffffbe0 3e tag > bffffbdc f return addr.... bffffbd8 e8fbffbf e8ffffbf old base ptr.... bffffbd `... e f g h bffffbd a b c d bffffbcc 1b inp[12-15].... U V W X bffffbc8 e8fbffbf inp[8-11].... Q R S T bffffbc4 3cfcffbf inp[4-7] <... E F G H bffffbc0 34fcffbf inp[0-3] 4... A B C D Figure 10.6 Basic Stack Overflow Stack Values
Stack Overflo w Exampl e (a)Another stack overflow C code (b) Another stack overflow example runs Figure 10.7 Another Stack Overflow Example $ cc -o buffer3 buffer3.c $./buffer3 Input value: SAFE buffer3 getinp read SAFE read val: SAFE buffer3 done $./buffer3 Input value: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX buffer3 getinp read XXXXXXXXXXXXXXX read val: XXXXXXXXXXXXXXX buffer3 done Segmentation fault (core dumped) void getinp(char *inp, int siz) { puts("Input value: "); fgets(inp, siz, stdin); printf("buffer3 getinp read %s\n", inp); } void display(char *val) { char tmp[16]; sprintf(tmp, "read val: %s\n", val); puts(tmp); } int main(int argc, char *argv[]) { char buf[16]; getinp(buf, sizeof(buf)); display(buf); printf("buffer3 done\n"); }
Canary Guards Like the legendary canary-in-the-mine, it detects stack smash attacks. Inserts a “Canary value” just below the return address (Stack Guard) or just below the previous frame pointer (Stack Smashing Protector). This value gets checked right before a function returns.
SSP Prevents overflow of local buffer variables Canary value checking only takes place at return time, so other attacks possible function arguments return address previous frame pointer local buffer variables local non-buffer variables Direction of stack growth Canary value
Alternatives to canaries Use a compiler that does full bounds checking, i.e., makes sure that the code always allocate enough memory for arguments Significant performance penalty (Java/C)
Static analysis Use a code analyzer to detect buffer overflows Since checking that arbitrary code does not overflow is an undecidable problem, the code must be annotated in order for this to work Depends on programmer expertise (costly) Some common and useful programming techniques are prohibited (performance and engineering costs) Advantage is that the compiled code does not suffer from performance deterioration
Safe libraries Many vulnerabilities in code are due to unsafe use of system libraries An alternative is to install a kernel patch that dynamically substitutes calls to unsafe library functions for safe versions of those Not possible for closed-source systems such as MS operating systems
Memory address randomization Patch at the kernel level, changing the memory mapping Small performance penalty, by extra memory lookups (actually, extra cache lookups) Makes it very difficult to perform a useful buffer overflow However, unlike some other strategies, does not improve robustness (liveness) properties