压缩、加密壳初认识

0x01 壳的简介

什么是壳:

​ 壳是一种概念上的东西,人们为了保护软件不会被轻易的修改或者反编译,希望软件能够获得一种保护,能如同乌龟壳保护乌龟一般,能有一个东西保护自己,于是壳就出现了。

关于壳的作用:

​ 壳的初始作用是保护软件,但后来发展的方向不一就出现了各种各样的壳,大致有压缩壳、加密壳、VM 壳的分类。压缩壳故名思意,主要作用是用于压缩方面,可以有效的减小软件的大小;加密壳,其主要作用是保护软件;VM 壳是一种很特殊的壳,它利用了虚拟机技术,可以很有效的保护指定地址代码,但很大的牺牲了效率,所以一般只在关键代码处使用。

0x02 加壳原理

关于涉及壳的一些基本概念:

​ OEP(Original Entry Point):程序的入口点。软件加壳一般隐藏了程序真实的 OEP(或者用了假的 OEP ), 我们需要寻找程序真正的 OEP,才可以完成脱壳。

不同编译器编译出来的 OEP 是不同的,但同一编译器编译出的 OEP 长得很相似。

​ IAT(Import Address Table):导入地址表。由于导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个 DLL 中。当 PE 文件被装入内存的时候,Windows 装载器才将 DLL 装入,并将调用导入函数的指令和函数实际所处的地址联系起来(动态连接),这操作就需要导入表完成。其中导入地址表就指示函数实际地址。 多数加壳软件在运行时会重建导入地址表,因此获取加壳程序正确的导入地址表也是手动脱壳操作中的一个关键问题。

​ PE (Portable Executable):可移植的可执行的文件。常见的 .EXE、.DLL、.OCX、.SYS、.COM 都是 PE 文件。PE 文件有一个共同特点:前两个字节为 4D 5A(MZ)。如果一个文件前两个字节不是 4D 5A 则其肯定不是可执行文件。更加具体的内容需要继续学习。

关于壳的基本加壳过程:

​ 加壳过程是用加壳程序对源程序进行压缩、加密、转换指令等操作,然后一般是在程序的开头加上一段壳程序。当加壳程序运行时,在程序开头的壳程序会对加壳程序进行解压缩、解密代码或者数据、解释执行相应代码等过程,壳程序运行完之后一般会将加壳程序恢复成源程序,加壳程序便依旧可以执行相应的功能。
​ 其中更加偏向原理方向的解释的话就会涉及更加深的内容了,将涉及到 PE 头信息段,IAT 的重建等知识。可以简略的讲在原PE文件(后面称之为源文件)上加一个新的区段(也就是壳),然后从这个新的区段上开始运行。
​ 具体过程还需要学习才能懂。

0x03 压缩壳

关于压缩壳的简介:

​ 压缩壳的特点就是减小软件体积大小,加密保护不是其重点。目前兼容性和稳定性比较好的压缩壳有 UPX、ASPack、PECompact 等。

关于压缩壳的原理:(待修改)

​ 首先我们可以将 PE 文件为两个部分,一部分为 PE 头,一部分为数据。加壳程序可以将数据进行压缩,从而得到了一份压缩后的压缩数据,我们再将 PE 头、解压代码、压缩数据依次排列形成新的 PE 文件。当我们运行这新 PE 文件时,解压代码将压缩数据解压还原成原数据,我们可以通过生成新的带壳 PE 文件,同时在 PE 头下申请和原数据一样大的空间存放原数据,由此,我们就可以正常运行带壳 PE 文件了。

0x04 加密壳

关于加密壳的简介:

​ 加密壳种类比较多,不同的壳侧重点不同,一些壳单纯保护程序,另一些壳还提供额外的功能,如提供注册机制、使用次数、时间限制等。加密壳还有一个特点,越是有名的加密壳,研究的人也越多,其被脱壳或破解的可能性也越大,所以不要太依赖壳的保护。

关于加密壳的原理:

​ 这个原理应该和压缩壳原理相差不多,所以不多讲了。

0x05 脱壳

关于壳的加载过程:

​ 大致可以分为 7 个部分。

1、保存入口参数

​ 加壳程序初始化时保存各寄存器的值,外壳执行完毕,再恢复各寄存器内容,最后再跳到原程序执行。通常用 pushad / popad、pushfd /popfd 指令对来保存与恢复现场环境。

pushad: 将所有的32位通用寄存器压入堆栈

pushfd:将32位标志寄存器EFLAGS压入堆栈

popad:将所有的32位通用寄存器取出堆栈

popfd:将32位标志寄存器EFLAGS取出堆栈

2、获取壳自己所需要使用的 API 地址

​ 一般外壳的输入表中只有 GetProcAddress、GetModuleHandle 和 LoadLibrary 这几个 API 函数,甚至只有 Kernel32.dll 以及 GetProcAddress 。如果需要其他的 API 函数,则通过 LoadLibraryA(W) 或 LoadLibraryExA(W) 将 DLL 文件映像映射到调用进程的地址空间中,函数返回的 HINSTANCE 值用于标识文件映像映射到的虚拟内存地址。

GetProcAddress:很明显的从函数名上可以理解,这是一个用于获得程序句柄的函数,其函数原型如下

FARPROC GetProcAddress(
HMODULE hModule,     //DLL 模块句柄
LPCSTR  lpProcName   //函数名
);
//参数hModule是调用LoadLibrary(Ex)或GetModuleHandle函数的返回值。
//参数lpProcName可以采用两种形式:第一种是以0结尾的字符串地址;第二种形式是调用地址的符号的序号(微软公司非常反对使用序号)

LoadLibrary 函数:

HMODULE LoadLibraryA(
  LPCSTR lpLibFileName    // DLL 文件名的地址
);
//返回值:若成功则返回模块的句柄,失败就返回 NULL

函数有两个名称(A / W):
Windows函数是区分字符集的:A表示ANSI,W表示Wide,即Unicode (Wide character-set),前者就是通常使用的单字节方式,而后者是双字节方式,方便处理双字节字符。Win98基本是使用ANSI字符串来进行内部操作的,但它仍可处理少数Unicode字串符函数,如MessageBoxW、MessageBoxExW等。而Win2000/XP所有核心函数都是Unicode字串符。

GetModuleHandleA(W) 函数:

HMODULE GetModuleHandleA(
  LPCSTR lpModuleName     //DLL 文件名地址
);
//返回值:若成功则返回模块的句柄,失败就返回 NULL

3、解密原程序的各个区块的数据

​ 壳出于保护原程序代码和数据的目的,一般都会加密原程序文件的各个区块。在程序执行时外壳将会对这些区块数据解密,以让程序能正常运行。壳一般是按区块加密的,那么在解密时也按区块解密,并且把解密的区块数据按照区块的定义放在合适的内存位置。

4、IAT的初始化

​ IAT 的填写,本来应该由 PE 装载器实现。但由于加壳时,自己构造了一个输入表,并让 PE 头中的输入表指针指向了自建的输入表。所以,PE 装载器就将对自建的输入表进行了填写。那么原来 PE 的输入表的填写,只好由外壳程序实现了。外壳要做的就是将这个新输入表结构从头到尾扫描一遍,对每一个 DLL 引入的所有函数重新获取地址,并填写在 IAT 表中。

5、重定位项的处理

​ 文件执行时将被映射到指定内存地址中,这个初始内存地址称为基址。当然这只是程序文件中声明的,程序运行时能够保证系统一定满足其要求吗?
​ 对于 EXE 的程序文件来说,Windows 系统会尽量满足。例如某EXE文件的基地址为 400000h,而运行时Windows系统提供给程序的基地址也同样是 400000h 。在这种情况下就不需要进行地址“重定位”了。由于不需要对EXE文件进行“重定位”,所以加壳软件把原程序文件中用于保存重定位信息的区块干脆也删除了,这样使得加壳后的文件更加小巧。有些工具提供“ Wipe Reloc ”的功能,其实就是这个作用。
​ 不过对于 DLL 的动态链接库文件来说,Windows系统没有办法保证每一次DLL运行时提供相同的基地址。这样“重定位”就很重要了,此时壳中也需要提供进行“重定位”的代码,否则原程序中的代码是无法正常运行起来的。从这点来说,加壳的DLL比加壳的EXE修正时多了一个重定位表。

6、HOOK-API

​ 程序文件中的输入表的作用是让 Windows 系统在程序运行时提供 API 的实际地址给程序使用。在程序的第一行代码执行之前,Windows系统就完成了这个工作。
​ 壳一般都修改了原程序文件的输入表,然后自己模仿 Windows 系统的工作来填充输入表中相关的数据。在填充过程中,外壳就可填充HOOK-API的代码的地址,这样就可间接地获得程序的控制权。

7、跳转到程序原入口点(OEP)

​ 从这个时候起壳就把控制权交还给原程序了,一般的壳在这里会有明显的一个“分界线”。当然现在越来越多的加密壳将OEP一段代码搬到外壳的地址空间里,然后将这段代码清除掉。这种技术称为 Stolen Bytes 。这样,OEP 与外壳间就没那条明显的分界线了,增加了脱壳难度。

关于寻找 OEP 的 7 中方法:

1、单步跟踪法

​ 单步跟踪法的原理就是通过 Ollydbg 的单步(F8)、单步进入(F7)和运行到(F4)功能,完整走过程序的自脱壳过程(不断使用 F8 向下运行,遇见向上跳转的就用 F4 ,),跳过一些循环恢复代码的片段,并用单步进入确保程序不会略过OEP。这样可以在软件自动脱壳模块运行完毕后,到达OEP,并 dump 程序。

​ 单步跟踪法是最直接的方法,其主要的缺点是单步跟踪时十分枯燥,使用起来所花时间久,还可能出现跑飞的情况,但适用范围广。

​ 主要原理是通过一次次的步过,让壳完成数据的解密等工作后,停止在程序的 OEP 处。

2、ESP定律法

​ ESP 定律法是脱壳的利器,是应用频率最高的脱壳方法之一。ESP 定律的原理在于程序中堆栈平衡的合理利用。由于在程序自解密或者自解压过程中,不少壳会先将当前寄存器内容压栈,如使用 pushad,在解压结束后,会将之前的寄存器值出栈,如使用 popad。因此在寄存器出栈时,往往程序代码被自动恢复,此时硬件断点触发。然后在程序当前位置,只需要少许单步跟踪,就很容易到达正确的 OEP 位置。

​ 我们将程序载入后,当全部寄存器压栈后对 ESP 寄存器设置硬件断点,在壳将程序自解密或自解压的过程后,会将之前所保存的寄存器值出栈,所以我们下好断点,就可以停在此处,可以很方便的找到 OEP。

适用范围:几乎全部的压缩壳,部分加密壳。只要是在 JMP 到 OEP 后,ESP=0012FFC4 的壳,理论上我们都可以使用。但是在何时下断点避开校验,何时下断OD才能断下来,这还需要多多总结和多多积累。

3、内存镜像法(二次断点法)

​ 内存镜像法是在加壳程序被加载时,通过 OD 的 ALT+M 快捷键,进入到程序虚拟内存区段。然后通过加两次内存一次性断点,到达程序正确 OEP 的位置。
​ 内存镜像法的原理在于对于程序资源段和代码段下断点,一般程序自解压或者自解密时,会首先访问资源段获取所需资源,然后在自动脱壳完成后,转回程序代码段。这时候下内存一次性断点,程序就会停在OEP处。

​ 加壳程序在运行时,先将 .code 段进行自解密,然后再将其他段进行自解密,所以我们需要先在其他资源段下一个断点,使得 .code 段已经完成自解密,然后再在 .code 段下断点,使得程序断在执行 OEP 处。

4、一步到达OEP

​ 所谓的一步到达 OEP 的脱壳方法,是根据所脱壳的特征,寻找其距离 OEP 最近的一处汇编指令,然后下 int3 断点,在程序走到 OEP 的时候 dump 程序。 如一些压缩壳往往 popad 指令距离 OEP 或者大 Jump 特别近,因此使用 Ollydbg 的搜索功能,可以搜索壳的特征汇编代码,达到一步断点到达OEP的效果。

  1. ctrl+f 查找 popad
  2. ctrl+l 跳转到下一个匹配处
  3. 找到匹配处, 确认是壳解压完毕即将跳转到 OEP 部分, 则设下断点运行到该处
  4. 只适用于极少数压缩壳

5、最后一次异常法

​ 最后一次异常法的原理是,程序在自解压或自解密过程中,可能会触发无数次的异常。如果能定位到最后一次程序异常的位置,可能就会很接近自动脱壳完成位置。现在最后一次异常法脱壳可以利用Ollydbg的异常计数器插件,先记录异常数目,然后重新载入,自动停在最后一次异常处。

步骤:
1、 保留所有异常,载入程序。按"SHIFT"+“F9”,并开始记数M,直到程序运行。
2、 重新载入程序。按"SHIFT"+“F9"忽略异常M-1次。
3、 查看堆栈窗口中,“SE 句柄"前面的地址。
4、 按"ctrl+g”,打开表达式跟随窗口,输入,在堆栈窗口查到的地址。
5、 F2下断,按"SHIFT”+"F9"来到断点处,去掉断点。
6、 在OD的右下角会出现一个SE句柄,按CTRL+G,输入SE 句柄前的地址。
7、 F2下断,SHIFT+F9来到断点处。
8、 去掉断点,F8走到程序的OEP。

SE 定义:
Seh异常就是数结构化异常处理,Win32 结构化异常处理是操作系统提供的一种服务。
好处:
在编译器的 SEH 层减少了直接使用纯操作系统的 SEH 所带来的危害
缺点:
将纯操作系统的 SEH 搞成非透明
用法:
当程序遇到Seh异常时,异常交给系统处理(这将是一个非常负责的过程,很容易跟飞),所以利用Seh异常可以一定程度的防止程序被调试。(seh异常在壳里是很常见的)

优点:
脱加密壳的非常有效
缺点:
1、异常过多容易产生厌烦
2、只适用于加密壳。

最后一次异常法就是躲过这些 seh。然后可以比较容易走到 oep

6、模拟跟踪法

​ 模拟跟踪法的原理就是使用 Ollydbg 下条件断点,SFX 相当于是一个自解压段,在自解压段结束时(eip的值转到代码段时),已经距离OEP很近,但是这种跟踪方法会比较耗时。

常见步骤:
1、先简单跟踪程序看看是否有 SHE 暗桩
2、打开内存窗口,查看并记录"SFX 输入表 资源"行内存地址
3、执行 " tc eip < 上一步骤中记录的地址"

tc eip < 上一步骤中记录的地址:
其中 tc 是指跟踪进入直到条件的意思,使用其会让 OD 自动进行单步跟踪操作。

7、“SFX”法

​ “SFX”法利用了 Ollydbg 自带的 OEP 寻找功能,可以选择直接让程序停在 OD 找到的 OEP 处,此时自解压已经完成,可以直接dump程序。

在 OD 的“调试选项”中的 “SFX” 选项中选择 “字节模式跟踪实际入口”,再使用 ctrl + F2 慢慢等待。

关于 DUMP 的方法及原理:

工具:PETools(以管理员身份运行)、 importREC(以管理员身份运行)

​ 在其中找到我们所需要进行 dump 的程序,选择完整转存,然后就可以得到转存的文件了,再使用 importREC 进行修复 IAT 。其中 OEP 地址,RVA 需要注意填写的正确,可以通过查询 FF 15 查找 IAT 起始地址,从而得到 RVA 。

IAT RVA:IAT 起始地址 - 程序虚拟起始地址
IAT 大小:IAT 起始地址 - IAT 结尾地址(可以超出,不能小)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值