我对混淆代码的粗浅认识


首先,我只知道这个东西来源于病毒领域,没有真正学习过,更没有用过病毒引擎,完全是脱壳过程
中自己的感受,认识很肤浅,甚至完全错误。请大家多指点。


混淆代码与花指令是否可以划等号?我觉得尽管二者不能完全划清界限,但还是有区别。花指令似乎大多是
完全的垃圾代码,一般通过在编程时插入宏来实现,只要能识别出来,大多可以直接拿掉(不考虑校验),
如使用dejunk插件。

混淆代码却难以简单地剥除。比如下面这几句代码,来自ExeCryptor。

_2fimh7mp:0058E96C 68 96 3C BD 79                    push    79BD3C96h
_2fimh7mp:0058E971 58                                pop     eax
_2fimh7mp:0058E972 81 C8 28 23 76 C3                 or      eax, 0C3762328h
_2fimh7mp:0058E978 81 E8 18 61 46 D9                 sub     eax, 0D9466118h
_2fimh7mp:0058E97E 81 E0 FD 23 0B 83                 and     eax, 830B23FDh
_2fimh7mp:0058E984 E9 C3 3B FF FF                    jmp     loc_58254C

显然难以通过定义Search/Replace字符串来加以清理。调试时逐句跟很耗费精力,而计算后的结果却往往
是有意义的,需要加以注意的值。我猜测这样的代码并不是编程时实现的,可能是用单独的混淆引擎生成的。

不知道dejunk是如何实现的。我自己写过一个IDA插件,模仿dejunk的文件格式,在IDA内去掉花
指令。用字符串比较的方式来判断,比如Themida壳中的花指令(到达oep前基本就只有这1种):

[Type 1]

;  push 0
;  push reg32
;  call loc_1
;  _THREE_BYTES_JUNKCODE
;loc_1
;  pop reg32
;  mov [esp+4],reg32
;  add [esp+4],imm32  ; 这个值可变
;  inc reg32
;  push reg32
;  ret
;  _TWO_BYTES_JUNKCODE  ; bytes数可变

[imm32 = 0x14]
S = 6A00??E803000000????C3??89??24048144240414??????????C3??
R = 90909090909090909090909090909090909090909090909090909090

[imm32 = 0x15]
S = 6A00??E803000000????C3??89??24048144240415??????????C3????
R = 9090909090909090909090909090909090909090909090909090909090

[imm32 = 0x16]
S = 6A00??E803000000????C3??89??24048144240416??????????C3??????
R = 909090909090909090909090909090909090909090909090909090909090

[imm32 = 0x17]
S = 6A00??E803000000????C3??89??24048144240417??????????C3????????
R = 90909090909090909090909090909090909090909090909090909090909090

[imm32 = 0x18]
S = 6A00??E803000000????C3??89??24048144240418??????????C3??????????
R = 9090909090909090909090909090909090909090909090909090909090909090

[imm32 = 0x19]
S = 6A00??E803000000????C3??89??24048144240419??????????C3????????????
R = 909090909090909090909090909090909090909090909090909090909090909090

[imm32 = 0x1A]
S = 6A00??E803000000????C3??89??2404814424041A??????????C3??????????????
R = 90909090909090909090909090909090909090909090909090909090909090909090

[imm32 = 0x1B]
S = 6A00??E803000000????C3??89??2404814424041B??????????C3????????????????
R = 9090909090909090909090909090909090909090909090909090909090909090909090

[imm32 = 0x1C]
S = 6A00??E803000000????C3??89??2404814424041C??????????C3??????????????????
R = 909090909090909090909090909090909090909090909090909090909090909090909090

[imm32 = 0x1D]
S = 6A00??E803000000????C3??89??2404814424041D??????????C3????????????????????
R = 90909090909090909090909090909090909090909090909090909090909090909090909090

显然,这里只有一种套路,但用字符串匹配,代码有任何变化,都需要新的S/R对,非常笨拙。到了
Themida的虚拟机部分,完全对付不了混淆代码。不知道用正则表达式能否做得灵活些(那个我也没
用过;-)。

为了读Themida的VM代码,重新写了个插件,希望能比上面的稍微"聪明"一点。程序大致分几步:

1. 清除由单条指令实现的垃圾代码
   指完全可以用nop替代的指令,基本上只有 lea r32,[r32]类型

   另外,把变形的jmp替换,如:

   _11d0000:011D173E 68 59 0F 2D 27            push    272D0F59h
   _11d0000:011D1743 81 2C 24 52 FA 0F 26      sub     dword ptr [esp], 260FFA52h
   _11d0000:011D174A C3                        retn  ; 这里等于jmp loc_11D1507


2. 清除垃圾指令序列
   指可以用nop替换的指令序列,如:

   _11d0000:011D001E F7 D8    neg     eax
   _11d0000:011D0020 F7 D8    neg     eax

   _11d0000:011D261C 4B       dec     ebx
   _11d0000:011D261D F7 D3    not     ebx
   _11d0000:011D261F F7 DB    neg     ebx

   _11d0000:011D80CD 57       push    edi
   _11d0000:011D80CE F7 1C 24 neg     dword ptr [esp]
   _11d0000:011D80D1 5F       pop     edi
   _11d0000:011D80D2 F7 DF    neg     edi

   需要注意的是,从第2步起,所有的代码模式中间都可能夹杂着其他的垃圾指令,如lea r32,[r32]
   或jmp。这也是第1步先行替换的原因(以简化判断)。如:

   _11d0000:011D1736 68 00 00 00 00                          push    0
   _11d0000:011D173B 29 2C 24                                sub     [esp], ebp
   _11d0000:011D173E 68 59 0F 2D 27                          push    272D0F59h
   _11d0000:011D1743 81 2C 24 52 FA 0F 26                    sub     dword ptr [esp], 260FFA52h
   _11d0000:011D174A C3                                      retn  ; 这里等于jmp loc_11D1507
   _11d0000:011D1507 5D                                      pop     ebp
   _11d0000:011D1508 F7 DD                                   neg     ebp

   这里垃圾代码被jmp分成了2段。

   由于对各种代码序列的识别顺序难以安排,一些模式可能要在别的代码被处理后才能识别,对给定的地址范围,
   插件需要运行3次才能清理干净.


3. 对可以按模式匹配的指令加以简化
  
   比如:

   _11d0000:011D01F6 68 76 5E 00 00     push    5E76h
   _11d0000:011D01FB 89 04 24           mov     [esp], eax

   等价于push eax

   _11d0000:011D3563 31 F7              xor     edi, esi
   _11d0000:011D3565 31 FE              xor     esi, edi
   _11d0000:011D3567 31 F7              xor     edi, esi

   等价于xchg esi,edi


4. 简化push/pop指令对
   这个可以归入3,但数量众多,所以单独处理,如:

   _11d0000:011D5547 52                push    edx
   _11d0000:011D5548 BA C5 42 00 00    mov     edx, 42C5h
   _11d0000:011D554D 01 57 08          add     [edi+8], edx
   _11d0000:011D5550 5A                pop     edx

   等价于 add [edi+8],42C5h


5. 简化mov r8/r32,imm指令

   如:

   _11d0000:011D55EC BA BC 55 00 00     mov     edx, 55BCh
   _11d0000:011D55F1 C1 EA 09           shr     edx, 9
   _11d0000:011D55F4 4A                 dec     edx
   _11d0000:011D55F5 E9 40 FC FF FF     jmp     loc_11D523A  ; 夹杂了jmp
   _11d0000:011D523A 81 CA D6 2B 00 00  or      edx, 2BD6h
   _11d0000:011D5240 81 F2 3A 24 00 00  xor     edx, 243Ah
   _11d0000:011D5246 81 F2 5E 73 00 00  xor     edx, 735Eh
   _11d0000:011D524C 81 F2 64 83 FF FF  xor     edx, 0FFFF8364h

   等价于mov edx, 0FFFFFFFFh

6. 简化mov mem32,imm32指令
   如:

   _11d0000:011D0054 C7 47 18 F9 28 00 00    mov     dword ptr [edi+18h], 28F9h
   _11d0000:011D005B F7 57 18                not     dword ptr [edi+18h]
   _11d0000:011D005E 31 FB                   xor     ebx, edi  ; 垃圾
   _11d0000:011D0060 31 FB                   xor     ebx, edi  ; 垃圾
   _11d0000:011D0062 F7 5F 18                neg     dword ptr [edi+18h]
   _11d0000:011D0065 81 67 18 C0 3B 00 00    and     dword ptr [edi+18h], 3BC0h
   _11d0000:011D006C 81 6F 18 EB 64 00 00    sub     dword ptr [edi+18h], 64EBh
   _11d0000:011D0073 8D 36                   lea     esi, [esi]  ; 垃圾
   _11d0000:011D0075 81 6F 18 D5 C3 FF FF    sub     dword ptr [edi+18h], 0FFFFC3D5h

   等价于mov dword ptr [edi+18h], 0


   暂时就只做了这些,Themida虚拟机里的混淆代码实在太多,要想清理干净恐怕是不容易。这只
是个尝试,希望把代码搞得可读性强些。另外,处理后的代码与原始代码看起来差别较大,调试起
来也难看,可以考虑做个OD插件(不会;-),或着把IDA的数据贴过去。现在bug不少,贴过去肯定是
跑不起来,只是个试验品。

   编码中一个突出问题是对代码的正确识别。IDA SDK中提供的isCode,isData,isHead,个人感觉
不大好用,经常出现误判或漏掉数据。在代码识别时我直接使用了z0mbie写的XDE反汇编引擎。但
仍有问题,主要是对8为寄存器访问代码,不能正确给出src_set和dst_set,不得已自己判断指令。
 
   对代码进行识别时,和以前用字符串匹配没有太多区别,只是增强了读入代码数据的过程(跳过
nop,及在jmp的目的地址继续)。这样就限制了仍然不能做得足够灵活。尤其是同样的助记符可能
对应不只一个opcode,使代码更加冗长。

下面是一个bug例子。

_11d0000:011D0451 30 6F 20                                xor     [edi+20h], ch
_11d0000:011D0454 66 8B 0C 24                             mov     cx, [esp]
_11d0000:011D0458 83 C4 02                                add     esp, 2          ; DeObfuscated code

_11d0000:011D045B 90 90 90 90 90 90 90 90+                db 0Dh dup(90h)

_11d0000:011D0468 E9 B8 07 00 00                          jmp     loc_11D0C25

_11d0000:011D046D 90 90 90 90 90 90 90 90                 db 8 dup(90h)
_11d0000:011D0475 90 90 90 90 90 90 90 90+byte_11D0475    db 0Bh dup(90h)         ; CODE XREF: _11d0000:011D037Dj

_11d0000:011D0480 C1 E8 02                                shr     eax, 2
_11d0000:011D0483 C1 E0 03                                shl     eax, 3
_11d0000:011D0486 09 C0                                   or      eax, eax
_11d0000:011D0488 0F 84 73 FD FF FF                       jz      loc_11D0201

已经经过前4步处理。011D0468的jmp是正确的。但第5步错误地将011D0469的B8 07 00 00 90
误判为mov eax,xxxxxxxx,而后续的指令也恰好满足判断条件:

_11d0000:011D0480 C1 E8 02                                shr     eax, 2
_11d0000:011D0483 C1 E0 03                                shl     eax, 3
_11d0000:011D0486 09 C0                                   or      eax, eax

这3句都是在对同一寄存器操作,且源对象集不包括内存数据或别的寄存器,于是判别为
符合条件的指令序列,进行合并。最后暂且改为连续判断4条指令是否符合条件。少清理
一些总比弄错了好。

   
    怎样才能更加灵活? 也许可以考虑定义一套类似脚本的东西,用来描述混淆代码模式。
然后对描述字符串进行分析,类似于编译过程。另外再用一个汇编引擎,从得到的结果汇编
出所有可能的opcode组合用于判断。这些东西已经超出我的能力了,写这篇东西,希望抛砖
引玉,哪位高人开发出能用于实战的工具。

    插件在IDA 4.7下使用,没有处理的混淆代码还有很多(我怀疑如果榨干全部水份,有
用的代码不组三分之一)。源码过分丑陋,就不拿出来献眼了;-)

附件:themida.rar