【转载】DELPHI程序Themida脱壳之VM-OEP的代码还原

Eddy 发布于2009-12-13 22:2:59 分类: 程序设计 已浏览loading 网友评论0条 我要评论

作者:Kissy

目标程序为一个下载者生成器,PEID查壳如图所示

1.jpg

设置OD忽略所有异常,用PhantOm.dll插件隐藏OD,设置如下

2.jpg

载入我们的目标程序。停在入口

3.jpg

由于这篇文章的重点是VM代码的修复,脱壳过程不再赘述,详情可参见FLY和windycandy两位大哥的脱文,或者参考我之前做的一个语音手工脱壳教程《DELPHI程序的TMD壳手工脱壳语音教程》
http://www.unpack.cn/viewthread.php?tid=23488&extra=page%3D1
这里我们采用论坛的一位朋友修改的FXYANG大哥的一个脚本取代手工的工作。运行脚本后停在伪OEP

4.jpg

这个时候IAT也随之处理完毕

5.jpg

我们这里记录下脚本结束后停留的地址00406A38以及此时寄存器中EAX的值

6.jpg

也就是004B8BB4。这里我找来一个无壳的DELPHI程序来观察入口部分的特征

DELPHI程序的入口特征很明显,但是也比较多变,总体来说有一定的规律可循:第一个关键CALL之前的代码基本类似,第一个关键CALL之后的代码我们可以看作是对寄存器的赋值操作以及一些CALL指令。虽然有些DELPHI程序的OEP处代码很长,其中夹杂着一些其它的操作,我们多留心寄存器和堆栈,可以慢慢跟出来。所幸TMEMIDA不会VM很多句,我所见最长的也不超过15句(先泄密下:这篇文章中的目标程序是14句 HOHO~~ ^_^).
那么我们这里也就可以写上部分修复后的OEP代码了:

  1. PUSH EBP
     
  2. MOV EBP,ESP
     
  3. ADD ESP,-10
     
  4. MOV EAX,004B8BB4
     
  5. CALL 00406A38

一般情况下就是这几句了,但是我们观察到OD的堆栈窗口中压入了一个值

8.jpg

我们发现对应的是EBX中的值0012FF9C,所以这里我们还要补上PUSH EBX这句代码。经验告诉我们,我们应该把这句话放在ADD ESP,-10的前面,那么这里我们能够修补出来的代码如下:

  1. PUSH EBP
     
  2. MOV EBP,ESP
     
  3. PUSH EBX
     
  4. ADD ESP,-10
     
  5. MOV EAX,004B8BB4
     
  6. CALL 00406A38

我们单步走到RETN,走出这个CALL来到了THEMIDA壳的VM代码部分

9.jpg

论坛里的一位前辈给出了从VM中找代码的一个思路,我总觉得不够通用,而且稍不留神,容易出错。我自己想出了个办法来简化和明确我们的操作
这里我就分2步来进行:
1.        找出所有被偷取的关键CALL
2.        在找到关键CALL的基础上通过寄存器和堆栈的变化来“猜”出前面提到的赋值操作,也就是MOV指令
之所以这么做,是为了尽可能避免在VM中迷失方向,因为CALL后面的地址是代码段,而非壳段,当我们从VM中来到代码段的时候,我们就知道我们抓住了主心骨 ^_^

现在OD停在007901F2我们单步走几步,经过2个JMP后在代码段F2下断,SHIFT+F9来到

10.jpg

继续单步经过几个JMP后对代码段下断,SHIFT+F9运行,我们会发现来到了

11.jpg

这也就是我们的第二个CALL了,这里就不要花力气思考在这2个CALL之间有些什么指令,我们现在的目标是先找到全部的CALL。单步到004672AE,继续F8,再次来到VM中。重复先前的单步+CODE段下断然后SHIFT+F9的操作,几次操作后再次来到程序的代码中,这里停留的地址也就是第三个CALL了

12.jpg

单步走出这个CALL,又到了VM中。我们现在有心理准备了,^_^ 不怕。继续用我们的老办法来对付他,很快就来到了004B8FE6处

13.jpg

我们发现,这里应该就是OEP附近了。分析下代码,我们再拿之前那个无壳的DELPHI程序的入口来做对比

14.jpg

那么OEP应该就是004B8FB4了,当然也有特例的情况,但大部分情况下就是这样了。我们后面也可以来验证一下。
那么到这里,我们的第一个找所有关键CALL的步骤也就完成了。我们写出部分的修复代码,未找到的部分用××××来表示,如下:

  1. PUSH EBP
     
  2. MOV EBP,ESP
     
  3. PUSH EBX
     
  4. ADD ESP,-10
     
  5. MOV EAX,004B8BB4
     
  6. CALL 00406A38
     
  7. ××××
     
  8. CALL 00467298
     
  9. ××××
     
  10. CALL 00466E90
     
  11. ××××

后面紧跟着就是正常的未被处理的代码了。

接下来的操作就比较简单了,因为我们找到了主心骨。^_^ 但是也是这篇文章的重点部分咯~各位看官别睡着咯~~
重新载入目标程序,跑完脚本后,停留在伪OEP处,单步走到CALL的出口RETN,再单步一次停留在VM的入口,我们用截图软件截下寄存器此刻的各项数值(有时候也需要截取堆栈,但是一般情况下不需要,此例中也不需要)如图:

15.jpg

然后在00467298处F4断下来,此刻再将寄存器中的各项值用截图软件截取下来

16.jpg

我们发现只有EAX和EBX中的值发生了变化。我们CTRL+B搜索EC DB 4B 00找到004BC85C处

17.jpg

那么可以写出:

  1. MOV EBX,DWORD PTR [004BC85C]――――――<1>
复制代码

CTRL+G来到EBX的004BDBEC中,发现正好与EAX中的数值对应上,

18.jpg

当然我们这里可以搜索C8 17 C9 02来查找地址
于是我们又可以写出:

  1. MOV EAX,DWORD PTR [EBX] ――――――<2>
复制代码

哈哈 我们发现这么做的好处了吧。不必受VM的困扰,抓住关键部分,从VM的入口再到VM的出口,从整体上来看他究竟干了些什么,这样就不会迷失在VM的海洋中了~
这样我们处理完一处了,我们转到EIP。走出这个CALL,停留在VM的入口,对寄存器截图

19.jpg

CTRL+G来到00466e90,F4断下来,再次截图做对比

20.jpg

发现只有EAX和EDX的值发生了改变(ESP的改变不是我们关心的)CTRL+B搜索3C 90 4B 00 发现没有找到,那么我们就直接写成:

  1. MOV EDX,004B903C――――――<3>

以及:

  1. MOV EAX,DWORD PTR [EBX]――――――<4>

继续单步走出这个CALL ,在VM的入口截图

21.jpg

CTRL+G来到正常代码的起始处004B8FE6,F4断下,再次截图

22.jpg

只有EAX和ECX做了变动,根据先前的方法很容易找到这里的2句指令:

  1. MOV EAX,DWORD PTR [EBX]――――――――<5>
     
  2. MOV ECX,DWORD PTR [004BC508]――――――<6>
复制代码

到这里我们修复代码的工作基本上就完成了,我们组装整理下,得到如下14条修复后的指令:

  1. push ebp
     
  2. mov ebp,esp
     
  3. push ebx
     
  4. add esp,-10
     
  5. mov eax,004b8bb4
     
  6. call 00406a38
     
  7. MOV EBX,DWORD PTR [004BC85C]
     
  8. MOV EAX,DWORD PTR [EBX]
     
  9. call 00467298
     
  10. MOV EDX,004B903C
     
  11. MOV EAX,DWORD PTR [EBX]
     
  12. call 00466e90
     
  13. MOV EAX,DWORD PTR [EBX]
     
  14. MOV ECX,DWORD PTR [004BC508]
复制代码

从我们先前找到的OEP-004B8FB4起用IDAFicator插件把这些命令导入或者逐条手工敲入,发现代码不多不少刚刚好,心里窃喜下先,证明我们的修复是没问题的,OEP找得也是正确的。^_^
在004B8FB4新建EIP,LOADPE修正镜像,完整转存,再用IMPORTREC修复IAT抓取保存,运行正常,至此我们的工作也就圆满结束了。

23.jpg



当然还有脱壳后的优化操作,但不在本文之列。忽忽~~

已经有(0)位网友发表了评论,你也评一评吧!
原创文章如转载,请注明:转载自Eddy Blog
原文地址:http://www.rrgod.com/program/235.html     欢迎订阅Eddy Blog

记住我的信息,下次不用再输入 欢迎给Eddy Blog留言