【网络安全】Metasploit 生成的 Shellcode 的导入函数解析以及执行流程分析(1)
2021 年 4 月,研究人員深入分析了 Cobalt Strike滲透測試技術,以及它的一些簽名規(guī)避技術是如何在檢測技術下失效的。在本文中,我們將深入討論Metasploit,這是一個可以與Cobalt Strike互操作的常用框架。
在本文中,我們將討論以下主題:
shellcode的導入解析——Metasploit shellcode如何定位來自其他DLL的函數(shù),以及我們?nèi)绾晤A先計算這些值來解析來自其他有效載荷變體的任何導入;
逆向shell的執(zhí)行流程——該過程非常簡單;
破壞 Metasploit 導入解析——一種非侵入性欺騙技術(不涉及鉤子),讓 Metasploit 以高可信度通知防病毒軟件 (AV)。
對于這個分析,研究人員在 v6.0.30-dev 版本下使用 Metasploit 生成了研究人員自己的 shellcode。使用以下命令生成的惡意樣本的結果為 3792f355d1266459ed7c5615dac62c3a5aa63cf9e2c3c0f4ba036e6728763903 的 SHA256 哈希值,并且可以在 VirusTotal 上找到,供愿意自己嘗試的讀者使用。
msfvenom -p windows/shell_reverse_tcp -a x86 > shellcode.vir在整個分析過程中,我們重新命名了函數(shù)、變量和偏移量,以反映它們的作用并提高清晰度。
初步分析
在本節(jié)中,研究人員將概述確定下一步分析(導入解析和執(zhí)行流分析)所遵循的初始邏輯。
雖然典型的可執(zhí)行文件包含一個或多個入口點(導出的函數(shù)、TLS 回調(diào)等),但 shellcode 可以被視為最原始的代碼格式,其中初始執(zhí)行從第一個字節(jié)開始。
從初始字節(jié)分析生成的 shellcode 概述了兩個操作:
從分析的角度來看,①處的第一條指令可以忽略。cld操作清除方向標志,確保字符串數(shù)據(jù)是向前讀取而不是向后讀取(例如:cmd與dmc的區(qū)別)。② 處的第二個調(diào)用操作將執(zhí)行轉移到研究人員命名為 Main 的函數(shù)中,該函數(shù)將包含 shellcode 的主要邏輯。
調(diào)用 Main 函數(shù)的反匯編 shellcode
在Main函數(shù)中,我們觀察到額外的調(diào)用,比如下圖中突出顯示的四個調(diào)用(③、④、⑤和⑥)。這些調(diào)用針對一個尚未識別的函數(shù),其地址存儲在ebp寄存器中。要理解這個函數(shù)的位置,我們需要退后一步并了解調(diào)用指令的操作方式。
Main 函數(shù)的反匯編
調(diào)用指令通過執(zhí)行兩個操作將執(zhí)行轉移到目標目的地:
它將返回地址(位于調(diào)用指令之后的指令的內(nèi)存地址)壓入堆棧。該地址稍后可以被 ret 指令用于將執(zhí)行從被調(diào)用函數(shù)(被調(diào)用者)返回到調(diào)用函數(shù)(調(diào)用者);
它將執(zhí)行轉移到目標目的地(被調(diào)用者),就像 jmp 指令一樣;
因此,來自Main函數(shù)③的第一個pop指令將調(diào)用者的返回地址存儲到ebp寄存器中。這個返回地址隨后被作為函數(shù)調(diào)用,在偏移量0x99、0xA9和0xB8(④、⑤和⑥)處調(diào)用。這種模式,在每次調(diào)用之前出現(xiàn)類似的推送,往往表明存儲在 ebp 中的返回地址是動態(tài)導入解析函數(shù)。
“普通”可執(zhí)行文件(例如:Windows上的可移植可執(zhí)行文件)包含必要的信息,因此,一旦被操作系統(tǒng)(OS)加載程序加載,代碼就可以調(diào)用導入的例程,比如那些來自Windows API的例程(例如:LoadLibraryA)。為了實現(xiàn)此默認行為,可執(zhí)行文件應具有操作系統(tǒng)可以解釋的特定結構。由于shellcode是代碼的一個基本版本(它沒有預期的結構),操作系統(tǒng)加載程序無法幫助它解析這些導入的函數(shù),更嚴重的是,操作系統(tǒng)加載程序將無法“執(zhí)行”shellcode文件。為了解析這個問題,shellcode 通常會執(zhí)行“動態(tài)導入解析”。
執(zhí)行“動態(tài)導入解析”的最常見技術之一是對每個可用的導出函數(shù)進行哈希,并將其與所需導入的哈希進行比較。由于shellcode開發(fā)者不能總是預測一個特定的DLL(例如:ws3_32.dll for Windows Sockets)及其導出是否已經(jīng)加載,觀察shellcode加載DLL并不罕見,首先調(diào)用LoadLibraryA函數(shù)(或它的一個替代方法)。在調(diào)用其他dll的導出之前依賴于LoadLibraryA(或替代)是一種穩(wěn)定的方法,因為這些庫加載函數(shù)是kernel32.dll的一部分,是少數(shù)幾個可以加載到每個進程中的 DLL 之一。
為了證實研究人員的上述理論,研究人員可以搜索所有調(diào)用指令,如下圖所示(例如:在搜索菜單下使用 IDA 的文本等選項)。除了第一次調(diào)用 Main 函數(shù)外,所有實例都引用 ebp 寄存器。這一觀察結果與研究人員將在下一節(jié)中觀察到的眾所周知的常量一起,支持了研究人員的理論,即存儲在 ebp 中的地址持有一個指向執(zhí)行動態(tài)導入解析的函數(shù)的指針。
shellcode中的所有調(diào)用指令
對ebp寄存器的大量調(diào)用表明它確實持有一個指向導入解析函數(shù)的指針,我們現(xiàn)在知道該函數(shù)位于對Main的第一次調(diào)用之后。
導入解析方案分析
到目前為止,我們注意到在最初調(diào)用Main之后的指令起到了至關重要的作用,因為我們希望它成為導入解析例程。在分析shellcode的邏輯之前,讓我們先分析這個解析例程,因為它將簡化對其余調(diào)用的理解。
從導入哈希到函數(shù)
直接位于Main初始調(diào)用之后的代碼是導入解析開始的地方,要解析這些導入,例程首先定位加載到內(nèi)存中的模塊列表,因為這些模塊包含它們可用的導出函數(shù)。
為了找到這些模塊,一種常用的shellcode技術便是與Process Environment Block(簡稱PEB)進行交互。
在計算過程中,進程環(huán)境塊(縮寫為 PEB)是 Windows NT 操作系統(tǒng)家族中的一種數(shù)據(jù)結構。它是操作系統(tǒng)內(nèi)部使用的不透明數(shù)據(jù)結構,其大部分字段不供操作系統(tǒng)以外的任何其他使用。PEB包含應用于整個進程的數(shù)據(jù)結構,包括全局上下文、啟動參數(shù)、程序映像加載程序的數(shù)據(jù)結構、程序映像基地址,以及用于為整個進程的數(shù)據(jù)結構提供互斥的同步對象。
如下圖所示,為了訪問PEB, shellcode訪問線程環(huán)境塊(TEB),它可以通過寄存器(⑦)立即訪問。TEB結構本身包含指向PEB(⑦)的指針。在PEB中, shellcode可以定位PEB_LDR_DATA結構(⑧),它反過來包含對多個雙鏈接模塊列表的引用。正如在(⑨)中所觀察到的,Metasploit shellcode利用這些雙鏈列表(InMemoryOrderModuleList)中的一個來隨后遍歷包含已加載模塊信息的LDR_DATA_TABLE_ENTRY結構。
一旦識別出第一個模塊,shellcode 在⑩ 處檢索模塊的名稱(BaseDllName.Buffer)和在? 處的緩沖區(qū)的最大長度(BaseDllName.MaximumLength),這是必需的,因為緩沖區(qū)不能保證以 NULL 結尾。
初始模塊檢索的反匯編
值得強調(diào)的一點是,與通常的指針(TEB.ProcessEnvironmentBlock、PEB.Ldr 等)相反,雙鏈表指向下一項的列表條目。這意味著列表中的指針將指向非零偏移量,而不是指向結構的起始位置。因此,雖然在下圖中 LDR_DATA_TABLE_ENTRY 在偏移量為 0x2C 處具有 BaseDllName 屬性,但從列表條目的角度來看,偏移量為 0x24 (0x2C-0x08)。這可以在上圖中觀察到,其中必須減去 8 的偏移量才能訪問 ⑩ 和 ? 處的兩個 BaseDllName 屬性。
從TEB到BaseDllName
恢復了DLL名稱的緩沖區(qū)和最大長度后,shellcode繼續(xù)生成一個哈希。為此,shellcode對最大名稱長度內(nèi)的每個ASCII字符執(zhí)行一組如下操作:
如果字符是小寫的,它會被修改為大寫。該操作是根據(jù)字符的ASCII表示來執(zhí)行的,這意味著如果值是0x61或更高(a或更高),就會減去0x20以進入大寫范圍;
生成的哈希(最初為0)向右旋轉13位(ROR) (0x0D);
大寫字符被添加到現(xiàn)有哈希中;
描述KERNEL32.DLL的第一個字符(K)的哈希循環(huán)的模式
在固定的注冊表大小(在edi的情況下是32位)上重復旋轉和添加,字符最終將開始重疊。這些重復和重疊的組合使操作不可逆,因此產(chǎn)生給定名稱的32位哈希/校驗和。
一個有趣的發(fā)現(xiàn)是,雖然LDR_DATA_TABLE_ENTRY中的BaseDllName是unicode編碼的(每個字符2個字節(jié)),但代碼通過使用 lodsb(見?)將其視為 ASCII 編碼(每個字符 1 個字節(jié))。
模塊名稱哈希例程的反匯編
哈希生成算法可以在Python中實現(xiàn),如下面的代碼片段所示。雖然我們前面提到過,根據(jù) Microsoft 文檔,BaseDllName 的緩沖區(qū)不需要以 NULL 終止,但大量測試表明,NULL 終止總是如此,并且通??梢约僭O。這個假設是使 MaximumLength 屬性成為有效邊界的原因,類似于 Length 屬性。因此,以下代碼片段期望傳遞給 get_hash 的數(shù)據(jù)是一個由以null結尾的Unicode字符串生成的Python 字節(jié)對象。
上述函數(shù)可用于計算 KERNEL32.DLL 的哈希值,如下所示。
生成 DLL 名稱的哈希后,shellcode 繼續(xù)識別所有導出的函數(shù)。為此,shellcode 首先檢索 LDR_DATA_TABLE_ENTRY 的 DllBase 屬性(?),該屬性指向 DLL 的內(nèi)存地址。此時,IMAGE_EXPORT_DIRECTORY 結構通過遍歷可移植可執(zhí)行文件的結構(? 和 ?)并將相對偏移量添加到 DLL 的內(nèi)存中基地址來識別。最后一個結構包含導出函數(shù)名稱的數(shù)量(?)以及指向這些名稱的指針表(?)。
導出檢索的反匯編
上述操作的架構如下,其中虛線表示從相對偏移量計算出的地址,該偏移量增加了 DLL 的內(nèi)存基地址。
從LDR_DATA_TABLE_ENTRY到IMAGE_EXPORT_DIRECTORY
一旦確定了導出名稱的數(shù)量及其指針,shellcode 就會按降序枚舉該表。具體就是,名稱的數(shù)量用作?處的遞減計數(shù)器。對于每個導出函數(shù)的名稱并且沒有匹配,shellcode 執(zhí)行一個哈希例程(?上的hash_export_name),與研究人員之前觀察到的類似,唯一的區(qū)別是保留了字符大小寫(hash_export_character)。
最后的哈希是通過將最近計算的函數(shù)哈希(ExportHash)添加到?處之前獲得的模塊哈希(DllHash)中獲得的。這是在尋求哈希和?之間的區(qū)別,除非他們匹配,否則操作重新開始下一個函數(shù)。
導出名稱哈希的反匯編
如果導出的函數(shù)都不匹配,例程將在InMemoryOrderLinks雙鏈表中檢索下一個模塊,并再次執(zhí)行上述操作,直到找到匹配項。
反匯編到下一個模塊的循環(huán)過程
上面的遍歷雙鏈表的架構如下圖所示:
遍歷InMemoryOrderModuleList
如果找到匹配,shellcode將繼續(xù)調(diào)用導出的函數(shù)。從前面確認IMAGE_EXPORT_DIRECTORY檢索其地址,代碼將首先需要映射函數(shù)的名字的順序(?),順序導出數(shù)量。一旦順序從AddressOfNameOrdinals表中恢復過來,地址可以通過使用序數(shù)AddressOfFunctions表中的索引(?)。
導入“調(diào)用”的反匯編
最后,一旦導出的地址被恢復,shellcode通過確保返回地址首先在堆棧上(刪除它正在在?搜索的哈希)來模擬調(diào)用行為,其次是所有參數(shù)根據(jù)默認的Win32 API __stdcall調(diào)用協(xié)定(?)。然后代碼在 ? 處執(zhí)行 jmp 操作,將執(zhí)行轉移到動態(tài)解析的導入,在返回時,將從初始調(diào)用 ebp 操作發(fā)生的位置恢復。
總的來說,可以將動態(tài)導入解析架構為嵌套循環(huán)。主循環(huán)按照內(nèi)存中的順序遍歷模塊(下圖中的藍色),而對于每個模塊,第二個循環(huán)遍歷導出函數(shù),在所需的導入和可用的導出之間尋找匹配的哈希(下圖中的紅色)。
導入解析流
【網(wǎng)絡安全學習資料獲取方式】
總結
以上是生活随笔為你收集整理的【网络安全】Metasploit 生成的 Shellcode 的导入函数解析以及执行流程分析(1)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CVE-2021-40444 0 day
- 下一篇: 【网络安全】Metasploit生成的S