Skip to content

PECompact

PECompact2 是一款极具代表性的 Windows 平台 PE 可执行文件压缩壳。相较于基础的 UPX 壳,PECompact2 具备更高的压缩比,并引入了结构化异常处理(SEH)和 API 劫持等反分析机制,增加了脱壳的难度。

一、 PECompact2 壳基础原理与特征

PECompact2 的设计目标不仅仅是减小文件体积,还包含了适度的代码保护功能。在静态分析时,可以通过以下特征进行初步识别:

  1. 区段特征:典型的 PECompact2 加壳程序通常包含少量区段(如 2 到 3 个),部分版本会生成包含 PEC2 字符的区段名(如 PEC2TO),但在 CTF 题目中,区段名常被恶意抹除或伪装成常规的 .text。

  2. 反调试与控制流混淆:在解压存根(Unpacking Stub)中,PECompact2 大量使用 SEH(结构化异常处理)来控制程序流。普通的单步跟踪(Step Over/Step Into)极易陷入异常处理的无限循环,或直接导致调试目标崩溃。

  3. API 重定向(IAT Hooking):在构建导入表(IAT)时,壳代码可能会对 GetProcAddress 和 LoadLibraryA 等 API 进行处理,使得直接提取的导入表存在指向壳代码空间的跳板指针(Thunks),而非真实的 API 绝对地址。


二、 执行流程解析

带有 PECompact2 壳的程序,其解压与执行流通常分为以下五个核心阶段:

  1. 入口点初始化:保存所有通用寄存器状态(如使用 pushad / pushfd),并设置初始的 SEH 链以接管后续产生的异常。

  2. 内存分配:调用 VirtualAlloc 在内存中开辟新的空间,用于存放解压后的原始代码或构建中间数据结构。

  3. 代码解压:将压缩的区段数据块循环解压,并写入至指定的内存区域(通常是原本的 .text 区段)。

  4. 导入表重建:解析原程序的依赖文件,动态获取所需的 API 地址并填充至原程序的 IAT 中。

  5. 跳转至 OEP:清理栈空间与异常链,通过尾部跳转(Tail Jump,如 jmp eax 或 push eax; retn)将执行权移交给原始入口点(OEP, Original Entry Point)。


三、 手动脱壳核心技术(寻找 OEP)

由于 CTF 题目常对标准壳进行“魔改”,自动化脱壳工具往往失效,必须依赖动态调试器(如 x64dbg、OllyDbg)进行手动脱壳。以下是应对 PECompact2 的三种常用策略:

1. ESP 定律法

尽管 PECompact2 结构复杂,但大部分变体在入口点依然遵循保存寄存器状态的逻辑。

  • 操作步骤:

    1. 程序载入调试器,在执行过第一条压栈指令(如 pushad)后,观察栈指针寄存器(ESP)的值。

    2. 在内存窗口定位至该 ESP 地址,设置硬件访问断点(Hardware Breakpoint on Access / Dword)。

    3. 直接运行程序(F9)。触发硬件断点后,程序通常已处于解压流程的尾声。

    4. 向下浏览反汇编窗口,寻找大跨度的跳转指令(特别是跳转到寄存器的指令,如 jmp eax),该指令的目标地址极大概率就是 OEP。

2. 单步追踪与 SEH 异常处理法

由于 PECompact2 依赖异常来转移控制流,遇到异常时直接单步会丢失控制权。

  • 操作步骤:

    1. 在调试器设置中,取消对特定异常的忽略,以便拦截壳代码主动抛出的异常。

    2. 单步向下执行,如果遇到非法访问指令,查看当前线程的 SEH 链,找到对应的异常处理函数地址。

    3. 在异常处理函数的入口处设置软件断点,或者直接使用 Shift + F9(传递异常并运行)跳过当前异常。

    4. 经过数次异常传递后,程序将进入一段无异常的连续解压循环。跳过循环后,寻找尾部的 jmp eax 即可定位 OEP。

3. VirtualAlloc 内存断点法

壳程序解压代码必然需要申请内存或在特定区块写入数据。

  • 操作步骤:

    1. 通过命令行或符号表对 API VirtualAlloc 下断点。

    2. 每次函数断下时,记录其返回的内存基址。

    3. 当该 API 不再被密集调用,且程序开始向目标执行区段大量写入数据时,在目标区段的 .text 内存段设置内存执行断点(Memory Execute Breakpoint)。

    4. 恢复运行,程序再次断下时,通常已解压完毕且刚好停在 OEP 处。


四、 内存转储与 IAT 修复难点

到达 OEP 后,需进行内存提取与导入表修复,这是 PECompact2 脱壳的难点所在。

  • 内存转储(Dump):使用 Scylla 或 OllyDumpEx 等插件,将当前进程空间中的映像保存为新的 PE 文件。

  • 处理重定向的 API(IAT 修复):

    1. 在 Scylla 中填入 OEP 的 RVA 并点击 "Get Imports" 时,可能会发现大量未能成功解析的无效指针(指向壳内存段)。

    2. 修复方案 1:如果使用 ImportREC,可以尝试加载 PECompact 专属的 API 修复插件。

    3. 修复方案 2:手动逆向壳的跳板代码。定位到无效指针所指向的地址,分析其汇编逻辑,找出真实目标 API 的地址,随后在重建工具中手动修正这些指针的映射关系。

    4. 修复完成后,点击 "Fix Dump" 将正确的导入表注入到提取出的 PE 文件中。


How to Unpack PECompact Tutorial