Software Security

Table of Contents

Defenses and bypassing them

Techniques that can make attacks harder:

Static analysis: can we prove code never results in undefined behavior?

Dynamic instrumentation: runtime checks for undefined behavior

Defenses overview

Stack canaries

Value between local vars and return address, compiler initializes with random value in function prologue. On return, check whether value is still the same, and crash if it isn’t.

Enable with -fstack-protector. %fs:40 is randomized at thread creation, canary is never left in a register, and is moved to a position in the stack, as a local variable.

Can still exploit, if we can jump over the canary. E.g. in this example, overwrite the len variable so memcpy writes to it:

void echo(int fd) {
    long len;
    char name[64], reply[128];
    /* ... */
    read(fd, name, 128);
    memcpy(reply + len, name, 64);
    /* ... */
}

Three approaches:

Data execution prevention (DEP)

OS marks data pages as non-executable (no-execute bit in page tables).

W⊕X: no memory is both writable and executable.

We can still exploit this, but no longer by jumping to own shellcode - need to reuse existing code.

Can use shared library functions (return-to-libc), or chain together parts of code (return-oriented programming).

Ret2libc

On x86_32, params pass on the stack, so params to function can be easily set.

In 64-bit, parameters are passed in registers, so only works if parameter is in %rdi. Maybe reuse additional code to load desired %rdi value (e.g. with a piece of code that pops to %rdi and returns).

Return oriented programming (ROP)

Any sequence of instructions ending in RET is a ‘gadget’, which can be chained together. Return oriented programming is a chain of such gadgets.

Might want some useful ones:

Given the right gadgets, can do arbitrary computation without any code. Tools help generate ROP chains, e.g. Ropper

If we run out of writable stack, build a new stack anywhere in memory (such as attacker-controlled heap memory). Function epilogue can load stack pointer (by using it twice). This is a “stack pivot”.

If we can’t find the right gadget, use libraries, especially libc. Can jump into middle of an instruction.

Address space layout randomization (ASLR)

Randomizes memory addresses of code, data, heap, and stack. Prevents attacker from finding code pointer to overwrite, or knowing what to overwrite it with.

Stack and heap randomized by OS alone:

Code and data require compiler support

Can still be attacked:

Information hiding

ASLR: randomness limited to base:

Shadow stack:

Control-flow integrity

Prevent turing completeness with ROP chains.

idea:

May be combined with runtime shadow stack.

in practice

gadgets left after this:

Data oriented programming

Perfect CFI means CFG is never violated. But, data guides code through CFG, so by manipulating data we change control flow.

Attacker can overwrite data e.g. using buffer overflow, and overwritten data drives a dispatching data

Data oriented programming example

Approach:

Sanitizers

Fully detect or mostly detect particular vulnerabilities. Big performance overhead, so not really used in production settings, but useful for debugging and testing.

Sanitizers in GCC/LLVM

Address sanitizer (ASan):

Memory sanitizer (MSan):

Research topics

Delta pointers: fast buffer overflow detection

SafeInit: automatically initialize to zero

DangSan: prevents use after free

Type-after-type

TypeSan