x86環境病毒分析的反彙編基礎知識

x86體系結構

大部分現代計算機體系結構(包括x86)在內部實現上遵循馮·諾依曼結構。這種結構包含3種硬件組件:

• 中央處理單元( CPU ),負責執行代碼

• 內存(RAM),負責存儲所有的數據和代碼。

• 輸入/輸出系統(IO),為硬盤、鍵盤、顯示器等設備提供接口。

指針(instruction pointer)的寄存器(register)從內存取得要執行的指令,這個寄存器中存有指令的地址。寄存器是CPU中數據的基本存儲單元,通過它,很多時候CPU不再需要訪問內存,從而節省了時間。算術邏輯單元(arithmetic logic unit, ALU)執行從內存取來的指令,並將結果放到寄存器或內存中。一條條取指令、執行指令的過程不斷重複,就形成了程序的運行。

馮諾依曼體系結構

x86環境病毒分析的反彙編基礎知識

1.內存

一個程序的內存可分為如下四個主要的節

x86環境病毒分析的反彙編基礎知識

**數據** 這個詞指的是內存中一 個特定的節,名為數據節 (data section),其中包含了一 些值。這些值在程序初始加載時被放到這裡,稱為靜態值(static value),因為程序運行時它們可能並不發生變化,還可以稱為全局值(global value),因為程序的任何部分都可以使用它們。

**代碼**

代碼節包含了在執行程序任務時CPU所取得的指令。這些代碼決定了程序是做什麼的,以及程序中的任務如何協調工作。

**堆**

堆是為程序執行期間需要的動態內存準備的,用於創建(分配)新的值,以及消除(釋放)不再需要的值。將其稱為動態內存(dynamic memory),是因為其內容在程序運行期間經常被改變。

**棧**

棧用於函數的局部變量和參數,以及控制程序執行流。

2. 指令

指令是彙編程序的構成塊。在x86、彙編語言中,一條指令由一個助記符,以及零個或多個操作數組成。

3. 操作碼和字節序

每條指令使用操作碼告訴CPU程序要執行什麼樣的操作。

反彙編器將操作碼翻譯為人類易讀的指令。

數據的字節序(enclianness)是指在一個大數據項中,最高位(大端,big-endian)還是最低位(小端,little-enclian)被排在第一位 (即排在最低的地址上)。

4. 操作數

操作數說明指令要使用的數據。有以下三種類型:

立即數(immediate)操作數是一個固定的值

寄存器(register)操作數指向寄存器

內存地址(memory address)操作數指向感興趣的值所在的內存地址,一般由方括號內包含值、寄存器或方程式組成,如[eax]。

5. 寄存器

寄存器是可以被CPU使用的少量數據存儲器,訪問其中內容的速度會比訪問其他存儲器要快。x86處理器中有一組寄存器,可以用於臨時存儲或者作為工作區。

最常用的x86寄存器,可以將它們歸為以下四類:

通用寄存器,CPU在執行期間使用。

段寄存器,用於定位內存節。

狀態標誌,用於做出決定。

指令指針,用於定位要執行的下一條指令。

x86寄存器

x86環境病毒分析的反彙編基礎知識

所有通用寄存器的大小都是32位,可以在彙編代碼中以32位或16位引用。

有4個寄存器(EAX、EBX、ECX、EDX)還可以8位值的方式引用,從而使用其最低的8位,或次低的8位。

**通用寄存器**

通用寄存器一般用於存儲數據或內存地址,而且經常交換著使用以完成程序。不過,雖然它們被稱為通用寄存器,但它們並不完全通用。

一些x86指令只能使用特定的寄存器。例如,乘法和除法指令就只能使用EAX和EDX。

**標誌寄存器**

EFLAGS寄存器是一個標誌寄存器。在x86架構中,它是32位的,每一位是一個標誌。在執行期間,每一位表示要麼是置位(值為1),要麼是清除(值為0),並由這些值來控制CPU 的運算,或者給出某些CPU運算的值。

對惡意代碼分析來說,最重要的一些標誌介紹如下:

**ZF** 當一個運算的結果等於0時,ZF被置位,否則被清除。

**CF** 當一個運算的結果相對於目標操作數太大或太小時,CF被置位,否則被清除。

**SF** 當一 個運算的結果為負數,SF被置位;若結果為正數,SF被清除。對算術運算,當運算結果的最高位值為l時,SF也會被置位。

**TF** TF用於調試。當它被置位時,x86處理器每次只執行一條指令。

**EIP指令指針** 在x86架構中,EIP寄存器,又稱為指令指針或程序計數器,保存了程序將要執行的下一條指令在內存中的地址。EIP的唯一作用就是告訴處理器接下來要做什麼。

注意:當EIP被破壞(即指向了一個不包含合法程序代碼的內存地址 )時,CPU 無法取得一條合法指令來執行,此時正在運行的程序就可能崩潰。當你控制了EIP,也就控制了CPU將要執行什麼,這也就是為什麼攻擊者試圖通過漏洞利用獲得對EIP的控制。通常,攻擊者先妥使攻擊代碼進入內存,然後改變EIP使其指向那段代碼,從而攻擊系統。

6. 簡單指令

**mov**

用於將數據從一個位置移動到另一個位置

mov指令可以將數據移動到寄存器或內存,其格式是:mov destination, source

由方括號括起來的操作數是對內存中數據的引用。例如,[ebx]指向內存中地址為EBX處的數據。

**lea**

lea指令用來將一個內存地址賦給目的操作數。

“load effective address“(加載有效地址)的縮寫。它的格式是lea destination, source。

例如,lea eax, [ebx+8]就將EBX+8的值給EAX。

lea指令並非專門用於計算內存地址。它還被用來計算普通的值,因為它所需的指令更少。

算術運算

加法和減法是從目標操作數中加上或減去一個值。

加法指令的格式是add destination, value。

減法的指令是sub destination, value。

sub指令會修改兩個重要的標誌:ZF和CF。

如果結果為零,ZF被置位;如果目標操作數比要減去的值小,則CF被置位。

inc和dec指令將一個寄存器加一和減一 。

x86環境病毒分析的反彙編基礎知識

乘法和除法都使用了一個預先規定的寄存器,因此其指令很簡單,就是指令碼加上寄存器要去乘或除的值。

mul指令的格式是mul value;

div指令的格式是div value。

mul或div指令要操作的寄存器一般會在之前許多條指令的地方被賦值,因此你可能需要在程序的上下文中來尋找。

x86環境病毒分析的反彙編基礎知識

mul value指令總是將eax乘上value。因此,EAX寄存器必須在乘法指令出現前就賦值好。乘法的結果以64位的形式分開存儲在兩個寄存器中:EDX和EAX。其中,EDX存儲了高的32位,EAX存儲低的32位。

div value指令將EDX和EAX合起來存儲的64位值除以value。因此,在做除法之前,EDX和EAX這兩個寄存器必須賦值好。除法的商將存儲到EAX,餘數則存儲在EDX中。

**模(mod)**運算會被編譯為在div指令後取EDX寄存器的值(因為除法保留了餘數)

邏輯運算

x86架構還使用邏輯運算符,例如OR、AND和XOR。其相應指令的用法與add和sub類似,對源操作數和目的操作數做相應的操作,並將結果保存在目的操作數中。

xor eax, eax就一種將EAX寄存器快速置0的方法。這麼做是為了優化,因為這條指令只需要2個字節,而mov eax, 0需要5個字節。

shr和shl指令用於對寄存器做移位操作。

shr指令的格式是 “shr destination, count”

shl指令的格式是 “shl destination, count”

shr和shl指令對目的操作數右移或左移,由count決定移多少位。移出目的操作數邊界的位則會先移動到CF標誌位中。在移位時,使用0填充新的位。移位運算全部完成後,CF標誌位中就包含了最後移出目的操作數的那一位。

循環移位指令ror和rol與移位指令類似,但移出的那一位會被填到另一端空出來的位上,即右循環移位(ror)會將最低位循環移到最高位;左循環移位Crol)則相反。

移位經常被用於對乘法運算的優化。由於不需要像乘法那樣設置寄存器、移動數據,移位會更簡單、更快。

在分析惡意代碼時,如果遇到一個函數中只有xor、or、and、shl、ror、shr、rol這樣的指令,並且它們反覆出現,看起來隨機排列的樣子,就可能是遇到了一個加密或者壓縮函數。最好是將其標記為一個加密函數,然後繼續後面的分析。

x86環境病毒分析的反彙編基礎知識

**NOP指令**

當它出現時,直接執行下一條指令。

這條指令的opcode是Ox90。在緩衝區溢出攻擊中,當攻擊者無法完美地控制利用代碼,就經常使用NOP滑板。它起到了填充代碼的作用,以降低shellcode可能在中間部分開始執行所造成的風險。

7. 棧

用於函數的內存、局部變量、流控制結構等被存儲在棧中。

棧是一種用壓和彈操作來刻畫的數據結構,向戰中壓入一些東西,然後再把它們彈出來。

它是一種後入先出(LIFO)的結構。

與棧有關的指令包括push、pop、call、leave、enter、和ret。在內存中,棧被分配成自頂向下的,最高的地址最先被使用。當一個值被壓入戰時,使用低一點的地址。

棧只能用於短期存儲。它經常用於保存局部變量、參數和返回地址。其主要用途是管理函數調用之間的數據交換。而不同的編譯器對這種管理方法的具體實現有所不同,但大部分常見約定都使用相對EBP的地址來引用局部變量與參數。

函數調用

許多函數包含一 段 “序言” (prologue),它是在函數開始處的少數幾行代碼,用於保存函數中要用到的棧和寄存器。相應的,在函數結尾的 “結語” (epilogue)則將技和這些寄存器恢復至函數被調用前的狀態。

下面列舉了函數調用最常見的實現流程

> 1. 使用push指令將參數壓入棧中。

> 2. 使用call memory_location來調用函數。此時,當前指令地址(指EIP寄存器中的內容)被壓入棧中。這個地址會在函數結束後,被用於返回到主代碼。當函數開始執行時,EIP的值被設為memory_location (即函數的起始地址)。

> 3. 通過函數的序言部分,分配棧中用於局部變量的空間,EBP (基址指針)也被壓入棧中。這樣就達到了為調用函數保存EBP的目的。

> 4. 函數開始做它的工作。

> 5. 通過函數的結語部分,恢復。調整E回來釋放局部變量,恢復EBP,以使得調用函數可以準確地定位它的變量。leave指令可以用作結語,因為它的功能是使ESP等於EBP,然後從棧中彈出EBP。

> 6. 函數通過調用ret指令返回。這個指令會從棧中彈出返回地址給EIP,因此程序會從原來調用的地方繼續執行。

> 7. 調整棧,以移除此前壓入的參數,除非它們在後面還要被使用。

x86架構還提供了其他彈出和壓入的指令,其中最常用的是pusha和pushad。它們將所有的寄存器都壓入戰中,並且常與popa和popad結合使用,後者從棧中彈出所有的寄存器。

pusha和pushad的具體功能如下。

> • pusha以下面的順序將所有16位寄存器壓入棧中:AX、EX、DX 、BX 、SP、BP、SI、DI

> • pushad以下面的順序將所有32位寄存器壓入枝中:EAX、ECX 、EDX 、EBX 、ESP、EBP、ESI、 EDI

在shellcode中,如果要將寄存器的當前狀態全部保存在棧上,以便稍後恢復,就常使用這些指令。編譯器很少使用它們,因此,看到它們,通常說明是某人手工寫的彙編代碼或者shellcode。

8. 條件指令

最常見的兩個條件指令是test和cmp。

test指令與and指令的功能一樣,但它並不會修改其使用的操作數。test指令只設置標誌位。

對某個東西與它自身的test經常被用於檢查它是否是一個NULL值。

cmp指令與sub指令的功能一樣,但它不影響其操作數。cmp指令也是隻用於設置標誌位,其執行結果是,ZF和CF標誌位可能發生變化。

9. 分支指令

最常見的分支指令是跳轉指令。程序中使用了大量的跳轉指令,其中最簡單的是jmp指令,它使得下一條要被執行的指令是其格式jmp location中指定位置的指令,又被稱為無條件跳轉,因為總會跳到目的位置去執行。這個簡單的跳轉無法滿足所有的跳轉需求。

x86環境病毒分析的反彙編基礎知識

10. 重複指令

重複指令是一組操作數據緩衝區的指令。數據緩衝區通常是一個字節數組的形式,也可以是單字或者雙字。

常見的數據緩衝區操作指令是movsx, cmpsx、stosx和scasx,其中x可以是b、w或者d,分別表示字節、字和雙字。這些指令對任何形式的數據都有效。

在這些操作中,使用ESI和EDI寄存器。ESI是源索引寄存器,EDI是目的索引寄存器。還有ECX用作計數的變量。

這些指令還需要一 個前綴,用於對長度超過1的數據做操作。movsb指令本身只會移動一 個字節,而不使用ECX 寄存器。

x86環境病毒分析的反彙編基礎知識

在x86下,使用重複前綴來做多字節操作。rep指令會增加ESI和EDI這兩個偏移,減少ECX寄存器。rep前綴會不斷重複,直至ECX=O。repe/repz和repne/repnz前綴則不斷重複,直至ECX=O或直至ZF= 1或0。

movsb指令用於將一串字節從一個位置移動到另一 個位置。rep前綴經常與movsb一起使用,從而複製一串長度由ECX 決定的字節。從邏輯上說,rep movsb指令等價於C語言的memcpy函數。movsb指令從ESI指向地址取出一 個字節,將其存入ED I指向地址,然後根據方向標誌(DF)的設置,將ESI和EDI的值加1或者減1。如果DF=O,則加,否則減。

在由C代碼編譯後的結果中,很少能看到DF標誌。但是在shellcode裡,人們有時候會調換方向標誌,這樣就可以反方向存儲數據。如果有rep前綴,就會檢查ECX是否為0,如果不等於0,則指令繼續從ESI移動一個字節到EDI並將ECX 寄存器減1。這個過程會不斷重複,直至ECX=0。

cmpsb指令用於比較兩串字節,以確定其是否是相同的數據。cmpsb指令用ESI指向地址的字節減去EDI指向地址的字節,並更新相關的標誌位。它經常與repe前綴一起使用。此時,cmpsb指令逐一比較兩串字節,直至發現一處不同,或比較到頭。cmpsb指令從地址ESI獲得一 個字節,將其與ED I指向位置的字節進行比較,並設置標誌位,然後對ESI和EDI分別加1。如果有「epe前綴,就檢查ECX的值和標誌位,如果ECX=O或者ZF=O,就停止重複。這相當於C語言中的memcmp函數。

scasb指令用於從一串字節中搜索一 個值。這個值由AL寄存器給出。它的工作方式與cmpsb一樣,但是它是將ESI指向地址的字節與AL進行比較,而不是與EDI指向地址的字節比較。repe操作會使得這個比較不斷繼續,直到找到該字節,或者ECX=O。如果在這串字節中找到了那個值,則其位置會被存儲在ESI中。

stosb指令用於將值存儲到EDI指向的地址。它與scasb一樣,但不是去搜索,而是將指定的字節存入EDI指向的地址。rep前綴與scasb一起使用後,就初始化了一段內存緩衝區,其中的每個字節都是相同的值。這等價於C語言的memset函數。

x86環境病毒分析的反彙編基礎知識

x86環境病毒分析的反彙編基礎知識



分享到:


相關文章: