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_hook
toone_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.