《惡意代碼分析實戰》筆記一

第一章靜態分析基礎技術

 

反病毒軟件掃描

反病毒軟件主要依靠一個已知惡意代碼可識別片段的特徵數據庫(病毒文件特徵庫),以及基於行爲與模式匹配的分析(啓發式檢測),來識別可疑文件。

不同的反病毒軟件使用不同的特徵庫和啓發式檢測方法,所以需要對同一個樣本運行多個反病毒軟件掃描。可是使用VirusTotal等

  1. 哈希

md5,sha-1等

對一個惡意樣本計算出一段獨特的hash之後,可以用於:

將hash值作爲標籤使用

與其他分析師分享hash值,以幫助識別惡意代碼

在線搜索這段hash值,看看這個文件是否已經被識別

 

  1. 查找字符串

strings程序可以檢測出可打印字符串,可以從找到的字符串中推測程序的一些功能

 

  1. 加殼與混淆惡意代碼

加殼和混淆代碼至少會包含LoadLibrary,GetProcAddress函數,這是用於加載和使用其他函數功能的

加殼的程序運行時,首先會運行一段脫殼代碼,來解壓縮加殼的文件,然後在於寧脫殼後的文件。對一個加殼程序分析時,只有這一小段脫殼代碼可以被解析

可以使用peid查殼

5.pe文件格式

pe文件以一個文件頭開始,其中包含代碼信息、應用程序類型、所需的庫函數與空間要求。這些信息至關重要。

6,鏈接庫與函數

導入函數是一個程序所使用,但是存儲在另一個程序中的那些函數,比如包含了對於很多程序都通用的一些代碼函數庫,代碼庫可以通過鏈接方式,被連接到主程序中。代碼庫可以被靜態鏈接,也可以運行時鏈接,或者動態鏈接。

靜態鏈接會增大可執行文件的體積,常見於unix,linux

運行時鏈接常見於惡意代碼,尤其當惡意diamante被加殼或者混淆時。使用運行時鏈接的可執行程序,只有當需要使用函數時,才鏈接到庫,而不是像動態鏈接模式那樣,在程序啓動時就會鏈接。

一些windows API允許程序員導入並沒有在程序的文件頭中列出的鏈接函數。最爲常見的是LoadLibrary,GetProcAddress。它們允許一個程序訪問系統上任何庫中的任何函數,意味着,當這些函數被使用時,我們無法靜態分析惡意代碼會鏈接哪些函數。

動態鏈接是最常見的。操作系統會在程序被裝載時搜索所需的代碼。

Pe文件頭中存儲了每個將被裝載的庫文件,以及每個會被程序使用的函數信息。程序所使用的庫與調用的函數,被識別後就可以推測程序的功能。如URLDownloadToFile,調用它的程序的功能之一可能是會連接到互聯網下載一些內容,然後在本地文件中進行存儲。

 

 

 

常見的dll程序:

Kernel32.dll:包含核心系統功能,如訪問和操作內存、文件和硬件等

Advapi.dll:提供了對核心windows組件的訪問

Use32.dll:包含了所有用戶界面組件,如按鈕、滾動條以及控制和響應用戶操作的組件

Gdi32.dll:包含了圖形顯示和操作的函數。

Ntdll.dll:該dll是windows內核的接口。可執行文件一般不直接導入,而是由Kernel32.dll間接導入,如果一個可執行文件導入了這個dll,說明該文件可能會使用不是正常提供給windows程序使用的函數。一些如隱藏功能和操作進程等任務會使用這個接口。

Wsock32.dll,Ws2_32.dll:聯網dll,連接網絡或者是執行網絡相關任務

Wininet.dll:包含更高層次的網絡函數,實現瞭如ftp,http,ntp等協議

 

 

 

命名約定:

Ex:遇到以Ex爲後綴的函數名。這是當微軟更新一個函數,新函數與原先函數不兼容時,還會支持原先的舊函數,此時奇函數後面會有Ex後綴。如果被顯著更新過兩次,則名字後面會有兩個Ex後綴

  1. W:以字符串爲參數的函數,名字後面可能會有A,或W。這是用於表明函數有兩個版本:A表示輸入參數類型爲ascii字符,W表示輸入參數爲寬字符字符串。在msdn中是不會出現A,W的。

 

 

導入函數:用於與其他程序和代碼進行交互時使用。在分析dll文件時需要特別關注。因爲dll文件本身就是實現一些導出函數然後被exe使用的。

 

 

7.pe文件頭與分節

pe文件格式包括一個pe頭,隨後跟着一系列的分節。

.text:包含了cpu執行指令,所有其他節存儲數據和支持性的信息,一般而言,這是唯一可以執行的節,也是唯一包含代碼的節

.rdata:包含導入與導出函數信息,還可以存儲程序所使用的其他只讀數據。有些文件還會包含.idata,.edata節,來存儲導入導出信息

.data:包含程序全局數據,從程序的任何地方都可訪問到。注意,本次數據不存儲在這裏。

.rsrc:包含可執行文件所使用的資源。

 

 

若虛擬大小比原始數據大得多,說明這個節在呢村中佔用了比磁盤上更多的空間,這可能說明存在加殼代碼,尤其當.text分節在內存中較磁盤上更大一些時。

 

惡意代碼經常會把一個嵌入的程序或者驅動放在資源節中,在程序運行之前,將其提取出來。針對這種情況,可以使用resource hacker進行提取。

 

總結一下我們可以從pe文件頭獲得的關鍵信息:

導入函數,導出函數,時間戳(程序在什麼時候被編譯),分節(文件分節的名稱,以及在磁盤與內存中的大小),子系統(指示程序是一個命令行還是圖形界面應用程序),資源(字符串、圖標、菜單項和其他信息)

 

 

 

 

 

第三章 動態分析基礎技術

啓動dll文件:

所有版本的windows都包含rundll32.exe,可以用於運行dll

語法如下:

C:\rundll32.exe DLLname,Export arguments

Exports值必須是一個DLL文件導出函數表中的函數名或者序號。

因爲惡意打的dll文件常會在DLLMain(DLL函數入口點)執行它們的代碼,而無論dll什麼時候被加載,DLLMain函數總會被執行,這樣我們就總能通過rundll32.exe加載DLL,而動態的地獲取信息。

另一種啓動dll的方法是修改PE頭部,並改變擴展名。

具體如下:

從IMAGE_FILE_HEADER的特徵域裏擦除IMAGE_FILE_DLL(0X2000標記。

Dll類型的惡意代碼也可能需要被安裝成一個服務,比如在某個dll中導出了InstallService函數

C:\rundll32.exe  DLLname,InstallService ServiceName

C:\net start ServiceName

ServiceName參數必須提供給惡意代碼,讓它能安裝並運行。而在windows系統中啓動指定的服務,可以使用net start命令。

 

第四章  x86反彙編

網絡數據使用大端序,x86使用小端序

 

 

 

一個程序的內存可以分爲以下四個節

數據:指內存中一個特定的節,名爲數據節(data section),其中包含了一些值。這些值在程序初始加載時被放到這裏,稱爲靜態值(static value),也可稱爲全局值(global value)

代碼:代碼節包含了執行程序任務時CPU所取得的指令

堆:堆是爲程序執行期間需要的動態內存準備的,用於分配、釋放不需要的值。其內容在運行期間經常被改變。

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

 

 

x86寄存器可以歸爲四類:

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

段寄存器:用於定位內存節

狀態標誌:用以做出決定

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

所有通用寄存器的大小都是32位,可以在彙編代碼中以32位或16位引用。比如,EDX指向完整的32位寄存器,而DX指向EDX寄存器的低16位

這四個寄存器(EAX,EBX,ECX,EDX)還可以8位值的方式引用,從而使用其最低的8位,或次低的8位。AL指向EAX寄存器的最低8位,AH指向它的次低8位

通用寄存器一般用於存儲數據或內存地址,而且經常交換着使用以完成程序。乘法指令和除法指令只能使用EAX,EDX。

EAX通常存儲了一個函數調用的返回值

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

ZF:運算結果等於0時,ZF被置位

CF:運算結果相對於目標操作數太大或太小時,CF被置位

SF:運算結果爲負數時,SF被置位,結果爲正數,則置0.對於算術運算,當運算結果最高位值爲1時,SF也會被置位

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

 

 

EIP寄存器,又稱指令指針或程序計數器,保存了程序將要執行的下一條指令在內存中的地址。

通常攻擊者先使攻擊代碼進入內存,然後改變EIP使其指向那段代碼,從而攻擊系統

 

intel彙編語法,把目標數放在前面

 

一條類似mov的指令是lea,即load effective address(加載有效地址)。格式爲lea dest,sour。Lea指令用於將一個內存地址賦給目的操作數。Lea eax,[ebp+8],是將ebx+8的值給eax

lea指令還可以用於計算普通的值,如lea ebx,[eax*5+5],其中eax是一個普通的數而不是內存地址,該指令的功能等價於ebx=(eax+1)*5

 

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

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

 

 

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

 

Shr,shl指令用於對寄存器做移位操作。移出目的操作數邊界的位則會先移動到CF標誌位中,在移位時,使用0填充新的位。Ror和rol是循環移位指令。移位 常用於對乘法運算的優化,一般的,左移n位,相當於乘以2的n次

 

在分析惡意代碼時,如果遇到一個函數中只有xor,or,and,shl,ror,shr,rol這樣的指令,並且反覆出現,則可能是遇到一個加密或者壓縮函數。這時,不用陷進去分析。

 Nop指令出現時,什麼也不做,直接執行下一條指令。Nop指令實際上是xchg eax,eax的一個僞名字。其操作位是0x90.在緩衝區溢出攻擊中,常會使用nop sled技術,其起到了填充代碼的作用,以降低shellcode在中間部分開始執行所造成的風險。

 

 

用於函數的內存、局部變量、流控制結構等被存儲在棧中。X86架構有對棧的內建支持,用於這種支持的寄存器包括ESP,EBP。ESP是棧指針,包含了指向棧頂的內存地址,在push,pop時該寄存器的值會相應改變。EBP是棧基址寄存器,在一個函數中會保持不變,因此程序把它當成定位器,用來確定局部變量和參數的位置。

與棧有關的指令包括push,pop,call,leave,enter,ret。在內存中,棧被分配成自頂向下的,最高的地址最先被使用。棧只能用於短期存儲,其經常用於保存局部變量、參數和返回地址。其主要用途是管理函數調用之間的數據交換。大部分常見約定都使用相對EBP的地址來引用局部變量與參數。

 

許多函數包含一段prologue,在函數開始處的少數幾行代碼,用於保存函數中要用到的棧和寄存器。在epilogue則將棧和這些寄存器恢復至函數被調用前的狀態。

 

 

函數調用時,單獨一個棧幀的佈局是這樣的

函數調用時,最常見的實現流程:

使用push指令將參數壓入棧中

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

通過prologue,分配棧中用於局部變量的空間,EBP(基址指針)也被壓入棧中,達到爲調用函數保存EBP的目的。

函數開始它的工作。

通過函數的epilogue,恢復棧。調整ESP來釋放局部變量,恢復EBP,以使得調用函數可以準確地定位它的變量。Leave指令可用於epilogue,其 功能是使ESP等於EBP,然後從棧中彈出EBP

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

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

 

每一次函數調用,就生成一個新的棧幀。

 

不用push,pop,也可以從棧中讀取數據。如mov eax,ss:[esp]指令就可以直接訪問棧頂,這和pop eax一樣,但是不會影響esp寄存器。

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

Pusha以下面的順序將所有16位寄存器壓入棧中:AX,CX,DX,BX,SP,BP,SI,DI

Pushad以下面的順序將所有32位寄存器壓入棧中:EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI

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

 

 

條件指令就是用來做比較的指令,最常見的兩個是test和cmp。

Test指令與and指令的功能一樣,但不修改其使用的操作數。Test指令只設置標誌位。Test執行後注意觀察ZF標誌位。Test eax,eax常用於檢查eax是否是一個NULL值。當然也可以直接將eax有0比較,但test指令字節更少,花費的CPU週期也更少。

 

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

 

常見的條件跳轉jcc

 

 

數據緩衝區通常是一個字節數組的形式,也可以是單字或者雙字。常見的數據緩衝區操作指令是movsx,cmpsx,stosx,scasx,其中x可以是b,w或者d,分別表示字節、字、雙字.這些操作中,會使用ESI,EDI寄存器。ESI是源索引寄存器,EDI是目的索引寄存。還有ECX用作計數的變量。這些指令還需要一個前綴,用於對長度超過1的數據做操作。

Movsb指令本身只會移動一個字節,而不使用ecx寄存器

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

 

movsb指令用於將一串字節從一個位置移動到另一個位置。Rep前綴經常與movsb一起使用,從而複試一串長度由ecx決定的字節。Rep movsb等價於c語言的memcpy。Movsb從ESI指向地址取出一個字節,將其存入EDI指向地址,然後根據方向標誌DF的設置,將ESI,EDI的值加1或者減1.如果DF=0,則加,否則減

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

 

cmpsb指令用於比較兩串字節,以確定其是否是相同的數據。Cmpsb指令用ESI指向地址的字節減去EDI指向地址的字節,並更新相關的標誌位。其與repe一起使用時,相當於memcmp

 

scasb指令用於從一串字節中搜索一個值,這個值由AL給出。Repe操作會使得這個比較不斷繼續,直到找到該字節,或者ECX=0。如果找到了那個紙,則其位置會被存儲在ESI中

 

stosb指令用於將值存儲到EDI指向的地址。Rep前綴與scasb一起使用後,就初始化了一段內存緩衝區,其中的每個字節都是相同的值。等價於memset。

 

 

 

一個標準的c程序的主函數有兩個參數,形式爲

int main(int argc,char ** argv),參數argc,argv在運行時決定,argc是一個整數,說明命令行中參數個數,包括程序名字本身,argv是一個數組中,指向了所有命令行參數,其中每條記錄都是一個指向字符串的地址。在32位系統中,每個地址都是4字節長。

 

 

第五章 IDA

當加載一個文件到IDA時,IDA像操作系統加載器一樣將文件映射到內存中。要讓IDA將文件作爲一個原始二進制文件進行反彙編,選擇界面頂部的Binary file即可。因爲惡意代碼有時會帶有shellcode,其他數據,加密參數,甚至在合法的pe文件中帶有可執行文件,並且當包含這些附加數據的惡意代碼在windows上運行或被加載到IDA pro時,它並不會被加載到內存中。

Pe文件被編譯加載到內存中一個首選的基地址,如果windows加載器無法加載到首選地址,則會進行基地址重定向,這在dll中經常發生。如果遇到進程中加載的dll的位置和ida中看到的不一樣,可以手動指定新的虛擬基地址

 

默認情況下,IDA的反彙編代碼中不包含pe頭或資源節,而這些地方常用於隱藏惡意指令。

 

箭頭的顏色:紅色表示一個條件跳轉沒有被採用,綠色表示跳轉被採用,藍色表示無條件跳轉被採用

向上的箭頭通常表示一個循環條件

 

必須使用反彙編窗口的文本模式來查看一個二進制的數據區。會顯示內存地址和節名。

文本模式顯示的左側被稱爲箭頭窗口,實線標記了無條件跳轉,虛線標記了條件跳轉,朝上的一個箭頭表示一個循環。

 

 

IDA將局部變量用前綴var_進行標記,而參數用前綴arg_標記,將局部變量和參數用相對EBP的偏移量作爲後綴來進行命名。

 

局部變量會在相對於ebp負偏移量的位置,參數會在正偏移量的位置

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章