概述
什么是壳
壳是在正常程序代码运行之前先运行的一段代码
为什么要加壳
加壳的主要目的有以下几点
- 程序太大,加压缩壳对主程序数据进行压缩,接收空间
- 为了防止程序被静态分析,加壳后加密原有数据,骗过IDA等静态分析工具
- 为了防止程序被动态跟踪,加壳后的代码检测调试器是否存在,是否运行在虚拟机,防止程序被恶意分析破解
- 恶意程序加壳后隐藏特征码,绕过杀软的扫描等
大致种类
一般的壳分为两种:
一种是普通的壳,如ASP,UPX等加壳后程序代码和壳代码之间有明显的分界线,即相互独立,一般壳代码负责还原原始程序数据,执行完毕后壳代码不再有用,将控制权完全交还给源程序。
另一种是强壳,这种壳一般与源程序相互交叉,即加壳时会修改原有代码,并且壳的代码会随着源程序的执行一直执行,如VMP,Themida壳等,加壳后修改源程序的OPCODE,该种壳与源程序没有明显的界限,所以脱壳时也比较麻烦。
壳代码的一般执行流程
- 解密源程序的数据
- 按照区段头的描述将源程序区段加载到指定位置
- 修复程序的IAT
- 修复程序的重定位
- 跳转到原始程序的OEP处
如何加壳
使用010Editor手动加壳
以前面的010Editor注册工具为例,手动加壳主要有以下步骤
- 在节表中添加一项,即添加一个区段,修改相应的数据用于保存壳的代码数据
虚拟大小和文件大小自己按需增加,虚拟地址根据上面的一个区段进行计算,比如上面的 虚拟地址
// 虚拟地址 = 上一区段的VirtualAddress+align(VirtualSize,0x1000)
0x00BFB000 = 0xBAB000+align(0x4FBCA,0x1000)
//文件偏移 = 上一区段的pointerToRawData+sizeOfRawData
0x008F9000 = 0x8A9400+0x4FC00
其中的0x1000为内存分页大小,一般32为下是0x1000
-
将FileHeader.NumberOfSection加一,区段数量加一
-
在文件末尾添加指定长度的空间,用于写入代码和数据
-
修改optionalHeader.sizeOfImage,加上新添加的数据长度
-
添加代码
- 在导入表中查找MessageBox函数,用于壳代码弹出对话框
- 计算OEP跳转偏移,计算公式如下
跳转偏移 = 目标地址 - 当前E9指令地址 - 5(jmp指令长度)
跳转回原OEP指令为
// 偏移 = 目标地址 - 当前地址 - 指令长度
// 偏移 = 0x2F8124 - 0xBFB02E - 5 = 0xFF6FD0F1
E9 F1D06FFF - 最终构造壳代码如下
- 修改壳区段的属性为 可读可写可执行,包含数据和代码
- 关闭重定位
将NtHeader.OptionalHeader.DllCharacteristic.Dynamic修改为0,或者将
NtHeader.Fileheader.File_Relocs_Stripped修改为1即可完成重定位的关闭
这样程序就不会发生重定位,而是将程序加载在imageBase处。 - 修改OEP
将NtHeader.OptionalHeader.addressOfEntryPoint修改为新的入口点
- 程序运行结果
此时程序没有产生重定位
- 在导入表中查找MessageBox函数,用于壳代码弹出对话框
-
在重定位表中添加几项,用于完成新添加代码的重定位
根据壳代码中的代码,计算需要重定位的地址如下
按照重定位表结构在重定位表后追加3个重定位项
修改数据目录表中的重定位项的size为修正后的size
保存后重新运行程序发现程序已经产生了重定位,且运行正常
总结
本次手动加壳主要涉及以下几个方面的知识点
- PE文件结构
- PE文件中区段的管理方式–>区段表
添加一个区段时需要修改的点:- 添加一个区段描述符表,修改相应的数据
- 将FileHeader中的区段数量加1
- 在文件末尾添加指定长度的数据空间
- 修改映像大小 sizeOfImage
- 程序中外部函数的调用方式–>IAT调用
由于dll等文件的重定位,导致我们调用dll中的外部函数时无法实现直到我们应该调用的地址,所以才会有IAT的调用,大致为:- 在内存中规定一段空间用于保存API地址
- 我们调用API时使用FF15调用该内存区域中的值
- 文件加载时根据导入表往这块内存的指定位置写入外部函数的真实地址
- 这样就实现了所谓的位置无关代码,即通过一段内存空间进行中转,加载时修正内存的方式,这样不管dll加载在何处,我们都能调用到正确的函数。
- 重定位的实现方式–>重定位表
程序中存在许多直接使用绝对地址的地方,比如我们期望程序加载在0x400000地址处,然后代码中使用了0x420000处的数据,但是当我们的程序由于某些原因不能放在0x400000处了,那么0x420000处就已经不是我们期望的数据了,这样就无法正确运行
重定位表中保存的就是哪些**地址****(注意重定位表中保存的是地址,真正需要修正的是该地址处的值)**处是写死的地址,在发生重定位时就要对这些地址处的内容进行修正,以便找到真正的数据。
注意:在重定位表中手动添加重定位项时,要在末尾添加两个字节的零,并且要修正数据目录表中重定位表的size,否则系统就不会对程序进行重定位了。