We were given a flask website, where it only show Access Denied. Checking the cookie, we notice it contains the session. Using flask unsign, we try to bruteforce the secret, and found it.
Flag: CTF{66bf8ba5c3ee2bd230f5cc2de57c1f09f471de8833eae3ff7566da21eb141eb7}
Disclaimer: This is my first time ever doing heap challenge, I spend a lot of time on learning while doing, so I'm sorry if I made some mistake during explaining my solution. Would love to have your comments or feedbacks if you found some misinformations on my writeup.
We were given a libc and a binary file called vuln
.
Checking the given libc, we know that the glibc version is 2.27
Here is the result of the decompilation with Ghidra
Reading the decompilation result, we notice some bug.
To make our life easier, let's make a wrapper in python to do that:
Skimming the decompiled code, it seems the solution that we need is only abusing the UAF bug:
getFlag
addressadmin_pointer
and student_pointer
is pointing to the same chunk, hence call Print Admin Info will call getFlag
).After I did the above steps, turn out the printed flag was a fake flag.
Seems like we need to trigger shell to look around for the real flag. Basically, our target is to do the above steps, but instead of putting the getFlag
address, we trigger the shell.
In order to do that, first I try to find execve("/bin/sh"
on the given libc with one_gadget
. We found some candidates.
To use that gadget, we need to leak libc base address. In order to do that, we can't rely on the tcache bin. We need to find a way, so that during we call the free
, the chunk got cached inside unsortedbin
, because unsorted bin chunk will store pointer to libc main_arena
. With UAF, we can leak the libc base address.
How to do that? The main idea is we need to:
0x90
unsortedbin
(If we use size smaller than 0x90, it will go to fastbin
)main_arena
)getFlag
address, we store our shell gadget addressLet's do that
In order to fulfill that, we need to find the heap_address first. We can use double free to retrieve the heap address:
tcache_entry->next
is null)tcache_entry
will create a loop. The tcache[0x20] will be like thisstudent_pointer->student_pointer->...
)Heap after creating new student named aaaaaaaaaaaaaaaa
First free
Free it again
Now, with UAF, we can print student info, which point to the tcache_entry
, and we successfully leak the heap_address (via print student info menu).
After that don't forget to empty the tcache. Set the tcache_entry->next pointer
to null, so that when we create a new student, the tcache will be empty (because the next pointer is null). We can use menu 4 (edit student) to null it, because the student_pointer
still point to our chunk data.
Now our tcache is empty, and we are ready to create our forged chunk.
After finding the heap address, we can create our 0x91
forged chunk (by creating new student), and then later with double free + UAF, we can overwrite the pointer of tcache to point to our forged chunk. We just need to create a lot of chunk (via new student menu) where the name last byte is 0x91
. I create new student 6 times, so that the next of my fake chunk is not the wilderness (because if it is, it will get consolidated).
Heap structure after that:
Now we have prepare our forged chunk, now we need to make a pointer point to the chunk (leaked_heap_address+0x30
) and then free it. The idea is to:
student_pointer
. So that tcache[0x20] = last_chunk
tcache_entry->next
to our forged chunk (leaked_heap_address+0x30
), so that tcache[0x20] = last_chunk->heap_address+0x30
tcache[0x20]
will point to heap_address+0x30
heap_address+0x30
and now we have a pointer to our forged chunk that is ready to be freed.Heap structure after that:
Now we have a pointer to our fake chunk, we are ready to leak the libc address of main_arena
. We just need to free it 7 times (to fulfill tcache[0x90]
bin), and then free it one more time, to make our chunk as the cache of unsorted bin.
Bins and Heap structure before the 8th free:
After the 8th free:
With UAF, simply print the student info, and because the student pointer is currently pointing to the leaked libc, it will print the libc address. We finally got the leaked libc address.
After that, we can reuse the first pitfall solution, but instead of putting the getFlag
address, we put our shell_address ready to be executed
Executing the full solution will give us shell and we can read the real flag.
Flag: CTF{ab7bdaa3e5ed17ed326fef624a2d95d6ea62caa3dba6d1e5493936c362eed40e}
Follow me on twitter