DEP(數據執行保護)是一種內存保護功能,允許系統將內存頁標記為不可執行。 ROP(面向返回的編程)是一種利用技術,允許攻擊者在啟用DEP等保護的情況下執行shellcode。在這篇文章中,我們將介紹應用程序的逆向過程,以發現緩衝區溢出漏洞,並開髮用於繞過DEP的ROP小工具鏈(Gadget Chain)。
我們將在開發過程中使用以下工具:QuoteDB、TCPView(一個查看端口和線程的小工具,只要木馬在內存中運行,一定會打開某個端口,只要黑客進入你的電腦,就有新的線程)、IDA Freeware、WinDbg(在windows平台下,強大的用戶態和內核態調試工具)和rp++。
QuoteDB是一個設計上易受攻擊的應用程序,創建它是為了實踐逆向工程並利用它進行開發。如下圖所示,該應用程序正在偵聽端口3700上的網絡連接:
我們已經使用TCPView確認程序確實在監聽端口3700。
現在我們需要對應用程序進行逆向工程,看看它是如何處理網絡連接的。 accept函數用於允許指定端口上的傳入連接,然後進程創建一個運行“handle_connection”例程的新線程,如下所示:
recv函數用於從連接的套接字接收數據:
我們已經開發了一個基本的Python腳本,它創建一個TCP套接字,並在端口3700上向遠程服務器發送1000個“a”字符:
我們已將WinDbg附加到QuoteDB.exe進程,並列出了加載的模塊,如下圖所示。
我們可以使用“bp”命令在recv函數調用後放置斷點,使用“bl”命令確認斷點已成功設置:
recv函數返回後,EAX寄存器包含以十六進制接收的字節數:
緩衝區的前4個字節表示一個操作碼,該操作碼被移到EAX寄存器中,然後打印在命令行中:
下圖顯示了WinDbg中的printf調用,我們可以觀察到第三個參數(=Opcode)由4個“A”字符組成:
該進程顯示源IP地址、源端口、緩衝區長度和十進制的操作碼:
應用程序從Opcode中減去0x384(十進制900),並將結果與4進行比較。這是一個帶有5個示例的開關,也顯示在下圖中。
EAX寄存器大於4,執行流被重定向到默認情況,該情況調用“log_bad_request”函數:
上述函數包含緩衝區溢出漏洞,如下圖所示,可執行文件在堆棧上分配0x818(2072)字節,用0初始化緩衝區,並在不檢查邊界的情況下將有效負載複製到此緩衝區:
發生溢出是因為要復制的字符數(0x4000)大於緩衝區的大小,並且可能會重寫返回地址:
我們選擇發送3000個“A”字符以利用該漏洞。如下所示,返回地址在堆棧上被重寫,程序因此崩潰:
我們使用了“msf-pattern_create”命令來生成一個唯一的模式,該模式將為我們提供偏移量。
應用程序在不同的地址崩潰,該地址用於使用“msf-pattern_offset”命令確定精確的偏移量:
我們修改了概念證明,以包括上述偏移量。在正確的地址崩潰後,ESP寄存器指向我們控制的緩衝區的最後一部分:
我們使用了narly WinDbg擴展來顯示加載的模塊及其內存保護,下圖顯示了該可執行文件是在啟用ASLR和DEP保護的情況下編譯的。
Windows Defender Exploit Guard可以用來啟用/禁用ASLR。我們需要進入“Exploit protection settings”,選擇“Program settings”頁簽,點擊“Add Program to custom”,選擇“Choose exact file path”選項:
我們想通過發送從“\x00”到“\xFF”的所有字節並確定它們如何寫入堆棧來找出哪些字符被認為是“不適合”的:
如下圖所示,沒有不適合的字符,不過為了研究,我們將“\x00”視為不適合字符,因為它通常是不適合字符。正因為如此,漏洞開發過程稍微複雜一些,但它可能更容易適應其他應用程序。
我們使用rp++工具從“SysWOW64\kernel32.dll”模塊中提取ROP小工具,因為ASLR是禁用的,所以我們可以選擇任何提供必要ROP小工具的DLL,但是,我們將在以後的文章中看到應用程序洩漏特定DLL中的地址。我們已將小工具中的最大指令數設置為5:
由於DEP保護,堆棧不再是可執行的,我們需要找到執行shellcode的方法。我們可以使用VirtualAlloc、VirtualProtect和WriteProcessMemory等API來繞過DEP。 VirtualAlloc函數用於保留、提交或更改進程地址空間中頁面的狀態。該函數有4個參數:
lpAddress
dwSize
flAllocationType
flProtect
我們的目的是將flAllocationType參數設置為0x1000(MEM_COMMIT),將flProtect設置為0x40(PAGE_EXECUTE_READWRITE)。我們需要在堆棧上創建以下框架:
VirtualAllocaddress
Returnaddress(Shellcodeaddress)
lpAddress(Shellcodeaddress)
dwSize(0x1)
flAllocationType(0x1000)
flProtect(0x40)
我們為每個元素分配了一個特定的值,需要在運行時使用正確的值對其進行修改。
如下圖所示,可以在ESP寄存器的固定偏移處找到框架:
kernel32.dll模塊的起始地址可以使用WinDbg來標識。所有ROP小工具的地址必須使用該值而不是“ROP.txt”文件中的加載地址來計算:
首先,我們需要找到一個保存ESP寄存器值的ROP小工具。我們確定了一個將ESP寄存器複製到ESI寄存器的寄存器:
我們修改了Python腳本,以包含kernel32地址和上述ROP小工具偏移量,如下所示:
我們已經成功地將執行流程重定向到我們的第一個ROP小工具,接著將其他ROP小工具鏈接在一起,因為ESP仍然指向我們的緩衝區:
現在我們需要找到從ESI寄存器中減去0x1C的方法。然而,由於缺少涉及使用ESI寄存器進行計算的ROP小工具,我們找到了一個將ESI寄存器複製到EAX中的ROP小工具。唯一的問題是ESI也被“POP ESI”指令修改,但是,它不會影響我們的利用:
在許多ROP小工具中發現的另一個寄存器是ECX。我們已經確定了一個ROP小工具,它從堆棧中彈出一個值到ECX寄存器中,另一個小工具將EAX和ECX寄存器加在一起。加上負值等於減去相同的正值:
通過在之前的EAX值上添加-0x1C (=ECX)值,EAX指向VirtualAlloc框架:
因為EAX在任何計算中都很有用,所以我們需要在執行任何其他操作之前找到保存它的方法。我們發現了一個ROP小工具,它將EAX寄存器複製到ECX中,ECX將用於修改框架中的值。事實上,EAX也被這個ROP小工具修改了,但這並不影響我們的利用:
我們修改後的概念證明如下圖所示。 “junk”值對堆棧對齊很有用,對應於“POP reg”和“retn4”指令。
再次運行Python腳本後,我們可以觀察到ECX寄存器的值與之前的EAX寄存器相同,並指向VirtualAlloc框架:
IAT(導入地址表)包含指向由其他DLL導出的函數的指針。例如,kernel32.dll在VirtualAlloc的IAT中有一個條目,即使VirtualAlloc實際地址發生變化,該條目也保持不變:
我們使用了“POP EAX”指令將VirtualAlloc IAT複製到EAX寄存器中,需要對其進行解除引用才能獲得VirtualAlloc地址,如下所示:
在更新Python腳本並再次運行之後,我們成功地獲得了EAX中的VirtualAlloc地址: