Memory Attacks & Defenses
Cause of Vulnerability
Abstraction
Assumptions because of abstraction over machine code
Assumptions:
- Basic statements are atomic (ie. assigments)
- Only one branch can be taken, functions start at the beginning, execute to the end, then return to call site
- only source code instructions can be executed
Truth:
- Statements compiled to many instructions that can be executed seperately (on x86)
eip
can be set anywhere
- Dead code (unused library functions) can be executed
No Boundary-Checking
Many C library functions are unsafe.
Examples
No boundary checking:
strcpy(buf, str)
(copies until
"\0"
)
strcpy(char *dest, const char *src)
strcat(char *dest, const char *src)
gets(char *s)
scanf(const char *format, …)
printf(const char *format, …)
Boundary checking:
strncpy(char *dest, const char *src, size_t n)
copies exactly n characters
off-by-one-overflow possible if we choose wrong n:
MAX_STRING_LEN-1
No typing
C, C++ are memory unsafe: data is not typed, direct memory access
Buffer Overflow
Goal: hijacking control-flow, stealing or modifying valuable information (= control/data corruption)
Buffer over-read reading adjacent memory until
'\0'
beyond buffer boundary
Buffer over-write overwriting adjacent memory
ret
also known as "saved eip"
1) Return Address
Basic Stack Code Injection
-
overwriting
ret
→ start of buffermust be guessed, does not need to be precise, we use a NOP-sled
-
Buffer string contains assembly instructions like
execve("/bin/sh")
- When program exists, we return to newly set address, buffer executed
Return-to-libc ret2libc
Allows bypassing DEP: No code injection.
Programs that use functions from a shared library (like
printf
from libc library), link entire library into their address space at run time.
-
overwriting
ret
→ library instructions, likesystem(), exec(), ...
-
Setting function arguments (
funcp
behindret
) to"/bin/sh"
Return Oriented Programming ROP
ROP is a generalisation of ret2libc attacks, no code injection, overwriting
ret
.
Instead of executing library functions, we execute sequences gadgets from the process memory → Turing-complete functionality in x86.
Gadgets are short sequences of machine code instructions that end with a return instruction.
Implementation of return function:mov eip, [esp]; add esp, 4
or justpop eip;
At the end we must undo unwanted side effects.
2) Pointer Variables
Function Pointer Overflow
C uses function pointers for callbacks.
Callback function pointer can be in the stack or as an argument of this frame
(funcp
behindret
)
.
Pointer Overflow
If pointer and its content both overwritable
*dst=buf[0]
→ possible to change memory everywhere.
3) Stack Frame Pointer
Off-by-One Overflow(1-byte overflow)
-
overwriting
sfp
→ buffer (on little endian architecture)
- buffer is arranged like a real frame but contains attack code
Memory Defenses
Buffer Overflows → Canaries, Data Execution Prevention DEP →
Return Oriented Programming ROP → Address Space Layout Randomization ASLR →
Return/Jump/Data Oriented Programming
Canaries
= Stack cookie, Stack guard, ProPolice, GS-Flag
Get checked on their integrity before returning from function.
Terminator Canary always the same value '\0', EOL, EOF, ... — can be known
Random Canary stored in global variable, can not be guessed — can be found in memory
Random XOR Canary string generated from control data XOR scrambled
can detect modification of
ret
even if canary untouched
— can be found or reverse engineered with data and hash algorithm
Problem
Lower performance.
Protect only against contiuous overwrites of the stack: Can be defeated with function-pointer-overflow when pointer and its content get
overwritten, like:
*dst=buf[0]
Solution: ProPolice Stack-Smashing-Protection
Rearranges stack to prevent function-pointer-overflow (puts pointers behind buffer variables).
Problem
Needs recompiling with a modified compiler.
Possible attacks:
- Overwriting vtable pointer (pointers to virtual methods - vtables) with attack code address in stack - the canary integrity is only checked before function return
- smashing canary, overwriting pointer to the exception handler with that of attack code (in heap)
Data Execution Prevention DEP
= WP, NX, XD
All writeable memory (stack and other data areas) is marked as non-executable.
Problem: protect against code injection but not against code reuse
Some languages need an executable stack.
Can be bypassed by: ret2libc, ROP, attacks on memory mapping routine and heap possible, ...
Address Space Layout Randomization ASLR
Make stack addresses, addresses of library routines, etc. unpredictable and different from machine to machine.
Random base addresses for: system call IDs, instruction sets, most importantly pointers
Problem: no protection against pointer leak
Address randomization does not change stack or library table layouts.
Only base shift must be guessed (or brute forced).
ROP is still possible by guessing the offset.
Possible Solutions
getting rid of sequences ending with return instruction.
Making sure that we return to where we came from after a return instruction.
Can still be bypassed with Return/Jump/Data Oriented Programming.