谈谈vmp的还原
上一部分回顾:谈谈vmp的还原
0x00 前言
写之前就在想该以一个怎样的方式阐述这些东西,最终还是决定以逆推的方式来描述。
另外 先回答下问题:分析的是vmp的主程序,很早的版本
0x01 虚拟化后
测试的指令
68 34504200 PUSH OFFSET zf.??_C@_02MECO@?$CFd?$AA@ ; /format = "%d"
E8 13010000 CALL zf.scanf ; \scanf
83C4 08 ADD ESP,0x8
817D FC 33020000 CMP DWORD PTR SS:[EBP-0x4],0x233
75 09 JNZ SHORT zf.00401062
被用来vm的两条指令:
817DFC33020000 cmp dword ptr [ebp-04],00000233
7509 jnz 00401062
先看看是cmp被vm之后的情况,因为既然大S提到了这个,看了他的帖子,所以叫他大S
1.lodsd -0x233
push -0x233
stack_top fffffdcd
2.lodsb [index]
push _context[index] <===> push ebp
stack_top ebp
fffffdcd
3.lodsb [disp]
push disp
stack_top disp
ebp
fffffdcd
4.pop disp
add ebp,dis <===> get addr input
stack_top ebp+disp
fffffdcd
5.pop addr (input)
push input
stack_top input
fffffdcd
6.pop input
add [-0x233],input ... get a
stack_top fffffe48
pushfw
stack_top fe480296
7.push eflags
stack_top 02960216
00fffffe48
8.push stack
stack_top ebp
02960216
fffffe48
9.pop stack
push [stack]
stack_top 02160216
fe480296
10.and(not(eflags),not(eflags))
stack_top fde9fde9
stack_top 0296fde9
fffffe48
11.lodsw ~0x400
push ~0x400
stack_top FDE9FBFF
FE480296
11.and(not(eflags),not(~0x400)) = and(eflags,0x400)
stack_top 02160400
stack_top 02960000
fffffe48
12.pop result
add [pushfw],result
stack_top fe480296 <==> 0296+result
13.lodsb [index]
pop _context[index]
_context[index] == 0296
stack_top fffffe48
14.pop a
eax == a <==> fffffe48
stack_top ebp
15.push lodsw[esi]
push 0x11
stack_top FF48FFEE
0018
16.push [pushfw]
stack_top FFEE0296
0018FF48
17.push [pushfw]
stack_top 02960296
FF48FFEE
00000018
18.and(not(pushfw),not(pushfw))
stack_top FD69FD69
FF48FFEE
00000018
stack_top FFEEFD69
0018FF48
19.and(0x11,pushfw)
stack_top FF480010 <==> and(0x11,0x269)
0018
20.push pushfw
stack_top 00100296
0018FF48
21.lodb push 0x11
stack_top 02960011
FF480010
0018
22.and(not(pushfw),not(0x11))
stack_top 0010FD68
0018FF48
23.and(not(result),not(0x11))
stack_top FF480287
0018
24.lodb [index]
push _context[index] <===> push [pushfw]
stack_top 02870296
0018FF48
24.lodb [index]
push _context[index] <===> push [pushfw]
stack_top 02960296
FF480287
0018
25.and(296,296)
stack_top 0287FD69
0018FF48
26.lodw ~0x400
push ~0x400
stack_top FD69FBFF
FF480287
0018
27.and(0x400,0x296)
stack_top 02870000
0018FF48
28.pop result
add [esp],result
stack_top FF480287
00000018
29.lodsb [index]
pop _context[index] <==> result save to context
stack_top 0018FF48
28.lodsb [index]
push _context[index]
stack_top 00000000
0018FF48
以上就是cmp的vm流程
hex如下
0042FB21 6C CD FD CF 05 38 FC 69 l妄?8黫
0042FB29 C4 C1 B6 08 0C 8E 99 57 牧?.帣W
0042FB31 FF FB 99 3B 5D 08 08 57 麢;]W
0042FB39 EE FF B6 08 FA 08 99 99 ???櫃
0042FB41 E9 08 A0 11 99 99 B6 08 ??櫃?
0042FB49 B6 08 99 57 FF FB 99 3B ?橶麢;
0042FB51 5D 08 18 0C ].
0x10 原由
那么我们先来看看是如何得到这些hex
代码很简单
匹配就行了,那么我们来看看匹配表,注意到上一篇的handle_size == 1488 / 8
恩,vmp实现虚拟引擎的核心可以说就是这些虚拟规则。注意到我是用的这些,而不是186个,因为在写之前,我担心版本太老,所以又去逆了逆v2.12.3,发现框架太体差不多,不过代码量优化了很多。还有昨天看了看大Z哥的帖子,对里面有句话感触很深(它们的价值来自于本身的神秘面纱)。但毕竟吧,vmp也3.x,代码重构了,我想也可以谈一谈了。扯远了。
查看引用表的地方
在看看RandIndexArray的引用,来到这个地方
注意到这里可能是乱序之后的,那么如果说不乱序的话,这个表和vm_opcode的应该是一一对应的关系
举例:
定位到handler_table,
则有
Handle_471094:
LODS BYTE PTR DS:[ESI]
PUSH DWORD PTR DS:[EDI+EAX*4]
对应
01 02 02 00 00 00 00 00
如下:
Btw:不保证全对哈,最好看看,哈哈
接着我们看一看Vmp_GetVmHandleIndex的调用
ida的有点乱,把这个函数整理之后
void _func_101()
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex( esi->_var1_vm_mnemonic, 0, esi->_var3_lval, (esi->ispushfw) != 0);
}
void _func_104()
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex( esi->_var1_vm_mnemonic, 0, esi->_var3_lval, 0));
}
void _func_3(}
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex( esi->_var1_vm_mnemonic, 0, esi->_var3_lval, 1));
}
int _getindex( int _n)
{
switch(_n)
{
case 1:
return 0;
case 2:
return 1;
case 3:
return 2;
case 5:
return 4;
case 6:
return 5;
default:
return 3;
}
}
_DWORD *__usercall Vmp_FindAndSetTOStruct@<eax>(struct_esi *a1@<eax>, int _size@<ebx>, int _i@<esi>)
{
esi->_esi_size = 0;
if( esi->_var1_vm_mnemonic > 0xc4)
{
switch(esi->_var1_vm_mnemonic)
{
case 0xC6:
case 0xC8:
case 0xC9:
case 0xCF:
case 0xD0:
case 0xD1:
case 0xD2:
case 0xD3:
case 0xD4:
case 0xD6:
case 0xD7:
case 0xD8:
case 0xD9:
case 0xDB:
case 0xDD:
case 0xDF:
case 0xE0:
case 0xE1:
case 0xE3:
case 0xE5:
case 0xE6:
case 0xEE:
case 0xF8:
{
_func_104();
return;
}
case 0xF6:
{
_func_101();
return;
}
case 0xF7:
{
_func_3();
break;
}
default:
return;
}
}
if( esi->_var1_vm_mnemonic == 0xC4)
{
_func_104();
return;
}
if( esi->_var1_vm_mnemonic > 0x40)
{
if( esi->_var1_vm_mnemonic > 0xb6)
{
if( esi->_var1_vm_mnemonic != 0xb8 && esi->_var1_vm_mnemonic - 0xbb >= 2)
{
return;
}
}
else if( esi->_var1_vm_mnemonic != 0xb6)
{
if( esi->_var1_vm_mnemonic == 0x8b)
{
esi->_hex = esi->_displacement_immediate;
esi->size += 4;
return;
}
if( esi->_var1_vm_mnemonic - 0xA0 >= 2 && esi->_var1_vm_mnemonic - 0xA3 >= 4)
{
return ;
}
}
_func_104();
return;
}
if ( esi->_var1_vm_mnemonic == 0x40 ) // mul
_func_104();
return;
if ( esi->_var1_vm_mnemonic > 0x2E )
{
if ( esi->_var1_vm_mnemonic != 0x3D )
{
if ( esi->_var1_vm_mnemonic == 0x3E ) // pop dx
_func_101();
return;
if ( esi->_var1_vm_mnemonic != 0x3F )
return;
}
_func_104();
return;
}
if ( esi->_var1_vm_mnemonic == 0x2E ) // wait
_func_104();
return;
if ( esi->_var1_vm_mnemonic - 1 >= 2 )
{
if ( esi->_var1_vm_mnemonic != 4 && esi->_var1_vm_mnemonic - 28 >= 2 )// !=4 || >= 0x1E
return;
_func_101();
return;
}
if ( esi->_var1_vm_mnemonic - 1 != 0 )
{
if ( esi->_var1_vm_mnemonic == 2 ) // pop
{
switch ( _esi->_var2_addressing_mode )
{
case 1:
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex( 2, 1, esi->_var3_lval, 0));// pop eax|ax
break;
}
case 2:
{
if ( _esi->_var3_lval >= 3 || _esi->_REG_Index != 4 )
{
int flag = !_esi->_var3_lval && (_esi->_REG_Index & 4) == 4;
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex(2, 2, _esi->_var3_lval, flag);
_hex2 = Vmp_GetEmptyVMContext(_esi->Struct_vtable_477C54,_esi->_var3_lval,_esi->_REG_Index,1);
saveToStruct(_esi, _hex2);
}
else
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex(2, 2, _esi->_var3_lval, 1));// pop sp|esp
}
break;
}
case 3:
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex(2, 3, _esi->_var3_lval, _getindex(esi->var5));
break;
}
case 5:
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex(2, 5, 1, _esi->_REG_Index));// pop prefiexreg_index
break;
}
case 6:
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex(2, 6, 2, _esi->_REG_Index));
break;
}
case 7:
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex(2, 7, 2, _esi->_REG_Index));
break;
}
default:
break;
}
}
}
else if ( _esi->_var2_addressing_mode == 1 )
{
switch ( _esi->_rand_switch )
{
case 1:
{
do
v5 = rand()%(-1);
while ( !v5 && 0 == *_esi->_Displacement_Immediate );
_lval_disp_imm = *_esi->_Displacement_Immediate - v5;
v6 = sub_47817C(_esi);
v7 = getStruct_1(_esi->Struct_vtable_477C54, v6 + 1);
*v7->_Displacement_Immediate = v5;
Vmp_FindAndSetTOStruct(v7, v5, _lval_disp_imm);
}
break;
case 2:
{
do
v8 = ~*_esi->_Displacement_Immediate & rand()%(-1);
while ( !v8 && 0 == ~*_esi->_Displacement_Immediate );
_lval_disp_imm = v8 | ~*_esi->_Displacement_Immediate & ~v8;
v9 = sub_47817C(_esi);
v10 = getStruct_1(_esi->Struct_vtable_477C54, v9 + 1);
*v10->_Displacement_Immediate = v8;
Vmp_FindAndSetTOStruct(v10, v8, _lval_disp_imm);
}
break;
case 3:
{
v11 = 0;
v12 = *_esi->_Displacement_Immediate;
while ( !(v12 & 1) ) //偶数
{
v12 >>= 1;
++v11;
}
v13 = System::__linkproc__ RandInt(v11);
_lval_disp_imm = v13 + 1;
v14 = *_esi->_Displacement_Immediate >> (v13 + 1);
v15 = sub_47817C(_esi);
v16 = getStruct_1(_esi->Struct_vtable_477C54, v15 + 1);
*v16->_Displacement_Immediate = v14;
Vmp_FindAndSetTOStruct(v16, v14, _lval_disp_imm);
break;
}
case 4:
{
v17 = 0;
while ( _esi->__var4 == 2 && *_esi->_Displacement_Immediate >= 0 || _esi->__var4 == 1 && (*_esi->_Displacement_Immediate & 0x8000) == 0 )
{
*_esi->_Displacement_Immediate *= 2;
++v17;
}
v19 = System::__linkproc__ RandInt(v17);
_lval_disp_imm = v19 + 1;
v20 = *_esi->_Displacement_Immediate << (v19 + 1);
v21 = sub_47817C(_esi);
v22 = getStruct_1(_esi->Struct_vtable_477C54, v21 + 1);
*v22->_Displacement_Immediate = v20;
Vmp_FindAndSetTOStruct(v22, v20, _lval_disp_imm);
break;
}
default:
{
_lval_disp_imm = *_esi->_Displacement_Immediate;
break;
}
}
if ( _esi->_rand_switch )
{
setAddress(&_esi->_esi_hex[8], *(&off_4CE17C + _esi->_var1_vm_interpreter_mnemonic));
if ( _esi->_var3_lval == 1 )
{
a2a = _lval_disp_imm;
LOBYTE(v66) = 0;
sub_409984(&str____4x[1], &a2a, 0, v64);
System::__linkproc__ LStrCat(&_esi->_esi_hex[8], *v64);
}
else if ( _esi->_var3_lval == 2 )
{
a2a = _lval_disp_imm;
LOBYTE(v66) = 0;
sub_409984(&str____8x[1], &a2a, 0, v67);
System::__linkproc__ LStrCat(&_esi->_esi_hex[8], *v67);
}
if ( _esi->__var4 == 1 )
{
a2a = *_esi->_Displacement_Immediate;
LOBYTE(v66) = 0;
sub_409984(&str___4x[1], &a2a, 0, v62);
setAddress(&_esi->_esi_hex[12], *v62);
}
else if ( _esi->__var4 == 2 )
{
a2a = *_esi->_Displacement_Immediate;
LOBYTE(v66) = 0;
sub_409984(&str___8x[1], &a2a, 0, v63);
setAddress(&_esi->_esi_hex[12], *v63);
}
}
else
{
Free_Mem(&_esi->_esi_hex[12]);
}
if ( _esi->_var3_lval == 1 )
{
if ( _lval_disp_imm & 0xFF00 )
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex( 1, 1, 1, 0);// LODS WORD PTR DS:[ESI] ADD AX,BX ADD BX,AX PUSH AX
Move_Word(_esi, _lval_disp_imm);
}
else
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex( 1, 1, 0, 0);// LODS BYTE PTR DS:[ESI] ADD AL,BL ADD BL,AL PUSH AX
saveToStruct(_esi, _lval_disp_imm);
}
}
else if ( _esi->_var3_lval == 2 )
{
if ( _esi->_ispushfw & 2 || _lval_disp_imm != Byte_Extension(_lval_disp_imm) )
{
if ( _esi->_ispushfw & 2 || _lval_disp_imm != Word_Extension(_lval_disp_imm) )
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex( 1, 1, 2, 0);// LODS DWORD PTR DS:[ESI] ADD EAX,EBX ADD EBX,EAX PUSH EAX
Move_Dword(_esi, _lval_disp_imm);
}
else
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex( 1, 1, 1, 1);// LODS WORD PTR DS:[ESI] ADD AX,BX ADD BX,AX CWDE PUSH EAX
Move_Word(_esi, _lval_disp_imm);
}
}
else
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex( 1, 1, 0, 1);// LODS BYTE PTR DS:[ESI] ADD AL,BL ADD BL,AL CBW CWDE PUSH EAX
saveToStruct(_esi, _lval_disp_imm);
}
}
else
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex( 1, 1, 0, 0);// LODS BYTE PTR DS:[ESI] ADD AL,BL ADD BL,AL PUSH AX
saveToStruct(_esi, _lval_disp_imm);
}
}
else
{
switch ( _esi->_var2_addressing_mode )
{
case 2:
{
if ( _esi->_var3_lval >= 3 || _esi->_REG_Index != 4 )
{
_vmp_setIndexToStruct( esi,_vmp_getVmHandlerIndex(1, 2, _esi->_var3_lval, _esi->_var3_lval == 0 && (_esi->_REG_Index & 4) == 4);//利用空间 push _context[]
v34 = Vmp_GetEmptyVMContext(_esi->Struct_vtable_477C54,_esi->_var3_lval,_esi->_REG_Index,0);//选择index
saveToStruct(_esi, v34);
}
else
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex( 1, 2, _esi->_var3_lval, 1);//// push sp|esp
}
break;
}
case 3:
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex(1,3,_esi->_var3_lval,_getindex(_esi->_var5));// pop e?x push ? ptr ?
break;
}
case 5:
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex( 1, 5, 1, _esi->_REG_Index));//push prefix_reg
break;
}
case 6:
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex( 1, 6, 2, _esi->_REG_Index));//mov eax,cr_index push eax
break;
}
case 7:
{
_vmp_setIndexToStruct( esi, _vmp_getVmHandlerIndex( 1, 7, 2, _esi->_REG_Index));// mov eax,dr_index push eax
break;
}
default:
{
break;
}
}
}
}
判断vm_mnemonic
再判断寻址方式以及类型
继续,我们看看vm_mnemonic怎么得到的
可以发现由传参决定并对于某些方式递归调用
继续寻找怎么来的
定位到cmp。看看vmp是如何实现对cmp,jcc的膨胀的
首先可以很直观的看到,在早期版本中
把sub cmp sbb放在一起来处理
那么看看是如何usedisasmstruct的
结合刚开始给的,则有先读operand[1]
注意到:
if ( _disasm->Mnemonic_Counter_4CE17C != 0x13 && _disasm->Mnemonic_Counter_4CE17C != 0x43 )
// sbb 0x44不算 Opcode_counter_4CE17C != 19 || != 67 _str_sub || _str_cmp
RestHex_Displacement_Immediate = ~RestHex_Displacement_Immediate;// 反码
else
RestHex_Displacement_Immediate = -RestHex_Displacement_Immediate;// sub || cmp 补码
然后其他的对应膨胀规则看看就明白了
分析sub_485884
分析sub_4857DC
可以看到这整套就是一个计算好初始eflags,然后压入_context的过程
分析sub_4858E0
这里就没注释了,可以对照规则
至此,早期版本中的cmp就这样vm了。
一个细节的地方
注意到Vmp_SetEsiStruct函数
v8 = (var1->Vtable_477C54->SetCapacity)();
if ( var6 & 8 )
Classes::TList::Add(_var1->Ptr_Struct_Vtable_40F3B8, v8);
那么我们可以这样理解,在早期版本
vm的基本单位是指令,而指令通过反编译得到 ----> _struct_disasm
加上一个list,抽象出来
_struct_disasm
{
……
…..
list > ls;
};
注意到esi是怎样生成的,通过那些规则,故
_struct_disasm
{
……
…..
list<_struct_esi> ls;
};
同时我们可以猜一猜vmp设计的思路
一开始应该是vm的各种运算(add sub mul xor)
这个不难,如果叫我们来实现,直接将代码替换就可以了
稍微面向一点
switch(mnemonic)
{
case _sub:
case _cmp
}
再面向一些,查表
int _table[] = {1,2,3,4……}
switch(mnemonic)
{
case _sub:
index = 1;
case _cmp:
index = 2;
}
最后在进行匹配
我不知道其他的vm引擎是怎么设计的,如果是我写的话应该也会这样。
第一步可以看成写编译器的语法分析
另外
{0xF6,0x0,0x2,0x0,0x0,0x0,0x0,0x0,"POP EAX NOT EAX NOT DWORD PTR SS:[ESP] AND DWORD PTR SS:[ESP],EAX"}
这个0xf6取得蛮有意思的
再说说jcc
注意到
这几个参数,已上个帖子提到
恩,很多人爆破提到的shr。这个我想动态跟一跟,因为我觉得设计得很好
注意到这里,很重要。
push add1
push 0
add [add1],0
stack_top 0042fbcd
push addr2
push 0
add [addr2],0 //这两个地址很有意思
stack_top 0042fb9b
0042fbcd
lodw [not(0x40)]
push not(0x40
lodb [index]
push _context[index] ----> push eflags
stack_top ffbf0287
not [esp] ---> 0040fd78
pop ax
and [esp],ax
stack_top fb9b0040
fbcd0042
0042
losb [index]
pop _context[index] ---> 0x40
stack_top 42fb9b
42fbcb
001848
losb [4]
push 4
stack_top fb9b0004
fbcd0042
0042
Lodb [index]
Push _context[_index]
Stack_top 00040040
0042fb9b
0042fbcd
Pop ax
Pop cx
Shr ax,cl ---> result == 0x4
Push ax
Stack_top fb9b0004
Fbcd0042
0042
//这个4很有意思
Lodb [index]
Pop _context[index] ---> 4
stack_top 0042fb9b
0042fbcd
Push esp //注意到这里
stack_top 0018FE98(addr_42fb9b)
0042FB9B
0042FBCD
Lodsb [index]
Push ax --> 0
Stack_top fe98000
Fb9b0018
Fbcd0042
Ff480042
Lodsb [index]
Push _context[index] ---> 0x4
Stack_top 00000004
0018fe98
Pop eax ---> eax == 0x4
add [esp],eax
stack_top 0018FE98(addr_42fb9b)
0042FB9B
0042FBCD
vmp先压了两个地址然后取eflags运算进行shr,通常会得到4。
那么这个4是用来干什么的?
我们看看这两个地址的内容
恩,没错。实现了分支
在注意到这里的地址
Stack_top 0018fe9c(addr_0042fbcd)
0042fb9b
0042fbcd
Pop eax
Push [eax]
Stack_top 0042fbcd
0042fb9b
0042fbcd
但是有个问题,vmp怎么修改esi
Lodsd [vmp_entry]
Push vmp_entry
Stack_top 0042F000(vmp_entry)
00000000(_addressing)
0042fbcd
Pop eax
Add [esp],eax
Stack_top 0042f000
0042fbcd
保存环境
Stack_top 00000000
0018FEF4
00000000
0018FEF4
0018FE84
7EFDE000
00000000
00425036
00000001
00000287
0042F000
0042FBCD
这样就返回vm_entry,改变esi了
看到这,相信大家应该有很多爆破的思路了吧
0x11 结束语
就这样,先写到这里。
本文由看雪论坛 waiWH 原创
转载请注明来自看雪社区
往期热门阅读:
点击阅读原文/read,
更多干货等着你~