tcache_perthread_struct to AAW
How to shell when there you cannot malloc to libc
Motivation
Recent pwnable challenge gave me this scenario:
- you have UaF
- you cannot malloc to libc, the code will verify the pointer to exclude from libc to stack.
- PIE is enabled
Obviously, leaking the PIE base was the main goal as it was note chall - double pointer in data section. But those attempts to leak the PIE base were failed, and I had to find another way to get shell.
Interestingly, the TLS section lived under the libc mapped region, and the code did not verify the pointer to exclude from TLS section. So, I wen digging on what I can do with TLS section…
Background
tcache_perthread_struct
Tcache is managed per-thread. This implies that the struct is stored somewhere in the thread’s memory space, and it is not shared across threads. If you telescope the TLS region, you will find a pointer to the heap. That’s right. It’s the pointer to the tcache_perthread_struct.
1
2
3
4
5
typedef struct tcache_perthread_struct
{
uint16_t num_slots[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
tcache’s alloc/free logic
This is a recap of how tcache works. We’ll only focus on writing part.
Allocation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static __always_inline void *
tcache_get_n (size_t tc_idx, tcache_entry **ep)
{
tcache_entry *e;
if (ep == &(tcache->entries[tc_idx]))
e = *ep;
else
e = REVEAL_PTR (*ep);
if (__glibc_unlikely (!aligned_OK (e)))
malloc_printerr ("malloc(): unaligned tcache chunk detected");
if (ep == &(tcache->entries[tc_idx]))
*ep = REVEAL_PTR (e->next);
else
*ep = PROTECT_PTR (ep, REVEAL_PTR (e->next));
--(tcache->counts[tc_idx]);
e->key = 0;
return (void *) e;
}
The assignment to *ep is the critical part.
1
2
3
4
if (ep == &(tcache->entries[tc_idx]))
*ep = REVEAL_PTR (e->next);
else
*ep = PROTECT_PTR (ep, REVEAL_PTR (e->next));
If the popped entry is the head of the tcache bin, the next pointer is directly assigned to the head of the bin. Note that REVEAL_PTR (e->next) is written to *ep without any protection.
On the other hand, freeing doesn’t contribute much to the writing, so skipping it for now.
Exploitation
Assumption: LIBC, HEAP leaked. UAF primitives available.
- allocate a chunk to TLS section.
- Overwrite the tcache_perthread_struct pointer to point to
target_addr-0x80. - allocate a
0x10chunk(A). - free the chunk(A)
- edit the chunk(A) to
PROTECT_PTR(value) - allocate a
0x10chunk(B) to get thevaluewritten totarget_addr. - (optional) allocate a
0x20chunk(C) for AAW totarget_addr+0x08.