• 标 题:[实例解析四][翻译]ARMADILLO和NANOMITES
  • 作 者:FTBirthday
  • 时 间:2004-1-13 周二, 下午11:18
  • 链 接:http://bbs.pediy.com

FTBirthday的Armadillo全程教程--第二章copy-mem II加壳方式

第二章.copy-mem II加壳方式

 

[实例解析四][翻译]牛壳ARMADILLO和 NANOMITES (第一部分)

 

现在我们又开始了,我们将要去击败Armadillo的最后一道防线。我们知道这将是一场硬仗,但值得去打。 所以这回我们要专门解释一下nanomites 保护和unpack的方法,那将会比我们在其它教程中见到的稍微难一点。所有nanomites和 copymemII配合使用,所以为了成功地dump,你需要遵照我在其它两篇教程中写的步骤。

这次我们选了一个比较容易dump的目标软件。这样做仅是为了避免重复我在上次教程中所写的步骤。无论如何,我们只有在dump之后才能对付 nanomites, 所以如果存在nanomites 保护,那么在dump之后作为一项附加任务,我们必须击败nanomites 保护。

 

NANOMITES到底是什么样的地狱般的阴险招数?

 

Nanomites是Armadillo嵌入子进程的INT 3中断,虽然在GETRIGHT FINAL版本里没有使用nanomites 保护,但是却在CRUSADER写的教程里的beta版本里用了。 可能有 50, 100甚至 200个INT 3,它们被放入了子进程的第一个section ,以阻止子进程脱离父进程运行。INT 3中断替代了原始文件的条件跳转。所以每当父进程接收到INT 3中断,它就到存放跳转类型的report中去,看一下标志以决定是否跳转,然后测算出新的EIP,改变到子进程中去然后继续运行直到下一个 INT 3。

问题是我们不知道每个跳转和INT 3的对应关系,所以我们将要在本篇教程中学习如何patch它。 首先我们要学如何手动patch它,然后(在第二篇教程中) 我们将要学习如何让计算机自动逐行patch它。

 

我们将要用 eSTOP SE 3。21做实验


下面是连接:

http://www.nwpsw.com/estopmain.html

在我的FTP里有一份拷贝。

 

我们dump这个程序的方法和以前的方法稍微有所不同。如果我们想尝试用一般的方法去dump它,它不会在 WriteProcessMemory断点停下。我第一次遇到这种情况时,我怀疑这个程序是否用了CopyMemII保护,但是当我停下来去看attach window时我看到了父进程和子进程的进程。 那么它是如何做的呢?

这个程序只是用了nanomites保护,所以在这种情况下, 父进程在unpack过程中没有做任何事情。子进程自己 unpack自己,修改它自己的IAT,自己unpack第一个section然后最终跳向它的OEP。

父进程仅在子进程开始运行时出现了一次,然后INT 3出现了。这回你要是想 dump eSTOP,你就必须得遵照GETRIGHT重建输入表的第二篇教程,找到 magic jump 最后在第一个section设一个BPM ,所以一旦输入表修复完毕它就停在它的OEP上了。一旦你到达这里dump这个进程然后用Import Reconstructor 来修复这一进程的输入表。

在这里如果我们运行dump下来的程序,它将在这里崩溃。

 


 

正如你看到的在406217出现了第一个INT 3,让我们欢迎丛林男孩! 我想Rambo(丛林男孩)可以帮助我解决这个问题,所以我打电话给他,但是他不在家,所以我读了CRUSADER’s 的教程,这可帮了大忙了。读完后,我知道了在 INT3 所在之处我们需要插入一些跳转。所以现在我们需要知道要插入何种类型的跳转。

 

在OLLYDBG中重新启动程序 (是原始程序,不是 dump下来的程序)。 现在我们去找在哪儿父进程开始和NANOMITES合作。在入口处停下,右击鼠标选择SEARCH FOR BINARY STRING 然后输入这个十六进制的字符串03 00 00 80 

在我的机器里它停在了 5C5BAF 

 


 

不用管内存地址,因为在其它Armadillo保护的软件中这个地址会变的。真正关键的是找到那些代码。第一次停下来时来到这一行:

 

005C5BAF   817A 0C 03000  CMP DWORD PTR DS:[EDX+C],80000003

 

最重要的是80000003,可以说那是开始子进程的地址, 因为这正是父进程从WaitForDebugEvent report中得到的。如果是其它任何值,那就不是nanomite保护。BPX 5C5BBC然后把antidebuger byte置0或者bp IsDebuggerPresent 然后把 eax中的值改为 0。运行程序,跳过第一次异常,继续运行。
试用版提示对话框将会弹出,按OK,然后通过shift+F9 组合键跳过一切异常。 现在它将会停在我们下的断点 BPX上。
你可以看到EDX指向WaitForDebugEvent report 所以右击鼠标选择 FOLLOW IN DUMP。我们可以看到下面的report。

 

在图中我们可以看到 API report ,80000003 也在其中。我们同样也可以看到report 的产生出406217(第一个INT 3的地址)。

看起来我们已经进入正轨了当父进程第一次接收到来自子进程的一个INT 3,它解码了一张report,这张report存储了所有INT 3需要的信息。这些信息可以被另存于任何一个安全的地方,以方便我们需要时直接查阅而不必重新启动父进程。

接下来的条件跳转,第二次来到这里时要留心,我们没有进入到decrypt routine。

 


我并不关心Armadillo解码report的方法, 我只是想知道解码后的值, 所以我将要喘口气然后直接解释如何做。

 

这是最有趣的一个section。 它需要 API GetThreadContext 去读取子进程的 EIP和标志寄存器的状态。在那儿BPX 然后继续直到你来到API。

用F8跟踪运行API,然后你将会看到下一行存有关于此API的信息。

005C5E4F      8B95 E0F3FFFF MOV EDX,DWORD PTR SS:[EBP-C20]           

在这一行右击鼠标选择 FOLLOW IN DUMP-MEMORY ADDRESS 你将会看到这个API数据信息。


 

如果我们看一下dump下来的程序,在第一个INT 3发生时,我们可以看到EIP的值是406218,标志寄存器EFL的值是00000202。


下面这张图片来自子进程,停在了第一个INT 3上。

 



正如你看到的接下来几行代码就在处理这些值,在Table 1中查看这个INT 3。 现在我们来看 Table 1。

 

运行到ECX 获得Table 1的起始值的那一行停下,Table 1包含了每一个INT 3的内存地址。

如果我到了这一行,再运行一下, ECX 会获得 table 1的起始值,在我的机器里如图:
 


在ECX上右击鼠标选择FOLLOW IN DUMP 到窗口去看Table 1。

 
TABLE 1

 

这张表将会非常有用。稍稍向下翻页我们将会看到我们当前所处的INT 3的地址值,但是在表中这个值是紧接着INT 3的下一个地址值。这对于我们通过patch 或者使用工具修复nanomites 非常有用。 

 

这儿你可以看到我所说的。这就是INT3的下一条指令地址。当父进程接收到INT 3时它就把子进程的EIP和这张表进行比较,以此获得子进程当前的地址以及从section开始的INT 3的数量。

 
这就是Table 1的一切。现在步进到指向 Table 2的地方。

 

EAX 包含了Table 2的起始值。当我们运行到这一行时同样在EAX上右击鼠标并选择FOLLOW IN DUMP ,我们将会看到Table 2。

 
TABLE 2

 

这张表包括了每一个INT 3的类型号。它们的顺序和Table 1是一样的,也就是取决于INT 3的地址,所以 0C属于Table 1中的第一个INT 3以此类推。

我们使用变量X来描述在两张表中属于同一个INT 3值,可以表示为:

  TABLE 1
Table 1的起始值 + X * 4

TABLE  2

Table 2的起始值+ X

 所以如果X=0

 Table1=3E5cE8 + 0 * 4= 3e5ce8
Table2= 3e6480 + 0  =3e6480

 这表明当X=0时,第一个INT 3在Table 1 中属于它的值是 [3e5ce8]=00401565 在table 2中是 [3e6480]=0c 。

 X=1 将是第二个INT 3
Table 1=3E5cE8 + 1 * 4=[ 3e5cec]=00401633
Table 2= 3e6480 + 1 =[3e6481]=04

 我希望你能够明白这些,因为这对你有用。
这部分可能不易理解,所以需要仔细地阅读。 我们需要搞清楚Table 2那些值的含义。它们是一些取决于标志值的跳转。 

 

PUSH ECX 保存它从Table 2获得的将要被执行的属于INT 3的条件跳转。这里,它的值是04 ,所以我们需要知道它是什么样的条件跳转,因为04代表相同的条件跳转。我们进入那个 Call 然后看到:

 005C7507  |。  FF248D C8765C>   JMP DWORD PTR DS:[ECX*4+5C76C8]

 这里我们看到了一个取决于ECX的值的跳转。取决于表中ECX的值和4的乘积+ 5C76C8的值,它将跳转到不同的地址。很容易通过改变ECX的值来看看它究竟 跳向何方,然后在OLLYDBG中加注解。 



这回如果ECX=04它将跳向5C750E。写下它,然后试着从00 改到11。


 

如果我们依次尝试其它的值,我们将的到列表如下:

 
0         前往

 

1         前往

 

以此类推

 


这是由从Table 2中获得的值产生的完整的一个跳转列表,它们和最终的那个跳转有关。这个列表将会非常有用,在其它Armadillo中从Table 2中获得的值产生的完整列表将有所不同。

 
改回ECX=04; 在Crusader的帮助下,我们知道如果EAX的值是 1,那将是一个跳转,但是如果EAX的值是0,将不是跳转,稍加跟踪你将会看到属于数字04的代码是:


005C750E  |> \B0 01         MOV AL,1    


这里把 AL的值改变为1,所以无论标志的值是什么它都会跳转。然后属于04 的跳转类型就是JMP。

现在我们需要知道它将跳向哪里。答案在走出这个CALL之后。

 
 

这里测试了EAX里的值,如果值是1,就跳。它带领我们去Table 3,在那儿父进程计算出新的EIP,就是子进程将要跳向的地方。



上图中的第二行是Table 3的起始值。这个表告诉我们所有的跳转将要跳向何方。

TABLE 3

 

这就是Table 3。它以四个字节为单位,所以你想在Table 3中定位任何一个值,

这样计算:Table 3起始值 + X * 4

我们跟踪到这一行,它将位移值移入EDX,EDX在Table 3中所对应的值将和

INT 3所在的地址值相加。

005C5EF9   。  8B95 20F3FFFF MOV EDX,DWORD PTR SS:[EBP-CE0]

当 EDX= 2A

在下一行 EAX 被赋予Table 3起始值

005C5EFF   。  A1 881A5E00   MOV EAX,DWORD PTR DS:[5E1A88]

在我的机器里 3E6870就是Table 3的起始值

INT 3的地址值是406218,在第三行她被赋给ECX,然后在第四行和EIP相加。

005C5F0A > 。  030C90        ADD ECX,DWORD PTR DS:[EAX+EDX*4]

Table 3起始值+位移2A x 4 给出了这个INT 3在Table 3中对应的值。

新的EIP= 406218+[EAX+EDX*4]=40621C

如果我在子进程中写下JMP 40621C 来代替INT3,就搞定了。 

看子进程如图

 


这里是我修复第一个INT 3。

 

右击鼠标选择 NEW ORIGIN HERE在 406217 然后运行。

 


这是子进程中的第二个INT 3 (40622E)。
如果我们返回父进程,它通过这个API 完成对第一个INT 3的处理

 005C5F41      FF15 80E05D00 CALL DWORD PTR DS:[<&KERNEL32。SetThreadC>; \SetThreadContext

这是和GetThreadContext正好相反的API ,GetThreadContext读取EIP。现在通过SetThreadContext写入新的EIP,改变子进程的EIP为40621C然后继续运行。

这就是父进程的工作方式,运行父进程去研究第二个INT 3。

停在数学运算的开始 

 
 

E DX 又一次指向了report,让我们看看那儿有什么。

 
 

又是80000003 这个值,然后是新的INT 3的地址值在40622E。 这回下来那一行的跳转跳了,跳过了tables被解码的代码段。

当我们停在GetThreadContext我们看到它给子进程赋EIP值为40622F,标志寄存器的值是246。

 
 

正如我们从PUSH ECX看到的,这次INT 3类型号是06。

 

进入CALL 去研究INT 3类型号为 06的跳转的类型。
到达我们曾经标记过的地方。

 

在第二行付给EAX标志寄存器的值246,所以这将是一个条件跳转。这一行暗示标志寄存器的值将回被检测。

 005C75DB  |。  83E0 40       AND EAX,40

 让我们看看40对于标志寄存器的意义。这次父进程标志寄存器的值EFL为287。我把它写下来,因为一会儿会用到的。
右击鼠标把它改为40。我可以看到标志已经改变了。

 Z 是唯一起作用的标志,所以如果我们把任何一个值和40相“与” ,只有当两个操作数都为1时结果才为1,40的二进制如图,只有当另一个操作数对应位为1或者同样的Z标志时结果才不为0, 因为在40中其它位的值都为0。

这样的解释很抽象,还是看图比较清楚一点。

 

这个跳转只取决于标志的状态,如果跳则是JZ,如果不跳则是JNZ。如果我们稍加跟踪在最后一行EAX=1,所以它一定是 JZ 或则 JE。我测试了所有的INT 3 类型号对应的跳转类型,得出下表。

INT 3 类型号对应的跳转类型表

Value

Jump

Hexa Value

1

JO

70

2

JNS

79

3

JNL

7D

4

JMP

EB

5

JNC

73

6

JE/JZ

74

7

JNA

76

8

JS

78

9

Don’t Know

 

A

JNE

75

B

JNO

71

C

JA

77

D

JP

7A

E

JNP

7B

F

JG

7F

10

Don’t Know

 

11

Special Case

 


 

漏掉了一些跳转,它们在这个软件中没有被使用。11H号是一个很特殊的号,因为跳转与否取决于和11的比较,如果为真则跳向循环尾部,并修改EAX为0,所以它变成了NOP。  

 

005C74FA  |。  837D F4 11    CMP DWORD PTR SS:[EBP-C],11

 

我们找到了Table 2 和跳转类型的关系,我们可以很容易的扫清一切道路。把我们先前记下的值重新写入EFL,我的机器里是 287,走出那个call 然后我们到达 test。

 

005C5EF5 > 。  85C0          TEST EAX,EAX

 

EAX 的值为1 所以它跳了。在Table 3 中找它跳向何方。我忘记提醒了,如果这里不跳,则它前往一个循环,在那里它读取Table 4获得它该跳向何方。

 

005C5EF9   。  8B95 20F3FFFF MOV EDX,DWORD PTR SS:[EBP-CE0]

 

在第一行把位移2B 赋给EDX,在接下来的一行 EAX 指向Table 3的起始值。

 

005C5EFF   。  A1 881A5E00   MOV EAX,DWORD PTR DS:[5E1A88]

 

这一行把子进程中INT 3的EIP 40622F和 Table 3 对应于这个INT 3的值27相加。新的 EIP是EIP=406256

 

我们可以在子进程中写入JE 406256。

 


 

这些就是子进程中被修复的INT 3。我们可以从Table 1中找到跳转的首字节, 然后我们前往Table 2找对应的值,第二个字节是table 3 中的值减去1。

 

如果你记住这一点,你将会看到对应于第一个INT 3 Table 3中的值是0000004 ,只取04 然后减1,所以变为03 这就是第二个字节 03 。

 

第二个INT 3,Table 3中对应值为 00000027  27-1=26 所以第二个字节是26。

 

所以我们可以修复所有的INT 3,第一个字节到“INT 3 类型号对应的跳转类型表”中去找对应值,然后第二个字节到Table 3中去找对应值。我希望你看懂了这篇教程,因为在第二部分我们将打patch代码让它自动修复。 

 

原著:Ricardo Narvaja

翻译:FTBirthday

 

译者注:

一字一句翻译好累,还得理解,用准用好每一个词,希望对大家有帮助。


FTBirthday的Armadillo全程教程--第二章copy-mem II加壳方式

第二章.copy-mem II加壳方式

 

[实例解析五][翻译]牛壳ARMADILLO和 NANOMITES (补充部分)

 

本章讲的是对eSTOP SE 3。21 的patch 的改进。也会改进针对日志中长跳转的列表记录方式。

开始几步和第一部分教程类似, 但是作如下补充:

0067A018    0FB6B9 90896700 MOVZX EDI,BYTE PTR DS:[ECX+678990]

这条指令将TABLE 4中的值赋给了EDI 。It shows the number of places it changes if it doesn’t jump(FTBirthday:此句话理解不通,故保留原文,请读者自己斟酌)。

剩下的都一样,但是用和0FF以及-0FF 的比较来代替结束它在日志中记录长跳转的值的过程。

对于短跳转的部分没有变化。



正如我们看到的

0067A0FA    3D 97984500     CMP EAX,dumpedlo。00459897

这就是那个用来结束记录过程的比较。它和TABLE 1的最后一个值进行比较,  最后一个INT 3的位置的最小值是1。 

你需要在67A105处下断点,在那儿截下那个过程。


再往下就是一个处理长跳转的新的过程。

首先,如果它跳转,就把跳转的位移值(这个值被放在 EBX中)加到 EAX中的地址值上去,然后进行比较,确定这个累加值是否在401000 678000之间 (这是进程的地址空间), 如果超过了范围则退出这个长跳转的处理过程 (这种情况下说明不是一个长跳转)。

最后,它测试如果前面两个跳转都不跳其位移是否小于5。对于长跳转这个条件不满足,所以当满足这个条件时也会退出这个长跳转的处理过程。 

如果所有的条件都成立则是长跳转,那它就前往nop。

这里我们设下几个BREAKPOINT CONDITIONAL LOG, 这将有助于记录长跳转的信息。

 

第一个是在67A12E


这是个空的 BREAKPOINT CONDITIONAL LOG用来判别不同的入口。

第二个是在67A12F 它记录了存放在EAX中的长跳转的位置。


第三个是在67A130 它记录了存放在EDX中的长跳转的类型。


第四个是在67A131 它记录了存放在EBX中的长跳转将要跳往的地址。


最后一个是在67A132 它记录了存放在EDI中的长跳转被调用的次数。


下图我们看到了修改patch之后在 LOG中的结果。正如你看到的它修复了短跳转并显示了六个长跳转。


第一个就是我们手动修复的那个INT 3: 40878F JNE 4088BB (查看第一部分教程的那张表,从而判定属于0A的跳转类型是JNE)。

以此类推:

411097                         为jne 4111c7

4190BA 为 je 419222

4190d3                         为je 419222

4190ec             为 je 419222

419105                         为je 419222

就是这么多了。

2003年5月

原著:Ricardo Narvaja

英文翻译:TeN$HiN

中文翻译:FTBirthday

译者注:

本人才疏学浅,尽管一字一句翻译,加上自己的理解,尽量用准用好每一个词,难免有疏漏之处,敬请见谅。