加密解密

关注公众号 jb51net

关闭
网络安全 > 加密解密 >

Getright 5 手动脱壳和重建IAT--第一部分(图)

佚名


这是一篇Armadillo加壳软件Getright 5.01的脱壳译文,我是参照Ricardo Narvaja的“Getright 5 脱壳和重建IAT”的文章以及Bighead[DFCG][YCG]的译文,一边实践一边再次翻译的。感谢Ricardo Narvaja和Bighead[DFCG][YCG]。
Armadillo for Dummies: Getright 5 手动脱壳和重建 IAT (第一部分)
因为Armadillo实在是太复杂了,以至于人们已经不得不沿用下面这个手动的方法去脱壳armadillo,这不是我的过错,只是因为armadillo实在是太难脱壳了,然后我将会在这个文档中一步一步地去完成脱壳,同时配上很多图片,只是想让它变得尽量容易理解.我并不想写一个10兆大的参考教程,所以我把这个文档分成两部分.第一部分主要讲如何转储(dump)armadillo保护的程序,然后我会在第二部分中提及一些以前在其它任何教程中都未曾见过的东西,那就是输入表(import table)的redirection和rebuilding.
在我们开始之前,我必须声明这篇参考教程只在Windows XP上测试.请不要在 windows98或者2000中尝试.原因是只有Windows xp才拥有必要的API,能够把子进程(child)从它的父进程father那儿脱钩下来.
这次练习,我们找到的自愿者是Getright 5 final. Crusader曾经写过关于这个软件的某一个版本的参考教程,但是这篇参考教程在很多方面都不同于crusader写的那篇.
crusader写的那篇参考教程(原版)----附(1)
crusader写的那篇参考教程(FTBirthday翻译版本)----附(2)
你可以从以下连接下载GetRight 5.不管怎样我在我的ftp中保存了一个副本,所以你可以从那儿下载,就不用去管它在网上被放在哪儿了.
http://www.getright.com/
抓起你的鼠标,安装好Getright然后准备作战.Armadillo将会变得软弱无力.
如何脱壳(UNPACK)以及转储(DUMP)Getright 5
在OLLYBG中载入GETRIGHT.exe然后整装待发.我将按照以下的步骤,很容易记住的,你可以在其它任何armadillo保护软件中按部就班.
第一步:重新设置检测ISDEBUGGERPRESENT API的字节
我将要解释如何在任何电脑中去寻找这个字节.
首先从这个连接下载命令行插件:
http://dd.x-eye.net/file/cmdbar10802.zip
解压它然后复制dll到OLLYDBG文件夹.只要你运行Ollydbg你将会在调试窗口的左下角看到一个白色的输入框,在那儿你可以键入很多命令.让我们键入:
Bp IsDebuggerPresent
我们将要使用BP而不是BPX,因为BP可以直接在API上下断点,那正是我们所需要的.
我们必须准确地键入Bp IsDebuggerPresent.记住我们必须保证大小写的顺序,因为如果我们键入了任何不同于以上形式的命令,那么一个错误提示(UNKNOWN COMMAND)将在命令行的右边产生.所以正确地键入BP IsDebuggerPresent然后按回车. 如果在按回车后没有错误提示出现,那就说明这个BP已经生效.
现在按F9运行程序,它将会中断在API IsDebuggerPresent

在这张图中我们可以看到OllyDBG中断在API IsDebuggerPresent(红色), 然后按F7步进到第三行(白色)
77E52749 0FB640 02 MOVZX EAX,BYTE PTR DS:[EAX 2]
一旦到达那儿,看一下disassembly,在那儿我们可以看到如下图的内容:

这儿我们可以看到在我的机器中偏移是7ffdf002,包含了值01.这个地址在其它机器中可能会不同. 值01意味着调试器被检测到了,所以写下这个地址,在任何时候我们在ollydbg中运行程序,都必须把它修改为00,从而避免被packer检测到.记住在ollydbg中运行或重运行程序的任何时候修改此值为00.
我们将以重启ollydbg (ctrl F2)来结束第一步,到转储窗口(DUMP)单击右键选择"前往 表达" 7ffdf002或者其它任何先前你记下的地址,然后把它的值从01修改为00.


把它改为00.

第二步:BP WAITFORDEBUGEVENT
这一步并非必要,但却是一个好的选择,当它停止时在转储(DUMP)区看到report.让我们看看该怎样做.
首先我们需要在命令行输入BP WaitForDebugEvent,和我们输入IsDebuggerPresent的方法是相同的.
现在运行它,当它停下时我们可以在堆栈窗口(Stack window)看到关于这个API的所有参数信息.

看一下第二行.那儿我们可以看到它将在哪儿显示report,所以到转储窗口(dump window), 右键选择 "前往 表达"= 12EFF8.注意这个值在一些机器上可能会不同,所以前往你的机器显示的值.

我们在上面的图中看到的是我们将要称之为"API report"的东西.主程序(让我们把它称为Father)通过那里得知secondary program(让我们把它称为son)发生了什么.
现在我们可以很容易地通过在命令行输入BC WaitForDebugEvent来删除这个断点.
所有的操作都是为了在转储窗口(dump window)看到report,在这个report中我们可以看到入口点OEP(entry point).
第三步: BP WRITEPROCESSMEMORY

像图中一样设置这个断点然后点击"运行".也许你会在一些例外(exceptions)处停下,但是你可以通过按SHIFT F9很容易地走过.一旦我们停在了WriteProcessMemory API不要做任何事情,看下图:
正如我们在下图中看到的, father将要复制给son的第一个块(block)的所有信息,都可以在堆栈窗口(STACK window)看到.

FTBirthday:由经验,我觉得很难走到这一步,但是我知道是在005F949E处call了WriteProcessMemory如图:

我通过一遍遍试验,得出了最好的方法:等到前两次在别处call了WriteProcessMemory后,左手一直按住shift, 右手连续按F9大约31次,就可以顺利到达.
这些信息被保存在父进程的一个缓冲区从3B8FB0开始,然后它会复制给子进程一个从538000开始的 1000 bytes (BYTES TO WRITE)的块(block),所以如果我们到了这一步,我们可以很容易理解child的第一个section是完全空的,所以当它试图执行到OEP时,它报告一个error因为那儿没有任何东西,所以 father得到了通知正如我们可以在father的report中看到的,所以它停止了运行,复制必要的数据块,然后继续运行直到下一个error.被复制的数据块的大小是1000 bytes,所以当程序试图执行任何超过这个大小的block外的指令时, 另一个error将会发生,然后这个error会被通知给father, father会复制另外1000 bytes的块block然后继续.
顺便说一下,我们可以假设第一个error发生是因为son (坏小子)call了它的入口点OEP,它的值应该就在 report上.显然OEP的值必定在第一个block,father复制过去来解决son报告的error.
这个块从538000开始知道538fff.OEP值必定在这些值中. 让我们看一下REPORT.看转储窗口(DUMP window) 我们先前已经知道了指向report的指针12EFF8.

太好了!在那儿我们至少看到了三次这个值: 538540.数学课告诉我538540比538000大,比538fff小 (那些是father试图拷贝给son的那个块的起止值)所以我可以确信538540就是child的OEP.
让我们从API WriteProcessMemory来改变断点的性质.知道哪个块被拷贝不是很好吗.可以通过下面的方法做到.首先选择bpx所在的行,按F2移除bpx.
右击选择"断点/条件记录".现在在对话框中准确地填入如图所示的值.这样做的目的是使我们在记录窗口中看到所有被father decrypts并拷贝给son的blocks.

第四步: NOP THE CRIPTER CALL
在先前的参考教程s中我们知道,这儿我们是在decripter call.Father为son decrypts了一个block.但是还有一个cripter call用来encrypts或者destroys已使用的blocks来避免被转储dump.现在的任务是如何找到这个call并把它nop掉.
我现在在WriteProcessMemory API,所以我打开"呼叫堆栈窗口"(Alt K)在那儿我可以看到:

在这个"呼叫堆栈窗口"越靠上面的是越最新被执行的.从CALLED FROM 我们可以看到5F949E是father calls API的地方. 如果我们向下看,我们可以看到另一个call.所以我们获得了decripter call的offset.
DECRIPTER CALL= 5F88D1
通过"前往 表达" 5F88D1跳到那儿 ,或者左键双击它.

这是好的CALL,它用来decrypts.现在去找坏的那个,稍稍向下翻屏或者右击选择"查找参考/呼叫目标" 选择出现的两个参考中的另一个.

它在这儿呢!
重新加密的call--ENCRIPTER CALL = 5F8A24

现在我们把它nop掉.我们选择这个call按空格写入nop.



第五步: 运行API并在OEP处设一个无限循环
现在到了执行WriteProcessMemory一次了. 我们有两条路可走,可以选择执行到返回它将会执行直到RET,然后按一次F7. 第二个选择是去stack第一行在那儿设一个BPX最后运行它.
在stack第一行提示程序会返回到5F94A4, 我们到那儿BPX (F2)然后运行它(F9).

现在该用PUPE了. PUPE是一个西班牙工具.你可以在www.google.com搜索一下. 通过下面几行代码我们将要在son的OEP设置一个无限循环.这会使son休眠直到我们叫醒它.
这是一张PUPE的图片.我们可以看到有两个进程, the son and the father.上面那个是son. 在右边我们可以看到进程句柄.

选择上面那个getright.exe然后右击选择"parchear".

写下出现在对话框中的original bytes然后用无限循环代码EB FE代替,如下面的对话框中所示.
最后一步就是"PARCHEAR"然后INFINITE LOOP就准备好了.

第六步: BP WAITFORDEBUGEVENT 和 NOP THE API
不要清除先前断点WriteProcessMemory,是时候写入新断点了.键入BP WaitForDebugEvent回车, 然后按F9运行

它停下时

你必须记住NEVER EVER RUN THIS API.在转储窗口"DUMP window"看一下report, 然后去堆栈窗口(STACK window).

正如我们看到的如果我们运行这个API我们应该去5F751D,所以让我们通过"前往 表达" 5F751D到主窗口.

再一次提醒不要RUN THIS API.所以右击5F751D选择"新建起源".这是跳过这个API的方法.

现在我们必须nop the call to the api以及有关的push.

所以在这些行上按空格键然后写入nop.当你完成这项工作时,将会如下图所示. 这一步结束了.



第七步:如何打入补丁
当做这一步时请注意.很多人不知道究竟该如何打补丁,所以努力试着理解我这里所作的, 然后学会在其它情况下运用.
第一步是改变跳转,

我们将要改变这个跳转为JMP 401000那是我们将要打补丁的地方, 父进程offset 401000 .

跳转设好了,现在去401000 (ctrl G)我们要开始写补丁了. 在report中我们可以看到三次entry point value.也许OEP在其它情况下会出现更多次.但是我们只需改变这三个.

father unpacks了在report中显示的block.所以我们将要欺骗father则他会相信从第一个section开始的所有blocks都存在错误, 一个接一个所有的blocks都会被unpacked.因为这一点我们需要知道第一个 section的起止点.点击"查看/内存"

这儿我们可以看到getright的sections.忽略Header section看text section,它从401000开始,结束在589000-1=588fff.
401000-588fff (第一个section的范围)
我们将要在report中用400000代替OEP的值.

正如你在图中看到的我写入了40000,然后patch会在每一个loop前加1000,所以第一个被unpack的block将在401000.
现在我们将要在401000处写下如下补丁.

00401000 8105 10F01200 0>picture1/add DWORD PTR DS:[12F010],1000
0040100A 8105 1CF01200 0>picture1/add DWORD PTR DS:[12F01C],1000
00401014 8105 20F01200 0>picture1/add DWORD PTR DS:[12F020],1000
这些行将我们先前在转储窗口(dump window)写的值增加1000.那些蓝色的值在不同的机器上可能会不同,所以修改为你在转储窗口(dump window)看到的实际值. 再次提醒,证实在转储窗口(dump window)写入了00 00 40 .最容易犯的错误就是在补丁(蓝色的)中的offset和转储窗口(dump window)的offset不匹配.
下一行该这样写:
0040101E 813D 20F01200 0>CMP DWORD PTR DS:[12F020],getright.00589000
比较是为了让我们知道何时所有的块都被unpacked了.
然后我们必须写下:
00401028 - 0F85 F6341F00 JNZ getright.005F7524
如果比较为假,则返回called loop的地方.然后我们需要在最后写下NOP,在那儿我们将要设下一个BP. 当它完成转储时,它将会停在那儿.

这儿我们可以看到完整的补丁代码
我们可以跟踪第一个循环,来检测report中的each entry是否每次都增加1000.
第八步: UNPACK
按F9运行程序然后等待它停在我们先前在补丁中设下nop的bpx处.如果没有错误发生,当我们停止时,看view-LOG我们将要看到所有的unpacked blocks.
这是假设的所有unpacked blocks的LOG.我没有完整的给出因为地方不够但是你可以在你的日志窗口中查看.
所以现在从401000到589000(包括588fff)的所有blocks都被unpacked了,然后dump已经就绪.
第九步:把CHILD从FATHER那儿脱钩
一旦我们停在NOP,让我们写下下面几行.
PUSH (child的handle )
Call DebugActiveProcessStop
如果handle由字母开始,你需要在它前面键入一个0,因为不这样做的话olly不会认出. 想知道son的handle只需看一下pupe, 记住child的handle是在两个进程中的上面的那个.你一样可以通过opening file attach来获得son的handle.那儿你可以看到两个名称相同的进程. 红的是father,黑的是son.在此情况下我得到的是B78 所以我将要写下0B78.记住handle在每次运行程序时都会改变.


在图示的nop处下断点,然后运行程序.看registers window,如果当程序停止时,EAX=1那意味着son已经从father处脱钩了,我们可以关闭OLLY.如果EAX=0 那么你要检查那几行代码,因为你可能犯了写错误(也许是写错了handle).
现在关闭OLLYDBG,然后再次打开他.不要载入任何东西.前往"文件/附加"然后找son,附加它(我们搞定它的father了:X).
一旦附加成功,程序将在INFINITE LOOP中 RUNNING ,需要按F12 to pause the去暂停程序,但是等一下,在做这个之前,我们先得把 EB FE改回原始值(那些我叫你记下的值,你有记下吗?) 55 8B,然后我们将停在OEP,已经准备好dump了.


我们已经准备好dump了,所以打开LORD PE然后找Getright.exe

选择Getright.exe进程然后选择INTELLIDUMP然后点击DUMP FULL, 保存后启动PEEDITOR (从www.google.com上搜索它)然后写入正确的ENTRY POINT值.
在这儿不要填写538540,而写入文件偏移,就像这样: 538540-400000 = 138540.

那就是full dump.在本参考教程的第二部分我们会学习怎样去找magic jump从而完成import table, 修复IAT,使程序运行,因为它设下了一些陷阱来避免被unpack.
现在我们保存转储(dump)文件(我把它命名为tute.exe),和原始的一样,只是IAT不同.
第二部分再见.
Ricardo Narvaja
翻译(西班牙语翻译为英语)DEGETE和Tenshin,(英语翻译为中文)FTBirthday.自由传播,但是请保留作者的姓名.