破解本身除了是一种游戏外,更是一种技巧性很强的运动,有点儿太极拳的味道——以柔克刚,四量拨千金!Pecompact壳是一款比较老的压缩壳,本文的目的就是通过这款老壳来介绍一下常见脱壳方法在实际使用时的灵活运用,防止个人习惯将我们禁锢,这是一个相当重要的问题!下面,我们以PECompact一>Jeremy Collake加壳的cmd.exe为示例程序来进行介绍。
一、单步跟踪法PK结构化异常处理
将加壳程序载入到OD中,壳先安装了SEH异常处理程序,然后通过“mov dword ptr ds:[eax],ecx”指令访问0x00000000内存地址空间,引发0xc0000005异常,从而实现反调试。
我们不妨想象一下SEH异常处理应用于反调试的基本方法,第一种方法是先判断程序在调试器中,然后制造异常,通过调用异常回调函数,实现反调试。第二种方法是不管在不在调试器中都制造异常,然后对EXCEPTION_RECORD中记录的异常地址处的指令进行修改,使程序继续执行。第一种方法有一个致命的缺陷——存在条件分支,只要存在条件分支,我们就可以通过修改跳转指令来改变程序执行流程,第二种方法虽然没有第一种方法中的缺点,但是却给了我们单步跟踪的机会,显然这款壳使用了第二种方法,不管在不在调试器中都会引发异常。
SEH结构化异常用于Anti_debug,最关键的地方在于回调函数中对CONTEXT结构的修改,不管如何修改,程序总要向下执行,要想执行的话要么是将异常指令修改为正常,要么就是将EIP的内容修改为正常指令处,不管怎么说检测CONTEXT结构中的EIP寄存器内容都有利于我们跟踪程序,因此一般来说,我们在OD菜单栏的“选项”一“调试选项”中将内存访问异常忽略。
直接按F8单步执行到引发异常处,按shift+F7进入系统领空,继续按F8单步执行到7C92E473处的“call ntdll.ZwContinue”指令处,按F7跟进ZwContinue函数,进入到界面call代码处,按F8来到7C92D04A处的第二层“call dword ptr ds:[edx]”时,按F7跟进,进入后会发现如下几条指令:
sysenter指的是进入rrng0中,这时虽然表示着追踪的结束,但结束也往往代表着新的开始,按F8执行到retn指令,自动返回到程序领空中:
执行到这里向上翻,就会看到代码,对照正好是SEH异常处理程序的地址。仔细分析代码,恰好是对异常发生处的指令进行修改,最明显的是4AD81EF5处的“mov byte ptr ds:[edx],OE9”,因为E9是jmp跳转指令的机器码,这时候再返回异常发生处查看指令已进行修改,到这里就算是走过了SEH反调试的代码。
这里之所以要按F7跟过ZwContinue函数,是因为加壳程序在此按F8时会跑飞,如果我们直接按ALT+F9返回程序领空,执行4AD81F03处的mov指令时还会继引发二次异常,对于SEH异常处理用于Anti_debug的程序,我们的原则就是不妨碍程序向下跟踪,一方面对于一些有SEH暗桩或者加SEH花指令的程序,我们可以使用躲的方法将异常指令直接nop掉,另一方面对于那些靠SEH异常回调函数起反跟踪的程序,要学会使用F8单步跟踪,这需要一定的技巧!
二、灵活使用ESP定律
在上面的过程中,我们停在了4AD81F03,参照一中的反正编指令,程序首先将“fs:[0]”进行了还原,“FS:[0]”指向TEB(进程环境控制块)结构,这是在ring3下的结构,它的第一个成员为EXCEPTION_REGISTRATION的地址,SEH异常处理的机制是通过栈中的链表结构实现的,链表中成员的添加与删除都是通过修改“FS:[0]”实现的。
对于这部分,我们要活用ESP定律,上面只能算是一个小插曲,ESP定律的本质是堆栈平衡不假,但我更乐意称它为地址守恒定律,计算机作为一种信息处理工具,其中的信息需要载体,而载体指的就是存储器,整个信息在不同载体上的流动中,守恒的应该是内存地址,就像进入房间杀人的刺客(信息),当刺客进入房间(内存)后,我们只要守住房间门(断点),就能找出与刺客有联系的主人(OEP)!
很多救程给我们的习惯是,看到pushad往往意味着ESP定律的可行性,pushad指令和程序中的如下代码其实是一个意思:
按F8单步执行到4AD81FI8,就相当于执行了类似pushad的指令,这时候我们在OD命令处输入“hr esp”下硬件访问断点,按F9运行程序,程序停在如下的代码处:
在“调试”一“硬件断点”中删除刚才下的断点,单步执行到OEP,上面的指令中EAX保存的就是OEP,再来看一下OEP的地址,恰好是“FS:[0]”中下面保存的数据。在很多壳中都喜欢使用连续的push保存通用寄存器的内容,而避免使用pushad指令,一方面来说这种做法没有任何意义,另一方面也说明很多朋友被这种习惯所禁锢了,老是按照动画教程中的操作去进行,这种现象就被称作惯性思维!
三、灵活使用内存镜像法
内存镜象法在脱壳中也是经常用到的,我们知道PE文件有很多节,这是与Intel保护模式下的分段机制相适应的,壳加密完PE文件后,在执行时,一般会先解码资源段,然后再解码.text段,所以我们可以使用这种方法,通过在不同的位置下断点,从而找到OEP。
将程序重新载入到OD中,按ALT+M打开内存镜象,在.rsrc段上按F2下“int 3”断点,这时候一般的教程都是,接着按ALT+M打开内存镜像,在.text或.code段上按F2下断点,按照这种思路进行操作,程序就会返回到如下的代码处:按F8跟踪一段指令后,程序就会再次异常退出,因此不能死板的使用数程中所说的方法,之所以会再次进入异常,是因为.rsrc解码后还是停在SEH异常处理程序的代码处,大家再结合上面描述的内容就能理解这一点了,这时候原来异常指令并没有进行修改,因此当我们在.text下断点按F9运行程序后,根本不可能走向正常的解码分支中。
正确的作法就是按F8执行完指令到“4AD81F02 retn”处,再按ALT+M,在.text段上按F2下“int 3”断点,程序就会执行到如下代码处:
正好是异常产生同时也是异常修正后的地址,按F8单步执行到二中所描述ESP定律产生的代码处,这对我们再使用ESP定律或者按F8单步跟踪就能走向OEP了。
四、IAT加密与API断点
虽然Pecompact是款老壳,但它的功能还是有优秀的闪光点的,至少对IAT进行了加密处理,如果我们正在写壳可以通过逆向分析它的功能实现来给自己增加一种思路,如下指令处就是IAT解密的函数处:4AD81F7E FFD7 call edi。在很多动画及文字脱壳教程中,总会有一些特殊的API断点,这些断点都是由分析者通过分析壳的特点得出来的,对于API断点首先我们需要理解以下两点。
1、API断点,一般下的是“int 3”断点,也就是0xcc,它是通过调试器修改断点地址处的指令,当CPU执行时进入中断处理器,从而断下程序,有些程序比如某些程序的debug版,因为没有执行cl.exe编译器的代码优化过程,所以经常碰到bp下断,而无法断下程序的现象。
2、对于IAT加密的壳来说,我们最好是通过分析它的输入表来查看关键的API函数,以辅助我们对壳进行分析,以pecompact为例,将加壳后的程序使用lordpe载入,单击“目录”——“导入表”,查看相关的API函数信息。
我们可以看出就几个函数,我们随便拿出一个API函数基本上都可以到达OEP。以VirtualAlloc函数为例,将程序载入到OD中,在OD命令处输入“bp VirtualAlloc”,按F2取消断点,按ALT+F9返回程序领空,然后按F8单步,不一会儿就能走到OEP处了。
当然因为这个壳实在是太简单了,所以我们可以轻易的随便下一个API断点就能找到OEP,不过正是因为简单,才能给像我这样的一个启发,在脱穿山甲的时候,只看一个一个的API断点就能雷倒不少人了,其实这些断点也是像我们这样分析壳找出来的,不要害怕。
五、总结
1、pecompact最大的特点就是IAT加密和SEH反调试,正是因为IAT加密,才使得我们能轻易的通过一个API断点就脱掉它。对于压缩壳来说,一个更简单的方法就是通过LORDPE查看其调用的API函数,然后下断。对于SEH反调试来说,一定注意不要按F8执行“call ZwContinuc”指令,因为那样程序就会跑飞的,要学会善于使用F7和F8。
2、通过上面的描述,基本上有如下几种方法可以搞定pecompact。
(1)单步跟踪+ESP定律
(2)内存镜像+ESP定律
(3)VirtualAlloc等API断点
本文并没有什么太高深的技术含量,目的只是想告诉大家一个常识——知识要活学活用,不要让习惯禁锢了自己的大脑,有些习惯是最好的,但是只要是好习惯在某些时候总会变成坏习惯,所以不管学知识还是做人,都不要有啥习惯,所谓变者通天下!
本文内容所提及均为本地测试或经过目标授权同意,旨在提供教育和研究信息,内容已去除关键敏感信息和代码,以防止被恶意利用。文章内提及的漏洞均已修复,作者不鼓励或支持任何形式的非法破解行为。