首页
社区
课程
招聘
[原创]cv变形代码的学习: 去除变形代码 直接将还原成ASM X86 CODE的一种方法
2009-2-18 23:52 16132

[原创]cv变形代码的学习: 去除变形代码 直接将还原成ASM X86 CODE的一种方法

2009-2-18 23:52
16132
去除变形代码   直接将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格式

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

上传的附件:
收藏
点赞7
打赏
分享
最新回复 (27)
雪    币: 83
活跃值: (605)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
orchid88 2009-2-19 01:10
2
0
好文章!应该加精!受益匪浅。花几天时间消化消化,谢谢楼主。
雪    币: 182
活跃值: (50)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
pathletboy 2 2009-2-19 01:25
3
0
等待UnCv
雪    币: 451
活跃值: (78)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
zhuwg 11 2009-2-19 08:36
4
0
在前排坐下来学习膜拜
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
天线宝宝 2009-2-19 09:10
5
0
保持队形 排队学习
雪    币: 7300
活跃值: (3758)
能力值: (RANK:1130 )
在线值:
发帖
回帖
粉丝
海风月影 22 2009-2-19 09:14
6
0
严重学习啊 啊啊啊啊
雪    币: 196
活跃值: (135)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
thinkSJ 4 2009-2-19 09:27
7
0
虽然仔细看了,但看完后发现还是不懂, :(
雪    币: 362
活跃值: (25)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
人很老实 2 2009-2-19 09:32
8
0
占座、排队、膜拜。
雪    币: 71
活跃值: (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
doking 2009-2-19 10:40
9
0
排队学习
雪    币: 1844
活跃值: (35)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
yingyue 2009-2-19 12:45
10
0
收下。。。。。。。。。。。很好
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
blackwjc 2009-2-19 15:19
11
0
学习中。。。
雪    币: 8861
活跃值: (2369)
能力值: ( LV12,RANK:760 )
在线值:
发帖
回帖
粉丝
cvcvxk 10 2009-2-19 17:36
12
0
学习,顶~

只有在这个讨论区才能看到以前看雪论坛的影子~
雪    币: 331
活跃值: (56)
能力值: ( LV13,RANK:410 )
在线值:
发帖
回帖
粉丝
Isaiah 10 2009-2-21 19:32
13
0
如果楼主能抄一个CV出来。估计会有市场
雪    币: 357
活跃值: (2593)
能力值: ( LV3,RANK:25 )
在线值:
发帖
回帖
粉丝
KooJiSung 2009-2-21 20:15
14
0
建议直接写个解码器,放出来膜拜。。
雪    币: 66
活跃值: (15)
能力值: ( LV9,RANK:330 )
在线值:
发帖
回帖
粉丝
theOcrat 8 2009-2-22 23:25
15
0
果然人家也是选择了用script来emulate/peephole, 欧耶
雪    币: 66
活跃值: (15)
能力值: ( LV9,RANK:330 )
在线值:
发帖
回帖
粉丝
theOcrat 8 2009-2-22 23:29
16
0
...........
雪    币: 37
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wead 2009-3-16 22:03
17
0
我该怎么配置编译环境啊??O(∩_∩)O~
雪    币: 437
活跃值: (243)
能力值: ( LV12,RANK:240 )
在线值:
发帖
回帖
粉丝
bzhkl 5 2009-3-16 23:57
18
0
windows下可以用cygwin   magic c++  这样可以象VC++一样调试

linux下可以用 KDevelop

脚本部分是 IDAPYTHON = =
雪    币: 107
活跃值: (311)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Fido 2009-4-9 18:13
19
0
膜拜!!!!!!!!!!!!!!!!!!!!!!!!!下载一个看看.学习方法
雪    币: 150
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cpuArt 2009-5-7 20:25
20
0
5555555 原帖的代码下不了了啊 为啥啊
雪    币: 150
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cpuArt 2009-5-7 22:24
21
0
有代码的兄弟发我一份啊 谢谢啦
[email]shijiaoan1985@163.com[/email]
雪    币: 251
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
雲飛揚 2009-5-8 17:42
22
0
有点像词法+语法分析器了,楼主能开发编译器了.
雪    币: 1272
活跃值: (736)
能力值: ( LV13,RANK:810 )
在线值:
发帖
回帖
粉丝
Anskya 20 2009-5-8 18:19
23
0
就相当于写个收缩器。。。
不过现在的替换代码还是根据现有的脚本进行替换。。。
如果某年某日真的做出一个。VISO的东西的话-缅怀被鹳狸猿不欢迎的那位大神

VISO做到极致。这些收缩器无意义。

事实证明。插入垃圾代码不足以解决问题。。。还需要垃圾数据进行配合
一些VISO的技巧呀技巧
雪    币: 332
活跃值: (25)
能力值: ( LV12,RANK:460 )
在线值:
发帖
回帖
粉丝
火影 11 2009-5-9 10:08
24
0
只有膜拜了
雪    币: 608
活跃值: (91)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
Second 1 2009-6-12 15:17
25
0
好文章.学习了.
游客
登录 | 注册 方可回帖
返回