接下來的時間會通過how2heap學習堆的知識,這個系列可能會更新很多篇,因為每天學習到的東西要保證吸收消化,所以一天不會學習很多,但是又想每天記錄一下。所以開個系列。
first_fit
此題的源碼經過簡化,如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 int main() { 5 char* a = malloc(512); //0x200 6 char* b = malloc(256); //0x100 7 char* c; 8 fprintf(stderr, "1st malloc(512): %p\n", a); 9 fprintf(stderr, "2nd malloc(256): %p\n", b); 10 strcpy(a, "AAAAAAAA"); 11 strcpy(b, "BBBBBBBB"); 12 fprintf(stderr, "first allocation %p points to %s\n", a, a); 13 fprintf(stderr, "Freeing the first one...\n"); 14 free(a); 15 c = malloc(500); 16 fprintf(stderr, "3rd malloc(500): %p\n", c); 17 strcpy(c, "CCCCCCCC"); 18 fprintf(stderr, "3rd allocation %p points to %s\n", c, c); 19 fprintf(stderr, "first allocation %p points to %s\n", a, a); 20 }
用gcc進行編譯處理,命令:gcc -g first_fit1.c
運行一下看輸出結果:
這個程序想讓我們明白的是假如我先malloc了一個比較大的堆,然后free掉,當我再申請一個小於剛剛釋放的堆的時候,就會申請到剛剛free那個堆的地址。還有就是,我雖然剛剛釋放了a指向的堆,但是a指針不會清零,仍然指向那個地址。這里就存在一個uaf(use_after_free)漏洞,原因是free的時候指針沒有清零。
接下來再放一些學習資料上面話,比較官方,比較准確。
fastbin_dup
還是先放一下程序源碼:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 int main() { 5 fprintf(stderr, "Allocating 3 buffers.\n"); 6 char *a = malloc(9); 7 char *b = malloc(9); 8 char *c = malloc(9); 9 strcpy(a, "AAAAAAAA"); 10 strcpy(b, "BBBBBBBB"); 11 strcpy(c, "CCCCCCCC"); 12 fprintf(stderr, "1st malloc(9) %p points to %s\n", a, a); 13 fprintf(stderr, "2nd malloc(9) %p points to %s\n", b, b); 14 fprintf(stderr, "3rd malloc(9) %p points to %s\n", c, c); 15 fprintf(stderr, "Freeing the first one %p.\n", a); 16 free(a); 17 fprintf(stderr, "Then freeing another one %p.\n", b); 18 free(b); 19 fprintf(stderr, "Freeing the first one %p again.\n", a); 20 free(a); 21 fprintf(stderr, "Allocating 3 buffers.\n"); 22 char *d = malloc(9); 23 char *e = malloc(9); 24 char *f = malloc(9); 25 strcpy(d, "DDDDDDDD"); 26 fprintf(stderr, "4st malloc(9) %p points to %s the first time\n", d, d); 27 strcpy(e, "EEEEEEEE"); 28 fprintf(stderr, "5nd malloc(9) %p points to %s\n", e, e); 29 strcpy(f, "FFFFFFFF"); 30 fprintf(stderr, "6rd malloc(9) %p points to %s the second time\n", f, f); 31 }
同樣的gcc編譯運行后看一下運行結果:
程序做了哪些事呢?
1.malloc申請了三個堆,並賦值。
2.free了第一個堆。
3.free了第二個堆。
4.再次free了第一個堆。
5.malloc又申請了三個堆。
發現:第五步malloc申請堆的時候,第一個堆申請到了free第一次的位置,第二個堆申請到了free第二次的位置,第三個堆又申請到了free了第一次的位置。
說白了就是連着free兩次一個堆是不被允許的,但是假如再其中加一個free其他堆,那么就可以對一個堆free兩次。這樣再我們malloc再申請的時候,就可以申請到兩個指針指向同一個堆塊了。
為了方便理解,我們試着在pwndbg里面看看:
首先在11行的位置下了斷點:
可以看到我們申請的三個堆,接下來我們在看一下free(a)、free(b)、再次free(a)時的情況:
--------------------------------------------------------------------------------------------------------
可以看到fastbins形成了一個環,但是其實應該是棧的樣子的,但是由於我們繞過了檢測,就可以形成環。
|Chunk A| -> |chunk B| -->| chunk A|
大概如上個圖,這樣我們就成功繞過了 fastbins 的double free檢查。原因如下:
fastbins 可以看成一個 LIFO 的棧,使用單鏈表實現,通過 fastbin->fd 來遍歷 fastbins。由於 free 的過程會對 free list 做檢查,我們不能連續兩次 free 同一個 chunk,所以這里在兩次 free 之間,增加了一次對其他 chunk 的 free 過程,從而繞過檢查順利執行。然后再 malloc 三次,就在同一個地址 malloc 了兩次,也就有了兩個指向同一塊內存區域的指針。
上面的情況是在libc-2.23版本做的實驗,但是好像版本不同的時候會有其他情況。這里就直接拿資料上的東西了。
-----------------------資料----------------------------------------------
看一點新鮮的,在 libc-2.26 中,即使兩次 free,也並沒有觸發 double-free 的異常檢測,這與 tcache 機制有關,以后會詳細講述。這里先看個能夠在該版本下觸發double-free 的例子:
#include <stdio.h> #include <stdlib.h> int main() { int i; void *p = malloc(0x40); fprintf(stderr, "First allocate a fastbin: p=%p\n", p); fprintf(stderr, "Then free(p) 7 times\n"); for (i = 0; i < 7; i++) { fprintf(stderr, "free %d: %p => %p\n", i+1, &p, p); free(p); } fprintf(stderr, "Then malloc 8 times at the same address\n"); int *a[10]; for (i = 0; i < 8; i++) { a[i] = malloc(0x40); fprintf(stderr, "malloc %d: %p => %p\n", i+1, &a[i], a[i]);} fprintf(stderr, "Finally trigger double-free\n"); for (i = 0; i < 2; i++) { fprintf(stderr, "free %d: %p => %p\n", i+1, &a[i], a[i]); free(a[i]); } }
首先先malloc申請了一個堆,接着連續free了7次。
然后malloc同樣大小的堆塊,申請了8次。
接下來又free了申請的前兩個申請的堆塊。
我們看一下運行結果:
可以從輸出看到,8次重新申請的堆塊都指向一個我們第一次申請的地址。
后記:最后這個列子我還是沒看出有啥可以學習到的。。。但是我疑惑的是,free了7次,為什么第8次malloc的時候,還是指向了第一次malloc的地址。