혜랑's STORY

[Dreamhack] Heap Allocator Exploit : Double Free 본문

2021 SISS 21기 활동/2학시 시스템

[Dreamhack] Heap Allocator Exploit : Double Free

hyerang0125 2021. 9. 17. 16:00

Double Free

Double Free는 해제된 힙 청크를 다시 해제할 때 발생하는 버그이다. 

힙을 해제하면 bin이라는 연결 리스트에 추가되고, 이는 힙을 재할당할 때 참조된다. 해당 버그가 발생하면 연결 리스트에 중복된 힙 주소를 추가하기 때문에 두 개의 객체가 동일한 메모리를 사용할 수 있다.

ptmalloc2 할당자는 Double Free를 방지하기 위해 동일한 힙 청크를 연속으로 여러번 해제할 수 없도록 하는 코드가 존재한다. 해당 코드를 이해하고 우회하는 방법을 알아보자.

// gcc -o dfb1 dfb1.c
#include <stdlib.h>
int main()
{
	char ptr = malloc(32);
	char ptr2 = malloc(32);
	
	free(ptr);
	free(ptr);
	return 0;
}

dfb1.c는 32 바이트의 크기로 할당된 청크를 두 번 연속 해제하는 예제이다. dfb1을 실행하면 비정상 종료를 하게 된다.

에러가 발생하는 원인은 다음과 같다.

if (__builtin_expect (old == p, 0)
{
       errstr = "double free or corruption (fasttop)";
      goto errout;
}

이전에 해제한 힙 청크의 포인터인 old와 현재 해제할 힙 청크의 포인터인 p가 같다면 에러 메세지를 출력하고 비정상 종료하게 된다.

힙을 할당하고 해제하면 할당된 힙 청크 주소가 크기에 맞는 bin에 들어가고 같은 bin의 크기로 재할당 요청이 오면 순차적으로 할당이 된다.

만약 Double Free가 발생해 중복된 주소가 bin에 들어가 있으면, 같은 크기로 할당 요청이 여러 번 들어왔을 때 동일한 메모리에 두 개의 객체가 할당될 수 있다. 이때 입력이 가능할 경우 free된 힙 청크의 메타데이터를 조작할 수 있게 된다.

다음은 Double Free의 이해를 돕기 위한 그림이다.

dreamhack 그림 자료 : 빨간색이 p, 파란색이 Old, 검정색은 None이다.

// gcc -o dfb2 dfb2.c
#include <stdlib.h>
int main()
{
	char *ptr = malloc(32);     // 0x602010 
	char *ptr2 = malloc(32);    // 0x602030
	
	free(ptr);
    free(ptr2);
	free(ptr);
	return 0;
}

dfb2.c는 old와 p 포인터를 다르게 하여 검증을 우회하고 Double Free를 발생시키는 코드이다.

마지막 ptr이 해제될 때 old 포인터는 ptr2의 주소가 저장되고, p 포인터에는 ptr의 주소가 저장되면서 old와 p는 다른 주소를 가지기 때문에 검증을 우회하여 Double Free가 발생한다.

다음은 line 10이 실행된 직후의 fastbin freelist를 확인한 결과이다.

Double Free가 발생하기 전의 모습이다. FD 포인터는 해제된 영역을 순차적으로 가리키고 있다.

다음은 ptr2를 다시 한 번 해제한 이후의 fastbin freelist이다.

위 결과를 보면 중복된 주소가 freelist에 들어가 있는 것을 확인할 수 있다.

fastbin에서 Double Free가 발생하고 같은 bin의 크기로 할당 요청이 들어온다면 0x602030, 0x602000, 0x602030 주소에 순차적으로 힙을 할당하여 두 개의 객체가 하나의 메모리를 사용할 수 있게 된다.