匯編Shellcode的技巧
來源 https://www.4hou.com/technology/3893.html
本文參考來源於pentest
我們在上一篇提到要要自定義shellcode,不過由於這是個復雜的過程,我們只能專門寫一篇了,本文,我們將會給大家介紹shellcode的基本概念,shellcode在編碼器及解碼器中的匯編以及幾種繞過安全檢測的解決方案,例如如何繞過微軟的 EMET(一款用以減少軟件漏洞被利用的安全軟件)。為了理解本文的內容,大家需要具了解x86匯編知識和基本文件格式(如COFF和PE)。
專業術語
進程環境塊(PEB):PEB(Process Environment Block)是Windows NT操作系統中的一種數據結構。由於PEB僅在Windows NT操作系統內部使用,其大多數字段不面向其他操作系統,所以PEB就不是一個透明的數據結構。微軟已在其MSDN Library技術開發文檔中開始修改PEB的結構屬性。它包含了映像加載器、堆管理器和其他的windows系統DLL所需要的信息,因為需要在用戶模式下修改PEB中的信息,所以必須位於用戶空間。PEB存放進程信息,每個進程都有自己的 PEB 信息。准確的 PEB 地址應從系統的 EPROCESS 結構的 1b0H 偏移處獲得,但由於 EPROCESS在進程的核心內存區,所以程序不能直接訪問。
導入地址表(IAT):IAT (Import Address Table)由於導入函數就是被程序調用但其執行代碼又不在程序中的函數,這些函數的代碼位於一個或者多個DLL 中,當PE 文件被裝入內存的時候,Windows 裝載器才將DLL 裝入,並將調用導入函數的指令和函數實際所處的地址聯系起來(動態連接),這操作就需要導入表完成.其中導入地址表就指示函數實際地址。
數據執行保護(DEP):與防病毒程序不同,數據執行保護(DEP)是一組對內存區域進行監控的硬件和軟件技術,DEP技術的目的並不是防止在計算機上安裝有害程序。 而是監視你的已安裝程序,幫助確定它們是否正在安全地使用系統內存。在Microsoft Windows XP Service Pack 2(SP2)和Microsoft Windows XP Tablet PC的2005版中,DEP是通過硬件和軟件來運行的。DEP的主要優點是阻止代碼在數據頁中運行。通常,代碼不會在默認堆和堆棧中執行。而硬件可以強制DEP檢測在這些位置運行的代碼,當運行發生異常時,軟件就可以通過DEP阻止惡意代碼利用Windows中的異常處理機制。
stdcall調用約定:__stdcall調用約定相當於16位動態庫中經常使用的PASCAL調用約定。調用約定是用來清理堆棧的,和__cdecl類似,stdcall調用約定也是按從右至左的順序壓參數入棧,由調用者把參數彈出棧。stdcall調用是把函數參數列表的前三個參數放入寄存器eax,edx,ecx
。返回值存儲在EAX寄存器中。 stdcall是Microsoft Win32 API和Open Watcom C ++的標准調用約定。stdcall是微軟win32 API和open Watcom C++的標准調用約定。
Shellcodes在網絡安全領域起着非常重要的作用,它們被廣泛應用於許多惡意軟件和漏洞。那么,為什么shellcode會如此受到惡意程序的歡迎?
Shellcode實際是一段代碼(也可以是填充數據),是用來發送到服務器利用特定漏洞的代碼,一般可以獲取權限。另外,Shellcode一般是作為數據發送給受攻擊服務器的, Shellcode是溢出程序和蠕蟲病毒的核心。
之所以命名為shellcode,是因為shellcode運行完以后都會返回一個命令行shell,但如今幾乎所有的編譯器生成的程序都能轉化為shellcode。因為編寫shellcode涉及到深入理解目標架構和操作系統的匯編語言。雖然我們可以在網上找到很多開源的shellcode,但為了達到不同的使用目的,每個安全研究員都會自己編寫一個各自的shellcode,同時重新編寫的過程也是理解shellcode的過程。
所以,本文的目標就是要教你如何在不同的檢測環境下,重編自己的shellcode。
基本的Shellcode編程
與Windows不同,為不同操作系統而編寫的shellcode需要不同的編寫方法,比如基於UNIX的操作系統提供了通過int 0x80接口與內核通信的直接方式,基於UNIX的操作系統中的所有系統調用都有唯一的編號,比如int 0x80就代表系統調用時設置中斷向量號0x80的中斷描述符,內核使用給定的數字和參數執行系統調用,但是這里會有一個問題,Windows系統並沒有直接的內核接口,這意味着系統調用必須有精確內存地址(函數指針),但經過硬編碼的函數地址並不能完全解決這個問題,因為Windows系統的每個函數地址會因為版本中甚至配置不同而發生變化,所以,shellcode的編寫一定要考慮系統的版本,如果要在Windows上編寫不受系統版本影響的shellcode,我們只要解決地址問題,這可以通過在運行時動態地找到函數地址來實現。
解決尋址問題
在本文中,我們將會使用解析PEB的方法來解決尋址的問題,簡單來說,該方法就是使用PEB數據結構來定位基地址的加載DLL,從而解析出EAT及其函數地址,目前幾乎所有的不受系統版本影響的shellcode的編寫都是用這個技術得到Windows API函數地址的。
在解析PEB的方法中,我們利用“FS”段寄存器,“FS”段寄存器可以指向在微軟系統環境中線程環境塊(TEB)的基地址,TEB是在用戶模式中分配和初始化的內存塊,TEB耗用一個內存頁(4KB),主要包括:線程異常處理鏈首、線程本地存儲數據以及由GDI和OpenGL圖形使用的一些數據結構,當在內存區運行shellcode時,我們需要將TEB向前偏移48字節,
如下圖所示,現在我們就找到了PEB的結構體指針,
在獲得PEB的結構體指針后,現在我們將PEB結構的起始位置向前偏移12個字節,以便獲得PEB塊內的LDR數據結構的地址,
LDR數據結構包含關於進程已加載模塊的信息,如果我們把LDR數據結構再進一步移動20個字節,我們就可以獲得進程dll中的 “InMemoryOrderModuleList” 加載模塊,
現在我們的指針指向InMemoryOrderModuleList,它是一個LIST_ENTRY結構,Windows將這個結構定義為一個“包含加載的進程模塊的雙向鏈表頭”。列表中的每個項都是一個指向LDR_DATA_TABLE_ENTRY結構的指針,這個結構是我們的主要目標,它包含加載的DLL(模塊)的全名和基地址,因為加載的模塊的順序可以改變,我們應該檢查全名,以便選擇正確的DLL,其中包含函數正在尋找,這可以很容易做到從LDR_DATA_TABLE_ENTRY開始向前移動40個字節,如果DLL名稱匹配我們正在尋找的,我們可以繼續,在LDR_DATA_TABLE_ENTRY內向前移動16個字節,我們現在終於有基地加載的DLL的地址,
現在PEB的指針指向了InMemoryOrderModuleList,InMemoryOrderModuleList是一個LIST_ENTRY結構,微軟將其定義為“包含加載進程模塊的雙向鏈表頭”。
由於InMemoryOrderModuleList字段是一個指針,指向LDR_DATA_TABLE_ENTRY 結構體上的LIST_ENTRY字段。但是它不是指向LDR_DATA_TABLE_ENTRY 起始位置的指針,而是指向這個結構的InMemoryOrderLinks字段。所以我們需要想辦法讓InMemoryOrderModuleList指向LDR_DATA_TABLE_ENTRY。
LDR_DATA_TABLE_ENTRY包含加載模塊的所有地址和名稱,因為模塊加載的順序可能改變,我們在加載的時候,應該校驗加載模塊的全名,以便選擇包含我們要查找的函數的動態庫。我們可以把LDR_DATA_TABLE_ENTRY向前位移40個字節,如果DLL名稱匹配,則證明是我們需要的動態庫,然后我們繼續把LDR_DATA_TABLE_ENTRY向前位移16個字節,就能得到具有基地址的加載模塊了。
至此,我們就完成了獲得函數地址的第一步,現在我們有了包含所需函數的DLL的基地址,所以,我們就必須解析DLL的導出地址表,以便能找到所需的函數地址,導出地址表位於能在PE頭部的可選位置內,從基址向前移60個字節,我們就得到了含有DLL的PE頭的內存地址,
最后我們需要用“模塊基地址+ PE頭地址+ 120字節”這個公式計算出地址表的地址,至此,地址表的地址就被導出了,在得到EAT地址后,我們就可以訪問由DLL導出的所有函數,IMAGE_EXPORT_DIRECTORY的結構如下所示,
此結構包含導出函數的地址,名稱和數量,使用相同大小的遍歷統計計算,可以在此結構內獲得所需的函數地址,當然,導出函數的順序可能會因系統版本不同而異,因此我們要在獲取函數地址之前,對函數名稱進行校驗,在確定函數名稱之后,函數地址的計算就好比計算幾個Windows數據結構的大小,因為你可以理解這個方法是關於計算幾個Windows數據結構的大小,並在內存中遍歷,不過在計算函數地址時,真正的挑戰是要建立一個可靠的名稱比較機制來選擇正確的DLL和函數,如果PEB解析技術太難實現,我們還會為你介紹一種更簡單的方法。
Hash API
metasploit項目中幾乎所有的shellcode都使用一個名為Hash API的匯編塊,它是由Stephen Fewer編寫的一段代碼,自2009年被公布以來,大多數Windows的Windows shellcode就一直在使用它。這個匯編塊使得解析PEB結構更容易,它使用基本的PEB解析邏輯和一些額外的Hash算法來快速找到所需的函數,通過計算ROR13函數和模塊名的哈希值,可以讓這個匯編塊的使用起來更加容易,Hash API的匯編塊使用stdcall調用約定時,所需的函數參數需要包含ROR13函數名的哈希值和包含該函數的DLL名,在得到我們所需要的參數和函數哈希值之后,如前面所述,這時就該解析PEB塊,並找到模塊名稱了,然后計算ROR13哈希值並將其保存到堆棧,最后再把這個哈希值移動到DLL的導出地址表並計算每個函數名稱的ROR13哈希值,ROR13哈希值等於每個函數名稱哈希值和模塊名稱哈希值的和,
如果匹配到我們要找的哈希值,意味着想要的函數被找到了,最后Hash API會使用棧上的參數跳轉到找到的函數地址執行。雖然我們最后會得到一段非常簡潔實用的代碼,但由於其廣泛的使用,一些殺毒軟件已經可以檢測到它了,甚至連一些殺毒軟件已經把使用ROR13哈希的列入到惡意程序黑名單了,不過配合使用一些編碼機制,目前該方法還是能繞過殺毒軟件的。
不過我們還是能找到其他方法來找尋找到API函數地址。
編碼器和解碼器的設計
在開始設計之前,我們要先像大家講清楚,單獨使用這個編碼器課不會產生完全不可檢測的shellcode,因為在運行shellcode之后,解碼器將直接運行並將整個shellcode解碼為其原始形式,這當然不能繞過具有動態分析機制的殺毒軟件了,
解碼器邏輯非常簡單,它將使用隨機生成的多字節XOR鍵來解碼shellcode,在解碼操作之后它將執行它,在將shellcode放置在解碼器頭部之前,應該使用多字節XOR密鑰加密, shellcode和XOR鍵應放在“<Shellcode>”,“<Key>”標簽內,
解碼器的邏輯非常簡單,就是使用一個隨機生成的多字節XOR密鑰來解碼shellcode,在解碼操作完成后運行shellcode,在將shellcode放置在解碼器頭部之前,應該使用多字節XOR密鑰來對shellcode加密,這時shellcode和XOR密鑰就分別位於“<Shellcode>”和”<Key>”標簽內。
我們在此使用了JMP及CALL的指令來獲得shellcode和密鑰的地址,然后在shellcode和密鑰的每個字節都要執行一次邏輯XOR操作,每次解密密鑰運行到末尾時,JMP及CALL指令都會把密鑰重置為起始地址,在完成解碼操作之后,JMP及CALL指令將調用跳轉到shellcode,使用字節更長的XOR密鑰來增加shellcode的隨機性,但如此反復的操作也增加代碼塊信息熵的大小。
因此要避免使用太長的解密密鑰,經過我們的測試,使用基本的邏輯運算如XOR,NOT,ADD,SUB,ROR,ROL,就能得到幾百種編碼shellcode的方法,而這些經過編碼的shellcode更是能產生無限可能的shellcode運行方式,殺毒軟件在解碼序列之前檢測到shellcode的概率很低,不過自從殺軟開發出了啟發式引擎以后,就能夠檢測到shellcode的解密循環過程。目前,安全測試人員在編寫shellcode編碼器時,還沒有能夠找到用於繞過用於檢測解碼循環的靜態方法的有效方式。
干擾寄存器的辦法
在x86架構中,所有寄存器都有特定的用途,例如ECX代表擴展計數器寄存器,它通常用作循環計數器,當我們在任何編譯語言中編寫一個基本循環條件時,編譯器可能使用ECX寄存器作為循環計數器變量,這樣代碼塊內連續增加的ECX寄存器就很容易觸發殺毒軟件的啟發式引擎檢測機制,解決這個問題的方法很簡單,不把ECX寄存器用作循環計數器,但是這個辦法對於所有的其它類型的代碼片段(如函數epilogue/prologue等)也非常有效。由於很多代碼識別機制取決於寄存器使用,所以我們可以通過干擾寄存器的辦法來降低編寫匯編代碼時被檢測到的風險。
垃圾混淆代碼
目前幾乎每個殺毒軟件都有辦法來識別代碼塊內的解碼器,據我們統計,大概有可能有幾百種方法。但不管是哪種方法,最終都要生成用於靜態地檢查的代碼塊簽名,所以為了避免被檢測到,我們可以使用解碼器代碼內部的隨機NOP指令來繞過靜態簽名分析,不過不一定非要使用NOP指令,可以是任何保持原始代碼功能的指令,其目的最終是向代碼塊總添加垃圾指令,以打破代碼塊內的惡意簽名,不過也要注意垃圾指令的大小,因為過多的垃圾混淆代碼會讓惡意程序的信息熵變大。
NOP指令的代碼如下:
從上圖可以看出,唯一的改變在於EAX和ECX寄存器,由於在垃圾混淆代碼時,負責對shellcode索引計數的寄存器是EAX,並且在每個XOR和MOV指令之間都會插入一些NOP填充,由於我們測試時使用的是 “windows/meterpreter/reverse_tcp ”命令,所以在加密之后shellcode有一個10字節長的隨機XOR密鑰,這些都會存放在解碼器內,使用nasm -f bin Decoder.asm命令將解碼器匯編成二進制格式(不要忘了移除shellcode中的換行符,否則nasm不能執行匯編命令)。
下面是殺毒軟件對編碼前的shellcode掃描的結果,
不出所料,大量的殺軟掃描器能檢測到編碼之前的shellcode。讓我們再來看看編碼后的掃描結果,
巧妙利用殺軟的緩解技術
雖然可以用很多辦法實現免殺,但殺軟的防范漏洞緩解技術卻將免殺的技術推向了一個全新的水平。微軟在2009年發布了增強緩解體驗工具包(EMET),EME是加固Windows防止惡意程序利用操作系統或軟件安全漏洞的最有效方法之一,它有幾個保護機制,
動態數據執行保護(DEP) 結構異常處理程序覆蓋保護(SEHOP) NullPage分配 堆噴射保護 導出地址表地址過濾(EAF) 強制ASLR 導出地址表訪問過濾(EAF +) ROP緩解措施 加載庫檢查 內存保護檢查 調用者檢查 堆棧翻轉(Stack pivot) Attack Surface Reduction(ASR)
在這些緩解中,EAF,EAF +和調用者檢查是我們最關注的三種技術,如前所述,metasploit框架中幾乎所有的shellcode都使用Stephen Fewer的Hash API,並且由於Hash API應用了PEB 及EAT解析技術,EMET可以輕松檢測和阻止shellcodes的運行 。
繞過EMET
調用者檢查可以檢測到EMET內發生的Windows API調用,並阻止Win API函數中的RET和 JMP指令,以便阻止所有ROP方式的漏洞利用,在HASH API中,在找到需要的API函數地址后,會使用JMP指令執行函數,但這會觸發EMET的調用者檢查,所以為了繞過調用者檢查,應該避免使用指向Win API函數的JMP和RET指令,我們要用CALL指令替換JMP指令來指向Win API函數,從理論上講Hash API應該能繞過調用者檢查,但是當我們查看EAF及EAF+緩解機制時,Hash API卻使用被調用的代碼阻止程序訪問導出地址表(EAT),並且還會檢查堆棧寄存器是否在允許的邊界內,或者嘗試檢測特定代碼塊的MZ / PE報頭和KERNELBASE,這是一種非常有效的用於防止EAT解析技術的方法,但EATEAT不是唯一一個包含函數地址的結構,由於導入地址表(IAT)也保存有應用程序使用的Win API函數地址,如果惡意應用程序獲取了所需的函數,則可以收集IAT結構內的函數地址。一個名為Joshua Pitts的網絡安全研究員最近開發了一種新的IAT解析方法,即在導入地址表中獲取LoadLibraryA和GetProcAddress Windows API函數,在獲取這些函數地址后,可以從任何庫中提取任何函數。
================ End