惡意程式碼分析實戰 Lab 5-1 習題筆記
Lab 5-1
問題
1.DllMain的地址是什麼?
解答: 這個我們用IDA Pro
開啟來查詢,因為最新的IDA Pro
不支援我們執行病毒那個虛擬機器的版本(xp pro 32),所以下面的分析都是在win7
上的
開啟IDA Pro
之後就會提示你是否開啟一個叫proximity browser
的東西,然後開啟這個東西就可以看到DllMain
的位置
如果沒有跳出來,這裡可以點開這個視窗
然後右鍵選擇Text view
就可以看到地址了
注意標黃的地方
標黃的地方都是一樣的地址,不是IDA
的bug,上面那些都是一些IDA
生成的一些註釋,真正在這個地址上的只有
mov eas, [esp+fdwReson]
這一行
所以DllMain
的地址就是在.text
節的0x100D02E
處
2.使用Imports視窗並瀏覽到gethostbyname,匯入函式定位到什麼地址?
解答: 我們開啟Imports
視窗
然後搜尋這個
然後雙擊這個找到的函式也行,其實Imports
這裡已經標明瞭地址了
於是這個問題的答案就是0x100163CC
,但是我們可以用雙擊去原文中查詢的方式
這裡可以看出這是在.idata
節的0x100163CC
處
最後的答案就是gethostbyname
在.idata
節的0x100163CC
處
3.有多少函式呼叫了gethostbyname?
解答: 這個按照說中說的做就行了,但是注意一點,不是在Imports
視窗按Ctrl x
是在IDA View
窗口裡面,也就雙擊Imports
窗口裡面gethostbyname
函式跳出來的那個視窗
這是右擊gethostbyname
之後的顯示,然後點選Jump to xref to oprand...
這個選項,會跳出這個
型別r
是被”讀取”的引用,CPU必須先讀取這個匯入項,再呼叫它,型別p
是被呼叫的引用
這裡顯示了18行,但是並不是18個函式呼叫了這個gethostbyname
,注意看,好多函式都是一樣的(+後面的是偏移地址)
然後我們自己數一下就會發現,其實只有五個函式引用了gethostbyname
然後注意看就是每個r
的型別,總有一個p
的型別,所以被引用的次數就是18/2=9次
所以答案就是五個函式引用了九次
4.將精力集中在位於0x10001757處的對gethostname的呼叫,你能找到哪個DNS請求將被出發嗎?
解答: 我們先跳過去看看
按一下g
就跳出這個視窗了
然後輸入0x10001757
這個地址,就會跳了
然後我們檢視這個程式碼
call函式預設將棧頂的一個值作為函式的引數傳遞給函式,然後棧頂現在是eax push進去的
所以我們找eax的值,然後找到了mov off_10019040
這個,然後檢視off_10019040
然後我們可以跳到off_1001940
定義的地方,看到了這個字串(藍色的是IDA標識的)
[This is RDO]pics.praticalmalwareanalys
然後這裡大家也看到了,ida顯示不全,我們繼續追查
雙擊後面的aThisIsRdoPics
就可以看到完整的字串了
[This is RDO]pics.praticalmalwareanalysis.com
然後我們回過頭來看當時的那段程式碼
將off_10019040
中的值給了eax
之後(off_10019040
是字串指標)
學過C語言的同學都應該知道指標
現在這個off_10019040
指標指向的是字串的第一個字元,也就是符號[
0Dh
轉換成十進位制是13
字串是這個[This is RDO]pics.praticalmalwareanalysis.com
+
在指標裡面的意思你可以理解成是指標向後移動,這個C語言裡面有講,然後我們講指標往後移動13
位
最後指標指向的是p
這個字元
所以最後push進棧的值是pics.praticalmalwareanalysis.com
於是我們可以得出這個問題的答案就是會對
pics.praticalmalwareanalysis.com
進行DNS
解析
5.IDA pro識別了在0x10001656處的子過程中的多少個區域性變數?
解答: 老規矩,跳先
然後會發現這些被IDA識別的變數
然後簡單的一數,是24
個,和書中的計算結果有點差異,然後我數了一下書裡的截圖,是24
個,這裡就不糾結這個了,方法知道了就行
6.IDA pro識別了在0x10001656處的子過程中的多少個引數?
解答: 引數是呼叫這個函式的函式傳遞給被呼叫函式的值,搞清楚這個就好辦了
我們可以看見這裡IDA
識別的結果是傳入了一個LPVOID
型別的lpThreadParameter
所以答案就是識別了一個引數
7.使用string視窗,來在反彙編中定位字串\cmd.exe /c。它位於哪?
解答: 這個我們按SHIFT F12
就調出String
視窗了
然後檢視這個字串,雙擊就行了
然後
所以答案就是在xdoors_d:10095B34
處
8.在引用\cmd.exe /c的程式碼所在的區域發生了什麼?
解答: 我們右鍵查詢aCmd_exeC
引用
然後跳到這個引用的地方
然後我們可以發現在這裡,字串被壓入了棧中
然後分析這個函式會發現開頭這個字串
'Hi,Master [%d/%d/%d %d:%d:%d]
然後可以在這個大概位置上
發現一些比較函式,然後點開之後會發現
這些有關於系統資訊和系統操作的字串
按書中的觀點這是一個遠端shell會話函式
9.在同樣的區域,在0x100101c8處,看起來好像dword_1008E5C4是一個全域性變數,它幫助決定走哪條路徑。那惡意程式碼是如何設定dword_1008E5C4的呢?(提示:使用dword_1008E5C4的交叉引用。)
解答: 先跳到這個地方,然後郵件檢視交叉引用
其他兩處都是cmp
函式
只有第一處是mov
改變了它的值
我們跳到這個地方檢視
可以看到這個dword_1008E5C4
的上面,有一次sub_10003695
函式的呼叫,而彙編中,函式呼叫的返回值儲存在eax
中
然後我們檢視sub_10003695
這個函式到底返回了什麼
我們雙擊這個函式就可以跳到這個函式的定義了
然後這裡注意一下,就是雙擊會跳到.text:100036C2
這個位置的sub_10003694
,但是其實這個位置是函式的結束,上面.text:10003695
處還有一個sub_10003695
(發現沒有,其實IDA命名函式是用他的位置加上sub來的)
sub_10003695 proc near ; CODE XREF:
VersionInformation= _OSVERSIONINFOA ptr -94h
push ebp
mov ebp, esp
sub esp, 94h ; 將esp增加94h也就是148d,37個位元組
lea eax, [ebp+VersionInformation] ; 將ebp+VersionInformation的地址賦值給eax
mov [ebp+VersionInformation.dwOSVersionInfoSize], 94h ; 將這個地址的值賦值成94h
push eax ; lpVersionInformation
call ds:GetVersionExA ; 在一個OSVERSIONINFO結構中載入與平臺和作業系統有關的版本資訊
; 上面有定義這個OSVERSIONINFO結構
xor eax, eax ; 將eax置0(因為GetVersionExA的返回值在eax中)
cmp [ebp+VersionInformation.dwPlatformId], 2 ; 這裡將dwPlatformId和2進行比較
; 為什麼和2比較我們下面解釋
setz al ; Set Byte if Zero (ZF=1) ;
leave ; High Level Procedure Exit
retn ; Return Near from Procedure
sub_10003695 endp
我們可以來檢視一個OSVERSIONINFOA
的結構
檢視dwPlatformId
中的可能的值
因為在windows系統中,VER_PLATFORM_WIN32_NT
代表的值是2
對windows核心不是很瞭解,但是從文件來看,VER_PLATFORM_WIN32_NT
等於2的話,就是代表系統是
Windows 7, Windows Server 2008, Windows Vista, Windows Server 2003, Windows XP, or Windows 2000
setz
意思是當ZF
標誌被設定時,AL
暫存器設1
因為剛剛我們cmp了兩個數,所以如果兩個數相同,ZF=1
,然後setz
,AL
被設定為1
,反之不相同的話,AL
被設定為0
(AL
是EAX
的低8位,對應的AH
是EAX
的高8位)
一般來說,會執行這個機器的都是上面那幾種windows機器,所以這裡比較一般都是會相同的,所以,AL
被設定成了1
,然後就是用retn
返回了eax
中的值
但是為什麼書上說的是返回的是1
,因為我們前面執行過
xor eax, eax
eax
現在是被異或都成了0
(eax
有16位)
我們現在分析一下,01234567
代表的是高8位(ah
),剩下的代表的是低八位(al
)
先是
xor eax, eax
這個執行完之後的eax
01234567 89ABCDEF
-------- --------
00000000 00000000
然後我們接著執行
cmp [ebp+VersionInformation.dwPlatformId], 2
setz al
之後的eax
,89ABCDEF
代表的al
被置為了1
,其餘的ah
儲存不變
01234567 89ABCDEF
-------- --------
00000000 00000001
然後這個值其實就是十進位制的1
所以sub_10003694
的返回值是1
於是
mov dword_1008E5C4, eax
最後的dword_1008E5C4
的值就被賦成了1
所以這個全域性變數在程式執行的時候一直保持的是1
10.在位於0x1000FF58處的子過程中的幾百行指令中,一系列使用memcmp來比較字串的比較。如果對robotword的字串比較是成功的(當memcmp返回0),會發生什麼?
解答: 這個函式我們剛剛看過,但是沒細入分析過
整個從0x1000FF58
開始的函式,第一個使用memecp
是這裡(標黃的那裡)
一開始是比較了quit
和eax
的值,因為這兩個值被壓入了棧中
然後我們找到robotwork
,慢慢往右下角拖就是了
它首先壓入了一個robotwork
字串指標,然後壓入了eax
,然後call memcmp
,如果兩個數相同,返回0
,然後
add eap, 0Ch
0Ch
是12d
,也是4(位元組)*3(個)
,因為push
後面跟的是立即數,所以一個數佔4
位元組,然後offset
也是4
個位元組,所以,一開始的push 9
,和後面的兩次push
,加起來一共是3次,所以這裡回收了這3個一共12位元組的空間
test eax, eax
如果eax
為0
,則ZF
置為1
,JZ
跳轉,eax
為0
說明前面的memcmp
比較的結果是相同,也就是
如果前面兩個數相同,則JZ
跳轉,JNZ
不跳轉
然後問題是當字串比較成功,memcmp返回0會發生什麼
會發生的是,JNZ
不跳轉,程式繼續按從上到下的順序執行,下面要執行的就是
push [ebp+s] ; 將ebp(esp是棧頂指標,ebp是棧基址)地址增加s
; (棧中,esp地址減小,棧空間增大,ebp增加,ebp將向棧底偏移)
; 將ebp向下s的指標地址壓棧
call sub_100052A2
jmp short loc_100103F6
然後就是呼叫了sub_100052A2
這個函式
這個函式是這樣的
其他的都可以不管,看最下的地方
這個函式查詢了
SOFTWARE\Microsoft\Windows\CurrentVersion
這個登錄檔的地方,用的是RegOpenKeyExA
其實這裡也只查詢了這個,沒有查詢書中說的那個WorKTime
,在這個登錄檔目錄下,甚至就沒有這個項
然後跳轉到這裡
書中說
將這一資訊返回給
push [ebp+s]
處傳給該函式的網路socket
s是這裡定義的
但是不知道是怎麼把這資訊返回給上面的的引數的
11.PSLIST匯出函式做了什麼?
解答: 我們開啟匯出函式表
這個函式有兩條執行大路徑
然後執行的選擇取決於這個sub_100036C3
這個函式返回的是1
call ds:GetVersionExA ; 呼叫函式檢視系統版本
cmp [ebp+VersionInformation.dwPlatformId], 2 ; 這個我們上面說過,如果等於2,是那些windows版本
; 包括`Windows 7, Windows Server 2008, Windows Vista, Windows Server 2003, Windows XP, or Windows 2000`
jnz short loc_100036FA ; 如果不想等,則跳轉結束
cmp [ebp+VersionInformation.dwMajorVersion], 5 ; 5代表特殊版本的windows
jb short loc_100036FA ; 無符號比較,如果[ebp+VersionInformation.dwMajorVersion]小於5跳轉
push 1
pop eax
leave ; High Level Procedure Exit
retn
cmp [ebp+VersionInformation.dwMajorVersion], 5
這裡的5
代表什麼意思
代表的就是這麼幾個版本的windows
所以這個函式的作用就是具體判斷目標主機的系統版本,如果是過低的版本,就直接跳轉結束
如果是符合要求的版本,則返回1
然後就是比較跳轉,如果eax
為0
,test
之後,ZF
為1
,然後JZ
跳轉
如果eax
不為0
,ZF
不為0
,然後JZ
不跳轉
也就是如果版本符合要求,就不跳轉(跳轉之後是直接結束)
不跳轉之後,push
了一個字串進去,然後呼叫strlen
返回字串的長度在eax
中,然後test eax, eax
如果eax(字串長度)
為0
,ZF
置為1
,JNZ
不跳轉
反之如果不為0
,JNZ
跳轉
假設eax
為0
,JNZ
不跳轉,我們走一下這條線
那麼下一個執行完push
之後,就是執行call sub_10006518
從彙編中可以看出,這個sub_10006518
執行了這個函式CreateToolHelp32Snapshot
,這個函式是
CreateToolhelp32Snapshot函式為指定的程序、程序使用的堆[HEAP]、模組[MODULE]、執行緒[THREAD])建立一個快照[snapshot]。
然後我們看跳轉那條線
跳轉這條線上(藍色
),它將字串壓入棧之後,又壓入了一個0
,然後呼叫了sub_1000664C
這個函式
這個函式依舊還是呼叫了CreateToolHelp32Snapshot
這個函式,然後還有GetLastError
和sprintf
這些個函式
而sprintf
函式的輸出是
所以我們不難想,GetLastError
是用於判斷CreateToolHelp32Snapshot
這個函式有沒有執行成功的
如果失敗,就執行endp(也就是圖中標註的exit_0)
如果成功,則執行retn(也就是圖中的return_0)
按照書中的說法
這兩條程式碼路徑都通過send將程序列表通過socket傳送
但是我是沒找到這個send
和socket
這兩個函式
唯一能扯上一點關係的就是這裡有個
push [ebp+s]
但是這個s
是如何看出就是代表socket
或者send
呢,不得而解
12.使用圖模式來繪製出對sub_10004E79的交叉引用圖。當進入這個函式時,哪個API函式可能被呼叫?僅僅基於這些API函式,你會如何重新命名這個函式?
解答:: 按照書上的做法
我們跳到指定位置之後,按照這個選單,點選
然後出來這個
直接預設點確定就行了
然後就出現這個了
我們關心的主要是下面這三層
可以看出這個函式sub_10004E79
主要呼叫的有
GetSystemDefaultLangID
和sprintf
和sub_100038EE
和strlen
而sub_100038EE
主要呼叫了send
和malloc
和free
和__imp_strlen
然後GetSystemDefaultLangID
是獲取系統的預設語言的函式,send
是socket
傳送的函式
由此我們可以按照書上的做法,將這個函式重新命名為send_languageID
13.DllMain直接呼叫了多少個Windows API?多少個在深度為2的時候被呼叫?
解答: 搜尋DllMain找到這個函式的位置
然後用上面的方法開啟檢視
會發現檢視極其龐大。。。(因為預設Recursion depth為-1)
我們重新開啟,將這裡改為1
然後就可以看到呼叫深度為1
的所有函數了
所以,DllMain
在深度為1
直接呼叫的API
也就是strncpy
、_strnicmp
、CreateThread
、strlen
這麼幾個
要知道全部的 API
,那就得一個一個數了。。。
同樣的方法檢視深度為2
時候的呼叫(很大的呼叫表)
14.在0x10001358處,有一個對Sleep(一個使用一個包含要睡眠的毫秒數的引數的API函式)的呼叫。順著程式碼往後看,如果這段程式碼執行,這個程式會睡眠多久?
解答: 我們跳到那個地方看看
Sleep
函式的引數是壓入棧的eax
,而這個eax
從哪裡來,是call ds:atoi
的返回值,再乘以3E8h
,最後就被壓入了棧中,供Sleep
做入參
那atoi
的引數是從前面的push eax
中來的,eax
的根源是從off_10019020
傳進來的
而off_10019020
的值放在這裡
[This is CTI]30
然後執行了這個(這個前面說過,指標的+
多少相當於指標往後偏移多少
add eax, 0Dh
0Dh
相當於十進位制的13d
往後偏移13
個位元組,最後指標指向3
(從0
開始數)
然後將指向3
的指標壓入棧,其實現在指標就相當於這樣
char origin_str[] = "[This is CTI]30";
char *p_str = origin_str;
p_str = p_str + 13;
最後輸出的p_str
就是30
,然後呼叫這個atoi
,它是把字串(char)轉換成整型(int)的函式
call ds:atoi
然後這個函式的輸出就是(int)30
imul eax, 3E8h
3E8h
就是1000d
,imul
是乘,然後30
乘1000
就是30000(3w)
然後我們從MSDN
確定一下單位
這裡顯示是以milliseconds
為單位,就是毫秒,30000
ms = 30
s
所以這個函式會休眠30
s
15.在0x10001701處是一個對socket的呼叫。它的3個引數是什麼?
解答: 我們還是先跳到這個函式的位置
這裡是將6
、1
、2
壓入了棧中,然後我們不知道具體的配置資訊,按照書中的做法
然後就會跳出一大堆的東西
然後我們查詢一下關於socket
有關的函式,這時候如果你對socket
的相關函式的引數不是很清楚的話,建議還是查查MSDN
我們都知道棧是先進後出的型別,先壓入的資料,其實是最後才呼叫的
最後壓入的是2
這個數(彙編不像其他高階語言,引數的順序可以顛倒),然後2
應該對應的就是af
這個引數
我們就點開2
的對話方塊,然後去MSDN
查對應的值,一般輸入都有的頭就可以了
這裡是AF
,一般這種對應表裡面,一個函式用的到的,肯定只會出現一次,果不其然,我們搜AF
之後,就會發現,這裡之後兩個AF
開頭的
除了第一個的AF_INET
之外,還有一個AF_OP_COMM
,其實這個並不是socket
函式的引數
太長了截不完,不信的同學可以自己上MSDN
去查(手動滑稽)
然後我們就可以將這個引數重新命名一下
大概看起來就像這樣,然後我們按這種方法來找(其實雙擊你找到的那個對話方塊裡面的引數名就可以重新命名了)
然後第二個壓入棧中的1
,在socket
中對應的是type
這裡都有的都是帶sock
的
然後注意就是不要和socket
混淆了,這裡是sock
,然後就是為什麼會出現兩個一摸一樣的SOCK_STREAM
,其實一個東西
一個是MS SDK
另一個是Virtual C++ 6.0
,其實都是一樣的,然後我們重新命名一下
最後一個6
,它是第一個壓入棧中的值,但是卻是最後呼叫的
他對應的是protocol
這裡只有搜IPPROTO
就可以了
一樣的方法
然後最後把這些符號常量重新命名之後,彙編程式碼成了這樣的
我們的函式入參就一目瞭然了,稍微解釋了一下這三個引數的意思
AF_INET 用於連線連線物件是IPv4時(對應的IPv6用的是 AF_INET6)
SOCK_STREAM 用於連線方式使用TCP時候(對應的UDP對應的是SOCK_DGRAM)
IPPROTO_TCP 用於繼續指明傳輸的方式是TCP(對應的UDP是IPPROTO_UDP)
16.使用MSDN頁面的socket和IDA pro中的命名符號常量,你能使引數更加有意義嗎?在你應用修改之後,引數是什麼?
解答: 就是上面剛剛分析的那些引數。。。感覺分析多了
17.搜尋in指令(opcode 0xED)的使用。這個指令和一個魔術字串VMXh用來進行VMware的檢測。 在這個惡意程式碼中被使用了嗎?使用對執行in指令函式的交叉引用,能發現進一步檢測VMware的證據嗎?
解答: 按照書上的做法我們試試
這裡搜尋要學會使用技巧,不然出一大堆沒用的東西
然後我們可以發現,出了一些不是運算的註釋和名稱之外,只剩下標黃這個東西可以值得點開看看,如果是在不確定,可以一個一個點開看看
我這一版本的IDA
並沒有標註這個二進位制的意思,書上是標註出來的
我們右鍵,會顯示出各種不同的編碼結果
可以發現有個VMXh
,但是書中說經過一個比較之後,後面會發現一個Found Virtual Machine
字串,但是目前暫時沒發現這個字串
18.將你的游標跳轉到0x1001D988處,你發現了什麼?
解答: 這個我們跳了看看就知道了
發現一些不知道什麼鬼的東西,然後按照書中說的,我們執行這個python
程式碼看看
執行這個指令碼的前提是裝了python
19.如果你安裝了IDA Python外掛(包裹IDA Pro的商業版本的外掛),執行Lab05-01.py,一個本書中隨惡意程式碼提供的IDA Pro Python指令碼,(確定游標是在0x1001D988處。)在你執行這個指令碼後發生了什麼?
解答: 執行這個python
指令碼之後,其實沒啥變化,如果你不仔細看的話
其實字元已經變了
然後現在這串字串已經變可讀了,反正就是在那一瞬間,哈哈哈
20.將游標放在同一位置,你如何將這個資料轉成一個單一的ASCII字串?
解答: 這個其實前面的就是ASCII碼,也就是db後面的
可以在設定裡面開自動註釋來顯示
21.使用一個文字編輯器開啟這個指令碼。它是如何工作的?
解答: 如下
本文完