查看原文
其他

逆向免杀 | 脱壳技术

计算机与网络安全 计算机与网络安全 2022-06-01

一次性进群,长期免费索取教程,没有付费教程。

教程列表见微信公众号底部菜单

进微信群回复公众号:微信群;QQ群:460500587



微信公众号:计算机与网络安全

ID:Computer-network

脱壳技术是支撑免杀技术的基础技巧之一。在黑客们试图对加壳后的恶意软件进行免杀操作时,需要做的第一步工作就是脱壳操作;当黑客们试图分析一个陌生木马所使用的免杀技巧时,需要做的第一步工作仍然是脱壳操作。因此,脱壳技术对于免杀来说虽然并非是常用技巧,但是我们仍然有必要对其做一个最基本的了解。


如果我们能在写壳的基础上学会脱壳,那在防御黑客入侵时就更会如鱼得水。一般来讲,脱壳其实就是加壳的逆向操作,我们需要借助壳的Stub部分将宿主程序还原到内存里,并在其执行宿主程序映像前将其转存(Dump)出来,最后再根据具体情况决定是否需要做其他修复,到此就完成了整个脱壳过程。


一、寻找OEP


脱壳操作中最重要的一步就是寻找原程序的OEP,因为我们在调试时判定程序是否执行到了宿主程序就需要以OEP作为标杆,我们在Dump宿主程序映像时也需要OEP信息,甚至在脱一些简单的压缩壳时,找到了OEP就几乎代表了成功脱壳,由此可见寻找OEP在脱壳技术中的重要性。


想要比较快速精准地寻找OEP,首先要做到的就是熟悉不同语言、不同编译器的OEP特征,这一点是非常有必要的。大家可以在平时多收集一些由不同语言、不同编译器编译出来的无壳程序,并用OllyDbg多多调试观察,为以后的脱壳工作打好基础。


下面介绍几种常见的寻找OEP的技巧,熟练掌握这些技巧可以帮助大家找到绝大部分加壳程序的OEP。


1、利用内存断点


加壳后的程序的特点就是其宿主程序部分的内容多半已经被加密了,因此在运行此程序时,壳的Stub部分肯定要对宿主程序所在的区段进行解密还原操作,以使其能正常运行。这就不可避免地涉及对宿主程序所在区段的数据操作,因此我们可以使用访问断点或写入断点来判断哪些指令正对此区段进行操作。


我们以《逆向 | C++ 加壳程序的编写思路》中的例子A1Pack Base为例,先用其将示例应用程序Demo1.exe加壳。加壳后的区段排列如图1所示。

图1  Demo1.exe加壳后的区段状况

由图1可知,加壳后程序Demo1.exe多了一个名为.A1Pass的区段,接下来我们查看一下Demo1.exe此时的OEP,为0x00011200,经过计算可得出其指向的位置位于.A1Pass段。我们都知道在默认情况下OEP只会指向.text段,由此我们可推断.A1Pass段保存有A1Pack Base的Stub部分。我们都能猜到A1Pack Base的Stub部分在运行之初会对.text段进行解密操作,因此可以通过在.text段设断点的方式来找出是哪段代码正在操作此区段中的数据。之所以这么做,是因为大多数的压缩壳与部分加密壳所做的最后一个工作往往就是还原宿主程序的数据信息,因此当我们找到Stub部分操作宿主程序区段的代码时,也就离宿主程序的OEP不远了。


运行OllyDbg加载Demo1.exe后,按快捷键Alt+M打开内存窗口,在其中选择.text段并右击,然后在弹出的快捷菜单中选择“设置内存写入断点”,如图2所示。

图2  为.text区段设置内存写入断点

此时我们已经在.text段的所有位置上设定了写入断点,按F9键运行程序,此时程序中断到了如下位置:

通过反汇编代码可知这是一个循环解密操作,一次仅解密一个字节的数据,因此需要在结尾的0x004113EF处下一个断点,并转到内存页面取消先前对.text段设定的写入断点,然后按F9键运行程序,从而跳过这个循环直达0x004113EF处。经过一段的单步跟踪调试,我们来到了如下位置:

从跳转的目标地址可知这是一个跨段跳转,且跳转目标位于.text区段,再次单步一次即运行到了宿主程序的OEP处。


由这次简单的调试跟踪经历可知,一般情况下壳的Stub部分与宿主程序的代码段往往不在一个区段中,因此当我们在Stub部分所在的区段中发现了指向宿主程序代码段的跨段跳转时,就代表我们已经找到OEP了。


其实对于这种简单的压缩壳,要找出其OEP还有一种更简单的方法,就是在给.text区段设置断点时将其设置为执行断点,因为大多数压缩壳将宿主程序解压解密后的第一件事就是运行宿主程序,因此执行断点所断下的地方一般就是加壳前程序的真实OEP了。


2、利用堆栈平衡


加壳程序将Stub部分植入宿主程序中后,必须保证在Stub部分中运行的代码不会影响宿主程序的正常运行。当然,保证宿主程序正常运行所需要注意的问题有很多,但其中最基础的一点就是要处理好堆栈平衡问题。


为了确保做到这点,很多壳都为Stub部分的入口与出口分别加上了pushad/pushfd与popad/popfd指令,这两条指令的作用是保存寄存器状态与还原所有寄存器状态。因此这两对指令成为很多壳的入口与出口特征,例如最典型的UPX壳。


根据此原理,加密解密界的前辈们总结出了经典的“ESP定律”,即“加壳程序开始运行时的栈状态与解压解密后宿主程序开始运行时的栈状态完全一致,因此加壳程序开始运行时ESP的值与解压解密后宿主程序开始运行时ESP的值完全一致”。


我们都知道ESP寄存器在绝大部分的情况下保存的是当前栈顶位置的地址,因此如果加壳程序严格遵守堆栈平衡原则的话,那么在其执行完Stub子程序后打算跳转到宿主程序执行时,ESP指向的栈顶位置就应该与程序刚开始运行时的一样。这就给了我们一个可以非常简便地找到加壳程序真实OEP的技巧。


我们以加了ASPack的Demo2.exe为例,用OllyDbg将其打开后在ESP处设下硬件访问断点,如图3所示。

图3  在ESP所指向的地址处设下硬件断点

现在我们已经在程序运行之初ESP所指向的地方设下了一个硬件断点,按照堆栈平衡的理论,当Stub程序做完所有工作后准备执行宿主程序时,此处的硬件断点就会被触发。我们直接按F9键运行程序,此时程序断到了如下位置:

由以上代码可知,正是popad这条指令触发了我们的硬件断点。接下来单步5次,从retn指令返回后就到了宿主程序的OEP处。


3、利用编译语言特点


熟悉不同语言、不同编译器的OEP特征很重要,但这仅仅是最基础的一步,除此之外我们还可以结合OllyDbg调试器的功能特点,在适当的时候利用我们的技巧快速定位真实OEP的位置。


简单地讲,OllyDbg可以在此处为我们提供的便利功能有两个:一个是特征码搜索功能;另一个就是调试功能。


(1)利用特征码快速定位OEP


我们利用特征码搜索功能搜索特征码,第一步就是提取特征码。以VC 2010的程序为例,我们暂且取其前5行反汇编代码作为特征码,具体如下:

得到反汇编指令后,我们想将其HEX部分的地址常量替换为"?",因为在不同的程序中这些地址会有所变动,而通配符"?"在OllyDbg中搜索时可代表任何字符。替换完后结果如下:

然后我们去除地址信息与反汇编信息,只留下HEX信息。去除后的结果如下:

最后将其整合为一行,并去除中间的空格,即完成了我们的特征码提取工作。最终结果如下:

这样一来,当我们确定加壳前的宿主程序是使用VC 2010开发的,且Stub部分已经将宿主程序解压解密完毕时,我们就可以按快捷键Ctrl+B执行二进制特征码搜索了,如图4所示。

图4  搜索特征码

这种方法虽然前期繁琐,但是却不失为一种较为精准的寻找方法。不过此方法的使用时机很重要,在搜索前要确保宿主程序的OEP处已经被解压解密,否则就有可能一无所获。


(2)利用关键API快速定位OEP


不同语言、不同编译器的OEP处所调用的第一个API函数是有所不同的,但是基本上都是一些初始化、获取系统环境信息的API,而这些API在普通壳的Stub部分是比较罕见的。因此我们可以将这些API总结出来,并设置相应的API断点,这样每当程序调用这个API时都会断下来,我们通过栈回溯找到调用代码,并判断其是否符合OEP的特征。但是并非每个API的首次调用都会发生在OEP附近,有的会深藏在OEP附近的某个call中,这就要求我们在寻找时讲究方法,并熟悉不同语言、不同编译器OEP的特点。下表是总结的一份关键API对照表。

这个对照表里的关键API所在位置各有不同,要想真正能熟练使用这个表,还需要大家对各个程序的OEP部分亲自进行调试才能总结出有用的经验。


4、利用跨区段跳转


利用跨区段跳转寻找真实OEP的思路与前面的思路有相似之处。绝大多数的壳在将Stub植入宿主程序中时都需要新建立一个区段,并修改程序的入口点使其指向Stub所位于的区段中,因此Stub部分便可以在宿主程序执行之前首先获得执行权限,从而对宿主程序完成外科手术般的各种处理。


但这会不可避免地制造出Stub部分与宿主程序的代码分离问题,在Stub执行完毕后如果想将执行权限转交给宿主程序,就必须使用跨区段跳转,而这个特点刚好可以被脱壳者利用。


跨区段跳转由于是跨区段的,因此其跳转的目标地址与跳转指令所在的地址往往不在一个内存分页上。我们以Demo1.exe在OllyDbg中的内存布局为例,如图5所示。

图5  Demo1.exe在OllyDbg中的内存布局

由图5可知,宿主程序的代码在内存中的地址范围是0x00401000~0x00409FFFF,而Stub在内存中的地址范围则是0x00411000~0x00401FFFF,两个存放代码的区段在内存的位置上至少要相差0x00010000之巨,正常的程序是很少出现这么远距离的跳转的。


如果位于.A1Pass区段的Stub在执行完相关操作后要跳转到位于.text区段的宿主程序中,那么就必然会出现一个跨区段的远距离跳转,这种跳转在绝大多数的脱壳操作中就会成为脱壳者找到真实OEP的“金钥匙”。


至于如何找到这个跳转,那就要考验逆向能力与脱壳能力了。这种偏技巧性的技术只能自己从不断的练习中总结。

二、转储内存映像


转储内存映像(Dump)又称转存内存映像或抓取内存映像,不过国内软件安全界的通常叫法仍然是英文Dump。


Dump操作将映射在内存中的映像文件按照PE区段表的信息保存到一个文件中。Dump操作是一种反向的、从内存映射到文件的行为,同时这也是将内存中成功解压解密的原程序保存下来的必不可少的一步。


在整个脱壳的工作中,Dump的执行直接决定着脱壳的成败。如果在Stub部分还未将宿主程序完全解压解密时Dump,那么Dump出来的原程序会因为其部分内容还未解密而导致其运行失败;如果在原程序已经运行了一段时间后Dump,那么Dump出来的原程序的数据段可能已经包含一些特定的初始化信息,因此会造成程序运行不稳定。


因此最好的Dump时机就是刚刚由Stub部分跳转到OEP处的时候,这时Stub已经完成了其所应尽的义务,且原程序还未执行,因此其数据段的所有信息都还保持着初始化的状态。此时Dump出来的程序各项数据特征是最接近加壳前状态的。


目前常用的能执行Dump操作的软件有很多,其中包括大名鼎鼎的PE编辑工具LordPE、PETools,而且OllyDbg中的一款插件OllyDump也可以执行这些操作。


我们以LordPE为例讲解一下Dump操作。


首先,我们要用OllyDbg等工具将目标程序脱壳,也就是让程序运行到真实OEP处,然后打开LordPE选择我们调试的目标软件进程,右击,并在弹出的快捷菜单中选择dump full即可,如图6所示。

图6  使用LordPE进行Dump操作

三、重建导入表


加壳软件诞生之初是没有加密壳这个概念的,正是由于导入表加密技术的诞生,从此加壳软件有了压缩壳与加密壳的划分。但是到了今天,对导入表做必要的处理已经成为大多数壳的默认选择。


对于那些对导入表动了手脚的壳来说,即便是我们成功完成脱壳也不能令程序正常运行,因为我们的导入表被破坏了,必须将其修复后才能使脱壳后的程序正常运行。


导入表中起关键作用的结构是IAT。从PE文件的设计角度来看,由于IAT在数据目录表中拥有一席之地,因此虽然从逻辑上看IAT是导入表的一部分,但设计者应该是将其当成了一个单独的结构来对待的,而且IAT还是导入表中唯一与程序运行紧密相关的结构。正因为如此,很多人都将“重建导入表”直接称为“修复IAT”。


1、导入表重建原理


导入表中与程序运行紧密相关的结构是IAT,它用于保存导入API的实际地址,在PE文件运行之初系统会根据导入表内导入的模块名与API函数名获取相应API的当前地址,并将这些地址按照相应的顺序保存在IAT中。而程序在运行时只需依靠IAT提供的API地址即可,因此只要IAT中有正确的API函数地址,那么即便是没有导入表也并不会影响程序的正常运行。


加壳软件正是利用了这个特点,在加壳前将原始导入表的关键信息备份下来,并删除原始导入表,然后在Stub部分运行时模仿系统加载PE的操作填充IAT,以确保宿主程序在失去导入表后仍然能正常运行。


重建导入表就是一个由IAT信息逆向推导出导入表的过程,由于IAT中保存有相应API的地址,因此只需遍历进程空间内所有已加载模块的导出表信息即可获得重建导入表所需的模块名称与API函数名称等信息,有了这些信息后就已经具备了重建导入表所需要的所有数据。


当然,导入表重建的过程是比较复杂的,但这不是我们讨论的范围,我们只需要知道什么时候应该重建导入表,以及重建导入表的原理是怎样的就足够了。


2、使用ImportREC重建导入表


ImportREC是目前最好用的导入表重建工具,它为我们提供了多种修复输入表的可能,以使得我们可以利用它从杂乱的IAT中重建一个新的输入表。但是ImportREC的使用是有一些先决条件的。


首先,我们最好已经将目标文件完全脱壳,且令其停在真实OEP处,并且在整个重建导入表的工作中都要保持此状态,这样做可以令ImportREC能更好地读取目标程序的IAT信息。


其次,我们要将已经脱壳的目标文件从内存中Dump出来,并保存为一个文件。


下面我们以一个加了UPX壳的文件Demo3.exe为例,讲解一下ImportREC的使用方法,以及基本的导入表重建步骤。


我们首先使用OllyDbg将Demo3.exe脱壳,并使之暂停在真实OEP处,记录下此地址0x00401357后,在将其Dump出来,保存的文件名暂定为Dump.exe。经测试,Dump出来的程序Dump.exe无法运行。


然后我们运行ImportREC,并选择正在调试的进程Demo3.exe,如图7所示。

图7  选择正在调试的进程Demo3.exe

接下来输入我们前面所记录下来的OEP地址,并单击AutoSearch按钮。如果此时弹出一个标题名为"Found something!"的提示框,则代表我们提供的OEP可能是正确的,就可以试着进行下一步操作了,如图8所示。

图8  使用ImportREC执行自动搜索IAT的功能

我们根据提示"Try'Get Import'."单击Get Imports按钮,让ImportREC分析IAT的结构从而得到基本信息,如图9所示。

图9  用ImportREC分析IAT得到的导入信息

由于本例中的所有API函数都能被正确识别,因此无须多做其他操作。如果发现有不能识别的信息,条目的最后会显示为valid:NO,此时我们需要单击Auto Trace按钮来执行API路径自动跟踪操作,从而修复并过滤掉不正确的导入信息。


最后我们单击Fix Dump按钮,在弹出的对话框中选中我们前面Dump出来的文件Dump.exe,ImportREC就会生成一个修复导入表的文件Dump_.exe,至此就完成了整个导入表的修复操作。


四、结语


本文所讲解的是脱壳技术的一些基础知识,其中如何寻找OEP是最重要的内容。黑客们就是依托这些技术脱掉目前市面上流行的大部分的壳的。


但是在免杀工作中涉及的壳主要以加密壳为主,这些壳根据种类的不同,所使用的反调试、反脱壳技术也不尽相同,这方面的知识需要大家自己在实战中逐步练习精进。

微信公众号:计算机与网络安全

ID:Computer-network

【推荐书籍】

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存