Unsorted bin attack
Unsorted bin attack?
Unsorted bin은 다양한 크기의 해제된 청크들이 다른 bin으로 정렬되기 전에 임시로 저장되는 공간이다.
해제된 청크가 Unsorted bin에 들어오면 청크의 fd, bk 포인터는 unsorted bin의 헤더를 가리키게 되어 이중 연결 리스트를 생성한다.
이 때 Unsorted bin에 있는 청크의 bk 포인터를 공격자가 덮어씌울 수 있다면 임의의 주소에 main arena 영역의 값을 쓸 수 있다.
1. 공격 조건
- Unsorted bin에 들어갈만한 청크를 생성할 수 있어야 한다.
- Unsorted bin에 들어간 청크의 bk 포인터 값을 변경할 수 있어야 한다.
- 해제된 청크와 동일한 크기의 청크를 요청할 수 있어야 한다.
(동일하지 않더라도 조작된 청크가 malloc 호출 시 바로 처리되도록 해야한다.)
2. glibc 2.23 Unsorted bin 동작 과정
for (;; )
{
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
- unsorted_chunks(av)는 Unsorted bin의 헤더를 가리킨다.
- unsorted_chunks(av)→bk는 가장 먼저 삽입되어 제일 오래된 청크를 가리킨다. (unsorted bin은 FIFO 처럼 동작한다.)
- Unsorted bin의 헤더와 가장 오래된 청크가 다르면 free chunk가 존재하므로 victim에 unsorted_chunks(av)→bk의 값을 저장한다
- bck에 victim→bk 값을 저장한다.
Unsorted_chunks(av)의 매크로를 살펴보자
/* The otherwise unindexable 1-bin is used to hold unsorted chunks. */
#define unsorted_chunks(M) (bin_at (M, 1))
unsorted_chunks는 bin_at 매크로를 호출한다.
bin_at의 매크로를 살펴보자
/* addressing -- note that bin_at(0) does not exist */
#define bin_at(m, i) \\
(mbinptr) (((char *) &((m)->bins[((i) - 1) * 2])) \\
- offsetof (struct malloc_chunk, fd))
bin_at 매크로는 &main_arena.bins[0] 주소에서 0x10을 빼서 prev_size와 size 필드가 있는 것처럼 가상의 청크 주소를 만들어 낸다.
(32bit 면 0x8을 뺀다)
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
- Unsorted bin의 마지막 청크를 bck로 설정한다.
- bck의 fd 포인터가 unsorted bin의 헤더를 가리키게 만든다.
- unsorted_chunks(av)는 bin_at 매크로를 통해 계산된 main_arena 구조체 내부의 주소이다.
최종적으로 bck→fd 에 main_arena 영역의 주소가 써지게 된다.
victim 청크의 bk를 조작하면 공격자가 원하는 주소에 main_arena 영역의 값을 쓸 수 있다.
→ libc leak이 가능하다
공격 시나리오
1. 정상적인 경우
unsorted bin에 chunk가 2개 있는 경우는 다음과 같다.
- victim = unsorted_chunks(av)→bk
- bck = victim→bk

chunk A 를 재할당 하면 다음과 같이 동작한다.

unsorted bin에서 chunk A를 제거한다.
- unsorted_chunks(av)→bk = bck
- bck→fd = unsorted_chunks(av)
2. 비정상적인 경우
unsorted bin에 chunk가 1개 있는 경우는 다음과 같다
- victim = unsorted_chunks(av)→bk
- bck = victim→bk

chunk A의 bk값은 main_arena를 가리켜야 하지만 bk 값이 조작된 상태이다.
chunk A를 재할당 하면 다음과 같다.

fake chunk의 fd 주소 값에 unsorted_chunks(av) 값이 들어가 있는 것을 볼 수 있다.
→ 원하는 위치에 main arena의 주소를 쓸 수 있다.
fake chunk의 fd에 main arena 주소 값을 쓰고 싶다면 chunk A의 bk 위치에 [원하는 주소 값 - 16]을 쓰면 된다.
예제 코드
#include <stdio.h>
#include <stdlib.h>
#define ALLOC_SIZE 0x410
long target;
int main(void){
fprintf(stderr, "target : 0x%lx\\n", target);
long *ptr = malloc(ALLOC_SIZE);
malloc(ALLOC_SIZE);
free(ptr);
ptr[1] = (long)&target - 16;
malloc(ALLOC_SIZE);
fprintf(stderr, "target : 0x%lx\\n", target);
}
1. chunk 할당
long *ptr = malloc(ALLOC_SIZE);
malloc(ALLOC_SIZE);
- malloc으로 0x410크기의 청크 2개를 할당한다.
해제된 청크가 Top Chunk와 병합되는 것을 막기 위해 일반적으로 2개의 청크를 할당한다.

청크가 2개 할당된 것을 볼 수 있다.

ptr 청크의 fd와 bk에 main arena영역이 저장된 것을 볼 수 있다.
2. unsorted bin에 chunk 삽입
free(ptr);
- 첫 번째 청크 ptr을 해제하여 unsorted bin에 집어넣는다.

첫 번째 청크 ptr1이 해제된 것을 볼 수 있다.
3. target 조작
ptr[1] = (long)&target - 16;
- ptr[0]은 해제된 청크의 fd 포인터를 가리킨다.
- ptr1[1]은 그 바로 뒤인 bk 포인터를 가리킨다.
- ptr 청크의 bk 포인터 값을 &target - 16으로 덮어씌운다.

ptr의 bk포인터가 &target - 16으로 변한 것을 확인할 수 있다.

청크를 확인해보면 bk가 0x601060으로 바뀌어 있다.
4. 공격 실행
malloc(ALLOC_SIZE);
- malloc을 다시 호출하여 공격을 실행한다.
malloc은 메모리 할당을 위해 unsorted bin을 확인하고 조작한 chunk를 발견하게 된다.
unsorted bin에서 unlink 하기 위해 bck→fd = unsorted_chunks(av) 연산을 수행한다.
→ bck는 조작된 &target - 16의 주소이다.
결국 *(target - 16 + 16) = main arena 주소 연산이 수행된다.

- target : 0x601070
- main_arena : 0x7ace509c0b78
target에 main arena의 값이 쓰인 것을 확인할 수 있다.

실행해보면 target의 값이 변경된 것을 볼 수 있다.
'Pwnable > Heap' 카테고리의 다른 글
| overlapping chunks (0) | 2026.01.21 |
|---|---|
| Unsorted bin Memory Leak (0) | 2026.01.20 |
| Unsafe Unlink (0) | 2026.01.15 |
| Fastbin Consolidate (1) | 2026.01.14 |
| Fastbin Duplicate (0) | 2026.01.13 |