PECompact
PECompact2 是一款极具代表性的 Windows 平台 PE 可执行文件压缩壳。相较于基础的 UPX 壳,PECompact2 具备更高的压缩比,并引入了结构化异常处理(SEH)和 API 劫持等反分析机制,增加了脱壳的难度。
一、 PECompact2 壳基础原理与特征
PECompact2 的设计目标不仅仅是减小文件体积,还包含了适度的代码保护功能。在静态分析时,可以通过以下特征进行初步识别:
-
区段特征:典型的 PECompact2 加壳程序通常包含少量区段(如 2 到 3 个),部分版本会生成包含
PEC2字符的区段名(如PEC2TO),但在 CTF 题目中,区段名常被恶意抹除或伪装成常规的.text。 -
反调试与控制流混淆:在解压存根(Unpacking Stub)中,PECompact2 大量使用 SEH(结构化异常处理)来控制程序流。普通的单步跟踪(Step Over/Step Into)极易陷入异常处理的无限循环,或直接导致调试目标崩溃。
-
API 重定向(IAT Hooking):在构建导入表(IAT)时,壳代码可能会对
GetProcAddress和LoadLibraryA等 API 进行处理,使得直接提取的导入表存在指向壳代码空间的跳板指针(Thunks),而非真实的 API 绝对地址。
二、 执行流程解析
带有 PECompact2 壳的程序,其解压与执行流通常分为以下五个核心阶段:
-
入口点初始化:保存所有通用寄存器状态(如使用
pushad/pushfd),并设置初始的 SEH 链以接管后续产生的异常。 -
内存分配:调用
VirtualAlloc在内存中开辟新的空间,用于存放解压后的原始代码或构建中间数据结构。 -
代码解压:将压缩的区段数据块循环解压,并写入至指定的内存区域(通常是原本的
.text区段)。 -
导入表重建:解析原程序的依赖文件,动态获取所需的 API 地址并填充至原程序的 IAT 中。
-
跳转至 OEP:清理栈空间与异常链,通过尾部跳转(Tail Jump,如
jmp eax或push eax; retn)将执行权移交给原始入口点(OEP, Original Entry Point)。
三、 手动脱壳核心技术(寻找 OEP)
由于 CTF 题目常对标准壳进行“魔改”,自动化脱壳工具往往失效,必须依赖动态调试器(如 x64dbg、OllyDbg)进行手动脱壳。以下是应对 PECompact2 的三种常用策略:
1. ESP 定律法
尽管 PECompact2 结构复杂,但大部分变体在入口点依然遵循保存寄存器状态的逻辑。
-
操作步骤:
-
程序载入调试器,在执行过第一条压栈指令(如
pushad)后,观察栈指针寄存器(ESP)的值。 -
在内存窗口定位至该 ESP 地址,设置硬件访问断点(Hardware Breakpoint on Access / Dword)。
-
直接运行程序(F9)。触发硬件断点后,程序通常已处于解压流程的尾声。
-
向下浏览反汇编窗口,寻找大跨度的跳转指令(特别是跳转到寄存器的指令,如
jmp eax),该指令的目标地址极大概率就是 OEP。
-
2. 单步追踪与 SEH 异常处理法
由于 PECompact2 依赖异常来转移控制流,遇到异常时直接单步会丢失控制权。
-
操作步骤:
-
在调试器设置中,取消对特定异常的忽略,以便拦截壳代码主动抛出的异常。
-
单步向下执行,如果遇到非法访问指令,查看当前线程的 SEH 链,找到对应的异常处理函数地址。
-
在异常处理函数的入口处设置软件断点,或者直接使用
Shift + F9(传递异常并运行)跳过当前异常。 -
经过数次异常传递后,程序将进入一段无异常的连续解压循环。跳过循环后,寻找尾部的
jmp eax即可定位 OEP。
-
3. VirtualAlloc 内存断点法
壳程序解压代码必然需要申请内存或在特定区块写入数据。
-
操作步骤:
-
通过命令行或符号表对 API
VirtualAlloc下断点。 -
每次函数断下时,记录其返回的内存基址。
-
当该 API 不再被密集调用,且程序开始向目标执行区段大量写入数据时,在目标区段的
.text内存段设置内存执行断点(Memory Execute Breakpoint)。 -
恢复运行,程序再次断下时,通常已解压完毕且刚好停在 OEP 处。
-
四、 内存转储与 IAT 修复难点
到达 OEP 后,需进行内存提取与导入表修复,这是 PECompact2 脱壳的难点所在。
-
内存转储(Dump):使用 Scylla 或 OllyDumpEx 等插件,将当前进程空间中的映像保存为新的 PE 文件。
-
处理重定向的 API(IAT 修复):
-
在 Scylla 中填入 OEP 的 RVA 并点击 "Get Imports" 时,可能会发现大量未能成功解析的无效指针(指向壳内存段)。
-
修复方案 1:如果使用 ImportREC,可以尝试加载 PECompact 专属的 API 修复插件。
-
修复方案 2:手动逆向壳的跳板代码。定位到无效指针所指向的地址,分析其汇编逻辑,找出真实目标 API 的地址,随后在重建工具中手动修正这些指针的映射关系。
-
修复完成后,点击 "Fix Dump" 将正确的导入表注入到提取出的 PE 文件中。
-