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
&exit

so 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
    • ret call… 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
    • x86 is 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