0x00 样本说明
分析样本是被0b500d25f645c0b25532c1e3c9741667的样本感染得到。感染前的文件是Tcpview.exe,一款windows网络连接查看工具。
感染前后文件对比图示如下:
|
感染前 |
感染后 |
文件大小 |
106496 字节 |
818688 字节 |
文件md5 |
4B6B70F4A199CF3EAC1554B08804BC4F |
041D85ECABF5F2E8366C5E0FDCB705F3 |
文件图标 |
分析样本的报毒情况如下:
杀软类型 |
病毒名 |
nod32 |
a variant of Win32/Virlock.J virus |
kav |
Virus.Win32.PolyRansom.f |
0x01 入口解密部分
该部分代码实际功能是将密文代码进行解密,而后对解密后的代码进行调用。详细的情况如下:
入口地址00401000下的代码是一些冗余代码,而后从地址0040101C下的jmp指令跳转到地址:sub_4C324E,该地址下是一段解密代码.代码被解密后保存到设定好的地址0x4C8000下,然后通过retn指令返回到解密后的代码处0x4C8000开始执行。解密代码具体情况如下:
密文起始地址 |
0x4BFA4E |
解密后数据存放地址 |
0x4C8000 |
解密算法 |
xor |
解密key |
0x7F |
解密长度 |
0x380 |
0x02 虚拟机执行代码
该部分代码实际功能和0x01部分的代码功能相同,也是将密文代码进行解密,而后对解密后的代码进行调用。详细情况如下:
从地址0x4C8000处开始,是一段功能为解密的代码,只不过该部分代码,解密方式并不再通过简单的xor指令进行解密。而是采用了通过一个小的虚拟机来执行一系列的功能代码。被虚拟机执行的代码功能是对指定地址为起始的代码进行解密,解密方式为xor。而后对解密后的代码进行调用。
虚拟机在执行代码的过程中主要包含三个关键地址,分别是执行的指令序列表地址;功能指令结构地址;虚拟机指令执行框架代码地址。三个地址下的数据都被加密,并采用不同的密钥。但采用的加密方式都是简单的xor加密。
1、 指令执行序列编号密文存储结构地址
每一个一个指令序列的长度是word,具体解密信息如下表所示。
密文起始地址 |
0x4C3E4E |
密文结束地址 |
0x4C4248 |
解密算法 |
xor |
解密key |
0xCBF8 |
通过上面的表格可以看出,该地址下指令数量共有(0x4C4248-0x4C3E4E)/2=0x1FD(509)条。但由于代码在执行的时候会有同一条指令的多次执行,实际执行指令数量远大于这个数值,经过分析实际执行的指令条目是1693245条。具体如何重复执行同一条指令可以参考指令功能代码存储部分中的相关说明。完整的解密后的指令执行序列编号表参见附件“指令执行序列编号表.txt”。下面是指令执行序列编号表起始部分。
0000 0100 0200 0300 0400 0500 0600 0700 0800 D500 0900 0A00 D600 0B00 D500 …… |
由于计算机数据存储采用大尾模式,上面编号,实际为:0000,0001,0002,0003,0004,0005,0006,0007,0008,00d5,0009,000a,00d6,000b,00d5……。
2、 虚拟机中执行的指令执行环境恢复保存代码密文地址
该部分代码下文称之为指令调度框架,实际其为一个小虚拟机的指令执行环境。具体代码解密情况如下图所示。
密文起始地址 |
0x4C524E |
明文起始地址 |
0x4C8600 |
解密算法 |
xor |
解密密钥 |
0xED |
解密长度 |
0x6E |
解密后明文调度框架代码如下:
004C8600 896C24 08 mov dword ptr [esp+8], ebp 004C8604 8BEC mov ebp, esp 004C8606 90 nop 004C8607 837D 0C 00 cmp dword ptr [ebp+C], 0 004C860B 74 03 je short 004C8610 004C860D 8B65 0C mov esp, dword ptr [ebp+C] 004C8610 90 nop 004C8611 81C5 792740B6 add ebp, B6402779 004C8617 81C5 17DBBF49 add ebp, 49BFDB17 004C861D 81C4 0E2740B6 add esp, B640270E 004C8623 81C4 E2D9BF49 add esp, 49BFD9E2 004C8629 5E pop esi 004C862A 90 nop 004C862B 5F pop edi 004C862C 5A pop edx 004C862D 90 nop 004C862E 59 pop ecx 004C862F 5B pop ebx 004C8630 90 nop 004C8631 58 pop eax 004C8632 90 nop 004C8633 9D popfd 004C8634 90 nop 004C8635 90 nop 004C8636 90 nop 004C8637 90 nop 004C8638 90 nop 004C8639 90 nop 004C863A 90 nop 004C863B 90 nop 004C863C 90 nop 004C863D 90 nop 004C863E 9C pushfd 004C863F 50 push eax 004C8640 90 nop 004C8641 53 push ebx 004C8642 51 push ecx 004C8643 90 nop 004C8644 52 push edx 004C8645 90 nop 004C8646 57 push edi 004C8647 56 push esi 004C8648 90 nop 004C8649 81EC FB3040B6 sub esp, B64030FB 004C864F 81EC F5CFBF49 sub esp, 49BFCFF5 004C8655 81ED 8B3040B6 sub ebp, B640308B 004C865B 81ED 05D2BF49 sub ebp, 49BFD205 004C8661 8965 0C mov dword ptr [ebp+C], esp 004C8664 90 nop 004C8665 8BE5 mov esp, ebp 004C8667 8B6C24 08 mov ebp, dword ptr [esp+8] 004C866B 90 nop 004C866C C3 retn 004C866D 90 nop |
指令调度框架代码中,分为3个部分。包括环境初始化部分,指令代码部分和环境保存部分。环境初始化部分和环境保存部分主要是对指令代码执行环境的栈恢复和栈还原,其中初始化部分中对第一条指令执行,未进行esp恢复。指令代码部分是上图中的红色部分[地址0x004C8634处],预留的地址长度是单条汇编指令最大长度0xA。
3、 指令功能代码存储地址
该部分代码或可称之为指令handle,是一个由指令结构体组成的数组。具体的解密信息如下表。
密文起始地址 |
0x4C164E |
密文数据结构 |
ins_struct{db flag; db length; db[length] data; } |
解密算法 |
xor |
解密密钥 |
0x9C |
所有的指令功能代码,是按照上面描述的指令结构,按照编号从大到小顺序排列。即密文起始地址下实际是一个结构体:ins_struct{n=509}。完整的解密后的指令结构体明文表参见附件“指令编号和功能代码数据关系表.txt”;完整的解密后指令序号和汇编指令代码对应关系参见附件“指令序号汇编指令对照表.TXT”。部分解密后数据如下表:
指令序号 |
指令结构体数据 |
汇编指令代码 |
00 |
00 03 83 EC 1C |
sub esp, 1C |
01 |
00 08 C7 44 24 14 00 00 00 00 |
mov dword ptr [esp+14], 0 |
02 |
00 08 C7 44 24 18 00 00 00 00 |
mov dword ptr [esp+18], 0 |
03 |
00 07 C7 04 24 00 00 00 00 |
mov dword ptr [esp], 0 |
04 |
00 08 C7 44 24 04 00 00 00 00 |
mov dword ptr [esp+4], 0 |
05 |
00 08 C7 44 24 08 00 00 00 00 |
mov dword ptr [esp+8], 0 |
06 |
00 08 C7 44 24 0C 00 00 00 00 |
mov dword ptr [esp+C], 0 |
07 |
00 04 8B 44 24 14 |
mov eax, dword ptr [esp+14] |
08 |
00 05 83 7C 24 14 00 |
cmp dword ptr [esp+14], 0 |
09 |
00 05 BB 00 00 00 00 |
mov ebx, 0 |
0A |
00 03 83 FB 01 |
cmp ebx, 1 |
0B |
00 05 83 7C 24 14 01 |
cmp dword ptr [esp+14], 1 |
…… |
…… |
…… |
D5 |
01 04 04 00 00 00 |
向后跳转4条指令 |
D6 |
01 04 4B 00 00 00 |
向后跳转0x4b条指令 |
D7 |
01 04 4A 00 00 00 |
向后跳转0x4a条指令 |
…… |
…… |
…… |
FE |
01 04 EA FF FF FF |
向前跳转0xea条指令 |
FF |
01 04 F8 FF FF FF |
向前跳转0x8条指令 |
结构中的flag域是一个指令标示符,结构中的data数据部分是一个公用体。Flag标示符用来标识data数据部分的数据类型。当flag值为0时,结构中的data数据部分是指令代码,将代码解密后写入到指令调度框架中执行;如果flag的值为1,是下一条指令编号相对当前编号的偏移值,即不执行代码而是跳转到下定位后的编号代码进行执行。需要注意的是,当flag为1时,只有当指令调度框架中的zf标志寄存器被置0时,才有实际跳转意义,否则按无效指令执行,直接执行下一条代码。详细汇编代码参考下图:
.data:004C802B cmp [esp+arg_4], 0 .data:004C8030 nop .data:004C8031 jz loc_4C8196 ;判断指令调度框架中保存的esp值是否为0,即判断是否为第一条指令,由于第一条指令部的flag为0,该条件无法满足,永远不会跳转。 .data:004C8037 jmp loc_4C825F .data:004C825F mov eax, esp .data:004C8261 nop .data:004C8262 sub eax, [esp+arg_4] .data:004C8266 sub eax, 4 .data:004C8269 nop .data:004C826A jmp loc_4C8191 .data:004C8191 mov ecx, 1 .data:004C8196 add esp, 104h .data:004C819C jmp loc_4C803C .data:004C803C cmp ecx, 1 .data:004C803F jnz loc_4C8047 .data:004C8045 sub esp, eax .data:004C8047 popf .data:004C8048 nop .data:004C8049 jnz loc_4C8081 ; 弹出指令调度框架中的标志寄存器,判断zf寄存器的值。如果zf_flag=0,解密计算跳转偏移数据,否则直接给跳转偏移值设置为0,而后执行下一条指令。跳转偏移值存放于ebx中。 .data:004C804F jmp loc_4C8078 .data:004C8078 and ebx, 0 .data:004C807B nop .data:004C807C jmp loc_4C8155 ; 此跳转直接跳过指令跳转功能的代码。跳转到4c8155。 .data:004C8081 mov ebx, [edi+2] .data:004C8084 nop .data:004C8085 mov dl, 9Ch .data:004C8087 xor bl, dl .data:004C8089 nop .data:004C808A jmp loc_4C8213 .data:004C8213 rol ebx, 8 .data:004C8216 nop .data:004C8217 xor bl, dl .data:004C8219 rol ebx, 8 .data:004C821C xor bl, dl .data:004C821E rol ebx, 8 .data:004C8221 nop .data:004C8222 jmp loc_4C814C .data:004C814C xor bl, dl .data:004C814E nop .data:004C814F rol ebx, 8 .data:004C8152 nop .data:004C8153 shl ebx, 1 ; 从004C8081地址到此处,功能是解密指令结构中的data数据,并乘2,用以作跳转偏移。 ;解密出来的偏移量是指令个数,每个指令长度是2byte,所以地址偏移需要乘2。 .data:004C8155 sub esp, 108h .data:004C815B nop .data:004C815C jmp loc_4C81A1 …… |
完整的指令执行序列顺序表参见附件“条件断点_log.txt”。该文件是通过od加载分析文件后设置条件记录断点,产生的记录日志。条件断点的设置方式是在地址0x004C80D4下设置记录条件“eax==ecx”,输出eax的值;在地址0x004C80B0下设置记录条件“eax==eax”,输出eax的值。
其中日志中的eax的值是执行指令序号,如果指令序号大于0xd5,该指令是跳转指令,只是用来记录,不是具体执行代码。完整的代码功能是将数据解密到0x401400地址处,其间会有对api函数调用。由于这部分代码篇幅过长,未作整理。
通过条件记录断点的记录结果,被执行的最后一条指令是call edi,指令编号是d4。其调用的地址是0x00401400。如要跳过虚拟机执行代码部分直接到对解密后代码的调用,可以通过直接在地址0x004C80B0下设置条件断点:“eax==0xd4”。断住后,执行到CALL地址内部的call edi处,即可跳转到解密后的代码地址0x00401400处。
0x03 病毒功能代码
病毒的所有功能代码执行前都是加密的,需要先进行解密然后再执行,且所有的病毒功能代码的对自身函数的调用,也都是加密的,也需要先解密进行执行。具体可参见下图。
加/解密的过程采用的算法是一个对称加密算法。都是先调用一个函数(fun_calc)计算密钥,然后再调用一个函数(fun_decode)对数据进行解/加密。由于计算密钥使用的数据和解/加密函数的地址都是硬编码写在函数内部的,所以不同功能函数的fun_calc、fun_decode函数虽然算法相同,但并不是调用的同一段函数。
从00401000处开始所有代码执行都是通过上面图示的情况进行执行的。代码量较大,不能静态分析,只能动态逐函数解密后分析。给全面分析带来较大的困难,下面的图示只对还原感染前文件相关的代码层次作了介绍。
loc_00401400 sub_00401522 sub_004014C8 //decode_00401576 loc_00401576[virus_fun] sub_00401674 sub_00401737 sub_004017C8 //decode_004017E5 loc_004017E5[virus_fun]//decode_00401914 + 792 //decode_data contain fun_00401914 and fun_00401C61 sub_00401737 sub_004017C8 sub_00401914 sub_004019A7 sub_00401981 //decode_00401A7E loc_00401A7E[virus_fun]//get_3functions_addresses sub_004019A7 sub_00401981 sub_00401674 sub_00401737 sub_004017C8 //decode_004017E5 loc_004017E5[virus_fun]//decode_ds:[004018C8]=004A4A30 //decode_data is original_file data sub_00401737 sub_004017C8 sub_00401C61 sub_00401D60 sub_00401CD7 //decode_00401DAD loc_00401DAD[virus_fun] //call two functions, get_system_version sub_00401D60 sub_00401CD7 sub_00401674 sub_00401737 sub_004017C8 //decode_004020B6 loc_004017E5[virus_fun]//decode_004020B6 + 73 sub_00401737 sub_004017C8 sub_00401522 sub_004014C8
loc_004020B6[virus_fun] sub_004021C2 sub_0040219C //decode_00402204 loc_00402204[virus_fun] |
上图中的所有包含地址的sub_xxxxxxxx标号,都代表一个call调用;所有包含地址的loc_xxxxxxxx标号,都代表一段病毒功能代码的调用。sub_xxxxxxxx调用主要是对loc_xxxxxxxx代码的解密,而loc_xxxxxxxx代码主要是关键的代码执行。
从上图中可以发现,当执行到loc_004017E5代码结束时,已经完成了对原始文件数据的解密。在这里直接读出数据即可完成样本的修复。
后续所有的功能函数分析,待以后继续添加。
附件地址:http://files.cnblogs.com/files/Mikhail/%E9%99%84%E4%BB%B6.rar