惡意樣本分析手冊(cè)——常用方法篇
一、文件識(shí)別
常見的可執(zhí)行程序格式有PE,ELF,MACH-O等,不同的格式有不同的標(biāo)志信息(參考理論篇),知道了目標(biāo)文件的格式后才能確定對(duì)應(yīng)的分析方法和分析工具。
可以使用16進(jìn)制解析器載入可執(zhí)行程序,然后查看是哪種類型的文件。
圖:PE文件格式
圖:ELF文件格式
一般二進(jìn)制文件的前四個(gè)字節(jié)為文件格式的magic,可以通過(guò)從網(wǎng)絡(luò)搜索獲得文件的信息,或者使用相關(guān)的工具(PEID,file)等進(jìn)行自動(dòng)識(shí)別。
二、靜態(tài)分析
靜態(tài)分析技術(shù)通常是研究惡意代碼的第一步。靜態(tài)分析指的是分析程序指令與結(jié)構(gòu)來(lái)確定目標(biāo)程序的功能的過(guò)程。在這個(gè)時(shí)候,病毒本身并不在運(yùn)行狀態(tài)。我們一般采用以下幾種方式進(jìn)行靜態(tài)分析:
1. 采用反病毒引擎掃描
如果尚不確定目標(biāo)程序是否為病毒程序,我們可以首先采用多個(gè)不同的反病毒軟件來(lái)掃描一下這個(gè)文件,看是否有哪個(gè)引擎能夠識(shí)別它。(www.virscan.org、www.virustotal.com )
注意:只能通過(guò)MD5值查詢,不允許將樣本進(jìn)行上傳。
圖:VitusTotal檢測(cè)結(jié)果界面
2. 計(jì)算哈希值
哈希是一種用來(lái)唯一標(biāo)識(shí)目標(biāo)程序的常用方法。目標(biāo)程序通過(guò)一個(gè)哈希算法,會(huì)產(chǎn)生出一段唯一的用于標(biāo)識(shí)這個(gè)樣本的哈希值,我們可以將這個(gè)值理解為是目標(biāo)程序的指紋。常用的哈希算法有MD5、Sha-1以及CRC32等。由于僅僅采用一種算法,特別是MD5算法,有可能使得不同程序產(chǎn)生同樣的哈希結(jié)果,所以一般會(huì)運(yùn)用多種哈希驗(yàn)證文件的唯一性。
圖 :計(jì)算文件校驗(yàn)碼
3. 查找字符串
程序中的字符串就是一串可打印的字符序列,一個(gè)程序通常都會(huì)包含一些字符串,比如打印輸出信息、連接的URL,或者是程序所調(diào)用的API函數(shù)等。從字符串中進(jìn)行搜索是獲取程序功能提示的一種簡(jiǎn)單方法。(在IDA和OD中都可以查找字符串)并不是所有的字符串都是有意義的,但是利用這個(gè)結(jié)果,也能夠給我們的靜態(tài)分析帶來(lái)很大的便利了。
圖:查看字符串信息
4. 查找導(dǎo)入函數(shù)
如果軟件被加殼的話,那么導(dǎo)入表中的函數(shù)會(huì)很少,所以可以從這里判斷文件是否被加殼。如果沒(méi)有加殼,那么導(dǎo)入表中會(huì)列出程序使用的大部分函數(shù)(除去程序動(dòng)態(tài)獲得的),我們就可以通過(guò)這些函數(shù)大致判斷一下程序的行為。
5. 解析宏
使用IDA反匯編程序的時(shí)候,IDA并不會(huì)將宏的名字解析出來(lái),相反,它只會(huì)使用宏對(duì)應(yīng)的數(shù)字進(jìn)行顯示,如下如所示:
如果只看這些數(shù)字,完全無(wú)法得知什么情況,好在IDA提供了解析機(jī)制,可以將數(shù)字轉(zhuǎn)換為宏名。在對(duì)應(yīng)的數(shù)字上右鍵,選擇Enum:
然后在彈出的對(duì)話框中選擇對(duì)應(yīng)的宏即可!
替換后的結(jié)果如下,這樣的話,就方便了靜態(tài)查看代碼。
6. 偵殼操作
病毒木馬編寫者經(jīng)常會(huì)使用加殼技術(shù)來(lái)讓他們的惡意程序難以被檢測(cè)或分析。正常的程序總是會(huì)包含很多字符串。而加了殼的惡意代碼通過(guò)分析所得到的可打印字符串就會(huì)很少。如果查找出的程序的字符串很少時(shí),那么這個(gè)程序就很有可能是加了殼的。此時(shí)往往就需要使用其它方法來(lái)進(jìn)一步檢測(cè)它們的行為。(常用PEiD進(jìn)行查殼)
圖:查殼
三、動(dòng)態(tài)調(diào)試
使用調(diào)試器對(duì)病毒進(jìn)行分析在反病毒工作中扮演著十分重要的角色。調(diào)試器允許你查看任意內(nèi)存地址的內(nèi)容、寄存器的內(nèi)容以及每個(gè)函數(shù)的參數(shù)。調(diào)試器也允許你在任意時(shí)刻改變關(guān)于程序執(zhí)行的任何東西。比如你可以在任意時(shí)刻改變一個(gè)變量的值——前提是你需要獲得關(guān)于這個(gè)變量足夠的信息,包括在內(nèi)存中的位置。在實(shí)際的動(dòng)態(tài)調(diào)試過(guò)程中,最常用的是OllyDBG和WinDbg,前者是病毒分析人員使用最多的調(diào)試器,缺點(diǎn)是不支持內(nèi)核調(diào)試,如果想調(diào)試內(nèi)核,WinDbg基本上就是唯一的選擇了。雖然IDA Pro也能夠進(jìn)行動(dòng)態(tài)調(diào)試,但是它遠(yuǎn)遠(yuǎn)不如OD方便。因此在實(shí)際分析的過(guò)程中,往往是將二者結(jié)合使用的。因?yàn)槿绻肐DA Pro在靜態(tài)分析中遇到了十分抽象的函數(shù),那么用OD動(dòng)態(tài)地執(zhí)行一下,該函數(shù)的功能往往就能一目了然了。關(guān)于常用工具OllyDbg和Windbg的使用方法請(qǐng)參考工具篇。
1. 脫殼
攻擊者為了保護(hù)攻擊代碼,提高分析難度,避免被殺毒軟件查殺,經(jīng)常對(duì)可執(zhí)行程序進(jìn)行加殼來(lái)達(dá)到以上目的,本節(jié)對(duì)一些常見的殼的脫殼方法進(jìn)行介紹(關(guān)于殼的基本信息可以參考文件封裝篇)。
(1) UPX
將UPX加殼程序使用OD打開時(shí)出現(xiàn)下面的對(duì)話框,選擇“否”。
(2) 單步跟蹤法
單步跟蹤法的原則是實(shí)現(xiàn)向下的跳轉(zhuǎn),越過(guò)向上的跳轉(zhuǎn),如果越過(guò)了某一個(gè)向上的跳轉(zhuǎn)后,程序跑飛了,那么再次調(diào)試到那個(gè)跳轉(zhuǎn)語(yǔ)句的時(shí)候就實(shí)現(xiàn)其跳轉(zhuǎn),然后再按照實(shí)現(xiàn)向下跳轉(zhuǎn),忽略向上跳轉(zhuǎn)的原則進(jìn)行調(diào)試。
如上圖所示,jnz為向上的跳轉(zhuǎn),而且它的下一句代碼也是向上的跳轉(zhuǎn),這時(shí)就需要在jmp的下面設(shè)置斷點(diǎn),直接讓程序越過(guò)這兩個(gè)跳轉(zhuǎn)。不能在下面的nop語(yǔ)句上f4運(yùn)行到光標(biāo),否則程序會(huì)跑飛。
按照這套法則,很快就會(huì)找到關(guān)鍵句popad,那么程序的入口就在附近了。如下圖所示,并且再看程序的地址,jmp跳轉(zhuǎn)的目標(biāo)地址為0x004010CC,這條指令所在的地址為0x0040EA0F,可見這是一個(gè)很大的跳轉(zhuǎn),說(shuō)明0x004010CC即是OEP。(一般有很大的跳轉(zhuǎn)的話,很快就會(huì)到達(dá)程序的OEP)
跳轉(zhuǎn)之后,就到達(dá)程序OEP。
開始進(jìn)行脫殼,這里使用OD插件進(jìn)行脫殼,選擇“插件”->”OllyDump”->”脫殼在當(dāng)前調(diào)試進(jìn)程”。
因?yàn)檫@里入口點(diǎn)地址正是我們的OEP:10CC,所以不需要修正,直接點(diǎn)擊脫殼。如果脫殼后的程序無(wú)法運(yùn)行,還需要進(jìn)行修正。這里使用方式1脫殼之后,可以運(yùn)行,不需要修正;使用方式2脫殼之后,無(wú)法運(yùn)行,可以使用Import REConstructor進(jìn)行修正。在軟件的下拉框中選中要進(jìn)行脫殼的程序(這里使用的是upx.exe),然后修改OEP為我們找到的值:0x10CC。
點(diǎn)擊AutoSearch或者GetImports獲取導(dǎo)入函數(shù),然后點(diǎn)擊showinvalid顯示無(wú)效的函數(shù),沒(méi)有的話就點(diǎn)擊FixDump,選取要修正的應(yīng)用程序,之后,會(huì)在應(yīng)用程序所在的文件夾下生成一個(gè)原文件名加下劃線的應(yīng)用程序。比如原始文件名為xxx.exe,那么Import REConstructor生成的文件名為xxx_.exe。至此,脫殼完畢。
(3) ESP定律法
關(guān)鍵句運(yùn)行之后,ESP的值會(huì)改變!
右鍵ESP,選擇Follow in Dump。
在ESP地址處設(shè)置硬件訪問(wèn)斷點(diǎn)。
設(shè)置完斷點(diǎn)之后,可以選擇Debug->Hardware breakpoints查看硬件斷點(diǎn)是否設(shè)置成功。
F9運(yùn)行程序,程序停在關(guān)鍵句附近,接著要?jiǎng)h除設(shè)置的硬件斷點(diǎn),直接店家上圖所示的Delete即可。找到OEP之后再進(jìn)行脫殼。
(4) 內(nèi)存鏡像法
首先需要對(duì)OD進(jìn)行設(shè)置,忽略所有異常
在內(nèi)存窗口中找到程序的.rsrc段,按f2鍵設(shè)置斷點(diǎn),shift+f9運(yùn)行程序,斷下之后,在程序的代碼段按f2設(shè)置斷點(diǎn)。
shift+f9運(yùn)行,程序?qū)⑼T贠EP附近。
2. ASPack
脫ASPack殼可以使用上面所說(shuō)的單步跟蹤法,ESP定律法和內(nèi)存鏡象法,這里以單步跟蹤法進(jìn)行演示,其他兩種方法的思路如上所述。
(1) 單步跟蹤法
單步跟蹤法除了上面說(shuō)的跳轉(zhuǎn)規(guī)則外,還有一個(gè)規(guī)則需要遵從,那就是近c(diǎn)all f7進(jìn)入,遠(yuǎn)call f8跳過(guò)。如果跳過(guò)了某個(gè)函數(shù)之后程序跑飛,那么再次調(diào)試時(shí)f7進(jìn)入此函數(shù)即可。
如下所示,載入程序后即可看到一個(gè)call指令,那么這個(gè)函數(shù)就需要進(jìn)入跟蹤。
接著按照跳轉(zhuǎn)指令和call指令規(guī)則進(jìn)行跟蹤。
跟蹤到如上圖所示的地址后,就快到大OEP了,可見這個(gè)程序采用的是push/ret的方式進(jìn)行跳轉(zhuǎn)的,而且跳轉(zhuǎn)的跨度很到,從0x004243BA處跳轉(zhuǎn)到0x00402360。跳轉(zhuǎn)之后即到OEP,接著按照上面所說(shuō)的方式進(jìn)行脫殼。
3. FSG
(1) 單步跟蹤法
按照前面講解的原則對(duì)fsg殼進(jìn)行單步跟蹤需要細(xì)心,拿此例來(lái)說(shuō),單步跟蹤的時(shí)候,看到如下所示的語(yǔ)句:
這個(gè)跳轉(zhuǎn)并沒(méi)有實(shí)現(xiàn),但是程序的OEP就在0x004010CC,我們可以跟隨到這個(gè)地址里面查看代碼,直接在這個(gè)地址右鍵,選擇follow:
但是并沒(méi)有看到我們想要的代碼:
遇到這種情況,直接右鍵,選擇Analysis->Analyse code,就可以將這些機(jī)器碼轉(zhuǎn)為匯編碼:

(2) ESP定律法
使用ESP定律法單步跟蹤的時(shí)候,要時(shí)刻注意著ESP值的變化,當(dāng)ESP的值發(fā)生變化后,就安裝上面所說(shuō)的方法設(shè)置硬件訪問(wèn)斷點(diǎn),然后運(yùn)行程序,程序?qū)⑼V乖趏ep附近,然后在單步跟蹤,很快就將找到程序OEP。
4. FSG變形殼
FSG變形殼在找OEP的方法和FSG的查找方法一樣,只是在脫殼的時(shí)候有點(diǎn)區(qū)別,按照FSG的脫殼方法找到OEP之后,進(jìn)行脫殼,使用OD的插件進(jìn)行脫殼,發(fā)現(xiàn)程序無(wú)法運(yùn)行,然后再對(duì)其導(dǎo)入表進(jìn)行修正。使用Import REConstructor載入目標(biāo)程序,修改OEP,點(diǎn)擊show Invalid會(huì)看到一些無(wú)效的程序。
在這些無(wú)效的程序上右鍵,選擇cut thunks,接著點(diǎn)擊Fix Dump選擇我們脫下來(lái)的程序。但是修正之后發(fā)現(xiàn)仍然無(wú)法運(yùn)行,將修復(fù)后的程序使用OD載入,直接F9運(yùn)行,看到OD中斷到下圖所示的位置:
第一種方法,將int 0x13使用nop覆蓋:
第二種方法,將jle改為jmp強(qiáng)制跳轉(zhuǎn),越過(guò)int 0x13:
修改之后,保存文件,就可以運(yùn)行了。也可以使用16進(jìn)制工具打開文件,找到相應(yīng)的位置,修改數(shù)據(jù)并保存。
四、FSG2.0
使用ESP定律法和單步跟蹤法結(jié)合可以很快找到程序的OEP,這里重點(diǎn)講解脫殼后的修復(fù)問(wèn)題。
使用Import REConstructor載入程序后,點(diǎn)擊Get Import可以看到只有37個(gè)函數(shù),這是很不正常的,而且沒(méi)有無(wú)效的指針。如果直接進(jìn)行Fix Dump程序依然無(wú)法執(zhí)行。
可以看到上圖中IAT表的大小只有0x94,這是不對(duì)的,需要對(duì)IAT進(jìn)行修復(fù),這里介紹兩種方法,第一種方法是通過(guò)OD,當(dāng)我們找到OEP的時(shí)候,可以看到下圖所示的情況:
函數(shù)GetCommandLineA所在的地址是0x4063E4,在dump窗口(左下窗口)中定位到這個(gè)函數(shù)的地址:
向上找,直到遇到一堆00,此時(shí)就找到了IAT表的開始地址,如下圖所示,開始地址是0x004062E4。
然后在往后找,同樣是找00,即IAT的結(jié)尾。可以看到它結(jié)尾
的地址是0x00406E00。
所以IAT的大小為0x00406E00-0x004062E4=0xB1C,接著就把RVA和Size寫入Import REConstructor中。
cut掉無(wú)效的函數(shù)指針,選取我們脫殼后的應(yīng)用程序進(jìn)行Dump即可。
第二種方法和第一種類似,都需要找到IAT的RVA進(jìn)行修改,不同的是不需要計(jì)算Size的值,隨便寫一個(gè)比較大的值,比如0x1000就行,這樣的話,肯定會(huì)有很多無(wú)效的函數(shù)指針,當(dāng)我們查看這些無(wú)效指針的反匯編代碼時(shí),會(huì)提示ReadError。
這種情況下,直接把無(wú)效的指針cut掉即可。
1. PECompact1.84
使用單步跟蹤法對(duì)PECompact殼進(jìn)行調(diào)試的時(shí)候,會(huì)看到有些向上的跳轉(zhuǎn),如果我們?cè)谶@些跳轉(zhuǎn)的下面下斷點(diǎn)運(yùn)行程序的話,程序?qū)⑴茱w,這個(gè)時(shí)候,就需要在這些跳轉(zhuǎn)的前面,找一個(gè)沒(méi)有實(shí)現(xiàn)的大跳轉(zhuǎn),跟隨進(jìn)去,設(shè)置斷點(diǎn),然后運(yùn)行程序,使用這種方法,一般很快就可以找到OEP。
例如:
有一個(gè)向上的jmp跳轉(zhuǎn),如果在它下面設(shè)置斷點(diǎn)并運(yùn)行程序的話,程序就將跑飛,這時(shí),需要重新載入程序,在這個(gè)jmp前面找到一個(gè)沒(méi)有實(shí)現(xiàn)的大跳轉(zhuǎn)。
跟隨進(jìn)這個(gè)跳轉(zhuǎn)地址,設(shè)置斷點(diǎn)運(yùn)行程序
按照這個(gè)思路進(jìn)行調(diào)試,即可找到程序OEP,接下來(lái)就可以進(jìn)行脫殼。
2. nspack
對(duì)于nspack的殼,最簡(jiǎn)單的方法就是使用esp定律法,當(dāng)然,使用前面所說(shuō)的其他方法也可以找到OEP。
3. Yoda
(1) 內(nèi)存鏡像法
首先使用peid查看一下殼,可以看到是yoda1.2的殼
使用OD載入程序,忽略所有異常,找到內(nèi)存的.rsrc段,設(shè)置斷點(diǎn)后運(yùn)行程序,然后再在代碼段設(shè)置斷點(diǎn),運(yùn)行程序,就可直接到達(dá)程序的OEP,脫這個(gè)殼的重點(diǎn)在于修復(fù)方面。按照上述方法進(jìn)行脫殼,使用Import REConstructor進(jìn)行修復(fù),發(fā)現(xiàn)大部分的指針都是無(wú)效的。
點(diǎn)擊Show Invalid顯示無(wú)效指針,然后右鍵選擇Trace Level1進(jìn)行修復(fù)。
修復(fù)之后,所有的指針都有效了。
然后再選擇目標(biāo)程序進(jìn)行fix dump。
還有一個(gè)方法是單獨(dú)尋找,比如在如下圖所示的情況下:
右鍵目標(biāo)指針,選擇16進(jìn)制查看。
我們選擇的指針就是第一行那個(gè),庫(kù)文件是advapi32,函數(shù)名為RegSetValueExA:
然后雙擊剛才選擇的指針,查找對(duì)應(yīng)的動(dòng)態(tài)庫(kù)和函數(shù)名
點(diǎn)擊OK之后,就會(huì)發(fā)現(xiàn)指針已被修復(fù)。
當(dāng)然這種方法適合無(wú)效指針較少的情況。修復(fù)完之后,選擇脫殼后的文件進(jìn)行Fix即可。
(2) 巧方法
打開OD,對(duì)OD進(jìn)行設(shè)置。
選擇SFX->Trace real entry bytewise。
然后載入目標(biāo)程序,OD將自動(dòng)停在OEP處。這種方法可以嘗試使用,不保證適用于每一種殼。
(3) 附加數(shù)據(jù)的處理
有些加殼的作者會(huì)將一些關(guān)鍵的代碼放到附加數(shù)據(jù)里,這樣的話,當(dāng)脫殼之后,附加數(shù)據(jù)里的代碼也會(huì)被脫掉,這樣就達(dá)到了保護(hù)代碼的作用。這小節(jié)就來(lái)講講如何處理帶有附加數(shù)據(jù)的情況。
首先使用peid對(duì)目標(biāo)程序進(jìn)行查殼,從peid的顯示信息上看,可以看到這是北斗殼,Overlay表示有附加數(shù)據(jù)。
這個(gè)殼比較簡(jiǎn)單,重點(diǎn)講解是脫殼后的修復(fù),這里介紹兩種方法。
第一種方法是使用16進(jìn)制編輯器載入原始的目標(biāo)程序,從數(shù)據(jù)末尾向上找,一直找到全為0的位置。
從CA00開始復(fù)制,一直復(fù)制到數(shù)據(jù)末尾,然后打開修復(fù)后的文件,在數(shù)據(jù)的末尾將復(fù)制的數(shù)據(jù)粘貼進(jìn)去即可。
第二種方法和第一種類似,只是找的速度相對(duì)較快,使用peid查看程序的節(jié)信息。
我們需要關(guān)注的是R偏移和R大小,找最后一個(gè)區(qū)段,使用0x400+0xC600=0xCA00,然后在16進(jìn)制查看器中定位到這個(gè)地址,即是我們復(fù)制的數(shù)據(jù)的開始地址。
五、多線程調(diào)試
OD調(diào)試軟件時(shí),它每次只能跟一個(gè)線程,如果遇到的軟件創(chuàng)建了很多線程,那么調(diào)試起來(lái)就比較麻煩了,本節(jié)介紹一下多線程的調(diào)試方法。
首先看一下線程創(chuàng)建的函數(shù):
- HANDLE CreateThread(
- LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
- SIZE_T dwStackSize,//initialstacksize
- LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction
- LPVOID lpParameter,//threadargument
- DWORD dwCreationFlags,//creationoption
- LPDWORD lpThreadId//threadidentifier
- )
重點(diǎn)看第三個(gè)和第四個(gè)函數(shù),其中第三個(gè)參數(shù)指定了新線程的入口地址,第四個(gè)參數(shù)為新線程所需的參數(shù),當(dāng)程序調(diào)用此函數(shù)來(lái)創(chuàng)建線程的時(shí)候,定位到線程的入口地址設(shè)置斷點(diǎn),一般情況下,程序會(huì)在后面調(diào)用Sleep或WaitForSingleObject函數(shù),這樣的話,程序的控制權(quán)就到了新線程里面。
如果程序沒(méi)有調(diào)用Sleep或WaitForSingleObject函數(shù),那么可以修改函數(shù)的代碼,強(qiáng)制程序調(diào)用這兩個(gè)函數(shù),這樣程序就轉(zhuǎn)到新線程中執(zhí)行。另一種方法是修改程序的EIP,使其指向新線程入口,如果有參數(shù)的話,還要修改寄存器的值,使其指向參數(shù)地址,不過(guò)這種方法可能會(huì)造成寄存器內(nèi)容不正確,環(huán)境異常,從而造成程序執(zhí)行崩潰。
還有一種方法來(lái)調(diào)試多線程。如果程序多次調(diào)用CreateThread,確定一個(gè)我們打算調(diào)試的線程,并讓這個(gè)線程創(chuàng)建成功,當(dāng)程序再調(diào)用CreateThread創(chuàng)建線程的時(shí)候,直接修改此函數(shù),讓它直接返回,這樣就不會(huì)再創(chuàng)建線程了,我們就可以只調(diào)試一個(gè)線程。
六、調(diào)試子進(jìn)程
有些樣本并不是單獨(dú)運(yùn)行的,它在運(yùn)行過(guò)程中可能會(huì)創(chuàng)建子進(jìn)程來(lái)完成惡意功能,遇到這種情況,就需要進(jìn)入子進(jìn)程中進(jìn)行調(diào)試。
如果父進(jìn)程創(chuàng)建子進(jìn)程時(shí)是以suspended的方式創(chuàng)建的,那么父進(jìn)程會(huì)向子進(jìn)程進(jìn)行代碼注入,寫入之后會(huì)調(diào)用ResumeThread函數(shù)恢復(fù)子進(jìn)程的運(yùn)行。遇到這種情況,要確定父進(jìn)程寫入代碼的地址,當(dāng)代碼寫入之后,調(diào)用ResumeThread恢復(fù)子進(jìn)程運(yùn)行前,附加到子進(jìn)程中,在代碼注入的地址下斷點(diǎn)并F9讓子進(jìn)程運(yùn)行。這時(shí)再回到父進(jìn)程中,運(yùn)行函數(shù)ResumeThread,這樣子進(jìn)程就可以斷在代碼注入的地址了。
還有一種創(chuàng)建子進(jìn)程的方式,就只是簡(jiǎn)單的開啟一個(gè)子進(jìn)程運(yùn)行,這種情況比較簡(jiǎn)單,需要注意的一點(diǎn)就是父進(jìn)程創(chuàng)建子進(jìn)程時(shí)傳給子進(jìn)程的參數(shù),使用OD打開要運(yùn)行的子進(jìn)程,傳入所需的參數(shù)即可開始調(diào)試。
PS:如果使用windbg調(diào)試的話,可以使用命令.childdbg 1開啟子進(jìn)程調(diào)試。
七、SYS調(diào)試
sys是驅(qū)動(dòng)文件,相對(duì)于應(yīng)用程序來(lái)說(shuō),它的調(diào)試難度稍微大一點(diǎn),調(diào)試sys需要進(jìn)行雙機(jī)調(diào)試,而前提就是搭建雙機(jī)調(diào)試的環(huán)境。
環(huán)境搭建
(1) 打開vmware中對(duì)應(yīng)的虛擬機(jī)設(shè)置界面,如下圖:
(2) 查看是否已經(jīng)有端口存在,如果有的話需要移除,否則windbg與vmware連接不上,比如下圖,就需要將打印機(jī)移除。
(3) 添加串口。
(4) 在硬件類型窗口中選擇串行端口,并點(diǎn)擊下一步。
(5) 在串行端口類型窗口下選擇輸出到命名管道,然后點(diǎn)擊下一步。
(6) 指定插槽。
配置如下圖所示,其中命名管道的com_1為管道名稱,可以作改動(dòng),但是在用windbg進(jìn)行連接的時(shí)候也要注意名字的一致,我這里保留默認(rèn)名。兩臺(tái)機(jī)器,一臺(tái)為【該端是服務(wù)器】保留默認(rèn)設(shè)置,最后一個(gè)下拉框選擇【另一端是應(yīng)用程序】。最后注意吧【啟動(dòng)時(shí)連接】的復(fù)選框選上。
然后點(diǎn)擊【完成】按鈕即可完成配置。
(7) 完成串行端口添加。
八、配置虛擬機(jī)
打開虛擬機(jī)中windows的系統(tǒng)盤,找到boot.ini文件,如果不顯示這個(gè)文件的話,在文件夾選項(xiàng)中設(shè)置為“顯示所有文件”,“不隱藏系統(tǒng)保護(hù)文件”,然后就可以看到boot.ini文件了。
打開boot.ini文件后,原始內(nèi)容如下
- [boot loader]
- timeout=30
- default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
- [operating systems]
- multi(0)disk(0)rdisk(0)partition(1)\WINDOWS=”Microsoft Windows XP Professional” /noexecute=optin /fastdetect
將最后一行復(fù)制并粘貼在[operating systems]下,然后修改一些參數(shù)即可
- [boot loader]
- timeout=30
- default=multi(0)disk(0)rdisk(0)partition(1)\WINDOWS
- [operating systems]
- multi(0)disk(0)rdisk(0)partition(1)\WINDOWS=”XP Debug” /fastdetect /debug /debugport=com1 /buadrate=115200
- multi(0)disk(0)rdisk(0)partition(1)\WINDOWS=”Microsoft Windows XP Professional” /noexecute=optin /fastdetect
1. windbg配置
下面設(shè)置調(diào)試機(jī)上的windbg啟動(dòng)參數(shù),使之連接一個(gè)管道,并把這個(gè)管道當(dāng)作一個(gè)串口來(lái)處理,首先建立一個(gè)windbg.exe的快捷方式,然后右鍵快捷方式圖標(biāo)選擇屬性,在屬性對(duì)話框的目標(biāo)一欄加上空格后添加windbg.exe -b -k com:pipe,port=\\.\pipe\com_1,baud=115200,pipe,然后點(diǎn)擊保存即可。
要進(jìn)行雙機(jī)調(diào)試的話,在虛擬機(jī)啟動(dòng)時(shí),選擇啟動(dòng)調(diào)試程序,當(dāng)虛擬機(jī)啟動(dòng)起來(lái)之后,再開啟windbg就可以連接到虛擬機(jī)中。
2. 設(shè)置windows內(nèi)核符號(hào)表
打開windbg,選擇菜單“File”->”Symbol File Path”,然后填寫:
- srv*c:\Symbols*http://msdl.microsoft.com/download/symbols
如下圖,如果我們選擇了Reload,那么相當(dāng)于輸入了.reload命令,這時(shí)開始下載符號(hào)。
以上方法是設(shè)置是WinDbg自動(dòng)用HTTP協(xié)議從微軟的網(wǎng)站上下載所需的符號(hào)表。下載完后可以在C:\Symbols目錄中查看,這個(gè)路徑也可以指定為其它路徑。
3. 尋找DriverEntry
首先使用命令uf nt!IopLoadDriver查看驅(qū)動(dòng)加載的入口,針對(duì)32位程序和64位程序,入口處的代碼不一樣,32位下的代碼如下
- nt!IopLoadDriver+0x663:
- 805a07c9 ffb570ffffff push dword ptr [ebp-90h]
- 805a07cf 57 push edi
- 805a07d0 ff572c call dword ptr [edi+2Ch]
- 805a07d3 3bc3 cmp eax,ebx
- 805a07d5 8b8d68ffffff mov ecx,dword ptr [ebp-98h]
- 805a07db 8945ac mov dword ptr [ebp-54h],eax
- 805a07de 8901 mov dword ptr [ecx],eax
- 805a07e0 0f8c91200500 jl nt!IopLoadDriver+0x67c (805f2877)
64位下的代碼如下:
- nt!IopLoadDriver+0x9fe:
- fffff80002cb545e 488bd6 mov rdx,rsi
- fffff80002cb5461 488bcb mov rcx,rbx
- fffff80002cb5464 ff5358 call qword ptr [rbx+58h]
- fffff80002cb5467 4c8b15da3bdaff mov r10,qword ptr [nt!PnpEtwHandle (fffff80002a59048)]
- fffff80002cb546e 8bf8 mov edi,eax
- fffff80002cb5470 898424e0000000 mov dword ptr [rsp+0E0h],eax
- fffff80002cb5477 4c3bd5 cmp r10,rbp
- fffff80002cb547a 0f848e000000 je nt!IopLoadDriver+0xaae (fffff80002cb550e)
可以通過(guò)搜索上述代碼中加紅的代碼定位到關(guān)鍵點(diǎn),然后bp下斷點(diǎn),之后輸入”g”讓虛擬機(jī)運(yùn)行,回到使用軟件KmdManager加載驅(qū)動(dòng),打開KmdManager,界面如下:
將sys文件拖入上圖所示的窗口中,在小方框中打勾(可以在卸載驅(qū)動(dòng)的時(shí)候再在下面的打勾),如下圖所示:
然后點(diǎn)擊Reg`nRun加載驅(qū)動(dòng),會(huì)看到斷點(diǎn)斷在我們?cè)O(shè)置的地方,然后F11進(jìn)入就達(dá)到了DriverEntry開始調(diào)試。
注意:32位的驅(qū)動(dòng)程序要在32位系統(tǒng)下調(diào)試,64的驅(qū)動(dòng)程序要在64位系統(tǒng)下調(diào)試。
九、DLL調(diào)試
使用OD調(diào)試動(dòng)態(tài)庫(kù)時(shí)有兩種打開方式,第一種比較簡(jiǎn)單,直接將dll文件拖入OD即可,OD自身會(huì)自動(dòng)識(shí)別出來(lái)當(dāng)前文件是動(dòng)態(tài)庫(kù)文件,然后啟用loadll.exe來(lái)加載此動(dòng)態(tài)庫(kù)文件。
另一種方法是通過(guò)OD打開rundll32.exe,傳入動(dòng)態(tài)庫(kù)文件的路徑作為參數(shù)來(lái)進(jìn)行調(diào)試。
上面兩種是比較直接的方式,但是有的dll文件會(huì)在od載入并斷下來(lái)時(shí),就已經(jīng)執(zhí)行完了惡意代碼,遇到這種情況,就需要下面介紹的一種調(diào)試技巧。
選擇菜單欄的Options->Debugging options。
在彈出的窗口中選擇Events,勾選Break on new module(dll).然后點(diǎn)擊OK。
這樣的話,在新模塊載入的時(shí)候,OD會(huì)斷下來(lái)。此時(shí)注意Executable modules窗口的變化,這里會(huì)顯示動(dòng)態(tài)庫(kù)加載的內(nèi)存地址。
如圖所示,我們的目標(biāo)動(dòng)態(tài)庫(kù)的基地址是0x10000000。然后使用IDA打開目標(biāo)文件,找到想要下斷點(diǎn)的地址,以下圖為例:
假如我們想要在sub_1000824C處設(shè)置斷點(diǎn),直接在OD中,定位到這個(gè)地址,然后設(shè)置斷點(diǎn)即可。如果在IDA中顯示的地址是0x2000824C,那么我們?cè)贠D中的0x2000824C地址處是找不到對(duì)應(yīng)的代碼的。這時(shí)需要計(jì)算代碼的偏移,很明顯,我們可以知道這個(gè)地址的RVA是824C,然后在OD中,將這個(gè)偏移加上OD加載的基地址0x10000000即可得到對(duì)應(yīng)代碼的絕對(duì)地址。然后設(shè)置斷點(diǎn)運(yùn)行程序即可(此時(shí)可以取消先前設(shè)置的Break on new module斷點(diǎn)了)。
十、JS調(diào)試
目前使用JS來(lái)執(zhí)行惡意功能或者下載惡意組件的情況已經(jīng)很平常了,所以這里介紹一下關(guān)于js的調(diào)試方法。
大部分的js腳本都是經(jīng)過(guò)混淆或者加密的,靜態(tài)分析的話根本不可能,一般在分析之前都需要對(duì)js腳本進(jìn)行一下美化,推薦美化網(wǎng)站:http://jsbeautifier.org/
下面進(jìn)入正題,講解js的調(diào)試方法。
1. Alert方法
在互聯(lián)網(wǎng)剛剛起步的時(shí)代,網(wǎng)頁(yè)前端還主要以內(nèi)容展示為主,瀏覽器腳本還只能為頁(yè)面提供非常簡(jiǎn)單的輔助功能的時(shí)候。那個(gè)時(shí)候,網(wǎng)頁(yè)主要運(yùn)行在以IE6為主的瀏覽器中,JS的調(diào)試功能還非常弱,只能通過(guò)內(nèi)置于Window對(duì)象中的alert方法來(lái)調(diào)試,在網(wǎng)頁(yè)中按F12鍵,點(diǎn)擊Console按鈕。
直接在這里輸入想要查看的值即可。如下圖所示,我們輸入var a = ‘abc’;alert(a);瀏覽器即彈出對(duì)話框,顯示變量a的值。
2. Console方法
隨著js能做的事情越來(lái)越多,責(zé)任越來(lái)越大,地位也越來(lái)越重要,傳統(tǒng)的alert調(diào)試方法漸漸的跟不上技術(shù)前進(jìn)的節(jié)奏,alert調(diào)試方式彈出的調(diào)試信息,窗口不太美觀,而且會(huì)遮擋頁(yè)面內(nèi)容,另外,使用alert方法,必須在程序邏輯中添加alert(xxx)的語(yǔ)句才能正常工作,比較麻煩,對(duì)于開發(fā)人員來(lái)說(shuō),后期還要手動(dòng)清除這些代碼。為了避免這種手續(xù),出現(xiàn)了console的調(diào)試方法。
和alert方法類似,只是將語(yǔ)句換成console即可。
也可以在js惡意腳本中添加console語(yǔ)句,輸出想要查看的目標(biāo)值。
3. JS斷點(diǎn)調(diào)試
JS斷點(diǎn)調(diào)試,即是在瀏覽器開發(fā)者工具中為JS代碼添加斷點(diǎn),讓js執(zhí)行到某一位置停止,方便我們對(duì)該處的代碼進(jìn)行分析和邏輯處理。為了方便說(shuō)明,我們先準(zhǔn)備一段示例代碼:
4. 使用sources斷點(diǎn)
方法一:使用前面說(shuō)的,在源碼中添加alert或者console,添加后如下所示:
結(jié)果為:
這種方法需要我們手動(dòng)添加代碼,比較麻煩,下面就使用斷點(diǎn)來(lái)調(diào)試
方法二:點(diǎn)擊F12,找到sources菜單,在左側(cè)樹中找到對(duì)應(yīng)的文件,然后點(diǎn)擊行號(hào)來(lái)添加或刪除斷點(diǎn)
設(shè)置完斷點(diǎn)后,刷新頁(yè)面,程序?qū)⒃跀帱c(diǎn)處停下來(lái),在sources界面中會(huì)看到當(dāng)前作用域中所有的變量和值。
如果想一行一行的跟蹤代碼,可以使用瀏覽器提供的調(diào)試按鈕:
這六個(gè)按鈕的功能依次為:
- Pause/Resume script execution:暫停/恢復(fù)腳本執(zhí)行(程序執(zhí)行到下一斷點(diǎn)停止)。
- Step over next function call:執(zhí)行到下一步的函數(shù)調(diào)用(跳到下一行)。
- Step into next function call:進(jìn)入當(dāng)前函數(shù)。
- Step out of current function:跳出當(dāng)前執(zhí)行函數(shù)。
- Deactive/Active all breakpoints:關(guān)閉/開啟所有斷點(diǎn)(不會(huì)取消)。
- Pause on exceptions:異常情況自動(dòng)斷點(diǎn)設(shè)置
通過(guò)使用這幾個(gè)鍵,就可以像調(diào)試可執(zhí)行程序一樣進(jìn)行調(diào)試了。
需要注意的一點(diǎn)是,直接在代碼區(qū)打印變量值的功能是在較新版本的Chrome瀏覽器中才新增的功能,如果你還在使用較老版本的Chrome瀏覽器,可能無(wú)法直接在斷點(diǎn)的情況下查看變量信息,此時(shí)你可以將鼠標(biāo)移動(dòng)到變量名上短暫停頓則會(huì)出現(xiàn)變量值。也可以在右邊Watch面板中輸入變量值來(lái)查看,此方法同樣適用于表達(dá)式。此外,你還可以在斷點(diǎn)情況下,切換到Console面板,直接在控制臺(tái)輸入變量名稱,回車查看變量信息。
5. debugger方法
這種方法是在源程序中添加“debugger;”語(yǔ)句,這樣當(dāng)代碼執(zhí)行到該語(yǔ)句的時(shí)候就會(huì)自動(dòng)斷下來(lái),接下去的操作就和上面介紹的斷點(diǎn)調(diào)試方法類似了。
十一、.net調(diào)試
調(diào)試分析.net程序,首選的工具就是dnspy,這是一款集靜態(tài)分析與動(dòng)態(tài)調(diào)試于一體的強(qiáng)大工具。
直接使用dnspy打開目標(biāo)應(yīng)用程序,界面上顯示的信息告訴了我們函數(shù)的入口點(diǎn),直接點(diǎn)擊Main即可進(jìn)入到main函數(shù)處。
或者右鍵左邊的樹結(jié)構(gòu),在彈出對(duì)話框中選擇Go to Entry Point也可以跳轉(zhuǎn)到程序入口點(diǎn)。關(guān)于dnspy的詳細(xì)使用方法,請(qǐng)參考工具篇的說(shuō)明。
找到程序入口后,就可以在關(guān)鍵的地方設(shè)置斷點(diǎn)(F9設(shè)置斷點(diǎn)),然后選擇菜單欄上的調(diào)試按鈕開始調(diào)試。
點(diǎn)擊上述按鈕后,會(huì)彈出一個(gè)對(duì)話框,在對(duì)話框中選擇要調(diào)試的程序和對(duì)應(yīng)的參數(shù)就可以開始進(jìn)行調(diào)試了。
如果程序沒(méi)有經(jīng)過(guò)混淆的話,dnspy基本可以解析處源碼,所以.net的程序一般都是經(jīng)過(guò)混淆的,如果知道它使用的混淆方法,可以在網(wǎng)上搜索對(duì)應(yīng)的去混淆的工具,如果不知道的話,只有通過(guò)動(dòng)態(tài)調(diào)試之后,自己對(duì)函數(shù)名或者變量名進(jìn)行重命名,方法是在目標(biāo)變量名或者函數(shù)名上右鍵選擇Edit Method。
如果要修改類名的話,選擇的是Edit Type。
在調(diào)試過(guò)程中,如果想要查看變量的值,直接將鼠標(biāo)放到變量上就將顯示變量的類型和值。
【本文是51CTO專欄作者“綠盟科技博客”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過(guò)51CTO聯(lián)系原作者獲取授權(quán)】