識別和避免反彙編中遇到的花指令
工作中需要花費大量時間和精力去分析花指令成為困擾逆向工程師以及安全響應人員的一件事。花指令就是一串沒有任何實際意義的指令。除了浪費時間之外,我發現有的人也會被他們發現的花指令感到震驚和興奮。這是因為他們在一個他們本來不能執行程式碼的記憶體中發現了可執行程式碼,也就相當於發現了一個漏洞利用程式或高階惡意軟體樣本。
在這篇文章中,我將通過介紹花指令產生的原因以及其我將討論如何識別花指令。 我們將重點關注x86反彙編,在其他的平臺上也是類似的情況。
問題的產生
人們在分析彙編程式碼的過程中,由於反彙編器將花指令反彙編成有效的指令,所以很多人預設就假設所有彙編程式碼都是實際的程式碼指令,這就是很多人所犯的第一個錯誤。由於X86平臺的指令是密集編碼的,所以很多指令都是一個位元組編碼的。乍一看,幾乎所有資料的反彙編程式碼都將產生可能有效的x86程式碼。
例如,我生成了一個16kb的隨機資料檔案,並對其進行了反彙編。 這產生了6,455個有效的32位x86指令,只有239個位元組的無法識別的資料。這意味著反彙編程式無法解析成有效指令的資料。可以看到超過98%的隨機資料可以被反彙編成有效的指令。 為了解釋我的意思是無法識別的資料,讓我們看看這個隨機資料的第一部分的反彙編,我們將遇到許多指令和一個位元組的無法識別的資料。
這個片段中的第一列資料是資料偏移量。第二列顯示組成指令的位元組,第三列顯示這些位元組的反彙編表示。對於除高亮行(偏移量0x16)之外的所有行,反彙編顯示一個有效的指令。偏移量0x16表示可能看起來像一條指令的內容,但符號“db”只是彙編表示法來宣告一個位元組。反彙編器表示在這個位置是位元組0xF6,它不能將其識別為指令的一部分。x86指令集是編碼密集,以至於每個位元組值都可能是下一個指令的潛在開始。在這種情況下,0xF6可能是一個指令的有效開始,取決於它後面的位元組,但這種情況下的其與後面的位元組0x4E並不構成有效的運算元。在隨機資料的16kb中,274個無法識別的位元組只有27個不同的值。在這27個值中,唯一一個與常用英文字元範圍重疊的是字母“b”(0x62)。
在這篇文章中,我們將堅持使用32位反彙編,因為它的流暢度更高,但在16位和64位英特爾彙編中也會出現同樣的問題。 當先前提供的隨機資料被拆分為16位程式碼時,它產生了96%的有效指令,而拆分為64位則產生了95%的有效指令。
你可能會想如何用大面積的零來表示著這段記憶體空間中沒有程式碼存在,這該多麼美妙。高階反彙編可能會智慧地識別大量的零程式碼,並跳過反彙編,但它們還是將大量的零反彙編為有效的x86指令。 這裡有幾個反彙編零位元組來說明這一點:
英文文字的問題更為嚴重。我生成了一個包含隨機文字(lorem ipsum)的60kb檔案,並對其進行了反彙編,生成了23,170條指令,並且沒有無法識別的資料。所以,100%的隨機文字被反彙編成有效的指令。 下面的片段顯示了標準Lorem ipsum反彙編的前三個單詞(“Lorem ipsum dolor”):
所以,我們在看反彙編程式碼時,不僅要看對應序列能否被反彙編成功,還是要看其是否是有效的指令。
解決方法
儘管編寫更好的啟發式的指令碼用來過濾掉花指令是可行的,但是目前我們最好的武器依舊是是人類的大腦。對於一個經驗豐富的逆向工程師來說,在本文中到目前為止所看到的程式碼片段有很多東西,他們是學習判斷你所看到的程式碼是否是花指令的關鍵。
特權指令
x86處理器有四個用於保護的“環”,就像“魔戒”一樣,但是不那麼有趣。其中兩個環(Ring1和Ring2)通常根本不使用。 核心模式執行Ring0和Ring3使用者模式則是常用的兩種特權級別。某些指令只能在Ring0中執行。這些特殊的特權指令也碰巧是單位元組操作碼,並且經常發生在反彙編花指令中。讓我們再來看看這個“Lorem ipsum”反彙編出來的指令,但這次我會突出特權指令的說明:
如果你知道你正在檢視的程式碼不是作為作業系統引導載入程式、核心或裝置驅動程式執行的,那麼當檢視到這些特權指令時我們應該意識到這些反彙編不是真正有效的程式碼。 突出顯示的指令都是IN和OUT指令的變體,主要作用是從硬體埠讀取和寫入資料。這些可能的指令必須在裝置驅動程式中使用,如果在Ring3使用者模式下執行,將會產生一個異常。即使你試圖反彙編核心程式碼,這些指令發生的頻率也會比你反彙編作業系統的任意檔案的要高得多。以下是你將經常在反彙編的花指令中看到的一些常見Ring0指令的列表:
IN (INS, INSB, INSW, INSD)
OUT (OUTS, OUTSB, OUTSW, OUTSD)
IRET
IRETD
ARPL
ICEBP / INT 1
CLI
STI
HLT
罕見的指令
在使用者模式程式碼中有許多Ring3指令是有效的,但是在編譯程式碼中非常少見。 我們可以將這些指令分為三類:便利指令,不太可能的數學指令和遠指標指令。我們來看看這些類別。
便利指令
ENTER
LEAVE
LOOP (LOOPE/LOOPZ, LOOPNE/LOOPNZ)
PUSHA
POPA
組合語言程式設計師可以使用ENTER和LEAVE指令來獲得函式序言和結語,這兩個指令可以手動的使用PUSH,MOV和SUB來完成。現代編譯器傾向於避免使用ENTER和LEAVE,因此大多數程式設計師也不會使用它們。因為他們一起佔據了操作碼範圍的近1%,所以它們在花指令程式碼中的出現是非常普遍的。
LOOP指令及其條件同類的LOOPZ和LOOPNZ提供了一種用匯編語言編寫迴圈的非常直觀且有用的方法。 編譯器通常選擇不使用這些,而是使用JMP和條件跳轉指令來製作自己的迴圈。
PUSHA和POPA指令提供了將所有暫存器的狀態儲存到堆疊的機制。這為組合語言程式設計師提供了一個可能方便的類巨集指令。由於它也儲存和恢復堆疊指標本身,這使得一個懶惰的編碼器在函式啟動時盲目地儲存暫存器資訊,並在函式結束時恢復它們的潛在用途變得複雜了。你不會在編譯程式碼中找到這些指令,但是它們也佔據了可用操作碼範圍的近1%,所以它們經常出現在花指令中。
不太可能的數學指令
浮點指令
F*
WAIT/FWAIT
浮點指令通常以字母“F”開始。雖然有些程式使用浮點數學,但大部分程式都不使用浮點數學。浮點指令佔用操作碼範圍的很大一部分,所以它們在花指令中的是普遍存在的。這就是你對你正在嘗試進行逆向工程的程式碼設計的知識將派上用場的地方。如果您使用3D圖形對程式進行反向工程,則需要查詢潛在的大量浮點指令。對於我們在FLARE團隊所做的工作,惡意軟體分析,浮點數學是非常罕見的。值得注意的例外是shellcode通常使用一些浮點指令作為獲取指向自身的指標。
SAHF
LAHF
SAHF和LAHF指令將AH暫存器的內容複製到標誌暫存器EFLAGS中。這是一種程式設計行為,不會從高階語言轉換下來,因此編譯器通常不會輸出這些指令。如果不是手動編寫彙編程式碼,那麼這些指令將會很少出現。由於這些是單操作碼範圍內的單位元組指令,所以它們經常在反彙編花指令中出現。
ASCII調整指令
AAA
AAS
AAM
AAD
“AA”系列指令涉及以二進位制編碼的十進位制形式處理資料。這是一個比較早的編碼方式,基本很難再現代計算中遇到。但是,您將經常在反彙編花指令中遇到這些指令,因為它們是單位元組指令。
SBB
SBB指令與SUB相似,只是它將進位標誌新增到源運算元。這可以在合法的程式碼中看到,特別是當試圖對大於機器字長的數字進行算術運算時(32位程式碼中的64位數學)。不幸的是,SBB指令不少於九個操作碼,佔可能範圍的3.5%。它們不是單位元組指令,但是有很多形式和很多操作碼,它們經常在反彙編的的花指令中出現。
XLAT
在你的彙編程式碼中XLAT是一個有趣的指令。它沒有直接的翻譯到一個單一的高階語言結構,因此,編譯器不像我們在FLARE團隊那樣喜歡它。由於它是一個單位元組指令,所以你會發現它比使用匯編語言找到程式設計師更頻繁。
CLC
STC
CLD
STD
這些指令清除並設定進位和目標標誌。 它們可能在流操作附近的編譯器生成的程式碼中找到(通常會在REP字首的地方)。儘管如此,由於它們都是單位元組指令,它們在花指令中出現的可能性非常高。
遠指標指令
LDS
LSS
LES
LFS
LGS
在英特爾架構中,遠指標的使用並不存在於16位時代。但是,設定遠指標的指令仍然佔用兩個單位元組操作碼和兩個位元組操作碼範圍中的三個值。因此,你會看到這些指令經常出現在反彙編花指令中。
指令字首
x86中的指令可以有字首。字首位元組通常會修改後續指令的行為。字首的常用用法是改變運算元的大小。例如,如果您正在以32位模式執行指令,並且希望使用16位暫存器或運算元執行計算,則可以在計算指令中新增一個字首,以通知CPU它是一個16位指令而不是一個32位的。有許多這樣的字首,不幸的是,其中許多都落在ASCII表的字母範圍內。這意味著,當反彙編ASCII文字(如我們的lorem ipsum),指令字首將是非常普遍的。這些指令字首會出現在正常指令中,但沒有在花指令中出現的頻繁。 如果您正在拆解32位程式碼,並且您看到大量使用的16位暫存器(AX,BX,CX,DX,SP,BP等,而不是EAX,EBX,ECX,EDX,ESP和EBP ),你可能正在看花指令。
反彙編器還將代表在指令助記符之前新增了某些符號的其他字首。如果你在程式碼中經常看到任何這些關鍵字,那很可能是花指令程式碼:
LOCK
BOUND
WAIT
段選擇器
FS
GS
SS
ES
在16位模式下,使用段暫存器(CS,DS,FS,GS,SS,ES)完成定址儲存器。程式的程式碼通常是基於CS“程式碼段”暫存器來引用的,程式處理的資料是從DS“資料段”暫存器引用的 ES,FS和GS是額外的資料段暫存器,合法地用於32位程式碼,但這是一個更高階的話題。有段選擇符字首位元組,可以在指令之前新增段,強制指定它參考記憶體基於特定的段,而不是它設計使用的預設段。 由於這些都佔用了那個寶貴的單位元組操作碼空間,所以它們會在反彙編的花指令中頻繁出現。從我反彙編的隨機資料中得到的下列指令顯示了一個在沒有定址記憶體的指令上使用的GS暫存器的段選擇符字首:
反彙編垃圾也會比普通程式碼更頻繁地使用這些段暫存器,而且編譯器不會輸出。 讓我們來看看反彙編花指令的另一行:
該指令從堆疊彈出SS“堆疊”暫存器。這是一個完全有效的指示; 然而,這是反彙編的32位程式碼和段暫存器通常不會像在16位模式中那樣改變。在同樣的反彙編中,只有上面幾行程式碼是另一個奇怪的程式碼行:
32位體系結構支援更多段暫存器的定址,而不是段暫存器的名稱。這條指令是將一些資料移動到第7段暫存器中,我的反彙編器命名為“segr7”。
結論
往小了說,反彙編花指令程式碼可能會浪費你的時間和精力。在最糟糕的情況下,它可以讓你正在分析的資料是不正確的。在這篇文章中,我們學會了通過識別和理解不可能的程式碼來發現反彙編的花指令。我們打破了不尋常的程式碼分類為最常見的情況,並討論他們的意義,為什麼他們會頻繁發生,為什麼他們不應該出現。通過學習這些指標,你可以輕鬆地發現反彙編中的花指令程式碼,節省你的時間。
原文作者:Nick Harbour
原文連結:https://www.fireeye.com/blog/threat-research/2017/12/recognizing-and-avoiding-disassembled-junk.html
參考連結:
特權級別:https://www.cnblogs.com/tswcypy/p/4166595.html
翻譯:看雪翻譯組-skeep
相關文章
- 彙編指令2016-08-23
- 反彙編命令U2018-03-22
- 彙編指令速查2013-06-08
- 基本彙編指令2006-12-16
- Java 反彙編、反編譯、volitale解讀2018-08-14Java編譯
- 三個程式設計中遇到的小問題彙編2013-05-23程式設計
- 彙編---移位指令2020-05-06
- 彙編指令(待完善)2018-10-20
- linux彙編指令2020-10-26Linux
- [ARM] ARM彙編指令2018-01-31
- 彙編跳轉指令2018-04-07
- 如何識別和避免間諜軟體2021-12-15
- Java中的常量:如何避免反模式2015-09-22Java模式
- 2020-12-6(從反彙編理解指標和引用的區別)2020-12-10指標
- ARM彙編指令集彙總2018-10-26
- Jack對分支迴圈語句偽指令反彙編前後的比較2020-04-04
- 反彙編工具objdump的使用簡介2016-03-27OBJ
- 反彙編專用工具——objdump2018-11-18OBJ
- 常用的x86彙編指令2024-08-10
- 反彙編器-javap.exe(轉)2007-08-15Java
- 我的彙編學習之路(1):指令2015-03-10
- mingw32 exception在sjlj與dwarf差別-反彙編分析2020-06-16Exception
- shell指令碼中不識別source命令2018-07-05指令碼
- 彙編debug程式跳轉指令的方法2018-01-26
- 學習筆記分享之彙編---2.彙編指令/語法2020-04-18筆記
- iOS逆向學習筆記 - 彙編(一) - 初識彙編2018-05-16iOS筆記
- C語言轉寫成MIPS指令集彙編以及MIPS指令集彙編中函式呼叫時棧的變化2024-05-04C語言函式
- 彙編--串傳送指令 MOVS2020-05-06
- 8086彙編指令快速參考2018-04-07
- Java常見知識點彙總(⑫)——==和equals的區別2020-03-07Java
- IDA Pro for Mac(最強反彙編工具)2022-08-06Mac
- 黑客反彙編解密 第一版2011-03-27黑客解密
- Android反編譯:反編譯工具和方法2015-01-15Android編譯
- hive-3.0.0 版本中遇到的bug 彙總2024-03-04Hive
- 基於DNN的人臉識別中的反欺騙機制2018-04-19DNN
- 使用匯編和反彙編引擎寫一個x86任意地址hook2023-12-21Hook
- shell 指令碼中雙引號、單引號、反引號的區別2017-01-25指令碼
- 一個Java反彙編器的修改 (7千字)2015-11-15Java