認識二進制安全與漏洞攻防技術 (Windows平臺)

二進制漏洞是指程序存在安全缺陷,導致攻擊者惡意構造的數據(如Shellcode)進入程序相關處理代碼時,改變程序原定的執行流程,從而實現破壞或獲取超出原有的權限。

0Day漏洞

在計算機領域中,0day漏洞通常是指還沒有補丁的漏洞,或是已經被少數人發現的,但還沒被傳播開來,官方還未修復的漏洞,也稱爲“零日漏洞”或“零時差漏洞”,主要強調即時性。由0day衍生出1day的概念,就是指剛被公開或剛發佈補丁的漏洞。

0day漏洞通常只掌握在少數人手中,可以通過自主挖掘漏洞或者收購來獲取 ,通過藉助漏洞未公開,官方未發補丁的有利條件達到攻擊或防禦的目的 。

PoC與Exploit

PoC(Proof of Conecpt),概念性證明,是證明漏洞存在而提供的一段代碼或方法,只要能夠觸發漏洞即可,下圖爲某漏洞實例PoC。如證明IE存在漏洞的html文件,證明Word存在漏洞的doc/rtf文件,或者是證明Apache服務器存在漏洞的http請求包,這些可能導致存在漏洞的程序崩潰,或者直接實現利用其執行任意代碼。xise

上圖爲某漏洞實例的PoC

Exploit是指能夠實現漏洞利用的代碼、程序或方法,它算是PoC的子集。Exploit也能用於證明漏洞存在,只是它在該基礎上進一步實現漏洞的利用。它可能直接包含惡意的攻擊行爲,也可能只是彈出個計算器等無惡意的行爲,如下圖爲某漏洞實例的EXP。http://caidaome.com

上圖爲某漏洞實例的EXP

可見PoC用於證明漏洞存在,Exploit用於證明漏洞存在的同時也證明漏洞可利用,因此Exploit是PoC的子集。xise

 

漏洞挖掘入門分析技能

所謂”工欲善其事,必先利其器”,漏洞分析及挖掘亦是如此,Windows平臺需要掌握最基本的彙編語言、C/C++以及Python/JS/VBS等腳本語言,挖掘及分析工具主要有OllyDbg應用層動態調試工具、強大的Windows調試工具WinDbg、跨平臺的神級反彙編工具IDA Pro、漏洞分析專用調試器Immunity Debugger以及開源的安全漏洞檢測工具Metasploit,基本技能需要熟悉操作系統,如內存管理、進程線程、內核、PE文件格式以及Shellcode編寫技術等,除此之外推薦相關書籍,如《漏洞戰爭》、《C++反彙編與逆向技術揭祕》、《Exploit編寫系列教程》、《軟件調試》、《0day:軟件調試分析技術》以及各大安全論壇,如:FreeBuf、安全客、看雪、CSDN等。騰訊分分彩

常用的漏洞分析方法主要有:靜態分析、動態調試、補丁比較以及污點追蹤等方法。http://kj169.cc/

常見的二進制漏洞

常見的二進制漏洞主要分爲:棧溢出、堆溢出、整數溢出、格式化字符串、雙重釋放、釋放重引用、數組訪問越界、內核級、類型混淆、沙盒逃逸以及PRC等。

 

棧與堆的區別

一、 棧區 : 編譯器自動分配釋放,主要用於存放函數的參數以及局部變量等,操作方式類似於數據結構中的棧(先進後出)。

二、 堆區 :一般由程序員分配釋放。若程序員不釋放可能由os回收,但是他與數據結構中的堆是兩回事,分配方式類似於數據結構中的鏈表。騰訊分分彩

三、 全局區 :也叫靜態數據內存空間,存儲全局變量和靜態變量,全局變量和靜態變量的存儲是放一塊的,初始化的全局變量和靜態變量放一塊區域,沒有初始化的在相鄰的另一塊區域,程序結束後由系統釋放。http://kj169.cc/

四、 4文字常量區 :常量字符串就是放在這裏,程序結束後由系統釋放。

五、 程序代碼區 :存放函數的二進制代碼。

堆與棧的分配

棧溢出漏洞原理與簡單實例

棧溢出是緩衝區溢出的一種,往往是對緩衝區的長度沒有判斷,致緩衝區的大小超過了預定的大小,在棧內保存的返回地址被覆蓋,這時候返回地址將指向未知的位置。造成訪問異常的錯誤。

當精心構造一段Shellcode保存在某個位置,並且通過滑板指令以及其他特定的方法跳轉至Shellcode的位置上並且得到執行,此時就可以通過Shellcode獲取到系統的管理員權限或者執行任意代碼。簡單實例如

Strcpy函數未執行前,正常返回地址爲0x411186,

Strcpy函數執行後,由於拷貝的字符遠超過了目標緩衝區長度,被0x41414141覆蓋,在該函數ret後,會將0x41414141賦值給eip,從而轉向執行0x41414141地址代碼。

 

堆溢出漏洞原理與簡單實例

堆溢出與棧溢出一樣,也是緩衝區溢出的一種,堆棧溢出的產生往往是由於過多的函數調用,導致調用堆棧無法容納這些調用的返回地址,一般在遞歸中產生。堆棧溢出很可能由無限遞歸產生,但也可能僅僅是過多的堆棧層級。簡單實例如

成功分配內存後,系統會寫入堆的塊首信息與雙向鏈表指針,如上圖,

在strcpy的時候,完全覆蓋了buff1,且超過40,這樣就破壞了堆的塊首和雙向鏈表指針,也就是被0x41414141(AAAA)替換了。

 

整數溢出漏洞原理與簡單

實例

整數分爲有符號和無符號兩種類型,有符號數以最高位作爲其符號位,即正整數最高位爲1,負整數最高位爲0,而無符號數沒有這種情況,它的取值範圍是非負數,在平時編程的時候常用的整型變量有 8位(單字節字符、布爾類型)、16位(短整型)、32位(長整型)等 ,每種整數類型在內存中有不同的固定取值範圍,比如unsigned short的存儲範圍是0-65535,但是當存儲的值超過65535的時候,數據就會截斷,例如輸入65536,系統就會識別爲0。

如果利用整數溢出後的值做爲內存拷貝的參數,那麼就會造成緩衝區溢出(堆/棧),利用方法就是緩衝區溢出的利用方法

整數類型與取值範圍

整數棧溢出

邊界檢查最大爲8,由於輸入的數值爲65540,溢出了4,所以繞過了邊界檢查,

Memcpy時,長度爲0x10004(65540),導致棧溢出。

整數堆溢出

當輸入2時,size減去5會得到負數,由於unsigned short int取值範圍的限制導致無法識別負數,反而得到正數65533(0xFFFD),最後分配到過大的堆塊,從而導致溢出覆蓋到後面的堆管理結構。

 

格式化字符串漏洞原理與簡單實例

格式化字符串漏洞的產生主要源於對用戶輸入內容未進行過濾,這些輸入數據都是作爲參數傳遞給某些執行格式化操作的函數,如printf、fprintf、bprintf、sprintf等等。惡意用戶可以使用”%s”和”%x”等格式符,從堆棧或其他位置輸出數據,也可以使用格式符”%n”向任意位置寫入任意數據,配合printf()函數和其他功能類似的函數就可以向任意地址寫入被格式化的字節數,可能導致任意代碼執行,或者讀取敏感信息,比如用戶名以及密碼等等。

當輸入參數包含“%s”或“%x”格式符時會意外輸出其它數據,非格式本身,如下圖。

釋放重引用漏洞原理與簡單實例

釋放重引用UAF(use after free)漏洞的成因是一塊堆內存被釋放了之後又被使用。又被使用指的是:指針存在(懸掛指針被引用)。這個引用的結果是不可預測的,因爲不知道會發生什麼。由於大多數的堆內存其實都是C++對象,所以利用的核心思路就是分配堆去佔坑,佔的坑中有自己構造的虛表。

觸發UAF漏洞需要一系列的操作,而不是像傳統的溢出一個操作就會導致溢出。IE瀏覽器中的DOM標籤由一個對象來表示,並且IE自帶的類中存在了一些對象管理的方法。分析UAF漏洞的要點在於搞清楚對象是在哪裏被分配的,哪裏被釋放的,哪裏被重用的。UAF的異常觸發點是很明顯的,就是對已釋放的對象進行操作導致的異常。所以異常點也就是重用點。而由於是對對象的操作,可以列出這個對象的所有方法,找出分配和釋放的方法,對其下斷來分析到底是怎麼發生的UAF過程。

代碼中分配buf1後進行了釋放,然後又分配了buf2,此時佔坑到已釋放的buf1。

代碼中重引用了已釋放的buf1指針,導致buf2被篡改。

上述實例中通過分配與buf1相同大小的堆塊buf2,實現“佔坑”,使得buf2分配到已釋放的buf1內存位置,由於buf1指針仍然有效,並且指向的內存數據是不可預測的,可能被堆管理器回收,也可能被其它數據佔用,因爲這種不可預測性,因爲將buf1指針稱爲“懸掛指針”,藉助懸掛指針buf1賦值“hack”,進而導致buf2被篡改爲”hack”。如果原有的漏洞程序引用到懸掛指針指向的數據用於執行指令或作爲索引地址去執行,就可能導致任意代碼執行,前提是用可按數據去佔坑釋放對象。

 

雙重釋放漏洞原理與簡單實例

雙重釋放漏洞主要是由對同一塊內存進行二次重複釋放導致的,利用漏洞可以執行任意代碼.

在釋放過程中,鄰近的已釋放堆塊的合併動作,這會改變原有的堆頭信息以及前後向指針,之後在對其中的地址進行引用,就會導致訪問異常,最後程序崩潰,正是因爲程序引用到了已釋放的內存,所以說雙重釋放漏洞就是Use After Free漏洞的子集.如果程序不存在堆塊合併動作,那麼雙重釋放後可能不會馬上崩潰,但會在程序中遺留隱患,導致在後續執行過程中的某一刻爆發.

第一次釋放p2

第二次釋放p2

釋放後出現錯誤程序崩潰

數組越界訪問漏洞原理與簡單實例

數組越界與溢出關係

數組訪問越界包含讀寫類型,而溢出屬於數據寫入

通常數組越界訪問是由於數組下標數值超出了數組元素個數導致的,比如定義buf[5],但程序卻通過buf[8]訪問數據,此時即爲越界,數組的讀寫操作有時是同時並存的,程序越界索引棧上分配的數組,同時又向其寫入數據。最終造成溢出。

部分溢出漏洞的本質就是數組越界

導致溢出的原因有多種,有些正是由於針對數組下表標範圍未做有效限制,導致允許越界訪問數組並對其寫入數據,造成溢出。

執行程序,分別輸入2和5作爲數組下標,當輸入的數組下標索引值爲0、1、2時,會依次得到正常的數組值:11、22、33,但從索引值3開始就超出原定數組array的範圍,如下標爲5時,就會導致越界訪問array數組,從而讀取到不在程序控制範圍內的數值。

當輸入索引值0、1、2時,讀取的數組值33是正常的,

當輸入索引值5時,越界訪問array數組,導致讀取到的數值不在程序控制範圍內。

 

漏洞攻防技術

在過去的二十多年當中,微軟在提高操作系統的安全性方面一直做着不懈的努力。從Win98到WinXP、Vista、Win7再到最新的Win10,每個版本的發佈都會帶來安全性質的飛躍。除了在安全功能的保護下大大提高了系統安全性,微軟還在內存保護方面做了很多的工作,來提高內存保護的安全性,如堆棧保護機制:GS、異常處理保護:SafeSEH、數據執行保護:DEP、地址空間分佈隨機化:ASLR、結構化異常覆蓋保護:SEHOP等技術。

GS:堆棧的保護

針對緩衝區溢出時覆蓋函數返回地址這一特徵,微軟在編譯程序時使用了一個很酷的安全編譯選項-GS,在VS2003及以後版本中默認啓用,主要用於檢測棧中的溢出。

在所有函數調用發生時,向棧楨裏面壓入一個隨機的DWORD,這個隨機數被稱爲”canary”,在OD/IDA中標註爲“Security Cookie”;

Security Cookie位於EBP之前,系統還在數據段(。data)的內存區域中存儲一個canary的副本,當棧中發生溢出的時候,canary將被首先淹沒,之後纔是EBP和返回地址;

在函數返回之前,系統做一個額外的安全驗證,確認棧楨中的canary與數據段中的副本是否一致,如果兩者不一致,說明棧楨中的canary已經被破壞,也就是棧中發生了溢出。

上圖爲VS安全編譯選項中的GS

校驗Security Cookie

代碼中可以看出,在函數頭部讀取了security_cookie值與ebp進行了異或並保存在內存變量中,

在函數返回前,取出保存到內存中的值與security_cookie進行比較,當兩個值相等時,說明該值被破壞有棧溢出發生。

突破GS方法:

1. 利用不受保護的內存突破,例如某函數中不包括4字節以上的緩衝區,即使開啓GS,也並沒有保護

2. 利用虛函數突破,將虛表指針指向我們所需要的位置;

3. 利用異常處理突破,GS機制並沒對SEH進行保護,我們可以通過超長字符串覆蓋掉異常處理函數指針,然後觸發一個異常,這時候就會轉入異常處理,但是異常處理函數指針被覆蓋了,那麼就可以劫持SEH來控制程序流程

4. 同時替換棧中和數據段(。data)中的canary。

SafeSEH:異常處理保

在Win XP SP2及後續版本的系統中,微軟引入了著名的S.E.H校驗機制SafeSEH。原理很簡單,在程序員調用異常處理函數前,對要調用的異常處理函數進行一系列的有效性校驗,當發現異常處理函數不可靠時將終止異常處理函數的調用。 SafeSEH實現需要操作系統與編譯器的雙重支持,二者缺一都會降低SafeSEH的保護能力。在VS2003及後續版本中默認啓用

編譯器:啓用/SafeSEH鏈接選項後,編譯器在編譯程序的時候將程序所有的異常處理函數地址提取出來,編入一張安全S.E.H表,並且將這張表放在程序的映像裏,當程序調用異常處理函數的時候會將函數地址與安全S.E.H表進行匹配,檢查調用的異常處理函數是否在安全SEH表裏。

操作系統:

1. 檢查異常處理鏈是否處於當前棧中,如果不在,程序終止異常處理的調用

2. 檢查異常處理函數指針是否指向當前程序的棧中,如指向,終止異常處理函數的調用

3. 調用RtllsValidhandler()來進行有效驗證

1)檢查程序上是否設置了IMAGE_DLLCHARACTERISTICS_NO_SEH標記。如果設置了這個標識,這個程序內的異常將會被忽略,所以這個標誌被設置時,函數直接返回效驗失敗

2)檢測程序是否包含安全SEH表,如果程序中包含安全SEH表,則將當前的異常處理函數地址和該表進行匹配,匹配成功則返回校驗成功。匹配失敗則返回校驗失敗。

3)判斷程序是否設置ILonly標識,如果設置了這個標識,說明該程序 只包含.NET編譯人中間語言,哈數直接返回校驗失敗

4)判斷異常處理函數是否位於不可執行頁上,當異常處理函數地址位於不可執行頁上,校驗函數會檢測DEP是否開啓,如果系統未開啓DEP則返回校驗成功,否則程序拋出訪問異常。

突破SafeSEH方法:

1.攻擊返回地址,如果啓用了safeSEH而沒有啓用GS,或者剛好這個函數沒有GS保護,那麼就可以直接攻擊函數返回地址;

2.利用虛函數表來劫持程序流程,這個過程中不涉及任何異常處理。SafeSEH也就沒有機會生效了;

3.利用未啓用SafeSEH的模塊繞過SafeSEH;

4.利用加載模塊之外的地址繞過;

5.利用Adobe Flash Player Active控件繞過;

6.從堆中繞過。

DEP:數據執行保護

溢出攻擊的根源在於現代計算機對數據和代碼沒有明確區分這一先天缺陷,就目前來看重新去設計計算機體系結構基本上是不可能的,我們只能靠向前兼容的修補來減少溢出帶來的損害,DEP(數據執行保護,Data Execution Prevention)就是來彌補計算機對數據和代碼混淆這一缺陷的。微軟從WinXP SP2開始提供這種技術支持。

DEP的基本原理就是將數據所在的內存頁標識爲不可執行,當程序溢出後執行Shellcode的時候,就會嘗試在數據頁面上執行指令,此時CPU就會拋出異常,而不是去執行惡意的指令

DEP主要作用是阻止數據頁(如默認的堆頁、各種堆棧頁以及內存池頁)執行代碼。微軟從WinXP SP2開始提供這種支持,根據實現的機制不同可分爲:軟件DEP和硬件DEP。

軟件DEP其實就是SafeSEH,它的目的是阻止利用S.E.H的攻擊,這種機制與CPU硬件無關,Windows利用軟件模擬實現DEP,對操作系統提供一定的保護。

硬件DEP纔是真正意義上的DEP,硬件DEP需要CPU的支持,AMD和Intel都爲此做了設計,AMD稱之爲NX,Intel稱之爲XD,兩者功能及工作原理在本質上是相同的。

操作系統通過設置內存頁的NX/XD屬性標記來指明不能從該內存執行代碼。爲這實現這個功能,需要在內存的頁面表中加入一個特殊的標識們(NX/XD)來標識是否允許在該頁上執行指令。當該標識位置爲0裏表示這個頁面允許執行指令,設置爲1時表示該頁面爲不允許執行指令。

上圖爲:系統屬性中的高級選項中的性能選項中的數據執行保護中能夠查看你的計算機是否支持硬件的DEP

根據啓動參數的不同,DEP工作狀態可以分爲四種。

(1)Optin:默認僅將DEP保護應用於Windows系統組件和服務,對於其他程序不予保護,但用戶可以通過應用程序兼容性工具(ACT,Application Compatibility Toolkit)爲選定的程序啓用DEP,在Vista下邊經過/NXcompat選項編譯過的程序將自動應用DEP。這種模式可以被應用程序動態關閉,它多用於普通用戶版的操作系統,如Windows XP、Windows Vista、Windows7。

(2)Optout:爲排除列表程序外的所有程序和服務啓用DEP,用戶可以手動在排除列表中指定不啓用DEP保護的程序和服務。這種模式可以被應用程序動態關閉,它多用於服務器版的操作系統,如 Windows 2003、Windows 2008。

(3)AlwaysOn:對所有進程啓用DEP 的保護,不存在排序列表,在這種模式下,DEP不可以被關閉,目前只有在64位的操作系統上才工作在AlwaysOn模式。

(4)AlwaysOff:對所有進程都禁用DEP,這種模式下,DEP也不能被動態開啓,這種模式一般只有在某種特定場合才使用,如DEP干擾到程序的正常運行。 [1]

在Windows 7中,DEP默認是激活的。不過,DEP不能保護系統中所有運行的應用程序,實際DEP能夠保護的程序列表由DEP的保護級別定義。DEP支持兩種保護級別:級別1,只保護Windows系統代碼和可執行文件,不保護系統中運行的其它微軟或第三方應用程序;級別2,保護系統中運行的所有可執行代碼,包括Windows系統代碼和微軟或第三方應用程序。默認情況下,Windows 7的DEP運行在級別1的保護狀態下。在“數據執行保護”配置面板中,我們能夠設置DEP的保護級別。如圖所示筆者的Windows 7默認“只爲基本的Windows程序和服務激活了DEP”,即DEP保護級別爲1。當然,我們也可選擇“除了以下所選擇的,爲所有程序和服務打開DEP” 切換到DEP保護級別2。

在保護級別Level 2可以選擇特定的應用程序不受DEP保護。在實際應用中,這個功能非常重要,因爲一些老的應用程序在激活DEP時無法正常運行。 例如,我我們在使用Word進行文本編輯時,它會自動被排除在DEP保護之外。需要注意的是,在將DEP保護切換到級別2之前,必須運行應用程序兼容性測試,確保所有的應用程序在DEP激活時能正常運行。從DEP中排除應用程序, 需要在DEP配置頁面使用“添加”按鈕,將應用程序的可執行文件加入到排除列表中。

突破DEP方法:

1. 利用ret2libc或rop

1) 通過跳轉到ZwSetInfromationProcess函數將DEP關閉再轉 Shellcode執行。

2) 通過跳轉到Virtualprotect函數來將Shellcode所在的內存也設置爲可執行狀態,然後轉入Shellcode執行

3) 通過跳轉到VirualAlloc函數開闢一段具有執行權限的內存空間,然後將Shellcode複製到這段內存中執行

2. 利用可執行內存

如果進程空間裏存在一段可讀可寫可執行的內存,那麼如果我們可以將Shellcode複製到這段內存中,並且劫持程序執行流程,那麼我們的Shellcode就有執行的機會

3. 利用.net控件

它可以被加載到IE客戶端中,而且加載到IE進程的內存空間後,這些空間與空間都具有可執行屬性

4. 利用java applet

類似.NET控件也可以被加載到IE客戶端中,而且加載到IE進程的內存空間後,這些空間與空間都具有可執行屬性

ASLR:地址空間佈局隨機化

ASLR(Address Space Layout Randomization),地址空間佈局隨機化是一種針對緩衝區溢出的安全保護技術。藉助ASLR,PE文件每次加載到內在的起始地址都會隨機變化。目前大部分主流操作系統都已經實現了ASLR。

微軟採用這種方式的目的是想要增加系統的安全性。在經典的棧溢出模型中,攻擊者可以通過覆蓋函數的返回地址,以達到控制程序執行流程的目的。通過將返回地址覆蓋爲0x7FFA4512,即JMP ESP指令。如果此時ESP剛好指向棧上佈置的Shellcode,則會被得到執行。

在以此類漏洞爲目標的漏洞利用代碼中,必須確定一個明確的跳轉地址,並以硬編碼的形式編入。在Vista之前的操作系統中,DLL會加載到固定地址,如在Wiin32系統中EXE文件的ImageBase默認爲0x400000,DLL文件爲0x10000000。ASLR的加入,使得加載程序的時候不再使用固定的基址,從而干擾Shellcode的定位。

ASLR需要操作系統和程序自身的雙重支持,操作系統方面從Windows Vista開始採用該技術,編譯器上於VS2005 SP1加入/dynamicbase鏈接選項支持隨機基址。

VS隨機基址選項

突破ASLR方法:

1) 利用沒有開啓ASLR的模塊進行繞過,比如java/Flash等模塊;

2) 利用部分覆蓋進行定位,基址隨機化只隨機了前2個字節,這樣就可以利用這個地址的後兩個字來在一定程度上控制這個程序;

3) 利用堆噴射進行內存定位;

4) Heap spary:堆噴射就是申請大量內存,佔領內存中的0x0c0c0c0c,並且在這些內存中放置0x90(nop)和Shellcode,最後控制程序轉入0x0c0c0c0c執行,只要不是0x0c0c0c0c整好處於Shellcode當中,那麼Shellcode就可以成功執行。

a) 思路:申請200個1MB的內存塊來堆噴,每個內存塊中包含0x90和Shellcode,堆噴結束後我們就會佔領0x0c0c0c0c附近的內存,只要控制程序轉到0x0c0c0c0c執行,通過0x90就滑板,最終執行Shellcode

b) 在程序中找到溢出點,覆蓋其返回地址,將返回地址覆蓋爲0x0c0c0c0c,這樣函數返回執行的時候就會跳到我們申請的內存中

5) 利用Java applet heap spray技術定位內存地址;

6) 爲.net控件禁用ASLR。

SEHOP:結構化異常處理覆蓋保護

SEHOP的全稱是Structured Exception Handler Overwrite Protection(結構化異常處理覆蓋保護),SEH攻擊是指通過棧溢出或者其他漏洞,使用精心構造的數據覆蓋結構化異常處理鏈表上面的某個節點或者多個節點,從而控制EIP(控制程序執行流程)。而SEHOP則是是微軟針對這種攻擊提出的一種安全防護方案。

微軟最開始提供這個功能是在2009年,支持的系統包括Windows Vista Service Pack 1、 Windows 7、Windows Server 2008 和 Windows Server 2008 R2,以及它們的後續版本。它是以一種SEH擴展的方式提供的,通過對程序中使用的SEH結構進行一些安全檢測,來判斷應用程序是否受到了SEH攻擊。SEHOP的核心是檢測程序棧中的所有SEH結構鏈表,特別是最後一個SEH結構,它擁有一個特殊的異常處理函數指針,指向的是一個位於NTDLL中的函數。異常處理時,由系統接管分發異常處理,因此上面描述的檢測方案完全可以由系統獨立來完成,正因爲SEH的這種與應用程序的無關性,因此應用程序不用做任何改變,你只需要確認你的系統開啓了SEHOP即可。在Windows Server 2008 和 Windows Server 2008 R2下SEHOP默認是開啓的,而在Windows Vista Service Pack 1、 Windows 7下默認則是關閉的。

SEHOP的任務就是檢查這條S.E.H鏈的完整性,在程序轉入異常處理前SEHOP會檢查S.E.H鏈上最後一個異常處理函數是否爲系統固定的終極異常處理函數。如果是,則說明這條S.E.H鏈沒有被破壞,程序可以去執行當前的異常的處理函數;如果檢測到最後一個異常處理函數不是終極BOSS,則說明S.E.H鏈被破壞,可能發生了S.E.H覆蓋攻擊,程序將不會去執行當前的異常處理函數。

攻擊時將S.E.H結構中的異常處理函數地址覆蓋爲跳板指令地址,跳板指令根據實際情況進行選擇。當程序出現異常的時候,系統會從S.E.H鏈中取出異常處理函數來處理異常,異常處理函數的指針已經被覆蓋,程序的流程就會被劫持,在經過一系列跳轉後轉入Shellcode執行。

由於覆蓋異常處理函數指針時同時覆蓋了指向下一異常處理結構的指針,這樣的話,S.E.H鏈就會被破壞,從而被SEHOP機制檢測到。

作爲對SafeSEH強有力的補充,SEHOP檢查是在SafeSEH的RtlIsValidHandler函數校驗前進行的,也就是說利用攻擊加載模塊之外的地址、堆地址和未啓用SafeSEH模塊的方法都行不通了,必須要考慮其它的方法。

突破SEHOP方法:

1. 不去攻擊S.E.H,而是攻擊函數返回地址或虛函數等;

2. 利用未啓用SEHOP的模塊;

3. 僞造S.E.H鏈。

Heap spray:堆噴射技術

Heap spray(堆噴射)是一種payload 傳遞技術,藉助堆來將Shellcode放置在可預測的堆地址上,然後穩定地跳入Shellcode。

不論是基於棧溢出還是堆溢出的緩衝區漏洞攻擊 ,在攻擊者成功造成系統溢出後都必須考慮跳轉地址(如函數返回點)的覆蓋。在以往的攻擊中,如何確定Shellcode在內存中位置,使得跳轉地址被覆蓋爲Shellcode的起始地址是攻擊者需要精確計算的。並且這往往也是攻擊中最難以實現的部分。另外,考慮到一些外部環境因素比如操作系統版本的不同,使得溢出攻擊變得更加難以實現。

但是後來出現的Heap spray技術大大緩解了這一問題。起初,技術人員發現可以通過Javascript申請大量的堆內存來消耗資源,造成目標主機的癱瘓。但是並沒有進一步利用。直到後來,SkyLined在2004年爲IE的IFRAME漏洞所寫的exploit中才第一次正式提出了Heap spray。之後經過不斷髮展,Heap spray逐漸成爲網頁掛馬的常用技術,並且被利用到文檔攻擊中(如PDF閱讀器)。

堆噴射實例代碼

通過執行大量的滑板指令最終運行Shellcode代碼

ROP:面向返回編程技術

ROP(Return-Oriented Programming),即面向返回編程,它借用libc代碼段裏面的多個retq前的一段指令拼湊成一段有效的邏輯,從而達到攻擊的目標。爲什麼是retq,因爲retq指令返到哪裏執行,由棧上的內容決定,而這是攻擊者很容易控制的地址。那參數如何控制,就是利用retq執行前的pop reg指令,將棧上的內容彈到指令的寄存器上,來達到預期。一段retq指令未必能完全到想攻擊目標的前提條件,那可在棧上控制retq指令跳到另一段retq指令表,如果它還達不到目標,再跳到另一段retq,直到攻擊目標實現。

在ret2plt攻擊方法,我們使用PPR(pop, pop, ret)指令序列,實現順序執行多個strcpy函數調用,其實這就是一種最簡單的ROP用法,ROP更是ret2plt的升級版。

ROP方法技巧性很強,那它能完全勝任所有攻擊嗎?返回語句前的指令是否會因爲功能單一,而無法實施預期的攻擊目標呢?業界大牛已經過充分研究並證明ROP方法是圖靈完備的,換句話說, ROP可以借用libc的指令實現任何邏輯功能。

下圖爲某漏洞實例利用到的ROP鏈,構造ROP需要進行ASLR繞過,如實例中採用了未啓用ASLR的模塊(MSVCR71.dll)。當使用堆噴射技術將Shellcode代碼噴射到內存後,由於DEP的關係,Shellcode無法得到執行,此時可以使用ROP技術,借用libc的指令來實現賦予Shellcode執行權限,如實例中,採用了VirtualProtect函數。

上圖爲某實例利用到的ROP鏈

構造rop鏈,用mona插件能輕鬆辦到,只要使用命令:!mona rop -m msvcr71.dll

 

Shellcode編寫

在計算機安全領域,Shellcode是一小段代碼,可以用於軟件漏洞利用的載荷。被稱爲“Shellcode”是因爲它通常啓動一個命令終端,攻擊者可以通過這個終端控制受害的計算機,Shellcode通常是以機器碼形式編寫的。

Shellcode特點

Shellcode不能是任意的機器碼,在編寫Shellcode時,必須注意Shellcode的一些限制:

1. 字符串的直接偏移

即使你在C/C++代碼中定義一個全局變量,一個取值爲“Hello world”的字符串,或直接把該字符串作爲參數傳遞給某個函數。但是,編譯器會把字符串放置在一個特定的Section中(如.rdata或.data)。

2. 函數地址

在Shellcode中,我們卻不能以逸待勞了。因爲我們無法確定包含所需函數的DLL文件是否已經加載到內存。受ASLR(地址空間佈局隨機化)機制的影響,系統不會每次都把DLL文件加載到相同地址上。而且,DLL文件可能隨着Windows每次新發布的更新而發生變化,所以我們不能依賴DLL文件中某個特定的偏移。

我們需要把DLL文件加載到內存,然後直接通過Shellcode查找所需要的函數。幸運的是,Windows API爲我們提供了兩個函數:LoadLibrary和GetProcAddress。我們可以使用這兩個函數來查找函數的地址。

3. 避免空字節

空字節(NULL)的取值爲:0×00。在C/C++代碼中,空字節被認爲是字符串的結束符。正因如此,Shellcode存在空字節可能會擾亂目標應用程序的功能,而我們的Shellcode也可能無法正確地複製到內存中。

雖然不是強制的,但類似利用strcpy()函數觸發緩衝區溢出的漏洞是非常常見的情況。該函數會逐字節拷貝字符串,直至遇到空字節。因此,如果Shellcode包含空字節,strcpy函數便會在空字節處終止拷貝操作,引發棧上的Shellcode不完整。正如你所料,Shellcode當然也不會正常的運行。

例如MOV EAX,0、 XOR EAX,EAX,兩條指令從功能上來說是等價的,但你可以清楚地看到第一條指令包含空字節,而第二條指令卻不包含空字節。雖然空字節在編譯後的代碼中非常常見,但是我們可以很容易地避免。

Shellcode的執行過程就是調用函數的過程。

“Windows下調用函數分爲兩步,一是參數入棧;二是CALL函數地址。”

系統模塊及函數地址如何獲得?

方法一:內存暴力搜索

從0x77e0000或0xbff00000開始搜索, 搜索到MZ和PE標誌時,就表示是kernel32.dll的加載地址

方法二:PEB獲取GetProcAddrees函數地址

1. fs寄存器指向TEB結構

2. 在TEB+0x30地方指向PEB

3. 在 PEB+0x0C 地方指向 PEB_LDR_DATA

4. 在PEB_LDR_DATA+0x1C 指向動態連接庫地址了,如第一個指向ntdll.dll

方法三:SEH獲得kernel基址
搜索異常鏈,得到UnhandledExceptionFilter地址,是kernel32的函數,可以向上搜索MZ,定位地址

HASH法查找所函數地址

如何自定位eip?

1. CALL/POP型

CALL指令做的操作是壓棧下一個地址,跳向指定地址,利用這個特徵可以利用CALL/POP操作定位當前位置。

2. CALL/POP改進型

前一種好用但是有缺陷,比如會出現較多的00,可能造成截斷,於是有了改進型。

3. 浮點運算型

浮點運算後位置保存在棧頂,通過POP操作可以獲取其位置。

4. 中斷型

使用INT 2c或者INT 2e可以獲取下一個執行地址,下一個執行地址將會保存於ebx。

在調試狀態無法達到預期的效果,如果想看見效果可以將調試器設置爲默認調試器,執行以下代碼看見效果:

__asm

{

Int 3

Int 2c

}

 

5. 異常處理型

在Shellcode代碼中構建一個異常處理函數,再構造一個異常進入異常處理中獲取EIP

這種方法編寫難度稍微大點,也是可行的。

Shellcode編碼與解碼

Shellcode在沒有編碼的情況下,如果shellcode機器碼中存在NULL(0x00),那有可能會被截斷,從而導致shellcode失敗,另外由於shellcode的特殊指令也可能會被檢測,所以有必要對shellcode進行編碼操作。

上圖左邊爲未解碼代碼,通過簡單的異或進行解碼運行,右邊爲解碼後的正常彙編代碼。

通過漏洞分析、復現及利用嘗試,能夠學習如何分析漏洞的利用方法、如何繞過保護機制、如何構造rop鏈,以及使用Windbg、ImmunityDebugger等調試軟件和mona插件調試程序和查找指令序列等知識,對漏洞利用的初級知識會有更深的認識和體會。

參考文獻:《漏洞戰爭》、《0day安全:軟件漏洞分析技術》等。

本文由安全客原創發佈 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章