緩沖區溢出保護機制
Windows
GS安全編譯選項
Visual Studio 2003及以后版本的Visual Studio中默認啟用了這個安全編譯選項。
GS編譯選項為每個函數增加了一些額外的數據和操作:
1、在所有函數調用發生時,向棧幀內壓入一個額外的隨機DWORD,這個隨機數被稱作“canary”,用IDA反匯編時,又被稱作“Security Cookie”。
2、canary位於EBP之前,系統還會在.data的內存區域中存放一個canary的副本。
3、當棧中發生溢出時,canary將被首先淹沒,之后才是EBP和返回地址。
4、在函數返回之前,系統將執行一個額外的安全驗證操作,稱作Security Check。
5、在Security Check過程中,系統將比較棧幀中原先存放的canary和.data中副本的值,若兩者不同,則說明棧中發生了溢出,系統將進入異常處理流程,函數不會正常返回。
canary產生的細節:
-
系統以.data節的第一個雙字作為Cookie的種子,或稱為原始Cookie。
-
在程序每次運行時Cookie的種子都不相同,具有很強的隨機性
-
在棧幀初始化以后系統用ESP異或種子,作為當前函數的Cookie,以此作為不同函數的區別,並增強Cookie的隨機性
-
在函數返回前,用ESP還原出Cookie種子
分析
Security Cookie具有很強的隨機性,在函數運行時猜解幾乎是不可能的。
但是,額外的數據和操作造成了系統性能的下降,為了將對性能的影響降到最小,編譯器在編譯程序的時候並不是對所有的函數都應用GS,以下情況不會應用GS:
函數不包含緩沖區
函數被定義為具有變量參數列表
函數使用無保護的關鍵字標記
函數在第一個語句中包含內嵌匯編代碼
緩沖區不是8字節類型且大小不大於4個字節
繞過GS
1、利用未保護內存
可通過緩沖區不大於4字節的棧溢出直接繞過GS。
2、覆蓋虛函數
程序只有在函數返回時才去檢查canary,在此之前並沒有任何措施,所以我們可以利用C++虛函數在程序檢查canary之前劫持程序流程。
由於C++對象在使用虛函數時,會先通過虛表指針找到虛表,然后從虛表中取出最終的函數入口地址進行調用,所以,當虛函數中變量溢出影響到虛表指針的時候,就可以控制虛標指針指向可控的內存空間,從而當函數執行虛函數時就可以控制程序流程。
3、異常處理
由於開啟GS時並沒有明確對SEH進行保護,所以我們可以通過異常處理來繞過GS。
首先通過超常的字符串覆蓋掉異常處理函數指針,然后只要觸發異常,就可以劫持程序流程。
4、同時替換棧中和.data中的canary
通過特定的溢出條件,來達到可以訪問.data中的數據的目的,根據canary的產生原理,同時修改棧中和.data段中的canary,來達到繞過GS的目的。
SafeSEH
實現原理:
在程序調用異常處理函數前,對要調用的異常處理函數進行一系列有效性檢驗,當發現處理函數不可靠時將終止異常處理函數的調用。集體實現需要操作系統和編譯器的雙重支持。
編譯器
編譯器通過啟用/SafeSEH鏈接選項打開SafeSEH保護,啟用該鏈接選項后,編譯器在編譯程序的時候將程序所有的異常處理函數地址提取出來,編入一張安全SEH表,並將這張表放入程序的映像里,當程序調用異常處理函數時,會將函數地址與安全SEH表進行匹配,檢查調用的異常處理函數是否位於安全SEH表中。
操作系統
1、檢查處理鏈是否位於當前程序的棧中,如果不在棧中,程序將終止異常處理函數的調用。
2、檢查異常處理函數指針是否指向當前棧中,如果指向當前棧中,程序則終止異常處理函數的調用。
3、前面兩項檢查都通過時,程序調用RtlIsValidHandler()函數,來對異常處理函數的有效性進行驗證。
RtlValidHandler():
- 判斷程序是否設置了IMAGEDLLCHARACTERISTICSNO_SEH標識,如果設置了這個標識,這個程序的異常會被忽略。所以當這個標志被設置時,函數直接返回校驗失敗。
- 檢測程序是否包含安全SEH表,如果包含,則將當前異常處理程序函數地址與該表進行匹配,匹配成功則返回校驗成功。
- 判斷程序是否設置ILonly標識,如果設置,說明該程序只包含.NET編譯中間語言,直接返回校驗失敗。
- 判斷異常處理函數地址是否位於不可執行頁,當位於不可執行頁時,檢測DEP是否開啟,若未開啟則返回校驗成功,否則拋出訪問違例異常。
- 如果異常處理函數地址沒有包含在加載模塊的內存空間,檢驗函數將直接進行DEP相關檢測:
判斷異常處理函數地址是否位於不可執行頁,當異常處理函數地址位於不可執行頁時,校驗函數檢測DEP是否開啟,未開啟則校驗成功,開啟則拋出訪問違例異常。
判斷系統是否允許跳轉到加載模塊的內存空間外執行,如果允許則校驗成功,否則校驗失敗。
異常處理函數可以執行的情況:
- 異常處理函數位於加載模塊內存范圍之外,DEP關閉。
- 異常處理函數位於加載模塊內存范圍之內,相應模塊未開啟SafeSEH,同時相應模塊不是純IL(中間語言)。
- 異常處理函數位於加載模塊內存范圍之內,相應模塊開啟SafeSEH,異常處理函數地址包含在安全SEH表中。
繞過SafeSEH
1、不攻擊SafeSEH
攻擊返回地址繞過SafeSEH,未啟用GS保護的棧溢出。
利用虛函數繞過,繞過方式同上。
2、堆
如果SEH中的異常處理函數指針指向堆區,即是安全校驗發現了SEH不可信,仍然會調用其已被修改過的異常處理函數,因此可以控制程序流。
3、利用未啟用SafeSEH模塊
若模塊未啟用SafeSEH,並且該模塊不是僅包含中間語言,這個異常處理就可以執行,所以如果我們能夠在加載的模塊中找到一個未啟用SafeSEH的模塊,就可以利用它里面的指令作為跳板來繞過SafeSEH。
4、利用加載模塊之外的地址
當異常處理函數指針指向類型為Map的映射文件的地址范圍內時,是不對其進行有效性驗證的,如果在這些文件中找到跳轉指令,就可以繞過SafeSEH。
5、Adobe Flash Player ActiveX
Flash Player ActiveX在9.2.124之前的版本不支持SafeSEH,只要在該控件中找到合適的跳板,就可以繞過SafeSEH。
DEP
溢出攻擊的根源在於現代計算機對數據和代碼沒有明確的區分這一先天缺陷,DEP(數據執行保護,Data Execution Prevention)就是用來彌補這一缺陷的。
基本原理
將數據所在的內存頁標識為不可執行,當程序嘗試在數據頁執行指令時,就會拋出異常。主要是為了阻止數據頁(如默認的堆頁、各種堆棧頁以及內存池頁)執行代碼。
軟件DEP:
就是SafeSEH,目的是阻止利用SEH的攻擊。
硬件DEP:
需要CPU的支持,AMD稱之為No-Execute Page-Protection(NX), Intel稱之為Execute Disable Bit(XD),兩者本質上相同。
操作系統通過設置內存頁的NX/XD屬性標記,來指明不能從該內存執行代碼。
硬件DEP的工作狀態:
- Optin:默認僅將DEP保護應用於Windows系統組件和服務,對於其他程序不予保護。
- Optout:為排除列表程序外的所有程序和服務啟用DEP,用戶可以手動在排除列表中指定不啟用DEP保護的程序和服務。
- AlwaysOn:對所有進程啟用DEP保護,不存在排除列表,這種模式下,DEP不可被關閉。
- AlwaysOff:對所有進程都禁用DEP,這種模式下,DEP也不能被動態開啟。
繞過DEP
1、Ret2Libc
Return-to-libc,由於DEP不允許我們直接到非可執行頁執行指令,我們就需要在其他可執行的位置找到符合我們要求的指令,讓這條指令來替我們工作,為了能夠控制程序流程,在這條指令執行后,我們還需要一個返回指令,以便收回程序的控制權,然后繼續下一步操作。
1、通過跳轉到ZwSetInformationProcess函數將DEP關閉后再轉入Shellcode執行。
2、通過跳轉到VirtualProtect函數來將shellcode所在的內存頁設置為可執行狀態,然后再轉入shellcode執行。
3、通過跳轉到VirtualAlloc函數開辟一段具有執行權限的內存空間,然后將shellcode復制到這段內存中執行。
2、可執行內存
找到一段可執行內存,將shellcode復制到這里進行執行,從而劫持程序流程。
3、利用控件
.NET文件具有和PE文件一樣的結構,當被映射到內存時,某些段也具有可執行屬性,可以通過這些段來執行shellcode。
Java applet同.NET一樣,加載內存時也會具有可執行屬性,都可以被我們利用。
4、ROP
同Ret2Libc原理類似,不同的是,不用通過調用系統函數來為shellcode進行鋪墊,直接通過執行程序中的小片段來達到想要達到的目的,一般情況下,不用執行shellcode,也就是不需要哪段內存必須要有可執行屬性。
ASLR
ASLR(Address Space Layout Randomization)通過加載程序的時候不再使用固定的加載基址,從而干擾shellcode定位。
1、映像隨機化
映像隨機化是在PE文件映射到內存時,對其加載的虛擬地址進行隨機化處理,這個地址是在系統啟動時確定的,系統重啟后這個地址會有變化。
2、堆棧隨機化
程序運行時隨機選擇堆棧的基址,與映像隨機化不同的是堆棧的基址不是在系統啟動時確定的,而是在程序打開時確定的,也就是說同一個程序任意兩次運行時的堆棧基址都不相同。
3、PEB與TEB隨機化
微軟在XP SP2之后不再使用固定的PEB基址0x7FFDF000和TEB基址0x7FFDE000,而是使用具有一定隨機性的基址。
TEB存放在FS:0和FS:[0x18]處,PEB存放在TEB偏移0x30的位置
繞過ASLR
1、未啟用ASLR的模塊
Adobe Flash Player ActiveX
Adobe 在Flash Player 10以后開始全面支持微軟的安全特性
2、部分覆蓋進行定位內存地址
映像隨機化只是對映像加載基址的前兩個字節做隨機化處理,這樣我們就可以在一定范圍內控制程序,同時,又由於程序雖然隨機化了基址,但是指令相對於基址的偏移並未發生變化,所以可以通過在相對基址較小范圍處找到跳板指令,即可繞過ASLR。
3、Heap Spray
Heap Spray並沒有官方的定義,理論上知識漏洞攻擊的一部分,只是一種輔助技術,需要結合其他的棧溢出或堆溢出等等各種溢出技術才能發揮作用。
總的來說,Heap Spray是在shellcode前面加上大量的slide code(滑板指令),組成一個注入代碼段。然后向系統申請大量內存,並且反復用注入代碼段來填充。這樣就使得進程的地址空間被大量的注入代碼所占據。然后結合其他的漏洞攻擊技術控制程序流,使得程序執行到堆上,最終將導致shellcode的執行。
最簡單的方式其實就是通過堆申請大量內存,例如200M就可以占領內存中0x0c0c0c0c的位置,然后再這些內存中放入滑板指令和shellcode,當控制程序流程執行到0x0c0c0c0c時,就有機會執行到shellcode。
正如這種方法原理所述,並不需要考慮所謂的基址,自然而然可以繞過ASLR。
還有一個原因就是,雖然基址隨機化,但是基址隨機化的范圍並不會到達0x0c0c0c0c這樣的高地址,所以這種方式繞過ASLR是完全可以的。
Java applet heap spray同樣可以實現繞過,不同點就是,Java applet僅能申請100M的空間,但已經足夠。
SEHOP
SEH是以單鏈表的形式存放在棧中的,而在這個鏈表的末端是程序的默認異常處理函數,專門負責處理前面的SEH鏈都不能處理的異常。
SEHOP的核心任務就是檢查這條SEH鏈的完整性,在程序轉入異常處理前,SEHOP會檢查SEH鏈上最后一個異常處理函數是否為系統固定的終極異常處理函數,如果是,則說明這個SEH鏈沒有被破壞,程序可以執行當前的異常處理函數。如果不是,則說明SEH鏈被破壞,而不會去執行異常處理函數。
繞過SEHOP
理論上來說,有三種途徑:
- 不去攻擊SEH,去攻擊函數返回地址或者虛函數等。
- 利用未啟用SEHOP模塊
- 偽造SEH鏈
前兩種不用多說,都屬於前面說過的內容
偽造SEH鏈
偽造條件:
- 跳板指令指向的地址必須位於當前棧中,而且必須能夠被4整除。
- 該地址存放的異常處理記錄作為異常處理鏈的最后一項,必須要指向終極異常處理函數,所以必須要知道FinalExceptionHandler指向的地址。
- 突破SEHOP檢查后,溢出程序還必須搞定SafeSEH。
顯然是非常困難的,但是當ASLR未開啟,或者處於未開啟SafeSEH的模塊里,則有可能偽造成功。
堆保護機制
PEB random:
PEB的隨機化給DWORD SHOOT攻擊增加了難度。
Safe Unlink:
拆卸雙向鏈表時,會對指針指向的內容是否是相應的堆塊做安全檢查。
heap cookie:
與棧中的Security Cookie類似,微軟在堆中也引入了cookie,用於檢測堆溢出的發生,cookie被布置在堆首部原堆塊的segment table的位置,占一個字節大小。
元數據加密:
塊首中的一些重要數據在保存時會與一個4字節的隨機數進行異或運算,在使用這些數據時,需要在進行一次異或運算來還原。
堆攻擊方式
1、利用chunk重設大小攻擊堆
基本思路:Safe Unlink只會對從FreeList[n]上拆卸chunk時對雙鏈表進行有效性檢驗,但是如果把一個chunk插入到FreeList[n]的時候,並沒有進行檢驗,因此可以利用。
發生插入操作的情況:
- 內存釋放后chunk不再使用會被重新鏈入鏈表。
- 當chunk的內存空間大於申請的空間時,剩余的空間會被建立成一個新的chunk,鏈入鏈表。
第二種情況下,有可利用的機會:
具體流程如下:
1、將FreeList[0]上最后一個chunk的大小與申請空間的大小進行比較,如果chunk的大小大於等於申請的空間,則繼續分派,否則擴展空間。
2、從FreeList[0]的第一個chunk依次檢測,直到找到第一個符合要求的chunk,然后將其從鏈表中拆卸下來。
3、分配好空間后,如果chunk中還有剩余空間,剩余空間會被建立成一個新的chunk,插入到鏈表中。
第一步沒有利用機會,第二步和第三步結合起來便可以利用:
我們在第二步時如果將后一個chunk的指針覆蓋,前一個chunk正常分配,在第二步時可以檢測出堆結構已經被破壞。
雖然檢測到堆結構出問題,但是程序依然會將第三步進行下去,也就是說將我們修改后的堆塊加入到鏈中,從而可以導致任意地址寫。
從而當我們再次申請堆時,申請的地址則有可能指向我們所設計的地址,從而達到我們的目的。
利用Lookaside表
Safe Unlink是對雙鏈表進行的檢驗,而對單鏈表並沒有防護措施,所以如果我們將單鏈表的后指針覆蓋為任意地址,在我們下次申請堆塊時,就有可能申請到我們所寫的地址,從而可能導致其他后果,比如可以修改異常處理鏈等。