Buffer Overflows on the Heap(1)

πŸ‘©β€πŸŒΎ: ???????????????????????????????

Chapter 4 - Dynamic Memory Management(4.65)

  • Book by Robert C. Seacord

Buffer Overflows on the Heap

Buffer overflows in the heap are not always appropriately addressed, and developers adopt solutions that protect against stack-smashing attacks but not buffer overflows in the heap.

  • the unlink technique
  • the frontlink technique
  • PREV_INUSE bit

The attacker may:

  • provide the address of the return pointer on the stack, and use the unlink() macro to overwrite the address with the address of malicious code.
  • overwrite the address of a function called by the vulnerable program with the address of the malicious code.
  • Buffer overflow overwriting boundary tag

The most difficult part of this exploit is determining the size of the first chunk so that the boundary tag for the second argument can be precisely overwritten. To do this, an attacker could copy and paste the request2size(req,nb) macro from dlmalloc into his or her exploit code and use this macro to calculate the size of the chunk.

1. #define unlink(P, BK, FD) { \
2. FD = P->fd; \
3. BK = P->bk; \
4. FD->bk = BK; \
5. BK->fd = FD; \
6. } 

How it works

  • To forge the pointer to node, attacker can get a chance to read/write memory:
    • flink and blink on Windows
    • fd and bk on Linux

Targets:

  • Function return address
  • Exception Handling ?
  • Important function call address

Suppose there are 3 variables need memory in the heap: buf0, buf1, and buf2.

1 buf0 = malloc(32); <-- //buf0 is allocated with chunk0 
2 read(0, buf0, 64);
3 buf1 = malloc(32);
4 buf2 = malloc(32);

Unlink:

int remove (ListNode * node)
{
node -> bk -> fd = node -> fd;   // (node -> fd) is (malicious payload)
node -> fd -> bk = node -> bk;   // (node -> bk) is (arbitrary address)
return 0;
} 

When buf0 is attacked, The attacker can overflow buf0’s memory chunk0 to modify chunk1’s fd and bk. When chunk1 is used (removed from the link) at Line 3, it becomes:

int remove (ListNode * node)
{
arbitrary_address -> fd = malicious_payload;   // ATTACK !
malicious_payload -> bk = arbitrary_address;   // actually no need
return 0;
} 

The attacker:

  • Supplies the address of a memory chunk and not the address of the shell code.
  • Arranges for the first four bytes of this memory chunk to contain executable code.
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
	char *first, *second, *third;
	first = malloc(666);
	second = malloc(12);
	third = malloc(12);
	strcpy(first, argv[1]);   // ATTACK: can overwrite the boundary tag!
	free(first);    <-- break // If the second chunk is unallocated, the free() operation will attempt to consolidate it with the first chunk.
	free(second);    
	free(third);
	return(0);
}

At the break point, To determine whether the second chunk is unallocated, free() checks the PREV_INUSE bit of the third chunk.

| P = x | 1st chunk (with content) | P = 0 | 2nd chunk | 3rd chunk |

In 2nd chunk’s head, there is a field called size of chunk, which is used to deduce the address of the 3rd chunk. If overload 1st chunk and change the value of chunk2’s size of chunk to be -4, the system will think chunk2 is chunk3 by mistake!

In this way, second chunk is consolidated with the first chunk. first = malloc(666);

Malicious Arguments:

first chunk
|   content   | filling bytes | even int | size |  fd   |  bk  |
|--shellcode--| xxxxxxxxxxxxx |    24    |  -4  | fp-12 | addr | \0 |
|----------------668 bytes---------------|4 byte|4 byte |4 byte| \0 |
  • addr is the malicious address to shellcode.
  • The address - 12 is included in the malicious argument so that the unlink() method overwrites the address of the free() library call with the address of the shellcode.

Original unlink:

int remove (ListNode * node)
{
	node -> bk -> fd = node -> fd;   // (node -> fd) is (fp-12)
	node -> fd -> bk = node -> bk;   // (node -> bk) is (addr to shellcode)
	return 0;
} 

So it becomes:

int remove (ListNode * node)
{
	(addr to shellcode) -> fd = (fp-12);   
	(fp-12) -> bk = (addr to shellcode);   // ATTACK!
	return 0;
} 
  • The unlink() macro writes four bytes of data supplied by an attacker to a four-byte address also supplied by the attacker.
  • Once an attacker can write four bytes of data to an arbitrary address, it is easy to execute arbitrary code with the permissions of the vulnerable program.
  • Can execute arbitrary code with the permission of the vulnerable program

An attacker can:

  • Can overwrite a Return address in stack with the address of the malicious code
first chunk
|   content   | filling bytes | even int | size |  fd   |  bk  |
|--shellcode--| xxxxxxxxxxxxx |    24    |  -4  |  ret  | addr | \0 |
|----------------668 bytes---------------|4 byte|4 byte |4 byte| \0 |
  • Overwrite the address of a function called by the vulnerable program with the address of malicious code.
first chunk
|   content   | filling bytes | even int | size |   fd    |  bk  |
|--shellcode--| xxxxxxxxxxxxx |    24    |  -4  |func_addr| addr | \0 |
|----------------668 bytes---------------|4 byte| 4 byte  |4 byte| \0 |
  • Examine the executable image to find the address of the jump slot for the free() library call.

  • Usually the frontlink technique is more difficult than the unlink one.

malloc_free_snip.c

...
/*
   ------------------------------ free ------------------------------
 */

static void
_int_free (mstate av, mchunkptr p, int have_lock)
{
  ...
  else if (!chunk_is_mmapped(p)) {
    ...
    nextchunk = chunk_at_offset(p, size);
    ...
    nextsize = chunksize(nextchunk);
    ...
    /* consolidate backward */
    if (!prev_inuse(p)) {
      prevsize = p->prev_size;
      size += prevsize;
      p = chunk_at_offset(p, -((long) prevsize));
      unlink(p, bck, fwd);
    }

    if (nextchunk != av->top) {
      /* get and clear inuse bit */
      nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

      /* consolidate forward */
      if (!nextinuse) {
	unlink(nextchunk, bck, fwd);
	size += nextsize;
      } else
	clear_inuse_bit_at_offset(nextchunk, 0);

      /*
	Place the chunk in unsorted chunk list. Chunks are
	not placed into regular bins until after they have
	been given one chance to be used in malloc.
      */

      bck = unsorted_chunks(av);
      fwd = bck->fd;
      if (__glibc_unlikely (fwd->bk != bck))
	{
	  errstr = "free(): corrupted unsorted chunks";
	  goto errout;
	}
      p->fd = fwd;
      p->bk = bck;
      if (!in_smallbin_range(size))
	{
	  p->fd_nextsize = NULL;
	  p->bk_nextsize = NULL;
	}
      bck->fd = p;
      fwd->bk = p;

      set_head(p, size | PREV_INUSE);
      set_foot(p, size);

      check_free_chunk(av, p);
    }

    /*
      If the chunk borders the current high end of memory,
      consolidate into top
    */

    else {
      size += nextsize;
      set_head(p, size | PREV_INUSE);
      av->top = p;
      check_chunk(av, p);
    }
    ...
}

Some Techniques

  • The House of Prime
  • The House of Mind
  • The House of Force
  • The House of Lore
  • The House of Spirit
  • The House of Chaos

πŸ‘©β€πŸŒΎ: My brain cells also overflow πŸ€•

Reference