一. 回顾
PE 文件的 Magic code(魔数、幻数)是 MZ 头、PE 头
PE 文件中头文件的信息有运行平台、时间戳、PE 文件属性、区段数量、扩展头大小
PE 文件中的扩展头信息有 OEP 的 RVA、ImageBase(0x400000)、代码段、数据段起始地址、数据目录表、数据目录表项数、文件对齐粒度、内存对齐粒度、映像总大小
PE 文件区段信息有区段名、虚拟地址、虚拟大小、文件偏移、文件大小、区段属性(C0000020, 60000020等)
PE 文件中数据目录表有导入导出表、异常表、tls表、资源表、IAT、重定位表
一个进程三环下的数据结构有进程环境块(PEB)、线程环境块(TEB)、tls 结构
导入表结构体有5个字段,第一个
OriginalFirstThunk
,指向 INT,第二个是时间戳,第三个是转发机制用到的ForWarderChain
,第四个是Name
,dll 名称字符,第五个是FirstThunk
,指向的是 IATINT 和 IAT 在文件中存在是一样的,都指向名称字符串的 RVA 或一个序号
二. 手工加壳
目标:将代码段加密,防止 IDA 等静态工具分析
步骤:
添加一个区段(文件大小、区段数量)
用 LoadPE 打开一个自己用 VS2017 编写的简单程序,添加区段,默认名为 NewSec


添加完成后发现无法打开程序,因为添加的虚拟大小、文件大小都是空的

使用 LoadPE 编辑区段信息,添加虚拟大小和文件大小都是 0x200
然后使用 010Editor 添加文件数据


在文件末尾添加 200H 个字节,保存后再次打开程序,发现能够运行
修改 OEP,增加 OEP 代码
将程序的入口点改为新添加区段的 RVA,原区段入口点为 0x11343

使用 OD 打开程序
直接跳转到原来的 OEP 可以成功,因为 jmp 指令后面是相对偏移

第二种方法,使用技巧获取基地址再跳转,
call
指令后,将 0x100005 入栈,pop eax
即eax = 0x100005
,然后减去 0x20005 得到模块基址,基址加上原 OEP 即为原 OEP 的入口地址
第三种方法,将上面的
sub add
合并计算出偏移,入口点地址+5-(入口点地址-新OEP+原OEP)

代码演化,去掉随机基址
在新的 OEP 处直接跳转到原来的 OEP

在新的 OEP 处定义出模块基址,然后加上原始 OEP,RVA

在新的 OEP 处通过
call pop
组合获取当前指令地址,再减去偏移,计算出模块基地址
加密代码段
代码段信息

在 010Editor 中操作代码段
选中代码段

异或加密代码段


增加解密代码
由于代码本身有重定位信息,加密之后重定位会出现问题,故应去掉随机基址
在 010Editor 中将 40 81 改为 00 81
将 .text 段的标志设为可写入

在程序入口点编辑代码进行解密

脱壳
脱壳目的:
- Cracker 脱壳、解密、破解
- 杀毒引擎解密、查杀病毒、扫描特征
脱壳步骤:
找到原始 OEP
一般来说,找到原始 OEP 或跟踪到原始 OEP 时,程序都会完成解密操作
dump 内存到文件
当可执行文件在内存中完成解密后,将内存中代码数据转储(dump)到文件,就可以进一步分析
修复文件(常见于修复 IAT,重建导入表)
从内存中转储的内存数据代码有一些与原文件中的内容不一致,比如 IAT 表,内存中 IAT 表会被初始化为函数地址表,而文件中 IAT 表与 INT 表内容一致,所以想要程序正常运行,一般都要修复 IAT,加壳后程序一般会自己处理导入表、IAT 及重定位等
导入表和 IAT、INT

IAT 在文件中保存的是一个 RVA 数组,每一项指向函数字符串结构,在内存中这个 RVA 数组被修改为函数地址,每个函数地址就是之前对应的函数字符串的函数,所以从内存中 dump 出的文件必须进行 IAT 修复或导入表修复

脱自己的壳
找到原始 OEP
单步跟踪,在
POPAD
附近寻找dump 内存到文件
在原始 OEP 处进行 dump 内存,因为这时内存未做太多的初始化,右击,选择
用OllyDump脱壳调试进程

填入当前模块的起始地址和原来的 OEP,取消选择
重建输入表
,脱壳修复 IAT 或修复导入表
使用 ImportREC 15pb专用版

打开该工具,选择脱壳进程,设置 OEP,方便查找 IAT,点击 自动查找IAT
,获取 IAT 信息,点击 获取输入表
,获取输入表信息,若输入表函数有无效的,就需要修复,最后将输入表信息转储到文件,完成修复