glibc Allocator: Unsorted Bin into Stack
Introduction
The “Unsorted Bin into Stack” attack is a technique that targets the Unsorted Bin to achieve an arbitrary memory allocation (in this case, on the stack). By corrupting the bk (backward) pointer and the size field of a chunk in the Unsorted Bin, an attacker can trick the allocator into treating a forged chunk on the stack as the next available chunk in the bin.
When a malloc() request is made that cannot be satisfied by the exact size of chunks in the Unsorted Bin, the allocator iterates through the bin. If we have corrupted a chunk’s bk to point to our stack-based fake chunk, and we’ve set the original chunk’s size to something small (to trigger the sorting/iteration logic), the allocator will eventually process our fake chunk.
Prerequisites
- Unsorted Bin Corruption: Ability to overwrite both the
sizeandbkpointers of a chunk in the Unsorted Bin. - Fake Chunk Control: Ability to forge a valid chunk header at the target location (e.g., the stack).
- GLIBC Version: Effective on glibc < 2.29. Newer versions check if
victim->bk->fd == victim.
Example Code
This PoC demonstrates the attack by returning a pointer to a stack buffer and using it to overwrite the saved return address.
#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};
intptr_t* victim = malloc(0x100);
malloc(0x100); // barrier
free(victim);
// Forge fake chunk on stack
stack_buffer[1] = 0x100 + 0x10;
stack_buffer[3] = (intptr_t)stack_buffer;
// VULNERABILITY: Corrupt unsorted bin size and bk
victim[-1] = 32;
victim[1] = (intptr_t)stack_buffer;
char *p2 = malloc(0x100);
intptr_t sc = (intptr_t)jackpot;
memcpy((p2+40), &sc, 8);
assert((long)__builtin_return_address(0) == (long)jackpot);
return 0;
}Attack Flow Explained
1. Bin Preparation
We allocate and then free a victim chunk. Because it’s large enough and not adjacent to the top chunk (thanks to p1), it’s placed in the Unsorted Bin.
2. Forging the Stack Chunk
We set up a “fake” chunk header on the stack:
stack_buffer[1]: This is thesizefield. It must be at least as large as the nextmallocrequest (plus metadata) to be considered a valid candidate.stack_buffer[3]: This is thebkfield. In a simple Unsorted Bin attack, this can point to any writable memory to avoid crashes during theunlink-like operation performed when the chunk is removed.
3. The Vulnerability: Corrupting the Freed Chunk
We assume a vulnerability (like a Use-After-Free or heap overflow) allows us to modify the metadata of the victim chunk while it’s in the Unsorted Bin.
- Shrink the size: We set
victim->sizeto a small value (e.g.,32). This ensures that when we later callmalloc(0x100), the allocator will decide this chunk is too small to satisfy the request and will move to the next chunk in thebkchain. - Corrupt BK: We point
victim->bkto ourstack_buffer.
4. Triggering the Return
When malloc(0x100) is called:
- The allocator looks at the first chunk in the Unsorted Bin (
victim). - It sees
size == 32, which is too small for a0x110(requested0x100 + 0x10) size. - It moves
victimto the SmallBin and follows itsbkpointer to the next chunk: ourstack_buffer. - It sees our
stack_bufferhassize == 0x110. - It satisfies the request by returning
&stack_buffer[2].
The attacker now has a pointer to the stack and can perform arbitrary writes, such as overwriting the saved return address to hijack the program’s control flow.