去除变形代码   直接将CV还原成ASM X86 CODE的一种方法
这篇文章主要参考了这个帖子的代码 http://www.woodmann.com/forum/blog.php?b=115 

这个断断续续搞了数月 并且不断修改的 时间周期长 所以下面举的例子不一定正确 
因为都是碰到错误然后修改程序 碰到的错误记不得了 所以模拟处一个错误 可能有错 欢迎拍砖= =
效果见附件 附件文本要用UE打开 记事本打开的话 换行不能被识别 是 UNIX格式的 因为YACC BISON用的是CYGWIN下的 printf("\n") 是unix下的换行格式。。 

从我上一篇文章可以看出 即使写出解码程序 解码出的东西分析起来依然不容乐观 垃圾代码还是非常多。 而且VM的 VM环境可以设置成很多个 并不是每处的解码程序都一样 且HANDLER的变形也很麻烦。(这篇文章不包括二次VM)

所以本文章的目的  
1:还原变形代码  (这个是参考这篇文章http://www.woodmann.com/forum/blog.php?b=115) 这个帖子提供的程序我用来还原我加密的handlers达不到理想的情况而且BUG多多 我增加大概1000多行代码左右和翻新了部分代码才达到我想要的效果 不过非常感谢作者的劳动 因为他的方向是正确的 特别分析语法部分用了 YACC FLEX, 使分析语法变得很方便,这个是编译原理中的课程~

2:把VM还原成相对简单的X86 ASM CODE

我把去除变形代码的大体原理说下
Add [esp] 8  包含操作符  左操作数 右操作数  当然左右操作数可以是表达式
我们可以把  Add [esp] 8 放入一个结构体
struct node {
  int op;          操作符  比如是MOV   ADD等
  int arg1;        左操作数 比如 EAX   EBX 等
  int arg2;        右操作数 比如 EAX   EBX 等
  int targ1;        左操作数类型 比如 寄存器 表达式 常数等等
  int targ2;        右操作数类型 比如 寄存器 表达式 常数等等
  struct exp *e;    表达式结构体  因为左右操作数不同时是表达式 所以一个就行了
  struct node *old; 
  struct node *next;  
  struct node *prev;  
};

操作数类型有NUMBER 常数  寄存器REGISTER  表达式 TYPE 
比如我们要还原
push eax   
or [esp] ecx  
pop edx
成 
mov edx eax 
or edx ecx 
 因为基于堆栈的运算比较难看 所以这个要还原  
这里就是有3个struct  node了 分别是 node1 node2 node3
这个操作就是
            node3->op = node2 ->op;
            node3->arg2 = node2 ->arg2;
            node3->targ2= node2 ->targ2;            
            
            node2 ->op = MOV;            
            node2 ->arg1=nxt->arg1;
            node2 ->targ1=REGISTER;  
            
  
            
            node2 ->arg2=tmp->arg1;   
            node2 ->targ2=REGISTER;              

            removeNode(node1, &code);
这里要注意下 一定要去掉第一个节点 不能把第一 二行改成mov edx eax  or edx ecx 然后去掉第三个node 为什么呢 看下面一种情况
push eax 
mov edx 2
or [esp] edx
mov edx 3
pop edx
这样精简后变成
mov edx eax 
mov edx 2
or edx 2
mov edx 3
因为程序后面有对MOV的优化 导致的结果就是 mov edx 3
很显然 结果并不是这样 应该是
mov edx 2
mov edx eax
mov edx 3
or edx 2
因为CV的变形是可以嵌套的 所以上面的的情况遇到也是理所当然的了= = 
上面是基于堆栈方面的优化 

代码就是下面的 (原帖例子只有几行简单的代码:))
这个是我不断增加的结果 所以比较难看 比较乱 - -
if(tmp->op==PUSH && tmp->targ1==REGISTER){
      //now search for pop register
          inPushPop = isUsed = 0;
        OnlyOneUsed = 0;
      for_each(tmp->next, nxt){
//        if(
        if( nxt->targ1==REGISTER && (isInUse(tmp->arg1, nxt->arg1)==1)/*(isSafe(nxt, tmp->arg1) == 0)/*说明不安全 说明假如 PUSH ECX  那么CH ECX 则可能被用过 */ 
          &&nxt->op!=POP&&nxt->op!=PUSH)
        {
          inPushPop = isInUse(tmp->arg1, nxt->arg1);
          rubish = nxt;
          inPushPop = 1;
        }
        if(nxt->targ1==REGISTER && (isInUse(tmp->arg1, nxt->arg2)==1) /*)*/&& nxt->targ2==REGISTER  && inPushPop==1 &&nxt->op!=POP&&nxt->op!=PUSH)
        {
          isUsed = 1;
          OnlyOneUsed++;
        }
        
  /*

push eax
pop  [esp+4]
pop eax       BUG
  */
        if(nxt->targ1==TYPE &&nxt->op!=POP&&nxt->op!=PUSH && nxt->e->targ1==REGISTER&&nxt->e->arg1==ESP
          &&nxt->e->targ2 != TYPE 
          && nxt->e->targ2 != REGISTER && nxt->e->targ2 != NUMBER)
        {
          ++expEspLeft;
          nxtspe = nxt;
        }
        if(nxt->targ2==TYPE &&nxt->op!=POP&&nxt->op!=PUSH && nxt->e->targ1==REGISTER&&nxt->e->arg1==ESP
          &&nxt->e->targ2!=TYPE&&nxt->e->targ2!=REGISTER&&nxt->e->targ2!=NUMBER)
        {
          ++expEspRight;
        }
                
        
        
        if(nxt->op==PUSH && nxt->targ1==REGISTER && nxt->arg1==tmp->arg1){

          break; // 说明有连续两个 PUSH EDX  先紧下面一个处理
        }
        
        
        if(nxt->op==POP && nxt->targ1==REGISTER && nxt->arg1!=tmp->arg1){
          if(inPushPop==0 && isUsed==0 && expEspLeft==1 && expEspRight==0)
          {
            
            nxt->op = nxtspe->op;
            nxt->arg2 = nxtspe->arg2;
            nxt->targ2= nxtspe->targ2;            
            
            nxtspe->op = MOV;            
            nxtspe->arg1=nxt->arg1;
            nxtspe->targ1=REGISTER;  
            
  
            
            nxtspe->arg2=tmp->arg1;
            nxtspe->targ2=REGISTER;              

            removeNode(tmp, &code);
            if(beDebug == 1)
            {
              printf("--push eax   or [esp] ecx  pop edx ---> remove(must remove the fisrt node) mov edx eax or edx ecx (remove one nodes)\n");
                                 //mov edx eax  or edx ecx
              printList(&code);
            }            
            break;
          
          
          }
            
        }
}

还有典型的就是增加垃圾的运算

比如 mov edx 4
变形成
Mov eax 2
Add eax 2   //这里的ADD可以是or and 等等垃圾运算
Mov edx eax
这种的话
直接循环检测每行 如果op==MOV  arg1==REGISTER arg2==NUMBER
然后再跑个二次循环 依次检测每个node 如果arg1一样且arg2类型为NUMBER就可以合并了 原帖对 dx cx等小16位寄存器处理不对 还少考虑了情况 比较下我的程序和原帖程序就可以发现
Mov eax 2   add eax 2就是 mov eax 4

  for_each(list->front, tmp){
    if(tmp->op==MOV && tmp->targ1==REGISTER && tmp->targ2==NUMBER){
      found = tmp;
      for_each(tmp->next, tmp2){
        if(isSafe(tmp2, found->arg1)==0 && (tmp2->op== PUSH || tmp2->op== POP))// tmp2 的操作影响 found的第一个寄存器
          break;  //确保不是 PUSH POP 的计算 且 第一个操作数没有改变
        if((tmp2->arg1 != found->arg1) || (tmp2->targ1!=found->targ1))
        {// 第一个操作数一定要一样 确保安全
          continue;
        }
        if(tmp2->targ2 == TYPE) break;  // 如果第二个操作数是表达式 就直接推出这一次循环
        undef = 0;
//        printf("op = %d found->arg2 = 0x%08x, tmp2->arg2=0x%08x\n", tmp2->op, found->arg2, tmp2->arg2);
        if((tmp2->targ2 == NUMBER || tmp2->targ2==NONE) && tmp2->op!=PUSH && tmp2->op!=POP && tmp2->op!=LODSB && tmp2->op!=LODSW && tmp2->op!=LODSD){  // mov eax, 6    add eax 2   变成 mov eax, 8
          if( (found->arg1<8) &&  (found->arg1>=0) )
          {
            found->arg2 = (found->arg2)&0xFF;
            HaveChange =1 ;
          }
          if( (found->arg1<16) &&  (found->arg1>7) )
          {
            found->arg2 = (found->arg2)&0xFFFF;
            HaveChange =1 ;
          }
          
          if(beDebug == 1)
          {
            printf("--calc some exps \n");                        
            printList(&code);
          }
          result = emulate(tmp2->op, found->arg2, tmp2->arg2, &undef);
          
          if( ( (found->arg1)<8) &&  ( (found->arg1)>=0) )
          {
            result = result&0xFF;
          }
          if( (found->arg1<16) &&  (found->arg1>7) )
          {
            result = result&0xFFFF;
          }
          if(undef)// 说明不能运算
            break;
          found->arg2 = result;
          HaveChange =1 ;
//          printf("result = %08x\n", result);
          removeNode(tmp2, list);
          HaveChange =1 ;
        }
        else{
          break;
        }
      }
    }
  }

我们就拿dump_wmimmc.sys中的一个看下 因为手上没有最新的CV 就拿他实验了 免费的最新CV学习对象= = 
看下结果  全部166个HANDLER还原见附件 


pop ax 
mov [edx ] al

sub al bl 
xor al 0000006b 
sub bl al 
movzx eax al 
pop dx 
push ecx 
mov ch dl

push 00007e41 
mov [esp ] esp 
add [esp ] 00000004

pop ax

pop cx 
pop ax 
movzx cx al 
push cx

pop cx 
shr [esp ] cl 
pushf

pop eax 
pop ecx 
cmp ecx eax 
pushf

pop cx 
shl [esp ] cl 
pushf

pop ax 
add [esp ] al 
pushf

push [edx ]

pushf

push [edi 0000001c ] 
popf 
pop cx

neg [esp ] 
pushf

pop ax 
pop cx

handler15就不列了 这个就是JXX判断 每个版本的应该一样的 这里效果不太好


pop ax 
or [esp ] ax 
pushf

sub al bl 
xor al 000000cc 
xor al 000000ff 
sub bl al 
movzx eax al 
cmp eax 00000007 
mov eax esp 
add edx eax

pop [edx ]

pop ecx 
pop eax 
movzx ecx ax 
push ecx

push [edi 0000001c ] 
popf 
pop cx

mov ax 0000beeb 
add ax bx 
add ax 00006ca7 
mov si 0000e43c 
sub bx ax 
movzx eax ax 
push ax

mov sp [esp ]

push [edi 0000001c ] 
popf 
pop ax

pop ax 
inc [esp ] 
pushf

cmp [edi 00000020 ] 00000000

pop ecx 
pop eax 
pop edx 
idiv ecx 
push edx 
push eax 
pushf

push [edi 0000003c ]

add al 0000000a 
sub al bl 
push ecx 
mov ch 0000000f 
push ebx 
mov bl 00000039

pop ax 
mov ebx 00000000 
mov [ebx ] al

and [edi 0000001c ] fffffdff

这个就是最新的CV前30个HANDLER的意义 估计有点BUG 没有人工去检测
我上一篇文章的handler自己验证了 还算满意。



2:把VM中间码还原成X86CODE
上一篇文章的解码程序我们可以改变下 去掉保存寄存器 恢复寄存器 就变为

堆栈变形码:(暂且这样称吧:))
mov edx esp
push edx
push 4
pop eax
add [esp] eax
pop edx
push edx
push edx
push 4
pop eax
add [esp] eax
pop edx
pop edx
push [edx]
push addr_real_eax
pop edx
push edx
push addr_real_ebx
pop edx
pop edx
pop [edx]
push addr_real_eax
push addr_real_eax
pop edx
pop edx
push real_eax
push 2
pop eax
add [esp] eax
pushfd
pop real_eflags
push addr_real_eax
pop edx
pop [edx]
push addr_real_eax
pop edx
push real_eax
push 3
push addr_real_edi
pop edx
push real_edi
push addr_real_ebx
pop edx
push real_ebx
pop eax
or [esp] eax
pushfd
pop real_eflags
pop edx
pop eax
sub [esp] eax
pushfd
pop real_eflags
push addr_real_eax
pop edx
pop [edx]

其中里面涉及的VM寄存器(保存原始寄存器的内存地址)也当做寄存器 
 比如push addr_real_eax 就是原来解码处的push block_1_addr  comment:regEax addr
Real_eax  就是原来解码程序中的regEax
相当于寄存器增加了3倍...

那么
Push 2
Push addr_real_eax
Pop edx
Pop [edx]
不就等于 mov real_eax 2了吗= = 
仔细看下我上面分析的 其实到了2e处 esp就是原来程序的ESP  所以VM也可以看做基于堆栈的变形
2e  64:mov esp,[esp]    comment:get regESP  //以上5行就是 mov edx, esp  add edx, 4  mov esp, edx
// 到这里 堆栈就是原始状态了
所以还原VM又转到了还原变形代码的问题上去了
好了 利用上面的只是 写出对应的还原代码
还原上面 堆栈变形码的 结果就是

Mov edx esp
Add edx 4
Mov real_eax [edx]
Sub real_eax 1
Mov eax real_ebx
Mov edx real_edi
Or edx eax
Mov eax 3
Mov edx addr_real_eax

56行的代码变成了9行 。 这里很容易人工还原了  只要左操作数为real开头的就是有用的 别的去掉 然后推理下
就是
Mov edx esp
Add edx 4
Mov real_eax [edx]
Sub real_eax 1

就是对应加密前的
Mov eax, [esp+4]
Add eax 2
Sub eax 3

这里优化代码把原程序都优化了  Add eax 2   Sub eax 3 变成Sub real_eax 1 :)

 找一份编程类工作 
 QQ  983732543 
  邮箱:983732543@qq.com

最后的附件用Ultraedit打开  你想同时打开多个文本 并对比 要设置下 不然总提示转换成DOS格式

上传的附件 还原效果.rar