fastbin_dup.c 소스코드
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main()
{
fprintf(stderr, "This file demonstrates a simple double-free attack with fastbins.\n");
fprintf(stderr, "Allocating 3 buffers.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);
fprintf(stderr, "1st malloc(8): %p\n", a);
fprintf(stderr, "2nd malloc(8): %p\n", b);
fprintf(stderr, "3rd malloc(8): %p\n", c);
fprintf(stderr, "Freeing the first one...\n");
free(a);
fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);
fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b);
fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);
fprintf(stderr, "Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
a = malloc(8);
b = malloc(8);
c = malloc(8);
fprintf(stderr, "1st malloc(8): %p\n", a);
fprintf(stderr, "2nd malloc(8): %p\n", b);
fprintf(stderr, "3rd malloc(8): %p\n", c);
assert(a == c);
}
몰랐던 함수들
우선 제대로 알지 못했던 fprintf, assert 함수에 대해서 알아보고 가자.(heap 공부만 하려는 거면 사실 별로 몰라도 되는 내용이긴 하나 그래도 모르는걸 냄겨두고 넘어가기는 찝찝하여 했다.)
- fprintf(FILE stream, const char format, ...)
- 특정한 스트림에 일련의 데이터를 특정한 형식에 맞추어 쓰게 된다.
- 이 때, 그 형식은 형식 문자열에 지정되어 있으며, 출력할 데이터는 형식 문자열 다음에 오는 인자들에 써주면 된다.
- 위와 같이 fprintf(stderr, const char* format)으로 된 경우 표준에러출력이기 때문에 버퍼를 거치지 않고 그냥 바로 출력 시킨다고 한다.
- printf()의 경우 버퍼를 거처 출력하기 때문에 에러가 날 경우 제대로 출력이 안될 수 있다.
- 따라서 디버깅용도의 출력문이다.
- assert
- 디버깅 용도의 함수
- assert함수를 에러가 날 것이라고 예상되는 곳에 위치시켜두고 에러가 발생하면 개발자에게 버그 발생위치, call stack등 디버깅에 유용한 정보들을 마구 뿌려준다.
- 위 fastbin_dup에서는 Double_Free가 제대로 발생하여 c Heap chunk 포인터가 a Heap chunk 포인터를 가리키게 되었는지 확인하는 용도로 사용되었다.
분석
fprintf(stderr, "This file demonstrates a simple double-free attack with fastbins.\n");
이 파일은 fastbin을 이용한 간단한 Double_Free 공격을 보여준다.
fprintf(stderr, "Allocating 3 buffers.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);
3개의 버퍼를 할당한다.
각각의 버퍼를 할당받은 것을 출력문을 통해 확인할 수 있다.
fprintf(stderr, "Freeing the first one...\n");
free(a);
첫번째 버퍼 힙 free
fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);
만약 여기서 한번 더 a Heap chunk를 해제하면 크래시가 발생한다. 이유는 free list(bin을 가리키는 듯)최상위가 a Heap chunk이기 때문이다. (이유는 드림핵 카테고리 Double_Free에 올려놔서 생략(근데 비공개잖아...? DreamHack가서 보십셔 여러분)
실제로 해당 주석을 풀고 실행하면 크래시가 발생한다.
fprintf(stderr, "So, instead, we'll free %p.\n", b);
free(b);
그래서 대신에 우리는 b Heap chunk를 먼저 해제해준다.
fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);
우리는 이제 a Heap chunk를 다시 해제할 수 있다. 이유는 free list의 최상위가 아니기 때문이다.
fprintf(stderr, "Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
a = malloc(8);
b = malloc(8);
c = malloc(8);
fprintf(stderr, "1st malloc(8): %p\n", a);
fprintf(stderr, "2nd malloc(8): %p\n", b);
fprintf(stderr, "3rd malloc(8): %p\n", c);
free list를 보면 a Heap chunk가 두번 해제(Double_Free)된 것을 볼 수 있다.
다시 a,b,c를 malloc으로 할당해주면 우리는 a Heap chunk를 두번 할당 받을 수 있다.
그 이유는 할당을 할 때 bin(free list)에서 같은 사이즈 혹은 비슷한 사이즈의 free된 chunk가 있는지 확인을 한 뒤 있다면 해당 리스트를 따라 할당하기 때문이다.
따라서 bin(free list)에 있는 a heap chunk(0xce6010)가 먼저 a에 할당되고 b heap chunk(0xce6030)가 b에 할당, 다시 a heap chunk(0xce6010)이 c에 할당되게 된다.