Windows漏洞利用開發 – 第1部分:基礎知識
1、前言
歡迎來到Windows漏洞利用開發系列文章的第1部分。在第一部分中,我將介紹一些為了更好的理解未來文章內容所需的基礎知識,包括一些彙編語法,Windows記憶體佈局以及偵錯程式的使用。當然這裡並不會面面俱到,所以如果你沒有接觸過彙編,或者在閱讀完第一篇文章後有什麼不清楚的地方,希望你能看看我提供的各種資源連結。
本系列接下來的文章寫作計劃是從簡單的(直接EIP覆蓋)到更復雜的(unicode,ASLR繞過,堆噴,裝置驅動程式漏洞利用等)的各種攻擊主題,使用實際漏洞攻擊作為列子。我沒有什麼更具體的計劃,所以當我想到更多主題時,我會繼續寫帖子
本系列文章是查詢和利用Windows應用程式漏洞,希望那些沒有太多技術知識的安全和IT專業人員會對軟體安全感興趣並將其技能使私人和公共領域的軟體更安全。免責宣告:如果你是一個想要參與非法或不道德行為的人,你可以不用看了。
這些帖子並不打算與Corelan Team,The Gray Corner和 Fuzzy Security等其他優秀教程進行競爭。相反,本系列文章是為了補充它們,併為解釋和例子提供另一個資源 。我極力鼓勵你去看看這些偉大的教程。
3、你需要什麼
如果您想要更好的理解本系列教程,您需要做以下準備:
安裝Windows:我計劃從Windows XP SP3開始,但隨著進展和覆蓋不同的主題/漏洞,我還可能使用其他版本,包括Windows 7和Windows Server 2003/2008。
偵錯程式:在Windows上,您還需要一個偵錯程式。我將主要使用 Immunity Debugger ,您可以在這裡下載 。你也應該安裝mona外掛。我還會使用WinDbg進行除錯。
Backtrack / Kali主機(可選):我的所有指令碼使用Kali,在我使用的任何遠端攻擊示例中將其用作“攻擊機器”。我計劃在大多數指令碼中使用Perl和Python,您可以選擇在Windows上安裝任何一種語言環境。
4、Immunity Debugger入門
讓我們先看一下偵錯程式,因為在整個教程中我們將花費相當多的時間除錯。我將主要使用Immunity偵錯程式,因為它是免費的,並且具有一些外掛和自定義指令碼功能。
我將使用Windows Media Player作為示例程式來介紹Immunity Debugger。開啟Windows Media Player和免疫偵錯程式。在Immunity中,單擊File - > Attach並選擇應用程式/程序的名稱(在我的示例中為wmplayer)。注意:您也可以直接從Immunity啟動WMP,方法是單擊檔案 - >開啟並選擇可執行檔案。

一旦你啟動了一個可執行程式或者連線到Immunity中的一個程序,你應該進入CPU檢視(如果沒有,按Alt + C),像這樣:

當您執行/附加到具有免疫功能的程式時,它會以暫停狀態啟動(請參閱右下角)。要執行該程式,您可以按F9(或工具欄中的播放按鈕)。要進入下一條指令(但暫停程式執行),請按F7。您可以使用F7逐步執行每條指令。如果在任何時候想要重新啟動程式,請按Ctrl + F2。本教程將不會提供關於如何使用Immunity的完整教程,但是會提及任何相關的快捷方式和熱鍵。
如您所見,CPU視窗被分成四個窗格,描述以下資訊:
- CPU指令:顯示記憶體地址,操作碼和彙編指令,附加註釋,功能名稱以及與CPU指令相關的其他資訊
- 暫存器:顯示與應用程式當前狀態相關的通用暫存器,指令指標和標誌的內容
- 堆疊:顯示當前堆疊的內容
- 記憶體轉儲:顯示應用程式記憶體的內容
讓我們從暫存器開始更深入地看看每一個。
4.1 暫存器
CPU暫存器作為用於快速訪問資料的小型儲存區域。在x86(32位)架構中,有8個通用暫存器:EAX,EBX,ECX,EDX,EDI,ESI,EBP和ESP。他們可以在技術上用於儲存任何資料,儘管它們最初是為了執行特定任務而設計的,而且在很多情況下,仍然可以用於儲存任何資料。

- EAX - 累加器暫存器
它被稱為累加器暫存器,因為它是用於常用計算的主暫存器(如ADD和SUB)。雖然其他暫存器可用於計算,但EAX通過分配更高效的單位元組操作碼而享有優先權。當涉及到編寫有限的可用緩衝區空間的shellcode時,這種效率可能很重要(更多內容將在未來的教程中介紹!)。除了用於計算之外,EAX還用於儲存函式的返回值。
這個通用暫存器可以全部或部分地被引用,如下所示:EAX是指整個32位暫存器。AX指最低有效16位,可以進一步分解為AH(AX的8個最高有效位)和AL(8個最低有效位)。

也適用於接下來的三個暫存器(EBX,ECX和EDX)。
- EBX - 基礎暫存器
在32位體系結構中,EBX並沒有真正的特殊用途,所以只要把它看作是可用的儲存暫存器。像EAX一樣,它可以被整體引用(EBX)或部分引用(BX,BH,BL)。
- ECX - 計數暫存器
顧名思義,計數器(或計數)暫存器通常用作迴圈和函式重複計數器,但它也可用於儲存任何資料。像EAX一樣,它可以在整個(ECX)或部分(CX,CH,CL)中被引用。
- EDX - 資料暫存器
EDX有點像EAX的夥伴。它經常用於除法和乘法等數學運算,以處理溢位,餘數儲存在EDX中,計算結果被EAX儲存。它也常用於儲存函式變數。像EAX一樣,它可以在整個(EDX)或部分(DX,DH,DL)中被引用。
- ESI - 源暫存器
EDI,ESI的通常用於儲存指向讀取位置的指標。例如,如果一個函式被設計為讀取一個字串,ESI儲存該字串的地址。
- EDI - 目的暫存器
雖然EDI可以(並且)用於通用資料儲存,但EDI主要用於儲存函式的儲存指標,例如字串操作的寫入地址。
- EBP - 基本指標
EBP用於跟蹤堆疊的底部。它通常用於使用EBP當前值的偏移量來引用位於堆疊上的變數,但如果引數僅由暫存器引用,則可以選擇將EBP用於一般用途。
- ESP - 堆疊指標
ESP用於跟蹤堆疊的頂部。隨著專案被移入和移出ESP,ESP相應地增加/減少。在所有通用暫存器中,ESP很少/從不用於除預定目的之外的任何其他用途。
- 指令指標(EIP)
不是通用暫存器,EIP指向CPU要執行的下一條指令的儲存器地址。正如您將在接下來的教程中看到的那樣,控制EIP您就可以控制應用程式的執行流程(以執行您選擇的程式碼)。
- 段暫存器和EFLAGS暫存器

您將在暫存器窗格看到一些額外的暫存器。我將不會詳細介紹,但請注意,EFLAGS暫存器由一系列標誌組成,這些標誌表示由計算和比較產生的布林值,並且可用於確定何時/是否採取有條件的跳轉(稍後詳細介紹)。
有關CPU暫存器的更多資訊,請檢視以下資源:
http:// wiki.skullsecurity.org/ Registers
http://www. swansontec.com/sregiste rs.html
4.2 記憶體
跳到CPU檢視的“記憶體”窗格,這就是您可以檢視記憶體位置內容的簡單方法。例如,假設您想檢視ESP中的記憶體內容,該內容在以下螢幕截圖中指向0007D47C。右鍵單擊ESP,選擇“按照轉儲”,並且“記憶體”窗格將顯示該位置。

4.3 CPU
正如您知道的那樣,今天的大多數應用程式都是用高階語言(C,C ++等)編寫的。編譯應用程式時,這些高階語言指令被翻譯成具有相應操作碼的組合語言,以幫助將指令進一步轉換為機器可以理解的內容(機器程式碼)。在偵錯程式中,您可以檢視CPU指令窗格中CPU正在處理的每個彙編指令(和相應的操作碼)。注意:對於Windows漏洞利用系列,將使用x86組合語言Intel語法。
您可以逐個執行程式的執行流程(F7)並檢視每條CPU指令的結果。我們來看看當前Windows Media Player的一條簡單的彙編指令:

這條指令的功能就是把暫存器ESP中的值賦給EBP。
這只是一個簡單的例子,您可以在Immunity中關注每條CPU指令的執行情況。以下有幾個更常見的彙編指令和語法你會遇到:

我當然不是專家,但是當談到理解並最終開發自己的漏洞利用程式碼時,你應該對Assembly有一個很好的把握。隨著我們的進展,我將討論更多的彙編指令,但我不打算深入討論組合語言,因此,如果您需要進行復習,那麼會有大量良好的線上資源,包括:(詳細連結見“閱讀原文”)
- x86彙編指南
- http:// Sandpile.org
- 組合語言程式設計的藝術
- Windows組合語言
如果你想買本書,你可以考慮Hacking: The Art of Exploitation,不僅涵蓋了Assembly的基礎知識,而且還涉及漏洞利用(主要在Linux環境中)。
對於本系列文章,我將盡我所能解釋我使用的任何程式碼示例,所以如果您對Assembly有一些基本的瞭解,理解起來應該沒問題。
5、Windows記憶體佈局
在我們談論堆疊之前,我想簡要地談談Win32程序記憶體佈局。預先說明,只是大概介紹,不會涵蓋諸如地址空間佈局隨機化(ASLR),虛擬地址到實體地址的轉換,記憶體分頁,實體地址擴充套件等概念。我打算在以後的部分文章中說這些內容,但現在我想讓事情變得非常簡單。
首先,在Windows Media Player附帶Immunity後,按ALT + M檢視記憶體對映(也可以選擇View-> Memory或單擊工具欄上的'M'圖示)。
您應該看到如下所示的內容(確切條目可能有所不同):

這是wmplayer.exe的記憶體佈局,包括堆疊,堆,載入的模組(DLL)和可執行檔案本身。我將使用在Corelan 關於基於堆疊溢位(Stack Based Overflows)的絕佳入門教程(已失效)中找到的記憶體對映的一個稍微簡化的版本,詳細介紹每個這些條目,具體條目已對應到Windows Media Player的Immunity記憶體對映。

讓我們從底部開始介紹,從記憶體部分從0xFFFFFFFF到0x7FFFFFFF部分,這通常被稱為“Kernel Land”。
- Kernel Land
這部分記憶體由作業系統保留用於裝置驅動程式,系統快取,分頁/非分頁池,HAL等。沒使用者訪問這部分記憶體。注意:有關Windows記憶體管理的詳細說明,您應該檢視Windows Internals書籍(目前有兩冊)。
- PEB和TEB(s)
執行程式/應用程式時,會執行稱為程序的可執行檔案例項 。每個程序提供執行該程式例項所需的資源。每個Windows程序都有一個執行程序(EPROCESS)結構,其中包含程序屬性和指向相關資料結構的指標。儘管這些EPROCESS結構中的大多數都駐留在Kernel Land中,但程序環境塊(PEB)駐留在使用者可訪問的記憶體中。PEB包含有關正在執行的程序的各種使用者模式引數。您可以使用WinDbg通過發出!peb命令輕鬆檢查PEB的內容。

如您所見,PEB包含可執行檔案的基地址,堆的位置,載入的模組(DLL)以及環境變數(作業系統,相關路徑等)等資訊。看看上面的WinDbg截圖中的ImageBaseAddress。請注意地址01000000。現在回到之前的Win32記憶體對映圖,並注意這與“Program Image”塊的第一個地址是如何相同的。您可以對堆地址和關聯的DLL執行相同的操作。
關於符號檔案的快速註釋..在除錯Windows應用程式時載入適當的符號檔案特別有用,因為它們為函式,變數等提供了有用的描述性資訊。您可以在WinDbg中導航到“檔案 - >符號檔案路徑..”。
按照這裡找到的說明: http:// support.microsoft.com/k b/311503 。
您也可以通過“除錯 - >除錯符號選項”來在Immunity中載入符號檔案。
關於整個PEB結構的更多細節可以在這裡找到。(見“閱讀原文”)
程式或程序可以有一個或多個執行緒,作為作業系統分配處理器時間的基本單元。每個程序都從一個執行緒(主執行緒)開始,但可以根據需要建立其他執行緒。所有執行緒共享分配給父程序的相同虛擬地址空間和系統資源。每個執行緒也有自己的資源,包括異常處理程式,優先順序,本地儲存等。
就像每個程式/程序都有一個PEB一樣,每個執行緒都有一個執行緒環境塊(TEB)。TEB儲存image loader和各種Windows DLL的上下文資訊,以及異常處理程式列表的位置(我們將在後面的文章中詳細介紹)。和PEB一樣,TEB駐留在程序地址空間中,因為使用者模式元件需要可寫訪問。
您還可以使用WinDbg檢視TEB(s)。

關於整個TEB結構的 更多細節可以在這裡.aspx)找到,關於程序和執行緒的更多細節可以在這裡.aspx)找到
- DLLs
Windows程式利用稱為動態連結庫(DLL)的共享程式碼庫,可以實現高效的程式碼重用和記憶體分配。這些DLL(也稱為模組或可執行模組)佔用了部分記憶體空間。如Memory Map截圖所示,您可以在Memory檢視(Alt + M)的Immunity中檢視它們,或者如果只想檢視DLL,可以選擇Executable Module檢視(Alt + E)。有OS /系統模組(ntdll,user32等)以及特定於應用程式的模組,後者通常可用於產生溢位漏洞(以後釋出的文章會說明)。
下面是Immunity中Memory檢視的截圖:

- Program Image
記憶體的Program Image部分是可執行檔案所在的位置。這包括.text部分(包含可執行程式碼/ CPU指令).data部分(包含程式的全域性資料)和.rsrc部分(包含非可執行資源,包括圖示,影象和字串)。
- Heap
堆是程式用來儲存記憶體的動態分配(例如malloc())部分。與棧不同,堆記憶體分配必須由應用程式管理。換句話說,該記憶體將保持分配狀態,直到程式釋放或程式本身終止。您可以將堆視為共享的記憶體池,而接下來將介紹的堆疊更加有組織。
- Stack
不同於堆,全域性變數的記憶體分配是相對任意且持久的,棧以有序的方式為本地(函式/方法)變數分配短期儲存。回想一下給定的程序可以有多個執行緒。每個執行緒/函式都分配了自己的棧幀。該棧幀的大小在建立後固定,並且在該函式結束時刪除棧幀。
- PUSH和POP
在我們看一個函式如何分配一個Stack Frames之前,讓我們快速看看一些簡單的PUSH和POP指令,這樣你就可以看到資料如何被放入棧並從棧中取出。該堆疊是後進先出(LIFO)結構,這意味著您放入棧的最後一個數據是您下一次能使用的第一個資料。您將物品“推”到堆疊頂部,然後從堆疊頂部“彈出”物品。我們來看看這個行動。
在以下螢幕截圖中,您將在CPU指令窗格(左上角)中看到一系列PUSH指令,每個指令都將從其中一個暫存器(右上方窗格)獲取值,並將該值放在棧頂部(右下窗格)。
我們從第一個PUSH指令開始(PUSH ECX)。

記下ECX的值以及棧頂部的地址和值(前一個螢幕截圖的右下角)。現在執行PUSH ECX指令。

在第一個PUSH ECX指令之後,來自ECX(地址0012E6FC)的值被推送到棧頂(如上圖所示)。注意棧頂部的地址如何減少4個位元組(從0012E650到0012E64C)。這說明隨著資料被推送到棧,棧如何向上增長以降低地址。另請注意,ESP指向棧頂部,EBP指向此Stack Frames的底部。您會注意到在下面的截圖中,EBP(基指標)保持不變,而ESP(棧指標)隨著棧的增長和收縮而變化。現在,第二個PUSH ECX指令將被執行。

再一次,ECX(0012E6FC)的值被推到棧頂,ESP將其值再調整4個位元組,正如你在上面的螢幕截圖中看到的那樣,最後的PUSH指令(PUSH EDI)即將執行。

現在,來自EDI(41414139)的值被推到棧頂,列表中的下一條指令即將被執行(一條MOV指令),並且EDI的值被改變。讓我們跳到POP EDI指令來顯示如何從棧中取出資料。在這種情況下,棧頂部的當前值(41414139)將被彈出並放入EDI中。


如您所見,EDI的值已經根據POP指令變回41414139。現在您已瞭解如何操縱棧,接下來我們來看看如何為函式建立Stack Frames以及如何將區域性變數放置在棧上。當我們在本系列的第2部分中討論基於堆疊的溢位時,理解這一點至關重要。
- Stack Frames and Functions
當一個程式函式執行時,會建立一個Stack Frames來儲存它的區域性變數。每個函式都有自己的Stack Frames,它放在當前棧的頂部,並導致棧向上擴充套件到較低的地址。
每次建立Stack Frames時,都執行一系列指令來儲存函式引數和返回地址(以便程式知道函式結束後該去哪裡),儲存當前Stack Frames的基指標,併為本地函式變數開闢空間。[注:我故意省略這個基本討論的SEH,但將在以後的帖子中解決它們]。
讓我們來看看使用我能找到的最簡單的函式(來自維基百科)來建立Stack Frames:

這段程式碼簡單地呼叫函式foo(),並傳遞給它一個命令列引數作為引數(argv [1])。函式foo()先宣告一個長度為12的變數c。然後它呼叫函式strcpy(),它將argv [1]的值複製到變數c中。正如註釋所述,沒有邊界檢查,所以這種使用strcpy可能會導致緩衝區溢位,我將在本系列的第2部分中演示這些緩衝區溢位。現在,我們只關注這個函式如何影響棧。
我使用Visual Studio命令提示符(2010)編譯了這個c程式(作為stack_demo.exe)。你可以直接從Immunity執行一個帶有命令列引數的程式,方法是選擇File-> Open(或者簡單地按F3),選擇你的可執行檔案,然後在給定的欄位中輸入你的命令列引數。

對於這個例子,我簡單地使用了11個A作為argv [1]。[我們會看看在第2部分中使用超過11的情況會發生什麼!]
如果你想動態除錯,你可能會想要在程式碼的相關部分插入一些斷點。由於地址可能發生變化,查詢我們相關程式程式碼的最佳方法是選擇檢視 - >可執行模組(或Alt + E)。然後,雙擊stack_demo.exe模組(或任何您命名為.exe的檔案)。

轉到以下內容:

你看到的第一行實際上是foo()的開始,但我們首先要看看main()。我已經設定了幾個斷點來幫助您瀏覽程式碼(由淡藍色突出顯示),您可以通過選擇所需的地址並按F2來執行相同的操作。讓我們來看看main()..
儘管main()除了呼叫函式foo()之外,還有一些工作必須首先發生,就像在偵錯程式中看到的那樣。首先,它將Argv [1](AAAAAAAAAAAAA)的內容推送到棧。然後,當函式foo()被呼叫時,返回地址被儲存到棧中,保證程式執行流程可以在函式foo()終止後恢復。
看看我已經註釋過的Immunity螢幕截圖 - 只需要注意現在紅色框中的內容; 在函式foo()被呼叫之前,你會看到一個指向argv [1]的指標被推入棧。然後執行CALL指令,並將下一條指令(EIP + 4)的返回地址也推送到棧。

如果您想要證明地址00332FD4包含0033301C(這是指向argv [1]的指標),請參閱該地址的轉儲內容:

您會看到向後寫入的內容為1C303300。讓我藉此機會快速介紹Little Endian。“位元組順序”是指位元組儲存在記憶體中的順序。基於Intel x86的系統使用Little Endian,它在最小的儲存器地址處儲存值的最低有效位元組(這就是為什麼地址以相反順序儲存的原因)。
作為一個例子,參考上面的十六進位制轉儲螢幕截圖 - 頂部地址(00332FD4)最小,底部地址(00333034)最大。因此,左上角的位元組(當前由1C佔據)佔據最小的地址位置,並且地址會從左到右和從上到下移動時變大。當您檢視諸如0033301C之類的地址時,最低有效位元組是一直到右邊的位元組(1C)。要將其轉換為Little Endian,您需要重新排序它,每次一個位元組,從右到左。下面是一個示意圖:

好的,argv [1]和返回地址現在已經被推送到堆疊,並呼叫函式foo()。如下圖。

請地址0012FF74處指向argv [1]的指標,並在其上方儲存RETURN值。如果回顧main()的前一個截圖,您會注意到0040103F的RETURN地址是CALL foo()之後的下一條指令,這是foo()結束後程序執行的地方。
現在讓我們看看函式foo():

一旦函式foo()被呼叫,首先發生的事情是當前基指標(EBP)通過PUSH EBP指令儲存到堆疊,這樣一旦函式終止,main()堆疊的基礎就可以恢復。
接下來,將EBP設定為等於ESP(通過指令MOV EBP,ESP),使堆疊幀的頂部和底部相等。從這裡開始,EBP將保持不變(在函式foo的生命週期中),隨著資料被新增到函式的棧幀,ESP將會長到更低的地址。這是暫存器的前後檢視,顯示EBP現在等於ESP。

接下來,通過以下指令為區域性變數c(char c [12])開闢空間:SUB ESP,10。
以下是這一系列指令後的棧:

請注意棧的頂部(作為ESP的結果)如何從0012FF6C更改為0012FF5C。
讓我們跳到strcpy()的呼叫,它會將argv [1](AAAAAAAAAAAAA)的內容複製到剛剛保留在變數c上的空間中。這裡看看偵錯程式中的指令。我只突出顯示了執行寫入棧的部分。

在下面的螢幕截圖中,您會注意到它會繼續遍歷argv [1]的值,寫入棧中(從c緩衝區的起始地址往下),直到寫入完所有的argv [1]到棧。

在我們看看函式終止時棧會是什麼情況之前,先看看下面的步驟圖,以強化函式foo()被呼叫時所採取的步驟。

在strcpy()完成並且函式foo()準備終止之後,必須在棧上進行一些清理。讓我們來看一下棧,當foo()準備終止並且程式執行被轉回到main()。

如您所見,執行的第一條指令是MOV ESP,EBP,它將EBP的值放入ESP中,因此它現在指向0012FF6C,並從棧中刪除變數c(AAAAAAAAAAA)。棧頂部現在包含已儲存的EBP:

當執行下一條指令POP EBP時,它將從main()中恢復先前的堆疊基址指標,並將ESP增加4倍。堆疊指標現在指向呼叫foo()之前放在堆疊上的RETURN值。當執行RETN指令時,它將使程式執行流程返回到緊接在CALL foo()指令之後的main()中的下一條指令,如下面的螢幕截圖所示。

函式main()將通過將棧指標向下移動(通過將其值增加4)並清除argv [1]。然後它將通過XOR儲存argv [1](EAX)的暫存器,恢復儲存的EBP並返回到儲存的返回地址。
這個列子足夠了解一個函式 stack frame是如何建立/刪除以及本地變數如何儲存在堆疊上的。如果你想要更多的例子,我鼓勵你檢視一些其他很棒的教程(尤其是那些由Corelan Team釋出的教程,已失效)。
這是Windows Exploits系列的第一部分的結尾。希望您熟悉使用偵錯程式,可以識別一些基本的彙編指令,並且瞭解(高層次)Windows如何管理記憶體以及堆疊如何操作。在下一篇文章中,我們將使用相同的基本函式foo()來引入基於棧溢位的概念。然後,我將立刻寫一個針對實際軟體產品的真實示例漏洞利用。
原文連結: https:// bbs.pediy.com/thread-22 5749.htm
文章來源:看雪社群