谈谈vmp的还原(3)

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

  

前两部分:



0x00 前言



vs2010,代码放在了github:vmp0x01 寄存器
   

先说context


init初始化context分两种情况



   

那么第二种,就是随机生成了



关于pushad pushfd的保存

 



选取空闲的context




代码如下:


int n = 0, k = 0;
char cpu[16] = {0};

int _getContextIndex( int type)
{
   if( tpye - 1 >= 2)
   {
       switch(type)
       {
           case 0:
           case 4:
               return dis->reg[0];
           case 1:
           case 5:
               return dis->reg[1];
           case 2:
           case 6:
               return dis->reg[2];
           case 3:
           case 7:
               return dis->reg[3];        
           case 9:
           case 0xD:
               return dis->reg[9];    
           default:
               return 0;                                            
       }
   }
   else
   {
       switch(type)
       {
       case 0:
       case 1:
       case 2:
       case 3:
       case 5:
       case 6:
       case 7:
       case 8:
       case 9:
       case 0xa:
           return dis->reg[_type];
       case 4:
           return -1;
       default:
           return 0;
       }
   }
}

for( int i = 0; i < 16; i++)
{
   if( dis->reg[i] < 0xff)
   {
           cpu[ dis->reg[i]] = 1;
   }
}
i = 0;
while( cpu[i] || rand()%2 != 1)
{
   i++;
   return _getContextIndex(tpye);
}
dis->reg[k] = n;
return _getContextIndex(tpye);

这里说下,看到这部分实现的时候,脑子里的想法是当成树结构,用dfs去识别判断,因为以为是真轮转。记得,还给校长说,加密与解密那个目录处理的方法好像是图算法。


再注意到a2/v13 == 2, 才会去get context 


随意看一个调用,很明显tpye == 2才会去




那么其他的情况在只是return context,那么就好办了,因为可以得到,并不是真轮转



0x10 变形



下面这个东西,也是我最佩服vmp作者的地方,因为就这点东西,就可以做很多事情了。




注意是倒叙进行判断的


首先这个函数,我也不知道怎么命名好,所以就当成fixed吧,可能叫plasmodium比较好 。


当我进行分析的时候,如果不是语言不在同一个频道,可能就膜拜了。

随意的,举个例子。这里可能会需要一些数据结构的知识。


记当前结构为一个_cur_node

记当前上一个节点为_prev_node

记当前下一个节点为_next_node


假定


_cur_node: LODS BYTE PTR DS:[ESI]  POP DWORD PTR DS:[EDI+EAX*4]
_next_node: LODS BYTE PTR DS:[ESI]  PUSH DWORD PTR DS:[EDI+EAX*4]


这两条在操作什么,很明显,可以看出vmp不是没有一些组合的

那么可能会出现下面的情况

_next_node变成PUSH ESP

new node,生成一个新的节点,记为_tmp_node


_tmp_node: POP EAX PUSH DWORD PTR ES:[EAX]


之后在插入,如下


Before


LODS BYTE PTR DS:[ESI]  POP DWORD PTR DS:[EDI+EAX*4]
LODS BYTE PTR DS:[ESI]  PUSH DWORD PTR DS:[EDI+EAX*4]


After:


LODS BYTE PTR DS:[ESI]  POP DWORD PTR DS:[EDI+EAX*4]
PUSH ESP
POP EAX PUSH DWORD PTR ES:[EAX]


注意到eax一致,然后明白这个函数了吗?

好像就这些了,我大概想阐述的东西,就这些。



0x11 关于我还原的思路



在我逆的过程中,一直在猜想vmp作者的构造思路。


最开始的时候,我的想法是把esi看成vmp_encodes,那么asm有套opcode的构造手册,vmp作者也应该有一套这样的手册,当然也不是说像intel手册那种。但我一直从各个角度去思考,都想不出,vmp作者是怎么构造出这些精彩的东西的。有兴趣的,可以尝试一下,如果是大一大二,我可能就这样继续下去了。


之后我想既然知道了规则,可能不同版本,会有一些变化,但大致框架如此。


而不同的是,我所想的是,是还原为encodes,而不是asm,这点也重要,因为即使trace,或者使用插件,是的,你人工可能可以识别,那么代码认识么,当然可以写成分析树或写个虚拟引擎自己跑,但我想,这也是vmp作者愿意看到的情况。


那么既然想还原成encodes,先解析esi,之后只要规则到位,那么我们可以得到disp,imm,sib,modrm,prefix。还需要确认的,就只剩下opcode了,所幸的是,相同mnemonic对应的opcode并不多,就大概解决类型不对等的情况了。






本文由看雪论坛 waiWH 原创

转载请注明来自看雪社区



往期热门阅读:





点击阅读原文/read,

更多干货等着你~

扫描二维码关注我们,更多干货等你来拿!