Return-To-libc
int system(const char *command);Uses fork to create a child process that executes the shell command specified in command using execl as follows:
execl("/bin/sh", "sh", "-c", commnd, (char *) 0);- Needs to find the address of
system()and string/bin/sh - Overwrite the return address of
system()and take control of it.
How To:
- setup stack frame to look like a normal call to
system() - prof talks about calling and returning on the stack (review!)
- when system runs the stack needs to look like this:
cmd="/bin/sh"
&cmd
&exitso it looks like a legit call to the system, but we’re going to use ret not call to the function.
Here, we are about to return to bar, but then transfers control to the system function.
- many different ways to get access to the system
Return Oriented Programming
- what if you need to do something more complicated?
- what happens if we jump to almost the end of some function?
- execute last few instructions
retcall… but where?- return to address on the stack, but if we change it, we can control this address
- choose to return to another tail of an existing function
- make complex shellcode out of existing application code
- most processors work the same way
x86is a bit different since instructions are variable length- more function tails to look at for the return call,
0xC3
- eventually becomes a Turing Computer
- load, store gadgets
- arithmetic and logic gadgets
- control flow gadgets
- OK I LITERALLY DON’T UNDERSTAND ANYTHING UP TO HERE
Control Flow Integrity (CFI)
- given a new attack technique, we must develop a new countermeasure
- Almost all attacks we’ve seen is attacker overwriting jump targets
- what if
ret,calls, etc could only go to known good targets - focus on protecting indirect transfer of control flow instructions
Direct
- advancing to next sequential instruction
- jumping to a hard-coded address
- static in code, so assume attacker can’t control them (if they can overwrite code segments, it is
gg no re)
Indirect
- jumping to or calling a function at an address in register or memory
- Forward Path: indirect calls and branches (function you are calling)
- Reverse Path: return addresses on the stack (return from called function)
- what is a legitimate target?
- how do we know if when we return, it is the right place?
Basic Design
- restrict all control transfers to the control flow graph
- assign labels to all indirect jumps and their targets
- before taking an indirect jump, validate that target label matches jump site
- this is like stack canaries.
- assign label to each target of indirect control transfer
- instrument indirect control transfers to compare label of destination, to ensure it is valid
Shadow Stack
- keep main stack for legacy
- shadow stack is unwritable, to save call control flow data
- on return from main stack, check the shadow stack that it is correct
- requires compiler support, fast hardware support.
Coarse-Grained CFI
- trade off speed for precision
- identify if control transfer is clearly wrong, but not that is right
- 2 labels, no shadow stack
- label for destination of indirect calls (forward)
- label of destination of
rets and indirect jumps (reverse)
Tradeoffs:
- additional computation needed for free branch instruction.
- more code size, needs strong enough computer
- needs reliable DEP
do not validate all data=dependent