惡意樣本分析精要及實踐9-IDA使用(二)
STATEMENT
聲明
由于傳播、利用此文所提供的信息而造成的任何直接或者間接的后果及損失,均由使用者本人負責,雷神眾測及文章作者不為此承擔任何責任。
雷神眾測擁有對此文章的修改和解釋權。如欲轉載或傳播此文章,必須保證此文章的完整性,包括版權聲明等全部內容。未經雷神眾測允許,不得任意修改或者增減此文章內容,不得以任何方式將其用于商業目的。
分子實驗室 https://molecule-labs.com/
前言
相關說明及聲明:
文章原文為K A, Monnappa. 2018年發表的《Learning Malware Analysis》,本文的相關內容均為相關內容翻譯及實踐記錄,僅為學術交流使用,如要引用請做原文引用如下所示:
[序號]K A, Monnappa. Learning Malware Analysis[M]. 2018.06. Published by Packt Publishing Ltd. Livery Place 35 Livery Street Birmingham B3 2PB, UK.
使用IDA反匯編
3. 反編譯windows API
惡意軟件通常使用windows API函數影響操作系統(例如文件系統、進程、內存以及網絡配置等)。如第二章靜態分析和動態分析部分,windows擴展主要依賴文件DLL動態連接庫文件。可執行程序的引用和調用來自于大量DLL中的提供不同功能的API。為了調用這些dll文件,需要先將其加載到內存中,然后調用API函數。檢查一個惡意樣本的dll引用情況可以指導我們分析其功能和能力。下面的表格展示了部分常見的DLL以及其執行功能:

3.1 弄清楚Windows API
為了展示病毒程序如何使用windows API并且幫助你了解關于一個API更多的信息。以一個病毒樣本為例。加載樣本到IDA,在引用窗口展示出的相關windows API函數里,檢查函數在windows引用情況。

無論什么時候,在遇到windows API 函數的時候,可以通過微軟的開發者MSDN中搜索或者在谷歌中搜索,https://msdn.microsoft.com/。MSDN文檔對于API函數進行了相關描述,如函數參數、參數類型、返回值等。這里取Creat or open file 作為舉例。
如 https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx 所示。
通過文檔可以知道這個函數的功能為創建和打開文件。第一個參數(lpfilename),用于記錄文件名稱。第二個參數(dwdesiredaccess),說明需要的權限如讀或血的權限,第5個參數也是對文件創建和打開一個已經存在的文件。
HANDLE CreateFileA( LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
Windows API使用匈牙利語命名變量。在這個語法中,變量前綴增加數據種類,這個有助于我們了解給數據種類。如第二個參數dwdesiredaccess,dw的前綴代表dword 32 位無符號整數。在win32 API支持的不同數據類型如(https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx)。下面的表格未一些數據類型:


與數據類型和參數不同,之前的函數樣本包括注釋,例如_in_和_out_,描述了函數使用的參數和返回的值。_in_表示輸入參數,調用必須通過提供參數給函數才能執行函數。_in_opt表示可選的輸入參數(可以為null)。_out_表示輸出的參數;表示函數將會輸出參數作為返回值。這個特性對于了解函數調用后是否從存儲中讀取任何數據到輸出函數很有幫助。_inout_對象可以讓我們分辨函數參數和函數的輸出。
在交叉參考中我們可以看到API調用情況,通過查閱相關API手冊,我們可以知道,相關API的輸入和輸出參數。以createfile為例,通過查看函數的相關的兩個函數,起始地址如下:


雙擊第一個參數,調轉到代碼反匯編窗口對應位置。并且高亮顯示。通過分散,IDA提供了一個叫做快速識別庫的技術(FLIRT),包括圖像匹配算法用于確定函數函數是庫函數還是一個引用函數(從dll引入的函數)。在這個例子中IDA能夠識別引入的分散的函數,并且將其命名為CreateFileA。IDA的分辨引用函數和庫函數的能力非常有用,因為當你分析惡意樣本的時候,不會去浪費時間分辨是引用的函數還是庫函數。IDA還會為參數添加參數的名字作為注釋,標記出Windows API函數調用的對應的參數的名稱。
.text:00401708 mov dword ptr [esp+18h], 0 ; hTemplateFile.text:00401710 mov dword ptr [esp+14h], 80h ; dwFlagsAndAttributes.text:00401718 mov dword ptr [esp+10h], 3 ; dwCreationDisposition.text:00401720 mov dword ptr [esp+0Ch], 0 ; lpSecurityAttributes.text:00401728 mov dword ptr [esp+8], 3 ; dwShareMode.text:00401730 mov dword ptr [esp+4], 80000000h ; dwDesiredAccess.text:00401738 mov dword ptr [esp], offset FileName ; lpFileName.text:0040173F call ds:CreateFileA
第一個參數表示需要創建的文件名lpFileName。第二個參數dwDesiredAccess內容80000000h,通過https://docs.microsoft.com/en-us/windows/win32/secauthz/access-mask-format,可以看到對應的是generic_read權限,這一部分應該在后面的針對widnows的API的詳細解讀中進一步細化。第5個參數值為3,通過https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea,可以知道代表OPEN_EXISTING,只有當其退出的時候打開文件或設備。

IDA的另一個特性是列出使用象征名標記Windows API,或C標準庫函數。例如在80000000h可以通過右鍵值,選擇使用標準象征內容參數,標記內容;這個操作將會出現一個窗口展示所有有關選擇的值的象征名字。你需要選擇一個適當的標志名稱這里就是Generic_read。用相同的方式,你可以替換掉第五個參數內容3,為象征名稱,OPEN_EXISTING;

在使用象征名替換了內容之后,反匯編窗口列被轉化成下圖所示內容。代碼變得更加可讀。在函數調用之后,句柄到文件(可以在EAX寄存器中找到)被返回。通過函數操作文件還可以通過其他API來實現,例如readfile()或者writefile(),也可以實現類似的效果:

3.1.1 ANSI和Unicode API函數
windows支持兩個相似的API設置:一個是對于ANSI字符,另一個是Unicode字符。很多函數使用一個字符作為參數,在參數的名字后面包含A或者W。例如CreateFileA。換句話說,API名稱的尾部,可以讓你分辨通過函數的字符的種類(ANSI或Unicode)。以上面的CreateFileA為例,A表示函數使用一個ANSI字符作為輸入。相應的CreateFileW則是表示函數使用一個Unicode字符作為輸入。在惡意軟件分析的過程中,當你看到一個函數名為CreateFileA或CreateFileW形式,可以刪掉尾字母A或W,然后在MSDN中搜索函數文檔。
3.1.2 執行API函數
你可能會遇到很多名字帶有Ex后綴的函數,例如RegCreateKeyEx(擴展RegCreateKey的變體)。當Microsoft升級一個與舊函數矛盾的函數的時候,升級的函數命名在原函數名的基礎上增加Ex。
3.2 32位和64位Windows API對比
讓我們看一個32位惡意樣本去了解惡意樣本如何運用大量API函數去影響操作系統的,讓我們嘗試了解如何反匯編代碼,去了解惡意程序的活動。在接下來的反匯編輸出中,32位的惡意樣本調用了RegOpenKeyEx API開啟了一個句柄執行run注冊表的值。當我們執行32位惡意樣本的時候,所有regOpenKeyEx的API參數被壓到棧上。
相關的文檔可以在 https://msdn.microsoft.com/en-us/library/windows/desktop/ms724897(v=vs.85).aspx 找到。
輸出參數phkResult是一個變量的指針(輸出的參數由**out**注釋指出)在函數調用后,指向打開注冊表值的句柄。這里可以注意到,phkResult的地址是從ecx寄存器復制過去的,這個地址是作為RegOpenKeyEx API的第5個參數錄入的。
lea ecx, [esp+7E8h+phkResult] ?push ecx ? ; phkResultpush 20006h ; samDesiredpush 0 ; ulOptionspush offset aSoftwareMicros ;Software\Microsoft\Windows\CurrentVersion\Runpush HKEY_CURRENT_USER ; hKeycall ds:RegOpenKeyExW
在惡意軟件通過調用RegOpenKeyEx打開run注冊值后,返回的句柄(在phkResult變量存儲)被移動到ecx寄存器中,并且作為RegSetValueExW的第一個參數傳遞。從MSDN關于這個API的文檔中,可以發現使用RegSetValueEx API設置一個變量到run注冊表的值中(持久化)。變量通過的第二個參數設置,system字符。對應的內容可以通過第五個參數的值去確認。從前面的描述中,可以確定eax保持由pszPath的地址的值。pszPath變量與在運行時的相關內容相關;因此通過查看代碼,很難判斷數據是病毒添加到注冊表里的(你可以通過調試病毒樣本確認)。但是在這點,通過靜態分析(反匯編),你可以確定病毒添加了一個入口到注冊表中作為持久化的方式:
mov ecx, [esp+7E8h+phkResult] ?sub eax, edxsar eax, 1lea edx, ds:4[eax*4]push edx ; cbDatalea eax, [esp+7ECh+pszPath] ?push eax ? ; lpDatapush REG_SZ ; dwTypepush 0 ; Reservedpush offset ValueName ; "System" ?push ecx ? ; hKeycall ds:RegSetValueExW
在添加了一個入口到注冊表中之后,病毒通過在句柄獲取值之前(存有phkResult變量)關閉句柄到注冊表值,如下所示:
mov edx, [esp+7E8h+phkResult]push edx ; hKeycall esi ; RegCloseKey
之前的例子展示了惡意樣本如何使用多個windows API添加一個入口到注冊表中,該注冊遍能夠在計算機重啟的時候自動運行。你還可以看到,惡意樣本如何獲得一個對象的句柄,并分享句柄到其他API函數執行其他行為。
當你在看從64位病毒程序反匯編輸出的函數的時候,可能會略顯不同,這是由于參數通過64位架構。接下的一個64位樣本調用CreateFile函數。在64位架構下,在寄存器中前4個參數被使用(rcx,rdx,r8和r9),并且剩余的參數被放置在寄存器中。在接下來的反匯編中,注意到第一個參數是如何通過rcx寄存器,第二個參數在edx寄存器中,第三個參數在r8,第四個在r9寄存器中。新增的參數被放置在棧中(注意這里沒有push指令),這里使用mov指令。注意IDA如何識別參數柄添加注釋到指令旁邊的。函數的返回值(到文件的句柄)從rax寄存器中被移動到rsi寄存器中:
xor r9d, r9d ? ; lpSecurityAttributeslea rcx, [rsp+3B8h+FileName] ? ; lpFileNamelea r8d, [r9+1] ? ; dwShareModemov edx, 40000000h ? ; dwDesiredAccessmov [rsp+3B8h+dwFlagsAndAttributes], 80h ? ; dwFlagsAndAttributesmov [rsp+3B8h+dwCreationDisposition], 2 ? ; lpOverlappedcall cs:CreateFileWmov rsi, rax ?
下面的反匯編為WriteFile API的,注意文件句柄在API調用之前被復制到rsi寄存器,現在通過writeFile函數第一個參數移動到rex寄存器。相同的方式,另一個參數被傳入寄存器進入堆,如下所示:
and qword ptr [rsp+3B8h+dwCreationDisposition], 0lea r9,[rsp+3B8h+NumberOfBytesWritten] ; lpNumberOfBytesWrittenlea rdx, [rsp+3B8h+Buffer] ; lpBuffermov r8d, 146h ; nNumberOfBytesToWritemov rcx, rsi ? ; hFilecall cs:WriteFile From the preceding example,
從之前的案例可以看到,病毒程序創建一個文件和寫入內容到文件,但是當你查找靜態代碼的時候,并不那么清楚的可以看出惡意軟件創建了什么文件或者寫入了什么內容到文件中。例如,想要知道軟件創建的文件名,你需要檢查ipFileName(傳入CreateFile的一個參數)地址的內容;但ipFileName變量并非硬編碼,并且只有當程序運行的時候才存在。
4. 使用IDA補丁二進制程序
當完成惡意程序分析,你想要修改二進制程序改變其內部工作原理或者逆向邏輯以便個人使用。你可以使用選擇Edit/Patch program菜單。需要注意的是,當你使用這個菜單堆二進制進行修改的時候,你并不會直接對二進制文件本身進行修改;這個修改只會在IDA數據庫中進行操作。如果需要應用修改到原始的二進制文件的話,你需要使用Apply patches to input file:

4.1 補丁程序字節
考慮到代碼通過32位惡意軟件dll執行(RDSS rootkit),通過檢測可以確保其運行與spoolsv.exe下面。這里的檢測會使用字符對比功能;如果自負對比失敗,則代碼跳轉到函數結束,并且回到函數調用。特殊的,這個dll的惡意行為只發生在當其被spoolsv.exe調用的時候;除此之外,其都無返回。
10001BF2 push offset aSpoolsv_exe ; "spoolsv.exe"10001BF7 push edi ; char *10001BF8 call _stricmp ? 10001BFD test eax, eax10001BFF pop ecx10001C00 pop ecx10001C01 jnz loc_10001CF9 [REMOVED] 10001CF9 loc_10001CF9: ? ; CODE XREF: DllEntryPoint+10j10001CF9 xor eax, eax10001CFB pop edi10001CFC pop esi10001CFD pop ebx10001CFE leave10001CFF retn 0Ch K A, Monnappa. Learning Malware Analysis: Explore the concepts, tools, and techniques to analyze and investigate Windows malware (p. 189). Packt Publishing. Kindle 版本.
假定你想要惡意dll執行惡意行為在任一程序下,例如執行在notepad.exe下面。你可以改變硬編碼的字符從spoolsv.exe到notepad.exe。為了實現這個,通過點擊aSpoolsv_exe定位硬編碼地址,在下面的內容中展示:

現在,將鼠標放在變量名上(aSpoolsv_exe)。此時,hex視圖窗口中將會同步展示地址信息。在hex-View-1標簽展示的hex和ascii導出內存地址。補丁字節內容,選擇Edit/patch program/change byte;將會如下圖所示帶來補丁字節日志。你可以修改原始的二進制字節通過輸入一個新的二進制值到欄目中。Address字段表示游標位置的虛擬地址,File offset字段指定二進制文件中字節所在的文件偏移量。
Original value字段顯示當前地址的原始字節;即使你修改了這些值,該字段中的值也不會改變:

您所做的修改將應用于IDA數據庫;要將更改應用到原始可執行文件,可以選擇“Edit | Patch program | apply patches to the input file”。下面的屏幕截圖顯示了“應用補丁到輸入文件”對話框。當您點擊OK時,更改將應用到原始文件;您可以通過檢查“創建備份”選項來保存原始文件的備份;在這種情況下,它會以.bak擴展名保存你的原始文件:

前面的示例演示了修補字節;以同樣的方式,您可以通過選擇Edit | patch program | Change word來一次打一個單詞(2字節)的補丁。您還可以從十六進制視圖窗口中修改字節,通過右鍵單擊一個字節并選擇Edit (F2),您可以通過再次右鍵單擊并選擇apply changes (F2)應用更改。
4.2 補丁命令
在之前的例子中,TDSS rootkit DLL執行了一個檢查判斷程序是否在spoolsv.exe下面運行。可以通過修改程序中的二進制信息將spoolsv.exe改為notepad.exe。可以通過逆向邏輯判斷DLL可以運行在任意進程下面。為了實現這個想法,我們可以修改jnz命令使其變為jz,通過選擇Edit|patch program|Assemble,如下所示。我們將要逆向邏輯并且讓程序運行在spoolsv.exe下時,程序不會表現任何惡意行為表現,而運行在非spoolsv.exe時將會表現出惡意行為。在修改了命令之后,點擊OK,命令將會被匯編,但是對話仍然保持打開狀態,提示你在下一個地址匯編下一個命令。如果沒有其他需要會變的可以點擊取消結束。為了將修改保存到原始文件中,選擇Edit|patch program|apply patches 將修改保存到文件中。

當你給任何命令打補丁的時候,小心需要確保所有的的命令的結合是正確的;除此之外,補丁的程序可能會出現無法預料的行為。如果新的命令比原始命令短的話,你可以使用nop命令保持長度完整。如果你在匯編一個新的命令超出原始的命令,IDA將會覆蓋原始程序的后面的命令,這個行為可能并非我們希望如此的。