This time I want to share a challange from last weekend’s QWB game, which only allows Chinese team to participate. This particular challenage one, I did not have time to look during the game, but when I solved it, I think it is worth to share. You can download the binary here.
Vulnerability
No surprise, the binary was compiled with full protection.
1 | Arch: amd64-64-little |
It is a typical menu program with add, edit, show, delete, and the organizor also provided a libc of 2.27 version.
1 | ---------menu--------- |
I soon identify the vulnerability, it locates in edit function:
1 | unsigned __int64 edit() |
we can edit the string content char by char, and the strchar could lead to heap overflow. Well, seem not difficult at all, with the tcache poisoning technique, it should be easy enough to solve. However, there is an address sanitization at add function:
1 | void add() |
and check_addr is like:
1 | __int64 __fastcall check_addr(__int64 addr) |
LOW and HIGH is initialized when the program start, LOW is the heap address of very first allocation, and HIGH = LOW + 0x5f0.
Oh, it also provided a secret function, which can leak a code address. The leak is a little bit obscure, I’ll spare this part for the readers to explore.
Analyze
If we fill all 20 slot, the bss layout is like following memory dump:
1 | pwndbg> tele 0x555555757040 0x60 |
LOW is 0x555555758260 and HIGH is 0x555555758850, so we can malloc can only return addresses within this range, otherwise, the session died. I considered this as extra mitigation towards tcache poisioning.
We’ve got a code address, a heap overflow. With heap overflow, we can modify 0x555555758350‘s content and overflow it to 0x555555758390‘s chunksize, change it from 0x41 to 0x441, then we delete it and got a chunk in unsortedbin. This allows us to leak libc address and create overlapping chunk, overlapping chunk also helps us to get heap address. So we can have code, libc, heap address, enough for bypassing PIE. What next?
Unsortedbin Attack in Glibc 2.27
A straightforward idea is to unlock the address sanitization by overwriting HIGH variable, with unsortedbin attack. I mentioned unsortedbin attack can be very useful in previous writeup, and how2heap concluded this technique can only apply to glibc without tcache feature, which is < 2.26. But is this true?
After some searching, I found CTF-All-In-One proposed a method. It provided a poc:
1 |
|
In short, we can change tcache->counts[tc_idx] == 0xff by doing tcache poisioning, and it will get into else statement in following code snippet, which will return directly instead of getting into another round. Noting that mp_.tcache_count = 7 by default.
1 |
|
Sound good, we can following the steps in poc and do unsortedbin attack against HIGH, in order to unlock the address check mitigation. But once we turn tcache->counts[tc_idx] == 0xff, it means we can no longer utilize this tcache bin, any free chunk of this size(0x40) will get into fastbin. In this challenge, 0x40 is the only size we can allocat, and there is no such fake size around regular control-flow hijack target like __malloc_hook.
Tcache recovering
I want to enable tcache again after unsorted bin attack, so we can perform tcache poisoning easily. I looked into the source of _int_free:
1 | static void |
Our target is getting into the if statement on line 9, and the key is to make tcache->counts[tc_idx] < mp_.tcache_count become true. What is mp_ then?
1 | pwndbg> p mp_ |
It is a structure in libc, which contains some information of the tcache status. And I spotted there is a fake size 64(0x40) in the structure, right before mp_.tcache_count. We can use fastbin attack to modify mp_.tcache_count here!
1 | pwndbg> p &mp_.tcache_count |
Our target is to make tcache->counts[tc_idx] < mp_.tcache_count become true, tcache->counts[tc_idx]=0xffffffffffffffff(-1) now, while the comparison is unsigned, we cannot found any tcache_count to fullfill the condition. However, we can make tcache->counts[tc_idx]=0xfffffffffffffffe(-2) and mp_.tcache_count=0xffffffffffffffff(-1). Then we are good to use tcache again.
Exploitation
Here is the exploit plan:
- leak code address
- leak libc address
- leak heap address
- tcache poisioning to make
tcache[idx]->count = -2 - unsortedbin attack aginst
HIGH - fastbin attack against
mp_.tcache_count, make it-1. - tcache poisioning against
__malloc_hooktoone_gadget.
The final exp, this is constructed when ASLR is off, some adjustments is necessary for remote target:
1 | from pwn import * |
Wrapup
In this writeup, we can see how to perform unsortedbin attack to overwrite a certain variable in glibc 2.27, and recover the tcache feature afterward. I think that the 0x40 size is carefully chosen by the designer, helpping us to get a foothole on mp_ structure. The writeup from r3kpig use another method to change HIGH variable, and this post mentioned the smallbin cache filling mechanisum can also achieve same effect.