需要用到的工具,Xp虚拟机,ArmaFP(查看壳的版本),dillodie1.6(脱壳机),吾爱OD,IDA8.3,脱壳程序(见附件),Beyond Compare(比较工具),LordPE,improtRec。
0x01. 了解 Code Splicing
首先将脱壳程序GameJack.exe拖到打开的ArmaFP32中
可以看到保护设置只有Strategic Code Splicing ,中文意思就是叫做代码拼接,刚看到这个意思表示有疑问,网上搜了一大堆都是讲怎么处理,没有讲他的实现是怎么样的,于是我用MASM32写了一个非常简单的小程序让Armadillo 4.4加壳,保护设置只选Code Splicing,
1 2 3 4 5 6 7 8 9 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
0x02 设置OD
分析完Code Splicing 的原理后开始第一步设置好调试器,StrongOD这样设置,Skip Some Exceptions 这个中文意思是跳过一些异常,Od也有自带的忽略异常,但是使用OD的忽略异常调试不了,所以按照以下设置
还有硬件断点需要保护起来,OD插件-》Drx-Protect-》Config-》Drx勾上
补充一下硬件断点对话框的使用,
0x03 寻找OEP
看过上一篇文章的朋友知道,Asprotect壳找OEP用最后一次异常代码段下断点法,在这里是行不通的,我这边没有使用原文的方法,我的思路是,程序都会把自己的代码放到内存中去执行,接着解密完宿主程序之后跳回宿主程序的OEP去执行,所以第一步先找到包含执行代码的这段内存,我们就在命令行下HE kernel32.VirtualAlloc,下硬件断点是因为有对函数头部F2断点的检测,接着F9,断下看看堆栈中VirtualAlloc 的参数 Size 等于 65000 这个大小像是一个可执行文件的大小,我猜测这应该是为存放代码开辟的一段内存,然后ALT+F9,可以看到EAX等于0,返回值等于0,说明没成功。继续F9跟前面一样看看EAX,这时候EAX,有值了,我的是00DC0000,你们的可能不一样,查看00DC0000这个地址的内存数据都是0,这时候说明内存申请成功,接下去就是要写入数据了,然后我们在命令行给这个地址下个硬件写入断点HW 00DC0000 ,F9,程序断在了004E43B3,
1 2 3 |
|
查看此时ECX的值,很小,显然这一句代码运行完,这段内存还没写完,还有其他地方往这边写入数据,这时候不去找其他地方往这片内存写数据,直接F9运行,接着断下这时候大概率这片内存已经数据完全写入了,删除硬件断点,ALT+M,查看内存找到00DC0000 这个段下F2断点,
程序断下了,在00DC0000 这个段内,
接着在代码段下F2断点,F9,这时候断在了00DF0338,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
然后F7一直单步走,当走到00DF039B的时候,
有硬编码的地址的指令,dd 0x47B4F8 ,
可以看到
两个模块之前的分隔00000000,变成了00DD6E36,可以看到IAT被做了手脚,到目前为止Code Splicing 线索还没有找到,从开头了解到类似JMP XXXXXXXX,XXXXXXXX代表一个内存地址,我们先修复IAT,接着再寻找Code Splicing,首先先确定一下IAT,IAT是一段存放API地址的一段数据,不同的模块之前用00000000隔开,这段IAT开始的地方是47A000,结束的地方是47B990。大小0x1990,我们CTRL+F2重新运行程序,在IAT开头下个硬件写入断点HW 47A000看看壳对IAT做了什么,F9运行,程序断下了,ALT+F9执行到用户代码,在数据窗口可以看到,在00DECC56断下
继续F9运行,在
1 2 3 |
|
这时候47A000的值为00DDA6F4,dd 00DDA6F4 数据窗口中跟随,然后反汇编,
可以看到IAT函数被替换了,接着我们在这一句,
1 |
|
下个F2断点,F9运行,断下,重复这样的操作,IAT在一个一个的解密,这时候我们要做到知己知彼,百战不胜。就需要看看IAT哪里加密了,因为最后我们还原iat后面需要对比是不是都还原了,这个壳已经有脱壳机了,所以我们可以拿到他的脱壳后的文件,打开dilloDIE.exe,
把 For Strategic Code Splicing 勾上,点击UnPack,选择目录下Gamejack,打开,这时候脱壳机对其脱壳,完成后会得到GameJack.exe.dDIE.exe,这个就是脱壳后的文件了。接下去就是分别打开脱壳后的,和没脱壳的,程序在OD上运行,然后复制他们的IAT表用Beyond Compare做对比,这边是对比后的样子,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|
这些就是被加密的IAT函数,被加密后这些API地址会变成别的地址。所以咱们需要还原IAT,就需要了解壳是如何还原IAT的,首先需要找到往IAT写入数据的地方,
地址00DECC54(这个地址在你们机器上可能不一样)上mov dword ptr ds:[eax],ecx 这个指令就是,经过多次调试发现,IAT最终解密都是在这个地址完成的,这样我们可以重新运行程序,在0047A000 这个地址下个硬件写入断点,F9运行,这时第一次断下,
还没解密,因为这个壳反调试方面做得还算很严密的,很多代码都是运行前是加密的,运行后又加密,而且还有校验,我们下个硬件写入断点HW 00DECC54,F9运行,程序断下,我们删掉硬件断点,在00DECC54上mov dword ptr ds:[eax],ecx,这条指令上下个F2断点,F9运行,程序断下,这时候F7单步一下,数窗口中查看EAX的值,
这就是恢复IAT表的开始,不是自上而下恢复的,这个很重要,记住这个地址0047A8C0,接下来我们找个没有恢复IAT函数的地址分析,就选0047A018这个地址,下断点的话要下HW 0047A014,F9运行,程序断在了00DECC56,
1 2 3 4 5 |
|
F8单步走,走到00DECC5F的时候,此时Eax等于0047A018,这时候就是要去还原0047A018这个地址的IAT,当然这边是没有还原而是用别的函数代替,我们的任务是要还原,所以我们一边F8一边分析,
1 2 3 4 5 6 7 8 9 10 |
|
我猜测将00DECAA3 75 11 jnz short 00DECAB6 ,这个地址的代码改成
00DECAA3 75 11 jmp short 00DECAB6 ,是不是就可以还原IAT了,
这时我们重新载入程序,在刚刚记住的这个地址0047A8C0下硬件写入断点HW 0047A8C0,F9运行,程序断下,接着F9运行,程序断下,这时程序刚好还原第一个IAT,这时我们将将00DECAA3 75 11 jnz short 00DECAB6 ,这个地址的代码改成 00DECAA3 EB 11 jmp short 00DECAB6 ,现在我们有个疑问代码是改了,但是我们不知道IAT什么时候全部被还原完了,根据壳的特性,代码执行前会先解密,执行完又会加密,我们就在00DECAA3 75 11 jmp short 00DECAB6,这个代码处,下个硬件写入断点HR 00DECAA3 ,F9运行,程序断在了00DC13C4这个地址,
1 2 |
|
这段代码是访问 00DECAA3 这个地址的字节码,取出字节码放到EDX,可以看到此时EDX的值是858B11EB,其中EB是我们修改后的,没修改前是75,所以我们把EDX,改成
858B1175,这样就可以骗过壳的校验,同时我们还要把00DECAA3 EB 11 jmp short 00DECAB6 ,这个地址的代码改回 00DECAA3 75 11 jnz short 00DECAB6 ,删除所有断点,这时候我们按照前面找OEP的方法,在代码段下F2断点,F9运行,程序断下,单步到OEP,这时候我们再翻看IAT,可以看到没有被还原的统统被还原了,只是不同模块之前的分隔00000000,变成了别的地址,这个不影响程序运行,这个时候是不是忘了Code Splicing没有修复,这个先不着急,我们先按照正常脱壳流程走一边,先用ImportRec获取IAT表,再用LordPe Dump出来,再用ImportRec获取的IAT表,修复Dump出来的文件,最后用再用LordPe 重建PE
其中重新排列文件不要勾,我们再将修复好的文件载入OD看看,这时候排除了IAT的因素的干扰,F9运行,
OD上显示已终止,可以看到现在EIP是035CDBB4,通过堆栈回溯,找到这段代码!
正如我们开始的时候学习的Code Splicing一样,通过JMP XXXXXXXX ,跳到一个内存地址执行被偷走的代码,我们先记下这个地址 004274F3 ,然后重新载入原来的程序,通过OD查看004274F3这个地址代码情况,可以看到都是0,我们就在004274F3这个地方下硬件写入断点 HW 004274F3,程序断下,再ALT+F9返回到用户代码,再倒回去看地址004274F3的代码情况,
代码是正常的,跟随这个JMP,
也是个正确的,这时我们需要追根溯源,回到EIP所在的代码,可以看到上一句执行的是msvcrt.memcpy函数,这是个代码复制函数,我们可以通过他的参数,可以找到他的源头,我们对这个函数的地址00DEB833,下个硬件执行断点HE 00DEB833,因为OD硬件断点有记忆功能,如果重新载入程序下个内存上不存在的地址的断点是下不了的,下完断点我们重新载入程序,
这边有一个问题需要注意一下,图中圈起来的红色圆点是打开和关闭这个硬断对话框的,另外一个圈起来的像减号的是隐藏这一行断点的按钮,点一下他就变加号了,同时这个断点消失了,就不能断下了,再点一下加号,这一行又出现了,最好每次重新载入程序点这个QUIT按钮关闭,再点红色按钮打开,同时下什么断点必须在这个对话框里面看的到,否则可能无效,说的好像比较听不懂,但是你们多试几次就知道了,回归正题,F9运行,程序断在了
1 |
|
通过堆栈可以看到,从01600020这个地址复制到00402000,数据大小是00078000,通过计算004274F3对应的地址是1625513,
,我们也在1625513这个地址下硬件写入断点HW 1625513,重新载入程序,F9运行,程序断下,接着再F9,再断下,再F8单步一下,运行到这边
命令行dd 01625513 ,然后数据窗口选中01625513这一行反汇编,可以看到
接着删除所有断点,再在01625513这个地址下硬件写入断点,F9运行,程序断下,再F9运行,断下,然后再F8单步一下,此时EIP在这边,
这时查看一下01625513这个地址的代码,
E9后面都是0,这应该是要写入东西的,删除所有断点,在
01625514下个硬件写入断点HW 01625514,F9运行,现在EIP在00DEB613,
下面我们分析一下这段代码处理的表的结构,这个结构是以00F97950为起始点,0为结尾标志,有很多个组,每组构成了一次代码拼接,一组有四项,
1 2 3 4 5 6 7 8 9 |
|
下面是利用上面的数据修改jmp后面的字节码的代码,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
|
调试跟踪这段代码可以在运行前在00DEB53C下个硬件执行断点。
这个表的每一组除了第一项,其他项都需要根据申请的内存基址计算出来的,所以我们再对这个表的起点00F97950下个硬件写入断点,重新运行程序,F9运行,程序断下,F9运行,再断下,
这时候EIP是00DEA6CC,然后执行命令dd 00F97950 ,看看数据窗口,可以看到,
可以看到第一项和第二项都有数据了后面都是零,所以断下的这段代码就是在构造这个表,让我们来看看这个表是如何构造的这又需要用到另外一个表,这时我们F7单步一直走,走到
1 |
|
这条指令就是跳回继续循环,再F7一下,走到这边
1 |
|
可以给这条指令下硬件执行断点,重新载入程序,F9运行,程序断下,F7单步一下,此时EAX等于00F81008
,所以表的起点是00F81008。接下来跟踪就可以从头开始看,这个表一直被填充,先看下代码拼接处执行的流程图,其中地址c和地址d之间的代码块包含被JMp指令覆盖的代码和垃圾指令
地址A和B之间相差5个字节,所以地址B可以通过地址A求出,刚刚说到构造这个表前面还有一个表,这个表每个数组有三项,其中第一项是地址A的偏移地址,第二项是地址D的偏移地址,第三项是地址C的偏移地址,其中代码段基址固定是400000,而内存基址是不断变化的,需要申请之后获得,EIP所在的这段代码会访问申请到的内存基址,通过这个表计算出数值再填入刚才那个表。
1 2 3 4 5 6 |
|
A的偏移地址 - 1 为什么还要减一,因为记录的不是jmp指令的偏移,而是E9 后面第一个字节的地址,方便等下修改E9的字节码,不需要再减一。
下面是对这段代码的部分关键注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
|
上面分析的代码可能有的地方有点错误,将就着看吧,写到这边已经有点晕了,就当大家都看懂了,之前文章,是修改那个关键内存的地址为下面这个代码段,
当然这个代码段刚好放的下,如果放不下我们就要,自己加个区段了,说到这边小明又要问了,IAT不是又要加个区段给他吗,IAT的话后面来,新的区段的基址就等于这个程序最后区段的地址加上大小,算出来等于7d4000,接着继续使用OD,在00DEA62C下个硬件执行断点,重新载入程序,程序断下,找到下面这一行
其中0xE0AFB0这个地址就是存放申请的内存地址的,记住现在这个地址等下需要,我们把他修改为7d4000,然后删除所有断点,在
00DEA78D 下个硬件执行断点,F9运行,程序断下,在数据窗口找到0xE0AFB0,右键撤销选择处修改,这么做就是修改前面的表格能够适合等下我们加的基址为7d4000的区段,这时EIP为00DEA78D
1 |
|
将jbe改为jmp,f7单步走过这条代码,再选中这条代码,右键撤销选择处修改,这个时候我们查看第一次的那个表,
1 2 3 4 |
|
需要将其修改为基址为02E10000对应的地址,因为等下我们需要用lordPE拷贝这段内存,作为基址为7d4000这个区段的数据。删除所有断点,在00DEB53C下个硬件执行断点,HE 00DEB53C,F9运行,程序断下,删除断点,这时我们需要打个补丁修改第三项的地址,
1 2 |
|
先用StrongOD申请一个内存,
将00DEB615这个地址的代码改为jmp 01690000,
在刚刚申请的内存中这样改
1 2 3 4 5 |
|
接着我们删除所有断点,在00DEB628下个硬件执行断点HE 00DEB628,
1 2 3 |
|
F9运行,程序断下,再将刚才打补丁的地方还原,
1 |
|
删除所有断点,然后在代码段下F2断点,F9运行,F7一直单步走到OEP,现在用lordPE找到我们的脱壳进程右键选择区域转存,找到我们刚才记住的内存地址,右键转存,名字就叫abc,选中这个进程右键修正进程大小,再右键完全转存,再用lordPE编辑这个刚才转存的exe文件,打开区段,选中区段表中的一项,右键从磁盘载入段,选中我们刚才转存的abc文件,打开,区段载入成功,接着点保存,接着用OD载入我们修复好的iat的那个文件,打开importREC,附加到那它上,点击iat自动搜索,点击获取导入表,点击显示无效的函数,选中右键剪切指针,再点击修正转储,选择刚才转储的exe文件,双击,最后用lordPE重建PE,成功脱壳。