No free? No problem! Exploit glibc 2.35 using orange cat - picoCTF 2024 high frequency troubles writeup
Here’s my writeup for the hardest challenge in picoCTF 2024 - high frequency troubles. I think I’m the 24th person to solve this challenge in the competition after struggling for like 4 days during the competition. I am one of the 31 teams that solved the problem, out of a total of more than 7000 teams. I’m definitely really proud of myself!
In this challenge, we have to combine a trick from an old exploit technique - House of Orange and some really new things in Glibc 2.35 to get shell. (Since there’s no function pointer in glibc 2.35, RCE is very hard…) Definitely learned a lot from this challenge!
In the end, I believe this is the unintended solution.
tl;dr
Use “free top chunk” trick from house of orange to gain free primitive. Then use some disgusting heap feng shui and tcache poisoning to leak libc and overwrite stderr.
Finally gain RCE using “house of cat” exploit chain by overwriting top chunk size and triggering __malloc_assert
. Remember to fix corrupted unsorted bin size.
Challenge
Challenge environment: Glibc 2.35, protection are all on.
I only show the important parts of the challenge here.
First, you can input a size_t
“size”, and then it will malloc a block of memory of that given size.
The first 8 bytes of this memory block will be the size we just input, and the next content will be read using gets
. (This is the vulnerability of course)
The first 8 bytes of the “content” will be the “mode flag”.
If mode equals 1, it will print the content after the mode flag. The other options are not important.
|size|mode|rest of the content...(this part will be printed if mode equals 1)
Vulnerability analysis
The vulnerability is clear: we can easily overflow the buffer using gets
.
The difficulties in this problem are:
-
You cannot free any chunks.
-
You can only operate on one chunk of memory at a time; you can’t perform arbitrary operations like heap note challenges.
-
gets
will have a NULL byte, making it seem like leaking addresses is impossible. -
In glibc 2.35, many function pointers have disappeared, making RCE challenging.
Exploitation Plan
However, these four difficulties are solvable.
-
It’s well known that we can modify size of top chunk to a certain small size (< 0x1000 I think) to free top chunk since glibc thinks the top chunk is too small and it’ll be freed, which is a classic trick from House of Orange. Though the
_IO_FILE
exploit in the original House of Orange (hijacking vtable) isn’t working anymore, we can still use this “free top chunk” trick. -
However we cannot go back to previous chunk to modify data like “heap note” challenges. What can we do now?
We can use heap feng shui to make the heap layout look like this:
---------------
Size a in tcache bin index 1
---------------
Size b in tcache bin index 0
---------------
Size a in tcache bin index 0
---------------
Size c in tcache bin index 0
---------------
Unsorted bin with libc address
---------------
We first take out size b to overwrite the next pointer of the bottom chunk (which has size a). Then we have poisoned tcache list of size a. (Next pointer of the third chunk, which is the first chunk in tcache list of size a, from the top is modified) Now we have a tcache poisoning so that we can allocate nearly everywhere by allocating size a!
- We can now first malloc to address of an unsorted bin header - 0x10. Then we input “mode” only (it’ll overwrite the header of the unsorted bin). However the “content” of this packet is now the beginning of the unsorted bin content, which is an libc address. We can leak libc now!
Remember to fix corrupted unsorted bin size using size c.
- Now we have libc leak and nearly arbitrary write using tcache poisoning. How to RCE?
We have two ways: the classic ROP or find a new way to exploit _IO_FILE
structure.
The common and stable way is to leak the address of stack by reading the address stored at __libc_argv
(argv address stored in libc). Then we can just hijack any return address to get shell. (with ROP of course)
The other “disgusting” way is that we can still exploit _IO_FILE
structure in a new way! I use “house of cat” exploit chain here. We modify stderr to a specially crafted _IO_FILE
structure and corrupt top chunk size and triggering __malloc_assert
to trigger fflush(stderr)
to gain RCE. I love these techniques.
Exploit Script
Epilogue
The challenge has a hint and flag also mentioned that this is an unintended solution. The intended solution is to exploit mmap
and tcache_perthread_struct
. You can see here for more details.
The intended solution maybe like “partial overwrites to control tcache” and exploit tls
as mentioned in the above blog post.
But I still think my solution is more interesting and resonable. (I mean, easy to come up with) All of these heap tricks are basic and well-known(?), but the combination of them is really interesting.
I hope you enjoy this writeup!