Post

tcache_perthread_struct to AAW

How to shell when there you cannot malloc to libc

tcache_perthread_struct to AAW

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.

  1. allocate a chunk to TLS section.
  2. Overwrite the tcache_perthread_struct pointer to point to target_addr-0x80.
  3. allocate a 0x10 chunk(A).
  4. free the chunk(A)
  5. edit the chunk(A) to PROTECT_PTR(value)
  6. allocate a 0x10 chunk(B) to get the value written to target_addr.
  7. (optional) allocate a 0x20 chunk(C) for AAW to target_addr+0x08.
This post is licensed under CC BY 4.0 by the author.