buffer-overflows-attacks-and-defenses (week 3) Flashcards
why are buffer overflow attacks such a substantial portion of all security attacks?
- buffer overflows are common
- buffer overflows are easy to exploit
- and with remote penetration attacks a buffer overflow gives the attacker exactly what they need: the ability to inject and execute attack code
to get a root shell running in a vulnerable program, what two subgoals must attacker first achieve?
- arrange for suitable code to be available in the program’s address space
- get the program to jump to that code with suitable parameters loaded into registers and memory
what are two ways the attacker can arrange the attack code in the victim program’s address space?
- inject it: the attacker provides a string as input to the program, which the program stores in a buffer. the string contains bytes that are actually native CPU instructions for the platform being attacked
- -> the attacker does not have to overflow any buffers to do this
- -> the buffer can be on the stack (automatic variables), on the heap (malloc’d variables), in the static data area (initialized or not) - it is already there: often the code to do what the attacker wants is already present in the program’s address space. The attacker need only parameterize
the code, and then cause the program to jump to it.
For instance, if the attack code needs to execute
“exec(“/bin/sh”)”, and there exists code in
libc that executes “exec(arg)” where “arg” is a
string pointer argument, then the attacker need only
change a pointer to point to “/bin/sh” and jump
to the appropriate instructions in the libc library.
3 ways to cause the program to jump to the attacker’s code:
- activation records: corrupt return addresses on the stack (automatic variables)
- function pointers: “void (* foo) ()” declares the variable foo which of type “point to function returning void.” function pointers can be allocated anywhere (stack, heap, static data area) and so the attacker need only find an overflowable buffer adjacent to a function pointer in any of these areas and overflow it to change the function pointer. then, later, when the program makes a call through this function pointer, it will instead jump to the attacker’s location
- longjump buffers: C includes a simple checkpoint/rollback system called setjmp/longjmp / if the attacker can corrupt the stat of the buffer then “longjmp(buffer)” will jump to the attacker’s code instead. like FP’s, longjmp buffers can be allocated anywhere, so the attacker need only find an adjacent overflowable buffer
4 buffer overflow defenses:
- writing correct code (brute force)
- OS approach to make storage areas for buffers non-executable; stops attacks from injecting attack code but because attackers don’t necessarily need to inject code to perpetrate a buffer overflow attack, this method leaves lots of vulnerabilities
- direct compiler approach: perform array bounds checks on all array accesses; this completely eliminates the BO problem but imposes lots of costs
- indirect compiler approach: perform integrity checks on code pointers before dereferencing them; doesn’t complete prevent BO attacks but does stop most BO attacks; less costs than direct compiler approach
what are some ways to write more secure code?
- grep source code for highly vulnerable library calls like strcpy and sprintf (should use strncpy and snprintf)
- off by one errors are v bad
- advanced debugging tools: fault injection tools, static analysis tools
THIS IS NOT ENOUGH
is it possible to make all program data segments non-executable?
no. modern systems have come to depend on the ability to emit dynamic code into program data segments to support various performance optimizations; making the entire address space for a process non-executable would sacrifice substantial program compatibility
is it possible to make the stack segment non-executable and preserve most program compatibility?
yes. but there are two exceptional cases in Linux where executable code must be placed on the stack:
- signal delivery
- GCC trampolines (not really used anymore)
what is still vulnerable in the OS approach?
heap and static data segments that are still executable
code pointer integrity checking (indirect) vs. bounds checking (direct)
instead of trying to prevent corruption of code pointers, code pointer integrity checking seeks to detect a code pointer that has been corrupted before it is dereferenced
unlike bounds checking, it does not complete prevent attacks. overflows that affect program state other than code pointers will still succeed, but it still has much better performance than bounds checking
what is the “canary”?
StackGuard is a compiler technique for providing code pointer integrity checking to the return address in function activation records [14]. StackGuard is implemented as a small patch to gcc that enhances the code generator for emitting code to set up and tear down functions.
The enhanced setup code places a “canary” word next to the return address on the stack. The enhanced function tear down code first checks to see that the canary word is intact before jumping to the address pointed to by the return address word. Thus if an attacker attempts a “stack smashing” attack, the attack will be detected before the program ever attempts to dereference the corrupted activation record.
although code pointer integrity has the disadvantage relative to bounds checking that it does not perfectly solve the buffer overflow problem, it does have some substantial advantages. what are these?
- performance
- compatibility with existing code
- ease of implementation