• 标 题:关于Armadillo 1.8x-2.x的anti-debug&加壳原理初步探讨和脱壳方法 (12千字)
  • 作 者:leo_cyl1
  • 时 间:2002-4-11 12:14:54
  • 链 接:http://bbs.pediy.com

目标软件:Wealth-Lab Developer 2.0
目标文件:WealthLab.exe
加壳方式:Armadillo 1.8x-2.x
使用工具:WinDbg或trw2000, peditor, WinHex 10.2 SR-2,m$的win32 sdk文档
     
URL:      http://www.silicmdr.com/downloads/WealthLabSetup.exe
本文作者:leo_cyl

    论题中关于Armadillo 外壳的讨论很少,以前hying曾经写过一些,但很少人能理解,现在就

以Wealth-Lab Developer 2.0为例子初步探讨对付Armadillo 外壳的方法。因为我在xp下没装s-ice

,只有WinDbg可用,所以本文兼做WinDbg的简单教学吧,同时为照顾使用trw2000的朋友,我会列出

等价的trw命令。

    Armadillo 外壳反跟踪和反dump挺强的,(至少用procdump不行)。当我们运行

WealthLab.exe后,在Program Files\Wealth-Lab Developer 2.0目录下产生WEALTHLAB.TMP0文件。

这是个不能运行的脱壳文件,也是Armadillo 外壳的至命弱点。

(一)对anti-debug的分析

      首先WealthLab.exe通过CreateFileA 产生ArmXXXX.tmp 文件(在window的temp目录)并作为

dll载入,Armadillo的一些重要功能在ArmXXXX.tmp中,它的一个主要引出函数是GetProgramInfo,

作用是un-packing 和 un-encrypting ,还有就是检查系统中有无调试器。
      用WinDbg载入WealthLab.exe,因为Armadillo采用seh技术改变程序的流程,所以使用以下命

令“sxd *”就是告诉WinDbg捕获异常后不要处理,有原程序处理,对应trw2000的命令是“faults

off”,另外Armadillo 调用IsDebuggerPresent来判断系统中有无调试器,如果用trw2000,当然检

查不到,但用WinDbg时要下断点“bp IsDebuggerPresent”,然后f5执行……中断后来到这里:

kernel32!IsDebuggerPresent:
77e52e92 64a118000000    mov    eax,fs:[00000018]
77e52e98 8b4030          mov    eax,[eax+0x30]
77e52e9b 0fb64002        movzx  eax,byte ptr [eax+0x2]
77e52e9f c3              ret    《==== eax=1表示系统有调试器,所以将eax改为0
                                    (如果用trw2000,忽略这里)

然后f5执行……中断后来到这里:
04064be 33854cfbffff    xor    eax,[ebp-0x4b4]
004064c4 8be8            mov    ebp,eax
004064c6 b804000000      mov    eax,0x4
004064cb cc              int    3  〈==== seh技术!
004064cc 8b0dbc574100    mov    ecx,[image00400000+0x157bc (004157bc)]

看一下seh链“dd fs:0”最后查到地址40a944。下命令“gn 40a944”(trw2000中要用 INT3HERE

OFF命令,并下断点bpx 40a944)来到这里:
0040a944 55              push    ebp
0040a945 8bec            mov    ebp,esp
0040a947 83ec08          sub    esp,0x8
0040a94a 53              push    ebx
0040a94b 56              push    esi
0040a94c 57              push    edi
0040a94d 55              push    ebp
0040a94e fc              cld
……
……

以上代码产生ArmXXXX.tmp,并获得引出函数地址(在此忽略),执行到00406807  时将调用

GetProgramInfo

004067f6  mov dword ptr [ebp-0x134],0x4157dc
00406800  lea    ecx,[ebp-0x154]
00406806  push    ecx
00406807  call dword ptr [ebp-0x490]{ARM1!GetProgramInfo (1000b0c0)} 〈=进入
0040680d  add    esp,0x4
00406810  and    eax,0xff
00406815  test    eax,eax
00406817  jnz    image00400000+0x6823 (00406823)

进入ARM1!GetProgramInfo (1000b0c0)后来到这……

1000913c 8065d800        and    byte ptr [ebp-0x28],0x0
10009140 8365fc00        and    dword ptr [ebp-0x4],0x0
10009144 0f018dbcfdffff  sidt    [ebp-0x244]      〈==[ebp-0x244]中防入idt表
1000914b 8b85befdffff    mov    eax,[ebp-0x242]
10009151 83c008          add    eax,0x8      〈=== int2 handle的地址
10009154 8b18            mov    ebx,[eax]
10009156 83c010          add    eax,0x10      〈===int3 handle的地址
10009159 8b00            mov    eax,[eax]
1000915b 25ffff0000      and    eax,0xffff
10009160 81e3ffff0000    and    ebx,0xffff
10009166 2bc3            sub    eax,ebx      〈== int2,3 handle的地址相减
10009168 83f81e          cmp    eax,0x1e
1000916b 7547            jnz    ARM1!ReleaseHook+0x3c5b (100091b4)
1000916d 8b8548fcffff    mov    eax,[ebp-0x3b8]
10009173 050c010000      add    eax,0x10c

这是第二个anti-debug代码,判断int2,3 handle地址的距离,正常的话小于0x1e。如果安装了

int3 handle(某些调试器会)的话,大于0x1e。奇怪的是WinDbg不能走完这段代码,在1000914b

处异常(trw2000就可以)。所以在10009144 处,我把eip改到10009168,并改eax小于0x1e。

f5继续执行,其间在kernel32!IsDebuggerPresent又会中断一次,将返回值改为0;来到这里:

1000b82e push    ebx
1000b82f push    0x3
1000b831 push    ebx
1000b832 push    eax
1000b833 call    ARM1!GetProgramInfo+0x8c8 (1000b988)
1000b838 pop    ecx
1000b839 push    eax
1000b83a call    dword ptr [10013048]{kernel32!CreateFileA (77e5a837)}
1000b840 cmp    eax,0xffffffff
1000b843 jz      ARM1!GetProgramInfo+0x78e (1000b84e)
1000b845 push    eax
1000b846 call    dword ptr [ARM1!NukeNow+0x6da1 (10013044)]
1000b84c jmp    ARM1!GetProgramInfo+0x799 (1000b859)
1000b84e call    dword ptr [10013060]{ntdll!RtlGetLastWin32Error}
1000b854 cmp    eax,0x2
1000b857 jz      ARM1!GetProgramInfo+0x79d (1000b85d)
1000b859 mov    byte ptr [ebp-0x1],0x1

eax为"\\.\SICE", "\\.\NTICE", 和 "\\.\SIWDEBUG"  ,哈哈,检测s-ice!如果它们存在或

RtlGetLastWin32Error(win98是GetLastError )不等于2,将设立DEBUG 标志。注意这里重复3次



以上是anti-debug的分析,对付它的方法大家都知道了吧!  :)

(二)脱壳

      WealthLab.exe运行后,在工作目录下产生WEALTHLAB.TMP0文件。这是个不能运行的脱壳文件

。把改名为WEAL-NUPACK.exe。用peditor查看,OEP是4086fc。用WinHex 10.2打开,发现从0x400到

0x40400共256k的内容被添入0x58,即 pop eax的机器码。只要找回这部分代码就是完整的脱壳文件

了。但不能用procdump,系统会崩溃,幸好WinHex提供了类似功能。先用peditor的FLC计算器计算

0x400和0x40400的虚拟地址,分别是0x401000,0x441000。
    WealthLab.exe运行后,在运行WinHex ,按ALT+F9 开始内存编辑,选WEALTHLAB.TMP0进程,

来到偏移0x401000初,选0x401000到0x441000(共256k)的一块内存,复制下来,并把它粘贴到

WEAL-NUPACK.exe对应的地方(0x400到0x40400共256k)。
    到此已经把加壳文件还原了,但还是不能运行!why?呵呵……因为OEP不对嘛!

(三)找OEP
    前面两步还挺容易,找OEP却把我难住了,足足花了3个小时才找到。因为外壳和原程序在两个

不同的进程中,地址空间不一样,debug起来有困难,幸好我们知道是外壳产生了原程序的进程,所

以断点CreateProcessA是关键!
    从新载入WealthLab.exe,躲过anti-debug,继续从第一步描述的地址开始,下断点“bp

CreateProcessA”,来到这里:

:00408908 6820594100              push 00415920
:0040890D 8D45B8                  lea eax, dword ptr [ebp-48]
:00408910 50                      push eax
:00408911 6A00                    push 00000000
:00408913 6A00                    push 00000000
:00408915 6A04                    push 00000004
:00408917 6A00                    push 00000000
:00408919 6A00                    push 00000000
:0040891B 6A00                    push 00000000

* Reference To: KERNEL32.GetCommandLineA, Ord:00CAh
                                  |
:0040891D FF15D4004100            Call dword ptr [004100D4]
:00408923 50                      push eax
:00408924 8B4D08                  mov ecx, dword ptr [ebp+08]
:00408927 51                      push ecx

* Reference To: KERNEL32.CreateProcessA, Ord:0044h
                                  |
:00408928 FF1544004100            Call dword ptr [00410044]
:0040892E 85C0                    test eax, eax
:00408930 7527                    jne 00408959
:00408932 C705E057410009000000    mov dword ptr [004157E0], 00000009

外壳在这里产生WEALTHLAB.TMP0进程,在此前已经把环境变量和命令行参数设定好了。f10执行10多

行来到这里:


* Reference To: KERNEL32.ResumeThread, Ord:022Ch
                                  |
:00406DC1 FF152C004100            Call dword ptr [0041002C]

……
……
……

* Reference To: KERNEL32.GetExitCodeProcess, Ord:010Bh
                                  |
:00406DE2 FF1540004100            Call dword ptr [00410040]
:00406DE8 81BD4CF2FFFF03010000    cmp dword ptr [ebp+FFFFF24C], 00000103
:00406DF2 7423                    je 00406E17
:00406DF4 C705E05741000A000000    mov dword ptr [004157E0], 0000000A

……
……
……

* Reference To: KERNEL32.Sleep, Ord:0296h
                                  |
:00406E19 FF1558004100            Call dword ptr [00410058]
:00406E1F 8B1524594100            mov edx, dword ptr [00415924]
:00406E25 52                      push edx

* Reference To: KERNEL32.SuspendThread, Ord:0298h
                                  |
:00406E26 FF1554014100            Call dword ptr [00410154]
:00406E2C 83F8FF                  cmp eax, FFFFFFFF
:00406E2F 7530                    jne 00406E61

……
……
……

* Reference To: KERNEL32.GetThreadContext, Ord:0167h
                                  |
:00406E6F FF1538004100            Call dword ptr [00410038]
:00406E75 85C0                    test eax, eax
:00406E77 7530                    jne 00406EA9
:00406E79 C705E05741000A000000    mov dword ptr [004157E0], 0000000A
:00406E83 A124594100              mov eax, dword ptr [00415924]

注意,实际上外壳自身作为调试器来启动WEALTHLAB.TMP0进程,把WEALTHLAB.TMP0进程挂起后调用

GetThreadContext获得线程Context即“线程上下文”,如果不懂的话,请看看有关操作系统原理的

书吧。获得线程Context有什么用呢?等一下有说明……,继续执行:

* Reference To: KERNEL32.ResumeThread, Ord:022Ch
                                  |
:00406ED2 FF152C004100            Call dword ptr [0041002C]
:00406ED8 E9EAFEFFFF              jmp 00406DC7

……
……
……

* Reference To: KERNEL32.VirtualProtectEx, Ord:02C4h
                                  |
:00406F08 FF1558014100            Call dword ptr [00410158]
:00406F0E 85C0                    test eax, eax
:00406F10 7523                    jne 00406F35
:00406F12 C705E05741000A000000    mov dword ptr [004157E0], 0000000A

……
……
……

:00406F35 8D8554F2FFFF            lea eax, dword ptr [ebp+FFFFF254]
:00406F3B 50                      push eax
:00406F3C 6A02                    push 00000002
:00406F3E 6894554100              push 00415594
:00406F43 8B0DCC224100            mov ecx, dword ptr [004122CC]
:00406F49 330DD8224100            xor ecx, dword ptr [004122D8]
:00406F4F 330DE0224100            xor ecx, dword ptr [004122E0]
:00406F55 330DF0224100            xor ecx, dword ptr [004122F0]
:00406F5B 51                      push ecx
:00406F5C 8B1520594100            mov edx, dword ptr [00415920]
:00406F62 52                      push edx

* Reference To: KERNEL32.WriteProcessMemory, Ord:02E9h
                                  |
:00406F63 FF1534004100            Call dword ptr [00410034]
:00406F69 85C0                    test eax, eax
:00406F6B 7523                    jne 00406F90

看看上面代码,呵呵…… 原来外壳将WEALTHLAB.TMP0进程的部分段(即内容是0x58的那部分)设为

类似不可读写或执行的属性,来触发自己解码。前面说过外壳将作为调试器而WEALTHLAB.TMP0正好

是被调试器!具体可看看hying以前关于Armadillo 外壳的帖子,精华里有。
继续执行:

:00407035 6A64                    push 00000064

* Reference To: KERNEL32.Sleep, Ord:0296h
                                  |
:00407037 FF1558004100            Call dword ptr [00410058]
:0040703D 8B0DD4224100            mov ecx, dword ptr [004122D4]
:00407043 330DF8224100            xor ecx, dword ptr [004122F8]
:00407049 330DE0224100            xor ecx, dword ptr [004122E0]
:0040704F 330DF0224100            xor ecx, dword ptr [004122F0]
:00407055 898D18F3FFFF            mov dword ptr [ebp+FFFFF318], ecx
:0040705B 8D9560F2FFFF            lea edx, dword ptr [ebp+FFFFF260]
:00407061 52                      push edx     
:00407062 A124594100              mov eax, dword ptr [00415924]
:00407067 50                      push eax

* Reference To: KERNEL32.SetThreadContext, Ord:0283h
                                  |
:00407068 FF158C004100            Call dword ptr [0041008C] 〈==关键啊!!!
:0040706E 85C0                    test eax, eax
:00407070 7523                    jne 00407095

外壳调用SetThreadContext来改变WEALTHLAB.TMP0进程的eip来达到改变EOP的目的!!!
关于SetThreadContext的说明请看MSDN文档。其中edx是Context指针,edx+b8是eip的值;
下命令“dd edx+b8"将看到OEP的地址是006a19c4。(有关Context结构请看附录),用peditor将

WEAL-NUPACK.exe的OEP改为006a19c4,到此脱壳完成,脱壳文件完美运行!!

(四)总结
    外壳作为调试器来调试加壳程序,并利用类似页异常的处理方式来动态解码,利用调试函数

SetThreadContext来动态改变加壳程序的OEP,达到隐藏OEP的目的。
    其实相对来说,Armadillo 比aspr要容易多了,(如果你对window的调试原理有了解的话)如

果再加上类似aspr的花指令和反反复复的seh,来加强反静态分析,应该是个保护很强的外壳。

附录:
  Context结构的声明(来自winnt.h 注释是我加的)
typedef struct _CONTEXT86 {
    DWORD ContextFlags;

    DWORD  Dr0;
    DWORD  Dr1;
    DWORD  Dr2;
    DWORD  Dr3;
    DWORD  Dr6;
    DWORD  Dr7;

    FLOATING_SAVE_AREA FloatSave;  //FLOATING_SAVE_AREA的大小是112byte
    DWORD  SegGs;
    DWORD  SegFs;
    DWORD  SegEs;
    DWORD  SegDs;

    DWORD  Edi;
    DWORD  Esi;
    DWORD  Ebx;
    DWORD  Edx;
    DWORD  Ecx;
    DWORD  Eax;

    DWORD  Ebp;              // 偏移是0xb8
    DWORD  Eip;
    DWORD  SegCs;              // MUST BE SANITIZED
    DWORD  EFlags;            // MUST BE SANITIZED
    DWORD  Esp;
    DWORD  SegSs;

} CONTEXT86;