谈谈vmp的还原

2018-04-07 waiWH 看雪学院 看雪学院

上一部分回顾:谈谈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;
           }
       }
   }
}

  1. 判断vm_mnemonic

  2. 再判断寻址方式以及类型


继续,我们看看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,

更多干货等着你~