Buffer Overflow
- common security flaw
- the following will discuss stack-based buffer overflow
- can also be executed on heap
- Goal: exploit buffer overflow to run injected code
- possible countermeasures:
- checking array bounds (program)
- address randomization (OS)
- dropping privileges when code executed inside setuid
Program memory stack
- stack fills top down, heap fills down top
- variable placement depends on type of variable: global, local, static, initialized etc.
- text segment - executable code of program
- data segment - static and global initialized data
- BSS segment - static and global un-initialized data
- heap - for dynamic memory allocation (
mallow
,calloc
, etc.) - stack - local variables, data related to function calls etc.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Stack Frame
- stack stores data used for function calls
- stack frame has 4 regions
- arguments - values of arguments passed into function; pushed in reverse order
- return address - next instruction after function call
- previous frame pointer - where function call was made
- local variables - compilers may randomize or add extra space for these
- we do not know exact addresses
- frame pointer (in ebp register) refers to a fixed location
- points to location of previous frame pointer
- can compute the offset of other stack members from ebp
1 2 3 4 5 6 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Buffer overflow steps
- overwriting stack frame return address with some random address
- overflowing buffer can cause:
- invalid instructions: location exists but data at location is invalid → program crashes
- non-existing address: if address does not map to any location → program crashes
- access violation: address space is protected → program crashes
- valid address and instruction → attacker's code may execute
Example
stack.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
Goal: replace stack frame return address with new address that points to malicious code
- find the offset distance between the base of the buffer and return address
- find the address to place the malicious code
- overwrite return address with address of malicious code
1 2 3 4 5 6 7 8 9 |
|
Distance to return addr
Set breakpoint at foo()
to find addresses:
in gbd
- set breakpoint at foo by using
b foo
- call
run
to start execution - program will break at
foo()
1 2 3 4 5 6 7 8 9 10 11 12 |
|
- use
gbp p
command to print out address of ebp register and of buffer - then compute distance
1 2 3 4 5 6 7 |
|
- frame pointer (ebp) is at
0xbfffeaf8
- therefore return address is at
0xbfffeaf8 + 4
- first address we can jump to is at
0xbfffeaf8 + 8
→ put 0xbfffeaf8 + 8
in return address location
- return address location is start of buffer to ebp + 4 bytes above ebp
→ distance to return address is 108 + 4 = 112 (from buffer's addr 0xbfffea8c
).
Address of malicious code
- turn off countermeasures to make this step easier
- investigate addresses using gbd → find address of function argument
- for better chance of finding it, fill badfile with no-ops, then place
the malicious code at the end of the file
- this creates multiple entry points: hitting any no op will eventually get us to malicious code.
- without no-ops need to guess address of malicious code exactly.
prog.c
1 2 3 4 5 6 7 8 9 |
|
When address randomization is turned off, can verify the program stack always starts from the same address
1 2 3 4 5 6 7 |
|
Contents of badfile
1 2 3 4 5 6 7 8 9 10 |
|
Constructing badfile
exploit.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
The shellcode contains instructions to run execve("bin/sh", argv, 0)
to gain access to the system
Registers used:
eax
=0x0000000b
(11) : value of system callexecve()
ebx
= address to"/bin/sh"
ecx
= address of the argument array.- argv[0] = the address of
"/bin/sh"
- argv[1] = 0 (i.e., no more arguments)
- argv[0] = the address of
edx
= zero (no environment variables are passed)int 0x80
= invokeexecve()
Overwrite return address
- the address we overwrite with should not contain 0 as byte, otherwise badfile will have
a 0 which causes
strcpy()
to end copying, e.g.
1 |
|
Executing the attack
Compile with countermeasures disabled:
1 2 3 |
|
Execute:
1 2 3 4 5 |
|
Countermeasures
- Developer: use safer functions like
strncpy()
,strncat()
and safer dynamic link libraries that check length before copying - OS: Address space randomization (ASLR)
- randomized start location of stack such that address changes every time code is loaded in memory
- guessing stack address in memory is difficult
- Difficult to guess %ebp address, and address of malicious code
- Compiler: Stack guard
- secret variable in the program
- compiler check for canary → detects "stack smashing" if variable has been modified
- Hardware: Non-executable stack
- NX bit (non-executable) separates data from code and marks certain areas of memory as non-executable
- defeat by return-to-libc attack
Defeating ASLR
-
turn on address randomization
1
$ sudo sysctl -w kernel.randomize_va_space=0
-
Compile setuid version of stack.c
1 2 3
$ gcc -o stack -z execstack -fno-stack-protector stack.c $ sudo chown root stack $ sudo chmod 4755 stack
-
Run the vulnerable code in an infinite loop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
#!/bin/bash SECONDS=0 value=0 while [ 1 ] do value=$(( $value + 1 )) duration=$SECONDS min=$(($duration / 60)) sec=$(($duration % 60)) echo "$min minutes and $sec seconds elapsed." echo "The program has been running $value times so far." ./stack done
Defeating at shell
Bash and dash turn setuid process to non-setuid process, and drop privilege. Before running setuid, set real user id to 0:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|