SuperAPI AI 工具集

unpacking-protected-binaries

安全与审计

查看项目

更新于 10-16 02:17

技能介绍

本技能主题为「Unpacking Protected Binaries」(脱壳受保护二进制文件),属于二进制逆向与恶意样本分析流程中的脱壳环节。

【基本理念】 对于 UPX 之外的保护壳,静态脱壳往往行不通。可靠的方法是让程序自行解包,再从内存中取出结果。无论保护壳多复杂,最终都必须在一块可读内存页中生成可执行代码,这一时刻就是等待的目标。受保护样本只能在隔离环境中运行。

【何时使用】 binwalk -E 显示文件大部分区域熵值均匀偏高;导入表只有少量条目(如 LoadLibrary、GetProcAddress);节区名缺失、随机,或为 UPX0/.themida/.vmp0;反汇编只看到一个小 stub 和一大块数据;工具报告文件被加壳,或分析发现几乎没有代码。

【何时不使用】 更广泛的恶意软件工作流应使用 analyzing-malware 处理隔离、初筛与 IOC 输出,再回到本环节做脱壳;.NET 程序集应使用 analyzing-dotnet-assemblies(专门覆盖 .NET 打包器与托管内存转储);没有加壳的混淆(导入表可读、熵正常、控制流混乱)应使用 analyzing-binaries;固件镜像的 binwalk -Me 提取属于另一项任务。

【先识别保护壳】 不同保护壳的应对方式差别很大,可先用 diec、rg 搜索特征字符串 UPX/MPRESS/Themida/VMProtect/ASPack/Enigma/Obsidium/PECompact,以及 binwalk -E 查看熵剖面。结构线索可用 readpe 或 rabin2 -S:原始大小约等于 0 而虚拟大小很大,说明节区在运行时解包;节区同时可写且可执行,说明自修改;入口点不在第一个节区,说明 stub 位于后续节区;存在 TLS 回调,说明代码在入口点之前就已运行。TLS 回调很重要:它们在入口点之前执行,若调试器只在 EP 下断点,保护壳的反调试检查已经跑完,因此应在 TLS 回调处下断点。

应对方式对照:未修改的 UPX 可直接 upx -d,几秒完成;修改过头的 UPX 需修复魔数或动态脱壳;常见加密壳与自定义 stub 在 OEP 处动态转储;Themida、VMProtect、Enigma 需转储加大量导入表修复,被虚拟化的函数仍会保持虚拟化;.NET 打包器转到 analyzing-dotnet-assemblies。可先尝试 upx -d target.exe -o unpacked.exe,成本为零。

【动态脱壳循环】 步骤为:在 stub 运行前中断(TLS 回调,若没有则 EP);运行直到解包后的代码出现在内存中;找到 OEP(真实程序的原始入口点);转储进程映像;重建导入表;修复 PE 头并验证转储可加载。

寻找 OEP 需要判断,可靠信号包括:内存的「先写后执行」,即对 stub 正在写入的节区设置执行硬件断点,首次执行处通常就在 OEP 附近,x64dbg 中可用页保护实现;尾部跳转,保护壳 stub 末尾会跳到自己节区之外很远的位置;编译器入口特征,真实入口点看起来像编译器的 CRT 启动代码,如 security_init_cookie、调用 __scrt_common_main 或标准序言;真实程序早期出现 GetCommandLine/GetModuleHandle 调用。x64dbg 工作流:在 Options → Events 中勾选在 TLS 回调和系统断点处中断;运行后在内存映射中为目标节区设置「Break on execute」;中断后确认代码看起来是编译器生成的,即到达 OEP。

【转储与导入表修复】 可在调试器于 OEP 处用 Scylla(内置于 x64dbg):已断在 OEP 时,Scylla → 设置 OEP → IAT Autosearch → Get Imports → 修复无效或未解析条目 → Dump + Fix Dump。也可外部转储:pe-sieve /pid <pid> /dmode 3 可转储并修复,适合自动化;hollows_hunter 可用于扫描全系统的已解包/注入模块。

导入表修复是大多数转储失败之处。保护壳用运行时解析的 stub 替换导入表,原始转储中的 API 调用会指向保护壳自己的 thunk 区域。Scylla 的 IAT 搜索可找到已解析的表;若返回未解析条目,通常意味着:转储发生在导入表完全解析之前,需继续运行;保护壳使用 API 重定向,每次调用经过一个计算真实地址的混淆 stub,需要手动解析或基于模拟的 unstubber;API 在首次调用时惰性解析,需先触发相应功能再转储。

转储后先验证:readpe dumped.exe | head -30 查看头部是否正常、EP 是否正确;rabin2 -i dumped.exe | head -20 查看导入是否解析为真实 API 名;最强验证是它能否运行,或反编译器能否输出合理结果。

【商业保护壳】 Themida、VMProtect、Enigma 等不只是加壳,还包括:虚拟化函数,选定函数被转换为定制 VM 的字节码,转储只能恢复其周围的程序,被虚拟化的函数仍是 VM 解释器循环,去虚拟化是按保护壳版本进行的研究级工作;激进的反调试与反 VM,需先处理检测(ScyllaHide、TitanHide 或虚拟机管理程序级调试器),否则进程会在到达 OEP 前退出或自毁;多个解包阶段并对早期阶段重新加密。实用决策是:若只有少数函数被虚拟化,转储并分析其余部分,再动态处理这些函数——挂钩其输入输出而非阅读其逻辑,这样无需攻破 VM 也能回答「它做什么」。

【无法转储时】 有些样本不会在一处完整解包:它们按需解密单个函数并在使用后重新加密,或完全在 JIT 式缓冲区中运行。可用基于跟踪的恢复:用插桩框架记录执行并从跟踪重建执行的代码路径;也可挂钩解密例程,记录每个明文块,从而零散但完整地得到代码。插桩工具可用 Frida、Pin、DynamoRIO。

【需要驳回的常见理由】 包括:upx -d 失败就认为不是 UPX(修改 UPX 头是最常见的规避方式,应检查节区名与 stub 模式);自己写静态脱壳器(仅在你会遇到上千次的单一家族时才值得,否则动态转储成本低一个数量级);转储无法运行就认为它错了(先确认你真正需要什么,很多转储不可运行但完全可分析);导入表损坏就认为转储无用(可在执行更晚时再转储,或手动解析,通常是时机问题);是 VMProtect 就认为不可能(只有被虚拟化的函数如此,其余照常转储,把这些函数当作可观察输入输出的黑盒);在入口点下断点(TLS 回调已经跑过);运行后没反应(反 VM 或反调试已触发,应先处理检测再脱壳)。

【参考资料】 analyzing-malware(隔离、能力模型、IOC,本环节所属的工作流)、analyzing-binaries(脱壳后分析与反分析处理)、analyzing-dotnet-assemblies(.NET 打包器与托管转储)、Detect It Easy、x64dbg + Scylla + ScyllaHide、pe-sieve、hollows_hunter、UPX。