Hacking Tube

HITCON CTF 2016 Quals -- Secret Holder

Category: pwn
Points: 100

I did not finish the challenge during the contest, but was able to solve it after the game. Damn if only I can fuzz some more...

64 bit ELF, with Partial RELRO, canary & NX enabled, no PIE.

Program allow us to:

• keep secret : new a secret ( using calloc() )
• wipe secret : delete a secret
• renew secret : edit a secret

There are three kinds of secret in the program:

• small : secret with buffer size 40
• big : secret with buffer size 4,000
• huge : secret with buffer size 400,000

There's a Use-After-Free vulnerability in the wipe function:

When wiping a secret, it does not check the has_keep_XXX flag ( a flag that indicates now there's a small/big/huge secret in the program). So for example if we keep(small) --> wipe(small) --> keep(big) --> wipe(small), it will free the big buffer, but we can still renew(big)

During the contest I did not know how to exploit the service. I successfully overlapped the small and the big buffer, but it only made me able to overwrite the top_size, which is useless since we can't control the size of the calloc function ( thus we can't do the House of Force attack ). I was also failed to overlapped these two buffer with the different base address ( that is, I can only overlapped small & big with the same base heap address), so I was unable to overflow the heap buffer and overwrite the chunk header.

After I asked the author of this challege, I realize that the key point of solving this challenge is to exploit the huge and the big's memory chunk.

While solving this challenge, I thought that if the malloc size was larger than 0x20000, glibc will always use mmap to allocate the memory space. But I was wrong -- if we keep(huge)--> wipe(huge)--> keep(huge), we will found that the second huge buffer was allocated by the malloc function, not mmap !! ( Thanks to the god damn sysmalloc !! )

So here's how we gonna exploit it:

• Use keep and wipe to make small & huge buffer be on the same heap address.
• keep(big) and make big buffer adjacent to the end of the small buffer.
• By doing renew(huge), we'll be able to overflow the whole big buffer, thus we can create a fake smallbin memory chunk at the address of the big buffer.
• By creating some fake chunks, we can use the unsafe unlink attack to overwrite &huge_buf ( all three buffer's address are store in the global data section ), so now the huge's buffer will be lying on the global data section.
• Now we can overwrite all three buffer address ( data pointer ) by doing renew(huge)
• renew(small/huge/big) to overwrite the content of any address.

After we overwrite the huge's buffer address, we can try to do the GOT hijacking attack. But first we'll have to leak some address. The program itself did not output any of our input , so I decide to hijack free's GOT first.

By overwriting free's GOT into puts@plt, and small's buffer into __libc_start_main@got.plt, we are able to leak the libc's address by doing wipe(small), since it'll call free(small), which is now puts(_libc_start_main@got.plt).

After that we're able to overwrite some GOT and hijack the control flow. Here I overwrite puts's GOT into the address of the one-gadget, which will make the program spawn a shell whenever it tries to call puts to print out some message.

Such a shame that I didn't solve this challenge ...... I might be able to solve it if I did some more fuzzing.....

flag: hitcon{The73 1s a s3C7e+ In malloc.c, h4ve y0u f0Und It?:P}