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

retalso known as "saved eip"

1) Return Address

Basic Stack Code Injection

  1. overwriting ret → start of buffer

    must be guessed, does not need to be precise, we use a NOP-sled

  1. Buffer string contains assembly instructions like execve("/bin/sh")
  1. 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.

  1. overwriting ret → library instructions, like system(), exec(), ...
  1. Setting function arguments ( funcp behind ret ) 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, 4or 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 (funcpbehindret) .

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)

  1. overwriting sfp → buffer (on little endian architecture)
  1. 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:

Data Execution Prevention DEP

= W⊕\oplusP, 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.