Fastbin Dup
개요 fastbin의 단방향 연결 리스트 구조를 악용해 같은 청크를 두 번 free(double free)함으로써 fastbin freelist에 순환 참조를 만들고, 이후 malloc 호출 시 임의 주소를 청크로 반환받아 원하는 메모리 영역에 쓰기 권한을 획득하는 기법.
전제 조건
- Double Free 취약점 — 동일한 fastbin 크기의 청크를 보안 검사를 우회하면서 두 번 free할 수 있어야 함
- 공격자 제어 가능 범위 — malloc/free 호출 횟수와 인자(크기, 내용) 모두 제어 가능해야 하며, 가짜 청크(fake chunk)를 원하는 타겟 주소 근처에 구성할 수 있어야 함
- 환경 조건 — glibc 2.27 이하 (tcache 비활성 또는 우회된 환경), 청크 크기가 fastbin 범위(x64 기준 0x20~0x80) 이내
공격 원리 fastbin은 LIFO 구조의 단방향 연결 리스트(singly-linked list)로 관리되며, free 시 청크의 fd 포인터가 기존 head를 가리키도록 설정된다. double free 방지를 위해 glibc는 free 직전 현재 fastbin head와 해제하려는 청크가 동일한지만 검사한다(인접 청크는 확인하지 않음). 따라서 A → free(A) → free(B) → free(A) 순서로 호출하면 head 검사를 통과하면서 리스트에 순환이 생긴다.
이후 malloc을 세 번 호출하면 세 번째 호출에서 A가 다시 반환되고, 이때 A의 fd 값을 가짜 청크 주소로 덮어쓰면 네 번째 malloc에서 해당 가짜 주소를 청크로 반환받는다.
[ free(A) → free(B) → free(A) 후 fastbin 상태 ]
fastbin head
│
▼
┌──────┐ fd ┌──────┐ fd ┌──────┐ fd
│ A │ ────────▶ │ B │ ────────▶ │ A │ ────────▶ (순환)
└──────┘ └──────┘ └──────┘
[ malloc() × 3 후, 3번째 반환된 A에 fake_addr 기록 ]
fastbin head
│
▼
┌───────────┐
│ fake_addr │ ◀── 4번째 malloc() 에서 반환됨
└───────────┘
가짜 청크 조건 (size 필드 검증):
fake_chunk->size & ~(PREV_INUSE|IS_MMAPPED|NON_MAIN_ARENA)
== 요청한 fastbin 크기
예) 0x70 크기 fastbin 공략 시:
*(size_t *)(fake_addr - 8) 값이 0x70 또는 0x71 이어야 함
공격 시나리오 예시
1. [초기 세팅]
A = malloc(0x60) // 동일 크기 청크 3개 할당
B = malloc(0x60)
C = malloc(0x60) // top chunk와 병합 방지용 guard
2. [Double Free로 순환 리스트 구성]
free(A) // fastbin: A → NULL
free(B) // fastbin: B → A → NULL
free(A) // fastbin: A → B → A (순환, head != A 이므로 검사 통과)
3. [가짜 청크 주소를 fd에 삽입]
ptr1 = malloc(0x60) // A 반환, A->fd 에 fake_addr 기록
ptr2 = malloc(0x60) // B 반환
ptr3 = malloc(0x60) // A 재반환
*(ptr3) = fake_addr // A->fd = fake_addr (__malloc_hook - 0x23 등)
4. [임의 주소 청크 획득 및 값 쓰기]
malloc(0x60) // B 반환 (리스트 소진)
victim = malloc(0x60) // fake_addr 반환 ← 여기에 원하는 값 기록
*(victim + offset) = one_gadget // 실행 흐름 탈취
주요 타겟
| 타겟 | 목적 |
|---|---|
__malloc_hook | malloc 호출 시 훅이 실행되므로 one_gadget으로 덮어 셸 획득 |
__free_hook | free 호출 시 실행, /bin/sh 포인터와 함께 system 주소 기록 |
__realloc_hook / __realloc_hook 인근 | size 검사 우회를 위한 0x7f 바이트가 자연스럽게 존재하는 위치로 자주 활용 |
| GOT (Global Offset Table) | RELRO 미적용 바이너리에서 라이브러리 함수 포인터 교체 |
| 스택 상의 지역변수 / 반환 주소 | 스택 주소 릭이 있을 경우 ROP 체인 구성 |
패치 이후 (glibc 2.32+) glibc 2.32부터 fastbin의 fd 포인터에 Safe Linking 기법이 도입되어, 포인터 저장 시 heap base >> 12 값과 XOR한 값을 기록한다. 이로 인해 단순히 fake_addr을 fd에 쓰는 것만으로는 원하는 주소를 반환받을 수 없으며, heap 주소 릭이 추가로 필요하다. 또한 glibc 2.27부터는 tcache가 기본 활성화되어 fastbin보다 tcache가 우선 사용되므로, fastbin dup 자체를 위해 tcache를 먼저 소진하거나 우회해야 한다.
/* glibc 2.32 _int_free() 중 Safe Linking 적용 부분 */
// free 시 fd 저장
p->fd = PROTECT_PTR(&p->fd, old);
// PROTECT_PTR 매크로:
#define PROTECT_PTR(pos, ptr) \
((__typeof (ptr)) ((((size_t) pos) >> PAGE_SHIFT) ^ ((size_t) ptr)))
/* malloc 시 복원 */
victim = REVEAL_PTR(victim->fd);
// REVEAL_PTR: 동일한 XOR 연산으로 원래 포인터 복구요약
fastbin의 느슨한 double free 검사(head 단순 비교)를 이용해 freelist에 순환 참조를 만들고, 이후 malloc 흐름에서 임의 주소를 청크로 반환받아
__malloc_hook등 핵심 포인터를 덮어쓰는 기법. glibc 2.32+의 Safe Linking 및 2.27+의 tcache 도입으로 단독 사용이 어려워져 heap 주소 릭과 tcache 우회를 함께 요구한다.