加壳与脱壳


知识点

加壳原理:加壳器是将一个可执行文件作为输入,输出一个全新的可执行文件,被加壳的可执行文件经过压缩,加密或者其他转换。多数假客气采用压缩算法压缩原始文件,加壳器通过加密原始可执行文件并且实施一些反逆向技术的实现如防反汇编,反调试以及虚拟化等,加壳器既可以打包整个可执行文件,包括所有的数据与资源节,也可以仅打包代码和资源节。

脱壳存根:可执行程序的入口点直线脱壳存根而不是原始代码,原始程序通常存储在杰克程序的一个或者多个附加的节中,脱壳存根负责脱壳源程序:

  • 将原始程序脱壳到内存中
  • 解析原始可执行文件的所有导入函数
  • 将可执行程序转移到原始的程序入口点

加载可执行文件:

  • 读取PE头信息
  • 根据头部信息为可执行文件的各个节分配内存/根据脱壳存根在内存中分配空间
  • 加载这些节到所分配的内存中

解析导入函数表:脱壳存根使用LoadLibrary导入每个库和GetProcAddress函数获取每个函数的内存地址。

尾部跳转:一旦脱壳存根完成脱壳,就必须转到OEP运行,一般使用jmp指令,或者使用ret或者call指令。有时恶意代码会使用操作系统转移控制的函数来掩盖尾部跳转NtContinue或者Nwcontinue。

加壳程序的特点:

  • 程序中导入函数很少,导入函数只有LoadLibrary、GetProcAddress
  • 使用IDA分析时IDA仅能识别出少量的代码
  • 使用OD打开程序时警告此程序可能被加壳
  • 程序节中包含加壳器的标识如UPX0
  • 程序拥有不正常的节大小
  • 使用PEID等加壳探测程序

熵计算:熵值是一种测量系统中或程序中混乱程序的技术,压缩或者加密数据更接近于随机数据因此熵值更高。

脱壳选项:

  • 自动静态脱壳
  • 自动动态脱壳
  • 手动动态脱壳

手动脱壳:

  • 找到加壳算法,编写程序逆向算法,缺点是效率较低
  • 运行脱壳存根,等脱壳完成后修改PE头部

修复导入函数表:ImpRec可以用来修复一个加壳程序的导入表:

  • 打开要修复的文件
  • 输入OEP地址的偏移
  • 单击IAT autoresearch按钮
  • 单击Import
  • 导入成功则显示valid:yes

使用自动工具查找OEP:使用OD中的插件OllyDump,通过Section Hop调用Find OEP。通常吗脱壳存根存在一个节里,而可执行程序被打包到另一个节中,使用step-into或者step-over方法,当城市从一个节跳转到另一个节时,OD可以探测到这种转移并在转移的地方进行中断。

手动查找OEP:

  • 最简单的手动查找策略就是查找尾部跳转指令,通常情况下是jmp指令或者是ret。通常尾部跳转指令时一串无效字节指令前的最后一条有效指令,填充这些字节的目的是为了保证字节对齐。
  • 尾部跳转指令的另一个显著特征是跳转地址的大小,一般跳转指令被用在条件分支中,因此跳转的距离较小,但是尾部跳转指令距离很大。
  • 观察jmp指向的指令序列,在程序运行前jmp指向的地址并没有有意义的指令但是脱壳存根运行之后则会出现有意义的指令。
  • 另一个查找尾部跳转的方式是在栈上设置读断点,要设置读断点必须使用硬件断点或者OD的内存断点,反汇编中的发不分寒暑都是以某种push指令开头的,在栈中记录第一个入栈的内存地址,然后在这个栈位置设置一个读断点,然后当脱壳存根运行完成之后会用pop指令再次命中这个栈位置,这个是ESP守恒定律
  • 如果程序通过使用 call指令来跳转到OEP时,这个call并没有ret,因此在这个call上使用step-over会让你前功尽弃,因为无法命中下一个断点,需要重新寻找刚才的函数并替换为step-into。
  • 在GetProcAddress函数设置断点,大多数脱壳器使用此函数来解析原始函数的导出表。
  • 对于命令行知程,在进程中,壳很在就调用了GetVersion和GetCommandlineA函数,所以你可以尝试在这些函数被调用时中断程序。
  • 在GUI程序中GetMoudleHandleA通常是第一个被调用的函数,程序中断后,检查前一个栈帧没查看何处调用了原始程序,调用GetMoudlehandleA和GetVersion函数的开始地址一般就是OEP。
  • 使用OD的RunTrace选项,此选项提供一些额外的断点选项使你能在较大的的范围内设置断点,例如有很多壳都会留下原始文件的text节,通常硬盘上的text节没有任何东西,但是这个节被保留在PE头部使得加载器能够危他创建内存空间,OEP总是位于源文件的text节中,通常它是这个节中第一个被调用的指令,RunTrace可以实现无论何时执行text中的指令,都能触发RunTrace设置的断点,一旦断点触发就找到了OEP。

手动修复导入表:导入表在内存中实际上有两个表,第一个表时函数名称或者序列号表,起哄包含加载器或脱壳存根所需要的函数名称或者序号,第二个表时所有导入函数的地址列表,当代码运行时,只需要第二个表,所以加壳程序可以通过移除名字阻止分析,在这种情况下需要手动重构这个表。

常见壳:

  • UPX:这是一款主要实现压缩可执行文件大小功能的壳,可以使用UPX的托克工具脱壳,遇到修改过的UPX可以查找OEP实现手动脱壳。
  • PECompact:这是一个商业壳,为性能和速度而设计,这个壳脱起来比较难,因为其中包含反调试异常与混淆代码,绕过异常的方式是将异常返还给程序处理,可以通过尾部跳转指令来查找OEP,step-over几个函数,然后会看到一个尾部跳转,它由一个jmp eax指令组成,之湖是多个0x00字节。
  • ASPack:此壳的目的是为了安全,使用了自我修改代码,使设置断点和分析变得困难,在用ASPack加过壳的程序上设置断点可能会立刻终止此程序,打开脱壳存根的代码会有一个PUSHA指令,确定用来存在寄存器的栈地址,然后再这些地址上设置硬件断点,调用POPAD指令时会触发硬件断点,OEP就在离尾部跳转的不远处。
  • Petite:与ASPack相似,有反调试机制,且为了干扰调试器使用了单步异常,同样需要将异常处理返还给程序,使用栈上的硬件断点使最佳策略来寻找OEP。
  • WinUpack:这是一个拥有GUI的壳,设计目的是优化压缩而不是安全,查找Upack的OEP最好方法是再GEtProcAddress上设置断点查找设置导入解析函数的循环。
  • Themida:这是一个非常复杂的壳,有反调试与反逆向分析的功能。同时阻止VMware、调试器以及ProcMon分析。除此之外Themida有一个内核模块。与多数壳不同Themida会在原始程序运行后一直运行,可以找到一些针对这个壳的自动脱壳工具,如果自动脱壳失败则需要用到ProcDump工具来将内存中的数据Dump下来。

课后练习

本章每一个实验都是前面章节实验的加壳版本,你的任务是脱壳这个文件并且识别这些文件在哪一章出现过。分析样本文件Lab18-01.exe到Lab18-05.exe。

Lab18-01

Lab18-02

Lab18-03

Lab18-04

Lab18-05


本章结束🎊

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×