overflow-heap

easy heap overflows

struct foo {
    char buffer[100];
    void (*func_ptr)(void);
};

heap overflow: adjacent allocations

class V {
  char buffer[100];
public:
  virtual void ...;
  ...
};
...
V *first = new V(...);
V *second = new V(...);
strcpy(first->buffer,
       attacker_controlled);

heap structure

  • where does malloc, free, new, delete, etc. keep info?
  • often in data structures next to objects on the heap
  • special case of adjacent heap objects problem
  • topic for later

sudo exploit

sudo bug

  • the bug:
for (size = 0, av = NewArgv + 1; *av; av++)
     size += strlen(*av) + 1;
if (size == 0 || (user_args = malloc(size)) == NULL) { ... }
...
for (to = user_args, av = NewArgv + 1; (from = *av); av++) {
while (*from) {
  if (from[0] == '\\' && !isspace((unsigned char)from[1]))
    from++;
  *to++ = *from++;
...
  • can skip \0 if prefixed with backslash
  • but strlen used to allocate buffer
  • disagreement about copied string length
  • heap overflow!

brute-forcing?

  • method: tried to lots of buffer overflows, get crashes
  • looked at them by hand, found interesting ones…

one crash

0x000056291a25d502 in process_hooks_getenv (name=name@...ry=0x7f4a6d7dc046 "SYSTEMD_BYPASS_USERDB", value=value@...ry=0x7ffc595cc240) at ../../src/hooks.c:108

=> 0x56291a25d502 <process_hooks_getenv+82>:    callq  *0x8(%rbx)

108         rc = hook->u.getenv_fn(name, &val, hook->closure);
  • they overwrote a function pointer on the heap!
  • next inquiry: where did that usually point?

sudoers.so

    *** interesting standard library function: ***
0000000000008a00 <execv@plt>:
    8a00:      endbr64 
    8a04:      bnd jmpq *0x55565(%rip)        # 5df70 <execv@GLIBC_2.2.5>
    8a0b:      nopl   0x0(%rax,%rax,1)
...
    *** usual value of function pointer: ***
000000000000ea00 <sudoers_hook_getenv>:
    ea00:      endbr64 
    ea04:      xor    %eax,%eax
    ea06:      cmpb   $0x0,0x51d36(%rip)        # 60743 <sudoers_policy@@Base+0x2003>
    ea0d:      jne    eaf8 <freeaddrinfo@plt+0x60a8>
    ea13:      cmpq   $0x0,0x51d45(%rip)        # 60760 <sudoers_policy@@Base+0x2020>
  • observations (that hold true even with ASLR):

    • addr(execv@plt) - addr(sudoers_hook_getenv) = -0x6000
    • last 12 bits of execv@plt always a00 (page alignment)

changing pointer (part one)

  • suppose hook_getenv pointer is 0xabcdef8a00

    • as bytes: 00 8a ef cd ab 00 00 00
  • then execv@plt pointer is 0xabcdef3a00

    • as bytes: 00 3a ef cd ab 00 00 00

  • only need to change the last two bytes
  • also: same change would work if pointer had different high bits
  • only four bits of random data from ASLR!

changing pointer (part two)

  • solution: guess hook_getenv pointer at 0x (unknown) 8a00

  • overwrite last two bytes with 00 3a


  • if right: will execute your program

  • if wrong: will crash


  • what if crashes? try again!

    • would work about once every 16 tries…
    • but actual exploit needed to write a 00 byte at the end (strcpy)
    • so worked ‘only’ about once every 4096 tries

into exploit

  • make SYSTEMD_BYPASS_USERDB program in current directory

  • run sudo, triggering buffer overflow to change
    sudoers_hook_getenv("SYSTEMD_BYPASS_USERDB", ...)
    into
    execv("SYSTEMD_BYPASS_USERDB", ...)

    • (well, try to change — it won’t always work)

heap smashing

  • ‘‘lucky’’ adjancent objects
  • same things possible on stack
  • but stack overflows had nice generic ‘‘stack smashing’’
  • is there an equivalent for the heap?
  • yes (mostly)

Linux memory allocation calls

  • brk()
    • set ‘break’ at end of heap region
    • one big memory region for dynamic memory
    • used to be only way to allocate memory
    • minimum size of changes = 4KB (x86-64)
    • want larger changes for speed
  • mmap()
    • allocate new memory region
    • more complex OS bookkeeping than brk()
      • adding to list of memory regions, not changing a size
    • minimum size = 4KB
    • want much larger allocations for speed

malloc()/free()/etc. as memory partitioners

  • for “small” objects (less than kilobytes)
  • malloc() allocates big chunks of memory
  • then subdivides them on the fly
  • and resizes (realloc()) if room
  • and/or reuses after free’d
  • … without contacting OS each time

malloc() metadata?

  • malloc/free use metadata to…
  • track unused chunks of memory
    • even after A=malloc(), B=malloc(), C=malloc(), free(B)
  • figure out how big allocation is when free() is called
  • figure out if nearby blocks of memory are free when free() is called
    • merge multiple free regions together
    • eventually free memory

some metadata tracking strategies

  • two common ideas for tracking metadata:
  • before ‘arenas’ of allocations (example: jemalloc [Firefox; *BSD default?])
    • lookup for free() by rounding memory address
  • before each allocation/beginning of free blocks (example: GNU libc malloc [Linux default; Windows default?])
    • lookup for free() by subtracting from pointer
    • use free space to hold metadata
    • probably linked lists or tree of free blocks
      • free blocks contain pointers for list or tree

heap object metadata

struct AllocInfo {
    bool free;
    int size;
    AllocInfo *prev;
    AllocInfo *next;
};

implementing free()

int free(void *object) {
  ...
  block_after = object + object_size;
  if (block_after->free) {
    /* unlink from list,
       prepare to merge with previous block */
    new_block->size += block_after->size;
    block_after->prev->next = block_after->next;
    block_after->next->prev = block_after->prev;
  }
  ...
}
  • arbitrary memory writes

vulnerable code

char *buffer = malloc(100);
... 
strcpy(buffer, attacker_supplied);
... 
free(buffer);
free(other_thing);
...

diagram showing allocated object next to two free space regions. Each allocated object has a size/free, prev and next field at its beginning followed by some free space; and the allocated object is prefixed by a size/free field.

vulnerable code

char *buffer = malloc(100);
... 
strcpy(buffer, attacker_supplied);
... 
free(buffer);
free(other_thing);
...

the strcpy modifies the size/free, next, and prev fields of the followingfree space region to the allocated object

vulnerable code

char *buffer = malloc(100);
... 
strcpy(buffer, attacker_supplied);
... 
free(buffer);
free(other_thing);
...

When freeing the allocated region, the malloc implementation will try to merge the free space before and after it into one new free space region.

part of this process will be removing the free space region after it from the linked list.

In this example, the attacker sets the prev pointer to point before the GOT entry for free (in the middle of the GOT table) and the next pointer to point to some shellcode. The shellcode is prefixed by a jmp skipping 8 bytes, then 8 bytes of junk.

when running block_after->prev->next = block_after->next in free(), block_after->prev is set by the attacker such that block_after->prev->next is the GOT entry for free. As a result the GOT entry for free ends up pointing to the jump before the shellcode.

when running block_after->next->prev = block_after->prev in the free(), the jmp before the shellcode occupies is mapped to block_after->next->size/free, the junk is mapped to block_after->next->prev, and the shellcode starts at block_after->next->next. The free()'s asignment overwrites block_after->next->prev. Because of the jmp this doesn't disrupt the shellcode execution.

vulnerable code

char *buffer = malloc(100);
... 
strcpy(buffer, attacker_supplied);
... 
free(buffer);
free(other_thing);
...

the later call to free() now runs the shellcode.

suitable write targets?

  • tricky problem: need to make extra write okay
    • solution in example: construct shellcode so we can write over part of it
  • probably cannot use to directly set pointer to existing code
    • likely crash due to memory being marked read-only
  • but still plausible strategies:
    • change existing VTable pointer
    • change data values (filenames, other buffer sizes, …)

heap overflow exercise

void operator delete(void *p) {
    ...
    block_after->prev->next = block_after->next;
    ...
}
...
class MyBuffer : public GenericMyBuffer {
public:
    virtual void store(const char *p) override {
        strcpy(buffer, p);
    }
private:
    char buffer[64];
};
...
    GenericMyBuffer *a = new MyBuffer;
    ...
    a->store(attacker_controlled);
    ...
    delete a;
    ...
heap object layout (allocated)
size + free (8B)
vtable pointer (8B)
buffer (64B)
heap object layout (free)
size + free (8B)
next pointer (8B)
prev pointer (8B)
exercise 1a:
assume space after a is free; if a at address 0x10000, and attacker wants to overwrite value at address 0x21210, where should attacker put encoding of 0x21210 in attacker_controlled?
A. 64 bytes in B. 72 bytes in C. 80 bytes in
D. 88 bytes in E. something else
exercise 1b:
if a at address 0x10000, and attacker wants to overwrite value at address 0x21210, how should attacker encode if placing 80 bytes in?
A. 0x21210 (as 64-bit int) B. 0x21210-8 C. 0x21210+8
D. 0x21210+16 E. 0x21210-16 F. something else
exercise 1c:
assume space after a is free; value at address 0x21210 with 0x31310, and they put 0x21210-8 80 bytes in, where should they put 0x31310?
A. 64 bytes in B. 72 bytes in C. 80 bytes in
D. 88 bytes in E. something else
exercise 1d:
if b at address 0x10000, and attacker wants to overwrite value at address 0x21210 with 0x31310, how should they encode 0x31310?
A. 0x31310 (as 64-bit int) B. 0x31310-8 C. 0x31310+8
D. 0x31310+16 E. 0x31310-16 F. something else

exercise 2:
Suppose space after a is not free. Can we still exploit?

double-frees

free(thing);
free(thing);
char *p = malloc(...);
// p points to next/prev
//   on list of avail.
//   blocks
strcpy(p, attacker_controlled);
malloc(...);
char *q = malloc(...);
// q points to attacker-
//   chosen address
strcpy(q, attacker_controlled2);
...

double-frees

free(thing);
free(thing);
char *p = malloc(...);
// p points to next/prev
//   on list of avail.
//   blocks
strcpy(p, attacker_controlled);
malloc(...);
char *q = malloc(...);
// q points to attacker-
//   chosen address
strcpy(q, attacker_controlled2);
...

double-frees

free(thing);
free(thing);
char *p = malloc(...);
// p points to next/prev
//   on list of avail.
//   blocks
strcpy(p, attacker_controlled);
malloc(...);
char *q = malloc(...);
// q points to attacker-
//   chosen address
strcpy(q, attacker_controlled2);
...

double-frees

free(thing);
free(thing);
char *p = malloc(...);
// p points to next/prev
//   on list of avail.
//   blocks
strcpy(p, attacker_controlled);
malloc(...);
char *q = malloc(...);
// q points to attacker-
//   chosen address
strcpy(q, attacker_controlled2);
...

malloc returns something still on free list
because double-free made loop in the linked list

double-frees

free(thing);
free(thing);
char *p = malloc(...);
// p points to next/prev
//   on list of avail.
//   blocks
strcpy(p, attacker_controlled);
malloc(...);
char *q = malloc(...);
// q points to attacker-
//   chosen address
strcpy(q, attacker_controlled2);
...

attacker can overwrite next pointer
controls where future malloc() goes

double-free expansion

// free/delete 1:
double_freed->next = first_free;
first_free = chunk;
// free/delete 2:
double_freed->next = first_free;
first_free = chunk
// malloc/new 1:
result1 = first_free;
first_free = first_free->next;
// + overwrite:
strcpy(result1, ...);
// malloc/new 2:
first_free = first_free->next;
// malloc/new 3: 
result3 = first_free;
strcpy(result3, ...);

double-free notes

  • this attack has apparently not been possible for a while
  • most malloc/new’s check for double-frees explicitly
    • (e.g., look for a bit in size data)
  • prevents this issue — also catches programmer errors
  • pretty cheap

double-free exercise

free(...) {
    freed->next = first_free
    first_free = freed;
}
malloc(...) {
    if (can use first free) {
        void *to_return = first_free;
        first_free = first_free->next;
        return to_return;
    }
}
vulnerable() {
    char *p = malloc(100);
    free(p);
    free(p);
    char *q = malloc(100);
    char *r = malloc(100);
    strlcpy(q, attacker_input1, 100);
    char *s = malloc(100);
    strlcpy(r, attacker_input2, 100);
    strlcpy(s, attacker_input3, 100);
}

goal: memory[0x123456] \(\leftarrow\) 0x789abc
what should input1/input2/input3 be?