0x00 UAF原理
如上代碼所示,指針p1申請內存,打印其地址,值
然后釋放p1
指針p2申請同樣大小的內存,打印p2的地址,p1指針指向的值
Gcc編譯,運行結果如下:
p1與p2地址相同,p1指針釋放后,p2申請相同的大小的內存,操作系統會將之前給p1的地址分配給p2,修改p2的值,p1也被修改了。
由此我們可以知道:
1.在free一塊內存后,接着申請大小相同的一塊內存,操作系統會將剛剛free掉的內存再次分配。
根本原因是dllmalloc:
參考資料:http://blog.csdn.net/ycnian/article/details/12971863
當應用程序調用free()釋放內存時,如果內存塊小於256kb,dlmalloc並不馬上將內存塊釋放回內存,而是將內存塊標記為空閑狀態。這么做的原因有兩個:一是內存塊不一定能馬上釋放會內核(比如內存塊不是位於堆頂端),二是供應用程序下次申請內存使用(這是主要原因)。當dlmalloc中空閑內存量達到一定值時dlmalloc才將空閑內存釋放會內核。如果應用程序申請的內存大於256kb,dlmalloc調用mmap()向內核申請一塊內存,返回返還給應用程序使用。如果應用程序釋放的內存大於256kb,dlmalloc馬上調用munmap()釋放內存。dlmalloc不會緩存大於256kb的內存塊,因為這樣的內存塊太大了,最好不要長期占用這么大的內存資源。
2.通過p2能夠操作p1,如果之后p1繼續被使用(use after free),則可以達到通過p2修改程序功能等目的。
0x01 一個利用場景
在網上找了一個有UAF漏洞的ctf程序,參考博客如下:
http://www.syjzwjj.com/use-after-free-tutorial/
作者已經分析的很詳細了,通過對整個程序的調試,來理解UAF的利用。
1. 結合IDA與程序運行,簡要理解程序的功能。
選擇1可以留言,信息會存到1個鏈表中
選擇2將會遍歷鏈表找到對應的節點,打印節點信息
打印完,可以對其進行刪除修改等操作
鏈表節點結構如下
Tips:在逆向代碼中應用數據結構參考《IDA Pro權威指南》第8章數據類型與數據結構
2. UAF漏洞代碼
鏈表節點被刪除后,可以繼續進入modify函數,modify函數之后可以繼續進入modify函數。
Delete函數如下:
Delete函數中對節點的進行了free操作,如果在循環代碼中,進行delete操作,釋放節點之后,再選擇2進入modify函數。
Modify函數如下:
Modify函數從用戶讀取數據,然后拷貝到對應的指針中,但此時使用的是一個已經釋放的指針。當輸入content時,會取content的長度作為大小分配內存,當分配內存大小等於msg結構大小(48字節,通過前面的結構獲得)時,會將剛才釋放的內存分配給content指針。
如下所示
Content指向了msg結構本身
接着將content拷貝到content指針中,即我們的輸入會拷貝到這個被釋放的節點內存中。
在循環代碼中,modify完之后可以繼續進入modify。 此時會再對msg結構的author,title,content指針指向的地址進行拷貝。 由於上一步已經能夠對msg結構進行隨意更改了,所以將幾個memcpy的目的地址修改成想要的地址即可進行任意內存(屬於該程序的合法內存)的修改了。
至此我們已經能夠完成任意內存地址的修改了。
下面需要考慮的就是完成一些命令執行的利用。
3. 漏洞利用執行命令
要執行命令,需要調用system函數,但是代碼中並沒有system函數,需要如何完成命令執行呢?
可以利用linux的延遲加載功能,改變strlen函數的指向,將原本要執行的strlen,改成執行system。
Tips:延遲加載
當調用標准函數時,需要從其他so文件中將標准函數加載進來,並不直接調用函數的地址,而是通過一張中間表跳轉到函數的真正地址。
以strlen函數的調用為例
在程序調試中,打印0x804c04c的信息
整個過程如下:
Call strlen跳轉到strlen函數,里面只有一句jmp ds:off_804c04c
當程序運行起來時0x804c04c里的值為0xb7658210,才是strlen的真正地址
即0x804c04c中存儲libc庫中的strlen的真正地址。
如果將0x804c04c的值改掉,改成system的地址0xb7614360。 雖然看起來調用的是strlen,但真正執行的是system函數。
修改前:
Call strlen
Strlen:
Jmp 0x804c04c
0x804c04c: 0xb7658210(strlen)
修改后:
Call strlen
Strlen:
Jmp 0x804c04c
0x804c04c: 0xb7614360 (system)
Tips: 尋址system的真正地址
由於整個程序並沒有調用system函數,所以在程序的重定位表中找不到system。 所以需要自己定位一下system在這個程序中的真正地址。
Libc被裝到0xb75d6000-0xb777a000 地址空間,大小為0x1a4000
編寫程序調用system函數
調試運行查看其libc地址空間
被裝入到0xb7e10000-0xb7fb4000,大小也為0x1a4000。
所以system在漏洞程序中的地址應為 =(system在調用程序中的地址-調用程序libc起始地址+漏洞程序libc起始地址)
0xb7e4e360
System在漏洞程序中地址= 0xb7e4e360-0xb7e10000+0xb75d6000= 0xb7614360
4. poc運行效果
執行一個mkdir hack命令建立一個hack目錄
Poc片段
5. 過程回顧
1) delete函數中釋放節點
2) modify函數傳入被釋放的指針
3) modify函數中分配內存大小可控,通過分配與節點相同的大小,取得被釋放內存的控制權
4) 修改將要被拷貝的目的地址msg->author指針指向將要被執行的函數strlen的中間表地址
5) 將strlen指向的真實strlen地址,修改為system的真實地址
6) 看似執行call strlen,實則執行了system函數
0x02 總結
在指針釋放后再申請相同大小的內存,系統會將釋放的地址進行分配,以提高系統運行速度,因此可以修改到被釋放的內存數據,如果被釋放的指針繼續被使用,則會造成UAF漏洞。
通過UAF漏洞,可能可以造成一些任意內存的修改,結合代碼特點,可能會造成任意內存的讀取或者,嚴重的能夠造成任意命令的執行,獲得shell。 取決於被釋放的指針是怎么使用的。
相關知識點:gdb調試(如何調試fork出來的程序),延遲加載(plt與got),標准函數在內存中的定位,UAF修改被釋放指針內容的原因dllmalloc