
简介一份面向 Themida/WinLicense V1.8.XV2.X 保护壳的脱壳工具合集主要适合逆向工程学习者、安全分析人员以及需要分析该系列壳保护机制的开发者。资源描述虽简短但强调其可以使用包内提供多种语言工程与实例覆盖从宏定义、配置脚本到可执行工具的相关内容便于直接复用。压缩包共计301个文件、约32.77MB核心内容包括 inc/vm/pas/cpp 等源码与工程文件dll/exe 执行工具chm/pdf 说明文档以及 lng/rc 等界面或资源辅助项目录结构兼顾代码示例与工具链便于按需取用另附少数历史配置与备份文件便于对比不同版本间的调整差异。目前已有 1322 人学习下载。通过这份资料读者可快速掌握针对该系列壳的脱壳思路参考现成示例减少环境配置成本也可将其中宏定义、汇编代码与工程配置迁移到自己的项目中提升分析效率并在实际调试中理解保护机制的具体表现。1. 为什么 Themida 1.8.X-2.X 的壳这么难脱先搞懂它到底在对抗什么我做过不少带 Themida 和 WinLicense 保护的分析任务对“最佳脱壳工具”这个说法最大的理解是不存在一个万能按钮但确实存在一套针对 V1.8.X-V2.X 最可靠的工具组合。你从调试器加载样本那一刻起壳就开始多路试探——PEB 里的调试标志、调试端口、硬件断点余量、时间差任何一路暴露进程会直接结束或走进一条假流程。不少人在这一步连续翻车不是不懂脱壳而是没把“隐藏调试器—找 OEP—重建 IAT”整条链路当作一件事来对待。这篇讲的是怎么选工具、怎么设参数、哪些地方必须手动兜底、哪些坑反复出现适合手里已有待分析样本、被反调试耗光耐心的安全测试和软件开发人员。2. 选对工具链三层配合才谈得上“最佳”说句实在话Themida 和 WinLicense 的脱壳从 1.8 到 2.X 变化不小但通用的落地路径没变先保证调试器不被发现再找个能准确抓内存和重建导入表的工具最后用脚本或手动判断把 OEP 从壳代码里摘出来。这三层少哪一层都会出问题——调试器暴露进程秒退dump 工具烂抓一百遍都是废文件OEP 判断错了后面所有修复都是白做。2.1 调试器与隐藏插件的搭配为什么选 x64dbg ScyllaHideThemida 1.8.X-2.X 的反调试不是单一检测点而是并发多路。常见的有 PEB 里的BeingDebugged、NtGlobalFlag、ProcessHeapFlags也有通过NtQueryInformationProcess查询调试端口还有把关键线程设为ThreadHideFromDebugger的骚操作。如果调试器不做隐藏壳的第一段解压代码根本走不到。我一般用 x64dbg 作主调试器配 ScyllaHide。选这个组合而不是老牌 OllyDbg主要三个理由一是 64 位样本在 OllyDbg 上没法稳定跟二是 x64dbg 的插件被 ScyllaHide 适配得更完整后者的隐藏能力明显强于 OllyDbg 时代三是 x64dbg 的条件断点记录和日志窗口在做大量 API 监控时更好用。ScyllaHide 的核心参数不是全勾最优这是很多人没意识到的。我常用的配置是下面这张表目标是在“足以骗过早期探测”和“不留下新的行为痕迹”之间取平衡。配置项推荐值作用Hide PEB: BeingDebugged勾选清掉 PEB 里的进程被调试标志Hide PEB: NtGlobalFlag勾选清掉堆调试相关全局标志Hide PEB: ProcessHeapFlags勾选掩掉堆头部调试标志NtQueryInformationProcess勾选截住调试端口类查询NtSetInformationThread勾选防止线程被隐藏后断点失效如果把 ScyllaHide 的全部功能都打开反而容易被 Themida 的行为检测识破因为有些系统调用在特定版本上不允许被改强行挂钩会留下更明显的痕迹。我一般只开这五项剩下保持默认先过了第一轮特征检测再随机应变。走到第 3 章后你会发现这个隐藏配置决定了后面 API 断点能不能用。2.2 转储与 IAT 重建Scylla 比 ImportREC 更适合这个版本段确定 OEP 之后要解决两件事把当前进程内存写成文件再把被壳抹掉的导入表补回来。老牌的 ImportREC 对 Themida 1.8 的某些早期变种还有成功率但在 2.X 上我几乎没见过它能一次修复干净问题出在它把壳自己构造的跳板表当成了真实导入表修出来的程序一运行就崩。这个版本段我更信任 Scylla。它能识别真实导入表和 fake jmp 跳板的差异也支持保存重定位表正好堵住 x64 样本换基址就崩的常见缺口。操作顺序有讲究先确认 OEP再点 Auto Search 让 Scylla 扫 IAT 起始地址然后 Get Imports 做一次预览确认没有大量 OrdinalOnly 或 Invalid 标记后再 Dump最后 Fix Dump。先 dump 再搜 IAT 是错误顺序内存布局已经离开上下文扫到的导入表会缺一截。给一个最干净的启动命令# 直接把样本交给 x64dbg 加载不要先开调试器再去 attach x64dbg.exe C:\samples\legit_app.exe这样做的原因是 Themida 对 attach 动作有专门的检测直接启动可以少暴露一路特征。加载后先停到系统断点确认 ScyllaHide 的注入日志正常再按 3.1 的步骤走。2.3 脚本引擎与“一键脱壳”的误区顺便说下 VMProtect 对比网上流传的脱壳脚本大多基于 OllyDbg 的 ODbgScript 或 x64dbg 的脚本命令写。基本思路是在VirtualAlloc、VirtualProtect、NtProtectVirtualMemory之类函数下断点记录内存块的分配和属性变化最后跳到壳认为的原始入口。这个思路在 1.8.X 上确实能跑通但到 2.X 之后脚本经常在某个普通的ret指令上停下来往后单步全是虚拟化指令继续走就进了死循环。所以我把脚本定位成“缩小范围”的工具而不是“一键脱壳”。配合日志来用# x64dbg 脚本把核心内存操作函数断下来再在断点属性里加参数日志 bp VirtualAlloc bp VirtualProtect bp NtProtectVirtualMemory设置完成后按 F9 运行每命中一次就在日志窗口看地址和长度最后被改成可执行属性的那块内存就是壳完成解压的现场。脚本推的方向如果和日志记录矛盾我会优先相信日志。同样难脱的还有 VMProtect很多人在搜 Themida 时会连带到“vmprotect 脱壳工具”。提醒一句两者思路不同工具链不能拿来直接换。Themida/WinLicense 主要靠频繁的 API 反调试和分阶段解密而 VMProtect 会把代码搬进自带的虚拟机解释器Scylla 对后者几乎无效。选工具之前先看清壳的种类别在错误的壳上浪费一下午。3. 用 x64dbg Scylla 跑通一次 Themida 脱壳的最小流程3.1 站在解压现场用内存属性变化判断壳是否走完跑通一次脱壳的关键不是从头单步而是找到“解压完成”的信号。Themida 一般会在原始入口片段里先解密代码节再转移控制权解密过程中会反复调用VirtualProtect或NtProtectVirtualMemory把内存区域从只读或读写改成可执行。我通常在两个函数上下断点# x64dbg 断点命令先断下来再在断点属性里记录 p1 地址、p2 大小、p3 新属性 bp VirtualProtect bp NtProtectVirtualMemory每次断下后日志窗口里会看到这次调用的参数。当某条记录显示目标模块名是样本本身、属性变成了0x40PAGE_EXECUTE_READWRITE或0x20PAGE_EXECUTE_READ并且地址落在代码节附近时基本可以认定解压循环已经把关键代码写到位。在这块区域下断点按 F9命中时就是壳要交出控制权的临界点。注意日志里会混进大量 ntdll、kernelbase 自己的调用那些不是壳的行为。判断标准只看两个维度模块名是否为加载样本本身属性是否朝着“可执行”变化。如果只有系统模块在刷日志说明还没到壳主流程继续跑。3.2 用硬件执行断点确认 OEP而不是普通断点壳在进入原始入口之前会做一次跳转常见形式是jmp或pushret。如果在这里下普通 INT3 断点很容易被 Themida 的断点扫描发现然后进程安静退出。我一般改用硬件执行断点让断点信息存在 CPU 调试寄存器里壳不容易扫描到。在 x64dbg 里对候选地址用bph命令设置硬件执行断点# x64dbg 硬件断点命令bph 后跟目标地址 bph 0x00401000设置后按 F9。命中时先不急着庆祝检查当前指令上下文如果看到push ebp / mov ebp, esp或sub rsp, 0x28这类常见编译器开头并且附近有指向字符串的引用这才是原始入口。判断不能只看一条指令Themida 自己的解密桩也会伪造类似序言要结合栈回溯和模块区段地址来确认。3.3 Scylla 的 Dump 与修复顺序以及 PE 头自检确认 OEP 后切到 Scylla 插件操作顺序固定为五步进程列表选当前进程ImageBase 填模块加载基址。点 Auto SearchScylla 扫出 IAT 起始地址和大小。点 Get Imports看列表里是否出现大量 Invalid 或 Ordinal 占位。没问题就点 Dump保存为 unpacked.exe。点 Fix Dump把导入表写进文件。有人喜欢在 Dump 之后直接用十六进制工具改文件那是在给自己找麻烦。正确路径是让 Scylla 把导入表和新 PE 头一起处理好改完再用脚本自检。这里给一个 Python 校验脚本读取 PE 区段信息判断壳是不是真的从镜像里消失了import struct def dump_sections(path): with open(path, rb) as f: head f.read(0x40) if head[:2] ! bMZ: print(not a valid MZ file) return pe_off struct.unpack_from(I, head, 0x3C)[0] f.seek(pe_off) pe_sig f.read(4) if pe_sig ! bPE\x00\x00: print(no PE signature) return coff f.read(20) num_sec struct.unpack_from(H, coff, 2)[0] opt_size struct.unpack_from(H, coff, 16)[0] f.seek(pe_off 4 20 opt_size) for i in range(num_sec): raw f.read(40) name raw[:8].rstrip(b\x00).decode(latin1, replace) vsize struct.unpack_from(I, raw, 8)[0] rawsize struct.unpack_from(I, raw, 16)[0] flags struct.unpack_from(I, raw, 36)[0] print(f{i1:02d} {name:8s} vsize0x{vsize:X} rawsize0x{rawsize:X} flags0x{flags:08X}) dump_sections(unpacked.exe)这段代码会遍历 PE 文件头里的区段表打印每个区段的名字、虚拟大小、原始大小和属性标志。正常编译器生成的 PE 区段名通常是.text、.data、.rdata壳处理过的文件则常常出现一组随机短名字节。修复前后各跑一次对比区段数量就能量化壳残留。flags0xE0000020表示“代码 可执行 可读 已初始化”如果这个属性的区段数量比正常程序多出好几个壳的代码段可能仍被保留在镜像里。在修复时Scylla 的参数里有一个New ImageBase这个字段在 x64 样本上要格外小心。Themida 2.X 开了 ASLR如果 dump 时把 ImageBase 改成 0且没有保存重定位表程序加载地址一变就会崩。一般来说保持原始 ImageBase并勾选包含重定位表的修复选项。修复完程序仍然启动报错时第一个要检查的就是这里。4. 手动兜底当 Scylla 失灵后的 OEP 精确定位与 IAT 手工修复4.1 用条件记录日志缩小 OEP 候选区Scylla 失灵大多不是因为工具不行而是 OEP 根本没找对。壳跳进虚拟化片段时你也会看到一些像正常代码的东西但它可能是解释器的一块调度代码。这时我会上执行追踪逐条记录跳转目标看执行流有没有从壳的调度区真正落到样本的.text区段。在 x64dbg 里对候选入口区块开启记录执行跑几十上百条指令后看日志分布。如果日志里连续出现push、sub rsp、call这类常规函数序言并且地址全部落在样本模块范围内这就是原始代码开始执行的证据。如果日志里大量出现同一个跳板地址或反复落在两个地址之间说明还困在虚拟机分发循环里没到 OEP。这一步不要追求快多跑几个来回。记录日志对性能有影响但 Themida 脱壳本来就是慢工细活宁可多花十分钟也不要抓一个带病的 dump 回去修。4.2 手工补 IAT从 GetProcAddress 调用点反向整理Themida 1.8 早期版本经常在运行时通过LoadLibrary和GetProcAddress动态拿 APIScylla 对这类动态 IAT 的静态扫描经常漏。遇到修复后列表里全是 Ordinal 或 Invalid 的情况我改用动态跟踪在 OEP 之后跟着执行流遇到call eax或call [reg]时查看目标 API 地址和所在 DLL记录下来。把这些记录写回 dump 文件最省事的办法是用 pefile 库先看当前导入状态import pefile pe pefile.PE(unpacked.exe, fast_loadFalse) pe.parse_data_directories() for entry in pe.DIRECTORY_ENTRY_IMPORT: names [] for imp in entry.imports: if imp.name: names.append(imp.name.decode(ascii, replace)) else: names.append(fordinal:{imp.ordinal}) print(entry.dll.decode(ascii, replace), names[:8])这段代码会打印当前 PE 的导入表。如果打印出来的 DLL 是user32.dll、kernel32.dll或程序本身依赖的库说明 Scylla 修复基本到位如果全是系统路径下的杂项 DLL或者导入项极度稀疏说明 IAT 还没补齐需要回到 4.1 重新找 OEP。pefile 的参数fast_loadFalse保证解析所有目录比默认模式多花一点时间但信息完整。注意pefile 只能解析不能自动补全动态 IAT。补全的工作是拿到上面打印的 DLL/API 列表之后手动修改导入描述符让新增 API 指向 Scylla 已留出的空白 IAT 位置。这块没有通用命令因为每个样本的 IAT 布局不同理解了结构之后再动手就不玄学了。4.3 WinLicense 的授权校验和脱壳要分开看待WinLicense 与 Themida 最大的区别是它在壳之外还会加入注册码、试用期、内存补丁一致性校验之类的业务逻辑。经常出现的情况是壳已经脱干净程序也能启动但几秒后弹窗提示授权无效。很多人误以为壳没脱干净回头重抓 dump重复几轮也没用。我处理这类样本时会先确认提示代码的归属。如果弹窗来自业务模块而不是壳的解密段那就说明脱壳已经完成剩下的是授权管理逻辑不在“脱壳工具”的解决范围内。分析时如果需要继续跟业务算法带着授权提示操作即可不要试图在脱壳阶段同时解决两个问题那会浪费大量时间。4.4 不要追求完整还原虚拟化代码Themida 2.X 会对关键函数做虚拟化处理让它变成壳解释器的字节码。Scylla、手工 IAT 都无法还原这段代码因为它在执行时不会直接调用熟悉的 API而是通过壳的指令解释循环。很多人在这一块死磕试图把虚拟指令一一翻译回原始汇编最后的产出往往只覆盖一小段还容易引入新错误。我的建议是除非分析目标就是这段被虚拟化的算法本身否则保留虚拟片段没有坏处。把 OEP 定位到能正常进入程序主流程的程度虚拟化部分当作黑匣子处理已经能满足大多数安全测试和软件维护需求。验证标准同样是程序能跑、关键 API 能被跟踪而不是追求“完美还原”。5. 避坑手册Themida 1.8-2.X 脱壳中反复出现的 5 类翻车在脱壳上吃过不少亏之后我把最常见的翻车点整理成下面五类每一条都是先讲现象再说原因和解决。这些坑不是偶然发生的基本每个样本都会踩中至少一个。5.1 现象样本还没跑到系统断点就崩溃原因ScyllaHide 没有随调试器完成注入或者杀软拦截了注入用的驱动加载。Themida 在早期阶段就开始探测调试环境插件没起来等于裸奔。解决以管理员身份启动 x64dbg确认 ScyllaHide 的注入日志显示钩子已装杀毒软件对调试目录做排除某些场景下用 TITANHide 替换 ScyllaHide能躲过更激进的特征识别。不要为了省事跳过这一步否则后面所有断点都没有意义。5.2 现象下普通断点后进程安静退出原因INT3 断点会修改内存字节Themida 的校验线程会周期性扫描代码段发现被改过的字节就触发异常处理器最终调用ExitProcess。普通断点在这个壳里非常不可靠。解决换成硬件断点只存在 CPU 寄存器里不改内存字节。如果硬件断点也无法命中检查是否开启了 ScyllaHide 的上下文记录隐藏必要时把目标线程的隐藏参数一起勾上。记住一个原则能不用 INT3 就不用 INT3。5.3 现象Scylla 修复完启动即报“入口点错误”原因修复时写入的 OEP 地址实际上还在壳代码里不是原始入口。Auto Search 找到的 IAT 地址可能是对的但入口点落在了解密桩中间程序跑了几条指令就失去上下文。解决重新用 3.2 的硬件断点确认 OEP同时在目标地址单步一次观察是否进入标准函数序言。也可以用 4.1 的日志法对比执行流。入口点错了的时候导入表修得再漂亮也跑不起来。5.4 现象脱壳后程序提示缺少系统 DLL原因壳把一部分 DLL 留到运行时手动加载静态扫描时这些 DLL 还没在导入表里出现另外重定位表没保存的话DLL 基址可能被覆盖也会产生这类报错。解决保持 ImageBase 不变并勾选保存重定位对运行时加载的 API 用手工 IAT 补丁如果依赖链上有第三方 DLL把对应文件放进分析目录避免路径问题干扰验证结果。5.5 现象网上教程在 32 位能跑通换 64 位样本全废原因32 位下壳依赖的 PEB 和调试端口检测路径在 64 位里有差异Themida 2.X 对 x64 的虚拟化也更激进。老脚本大量基于 32 位 API 行为直接套在 64 位进程上不生效。解决64 位样本优先用日志法和硬件断点不依赖脚本Scylla 里注意进程位数是否匹配不要在一个 32 位调试器里加载 64 位 dump修复结果会全部对不上。遇到网上教程先看样本位数再看壳版本最后才看工具参数。6. 验证脱壳成果用运行对比和静态扫描确认壳真的没了6.1 三道验证静态特征、模块视图和运行行为脱壳只完成一半另一半是验证。我至少跑三道确认缺一不可。第一道静态扫描用 Detect It Easy 的命令行版在修复后的文件上扫特征# 在 Git Bash 或 Linux 分析机上执行输出 JSON 后过滤壳特征 diec -j unpacked.exe | grep -i themida没有任何输出说明 DIE 的特征库里没有发现 Themida 特征有输出也不一定代表脱壳失败可能只是区段名残留。继续第二道确认。第二道运行验证用 x64dbg 重新加载脱壳后的样本停在入口后打开模块窗口检查是否存在设备路径下的驱动模块。正常程序模块列表里只会出现系统 DLL 和程序自己的模块如果出现壳相关的驱动名说明注入还没清干净。第三道运行行为验证直接运行脱壳程序观察窗口或控制台表现是否和原程序一致再用进程监视器看 DLL 加载顺序。如果启动后大量访问非系统路径的临时文件壳的代码可能仍在运作需要回到第 4 章处理。这三个验证过完才敢把脱壳产物放进后续分析。我自己的习惯是把它当“后悔药”用宁可验证多花十分钟也不要等部署到分析环境里才发现 dump 带病。日志看得多了OEP 的节奏自然就熟了哪个地址可疑、哪条 API 是伪跳板慢慢会有手感。希望帮到你。本文还有配套的精品资源点击获取