这里把对krap样本解密和分析过程与大家一块交流一下。
说一下流程,否则容易乱
1 解密-(pack部分)--> 2二次解密(pack部分)--->3内存中解密(pack部分)--->4替换自身模块基址映像(pack部分)---->5注入explorer.exe(病毒自身任务完成,被删除。注入explorer也仅是为了再次注入svchost,完成后,explorer中的线程退出)----> 6 注入svchost(在这个傀儡进程里面干活是这个病毒真正的目的,下载download)
// --------开始了 -----------------------
1绕过虚拟机的解密头
Packed.Win32.Krap 开头是个略有变化的解密头部,变化的是解密最开始绕过虚拟机的技巧。
004010AB 55 push ebp
004010AC 8D4D E8 lea ecx,dword ptr ss:[ebp-18]
004010AF 8365 E8 00 and dword ptr ss:[ebp-18],0
004010B3 51 push ecx
004010B4 51 push ecx
004010B5 6A 00 push 0
004010B7 FF15 18314000 call dword ptr ds:[<&COMCTL32.LBItemFromPt>; COMCTL32.LBItemFromPt
004010BD FF15 F8304000 call dword ptr ds:[<&WS2_32.WSAGetLastError>; WS2_32.WSAGetLastError
004010C3 3D 78050000 cmp eax,578 // 验证网络函数的LastError返回值
.--004010C8 0F85 15010000 jnz __krap_.004011E3 // 没有仿真该处直接被引到退出流程
|
| ........
|
| //退出进程
.->004011E3 FF15 04314000 call dword ptr ds:[<&KERNEL32.TerminateP>; kernel32.TerminateProcess
004011E9 8BE5 mov esp,ebp
004011EB 5D pop ebp
004011EC C3 retn
在不同的被打包的程序中,call LBItemFromPt 这处调用不断变化。例如另一个变种(Win32.LdPinch.arqy)
使用了GetEffectiveClientRect这个函数。
WSAGetLastError比较有意思的地方是,如果上一次的调用是不同的API时,会有不同的返回值结果。
比如调用VirtualAlloc返回值是0x57,调用GetFileVerSionInfoSizeA返回值是0x714。AVer的WSAGetLastError如果
是简单函数仿真的话将被绕过。
接着是解密前的配置工作,简单说一下思路。
就是把这个要解密的次数,大小等信息先解密出来。以后的解密工作是根据这些配置进行的。
我逆出来的解密配置结构大致如下,解密配置和解密后面的数据都是用的一个算法,单字节解密。
typedef struct _decrypt_info
{
unsinged int encrypt_buff_off; 被加密数据的偏移
unsinged int encrypt_buff_len; 被加密数据的大小
}decrypt_info;
typedef struct _virus_config_info
{
unsigned int alloc_mem_len1; 分配解密内存长度1
unsigned int alloc_mem_len2; 分配解密内存长度2
int decryp_count ; 需要解密的次数
int unknown;
decrypt_info di[1] ; 解密需要的信息,需要解密多少次,就有decrypt_info[decryp_count]长度
}virus_config_info;
几个重要的数据
ebp-0x0c --- 记录最后key的偏移
ebp-0x10 --- 控制解密的次数,
ebp-0x28 --- key的首地址
ebp-0x1c --- 累加解密长度
ebp-0x18 --- 本次解密长度
004010CE 29FF sub edi,edi
004010D0 897D FC mov dword ptr ss:[ebp-4],edi
004010D3 6A 00 push 0
004010D5 9D popfd
004010D6 81D7 0F734000 adc edi,__krap_.0040730F ; key 首地址+偏移
004010DC 037D E8 add edi,dword ptr ss:[ebp-18]
004010DF 897D F4 mov dword ptr ss:[ebp-C],edi ; 记录最后key的偏移
004010E2 89E9 mov ecx,ebp
004010E4 BE A8010000 mov esi,1A8
004010E9 F7DE neg esi
004010EB 01F1 add ecx,esi
004010ED 894D F0 mov dword ptr ss:[ebp-10],ecx ; 配置段的首地址
004010F0 BE 0E734000 mov esi,__krap_.0040730E
004010F5 8975 D8 mov dword ptr ss:[ebp-28],esi ; key偏移减1
004010F8 8B4D F0 mov ecx,dword ptr ss:[ebp-10]
004010FB 83E9 F0 sub ecx,-10
004010FE 894D E4 mov dword ptr ss:[ebp-1C],ecx
00401101 816D FC 2800000>sub dword ptr ss:[ebp-4],28
00401108 F755 FC not dword ptr ss:[ebp-4]
0040110B 8345 FC 01 add dword ptr ss:[ebp-4],1 ; 得到第一次在栈中解密的长度
0040110F FF75 D8 push dword ptr ss:[ebp-28]
00401112 FF75 FC push dword ptr ss:[ebp-4]
00401115 FF75 F4 push dword ptr ss:[ebp-C]
00401118 8DBD 58FEFFFF lea edi,dword ptr ss:[ebp-1A8]
0040111E 57 push edi
// 这里对栈中数据进行解密,就是解密配置信息
0040111F E8 DCFEFFFF call __krap_.00401000
这个版本的Krap是分3段进行解密。
解密前的堆栈:
0012FE18 FA034B1C
0012FE1C 805B639F
0012FE20 00000001
0012FE24 E56C6946
0012FE28 8128E5B0
0012FE2C 8128E560
0012FE30 00000000
0012FE34 FF29F178
0012FE38 FF29F178
0012FE3C FA034B34
0012FE40 805B056B
解密后的堆栈:
0012FE18 000013F0 ------>开始加密的代码长度 --、——————长度相加,刚好是解密总长度
0012FE1C 000026C0 ------>文件内部加密的PE文件长度 --/ ------>多重加密,第一次解密后仍然是加密的
0012FE20 00000003 ------>控制解密次数
0012FE24 00001A00 ------>解密及获得API地址后,分配内存的长度
0012FE28 00004000 ------>加密数据的起始偏移,在data节中
0012FE2C 0000330E ------>第一次解密的长度
0012FE30 00002000 ------>加密数据的起始偏移,在rsrc节中
0012FE34 00000096 ------>第二次解密的长度
0012FE38 00003248 ------>加密数据的起始偏移,在rdata节中
0012FE3C 0000070C ------>第三次解密的长度
解密的函数比较简单,我也不知道是个啥加密算法,不多说了配合F5看一些的情况吧。
int __stdcall decode_buff(char *alloc_buf/*解密后的数据buff*/, char *encode_buf/*加密数据*/, int size, char *key)
{
int result;
int v5;
char v6;
int _ch;
signed int eax_ret;
unsigned int index;
signed int v10;
signed int v11;
eax_ret = 0;
index = 0;
v10 = -1;
while ( index < size )
{
v6 = ~((unsigned __int8)~(unsigned __int8)*(_DWORD *)&encode_buf[index] | (unsigned __int8)~(_BYTE)v10);
_ch = *(_DWORD *)&key[eax_ret];
v11 = -1;
result = (int)&alloc_buf[index];
alloc_buf[index] = 0;
alloc_buf[index] -= ~(_BYTE)_ch + 1 + v6;
alloc_buf[index] = -alloc_buf[index];
v5 = eax_ret++ + 1;
if ( eax_ret == 1 )
eax_ret = 0;
v11 = v5;
if ( (unsigned int)eax_ret > 0x488000 )
break;
index -= v10;
}
return result;
}
接着分配内存进行第一次解密,同时将解密过程中的信息存放的堆栈中,
$-1AC > 00080000 -------->存放当前解密次数,用于和配置中的次数相比较
...
$-48 > 00080000
$-44 > 00000000
$-40 > 00001FE0
$-3C > FF20B000
$-38 > E1C60400
$-34 > 00000001
$-30 > 00000000
$-2C > 00000408
$-28 > 0040730E ------->加密的数据区
$-24 > 00003AB0 ------->分配的内存长度
$-20 > 00400000 ------->硬编码写入映像的基址例如0x400000
$-1C > 0012FE28 ------->存放的配置信息+0X10偏移的指针
$-18 > 00000000 ------->解密长度的总计
$-14 > 80616CDB ------->分配内存的偏移0x1960处,是解密后运行的入口点
$-10 > 0012FE18 ------->存放的配置信息
$-C > 0040730F
$-8 > FFFFFFFE ------->存放分配的内存的首地址
$-4 > 00000028
$ ==> > 0012FFF0
//第一次解密
00401124 8B5D F0 mov ebx,dword ptr ss:[ebp-10]
00401127 8B5B 04 mov ebx,dword ptr ds:[ebx+4] ; 从配置中取一次解密长度0x26c0
0040112A 8B75 F0 mov esi,dword ptr ss:[ebp-10]
0040112D 031E add ebx,dword ptr ds:[esi] ; 累加0x13f0
0040112F 895D DC mov dword ptr ss:[ebp-24],ebx
00401132 BB 40000000 mov ebx,40
00401137 53 push ebx ; 页属性,可执行,可读写
00401138 68 00300000 push 3000
0040113D FF75 DC push dword ptr ss:[ebp-24]
00401140 6A 00 push 0
00401142 FF15 00314000 call dword ptr ds:[<&KERNEL32.VirtualAlloc>] ; kernel32.VirtualAlloc
// ...
// 累加解密次数,和配置中的次数进行比
0040117E 8B79 08 mov edi,dword ptr ds:[ecx+8] ; 取配置中的比较次数
00401181 39FE cmp esi,edi ; 比较解密次数
00401183 73 2C jnb short __krap_.004011B1
00401185 FF75 D8 push dword ptr ss:[ebp-28] ; 密钥的首地址
00401188 8B4D E4 mov ecx,dword ptr ss:[ebp-1C]
0040118B FF71 04 push dword ptr ds:[ecx+4] ; 解密的长度
0040118E 8B45 E4 mov eax,dword ptr ss:[ebp-1C]
00401191 8B5D E0 mov ebx,dword ptr ss:[ebp-20] ; 基址0x400000
00401194 0318 add ebx,dword ptr ds:[eax]
00401196 53 push ebx ; 加密的数据
00401197 8B75 F8 mov esi,dword ptr ss:[ebp-8]
0040119A 0375 E8 add esi,dword ptr ss:[ebp-18]
0040119D 56 push esi
0040119E E8 5DFEFFFF call <__krap_.decode_buff>
004011A3 8B4D E4 mov ecx,dword ptr ss:[ebp-1C] ; ecx -> decrypt_info * 指针
004011A6 8B45 E8 mov eax,dword ptr ss:[ebp-18]
004011A9 0341 04 add eax,dword ptr ds:[ecx+4]
004011AC 8945 E8 mov dword ptr ss:[ebp-18],eax ; 累加解密的长度
004011AF ^ EB AE jmp short __krap_.0040115F
// 等于3次后解密完毕
004011BF 894D EC mov dword ptr ss:[ebp-14],ecx ; 解密后运行的入口点
004011C2 8B8D 64FEFFFF mov ecx,dword ptr ss:[ebp-19C]
004011C8 51 push ecx
004011C9 8B5D F0 mov ebx,dword ptr ss:[ebp-10]
004011CC 8B45 F8 mov eax,dword ptr ss:[ebp-8]
004011CF 0343 04 add eax,dword ptr ds:[ebx+4]
004011D2 50 push eax
004011D3 8B45 EC mov eax,dword ptr ss:[ebp-14]
004011D6 50 push eax
004011D7 68 000000B8 push B8000000
004011DC FF75 EC push dword ptr ss:[ebp-14] // 压入解密后的入口点
004011DF C2 0400 retn 4 // 跳向新的入口 00921690 |