简介这是一款面向开发者、逆向工程学习者与软件资源分析人员的EXE文件解压工具核心用途是提取可执行程序及部分安装包内部的图像、文本、音频与二进制资源帮助查看程序组成、调试素材或开展教学分析。压缩包为rar格式共167个文件约5.14MB以txt说明文档、exe主程序与解包组件、dll动态库、unp及wcx插件为主并配有ini配置、doc文档、htm帮助页和图标等整体围绕多引擎解包与格式兼容组织。已有3511人学习下载在EXE资源提取与结构分析场景中具有一定参考价值。包内还包含语言配置、许可协议、变更记录以及bin、docs等目录读者可据此了解运行依赖和使用说明完成资源提取、结构查看与素材复用使用时需遵守软件许可与版权规定仅限合法合规的研究与分析。1. EXE 文件解压不是反编译先看容器再谈逆向拿到一个 EXE双击能跑安装前改了哪些文件、图标和资源能不能复用、卸载后残留什么很多人第一反应是找反编译。实际更靠前的一步是 EXE 文件解压把可执行文件当成容器先把资源段、安装载荷、压缩数据和附加在文件尾部的数据完整取出来再决定要不要看代码。EXE 可执行文件解压工具解决的正是这个前置问题它适合做软件资产迁移、安装包巡检、故障排查和授权范围内的安全分析不适合拿来做绕过授权的事。判断一个 EXE 值不值得继续拆先看它是不是 PE、节区里有没有大块资源、文件尾部有没有 overlay这三步比盲目上反编译器稳得多。2. PE 结构与资源定位三个偏移决定解压入口EXE 文件解压最容易翻车的地方是把“解压”当成“双击后看它往外释放什么”。静态解包的第一步是认容器这个文件是不是 PE节表在哪里资源目录指向哪一段文件尾部有没有附加数据。PE 不是压缩包它没有统一的“解压口令”但它的结构足够规整规整到用几十行脚本就能把关键偏移列出来。我一般先跑一个只读检查把节区、overlay 和字符串样本落到 JSON 里再决定用图形入口看资源树还是用命令行入口批量跑。2.1 先认 PEMZ、e_lfanew 与节表PE 文件开头两个字节是MZ偏移0x3c处的 4 字节是e_lfanew它指向真正的 PE 签名PE\0\0。PE 签名后面是 COFF 头里面有两个关键字段NumberOfSections表示节区数量SizeOfOptionalHeader表示可选头长度。节表紧跟在可选头后面每项 40 字节字段包括节名、虚拟大小、虚拟地址、原始大小、原始偏移。很多人只看节名找.rsrc但节名可以改真正可靠的入口是可选头里的数据目录资源目录是索引 2每项 8 字节前 4 字节是 RVA后 4 字节是大小。PE32 和 PE32 的数据目录起始偏移不同PE32 从可选头96开始PE32 从112开始所以脚本不要硬编码节表位置直接用SizeOfOptionalHeader推算。字段常见偏移/来源用途容易误判的点e_lfanewDOS 头0x3c找 PE 签名不要假设 PE 头固定在0x80NumberOfSectionsCOFF 头2节区数量为 0 不代表不是 PE但通常异常SizeOfOptionalHeaderCOFF 头16算节表起点PE32/PE32 长度不同必须读PointerToRawData节表项20文件中的真实数据偏移不要和VirtualAddress混用SizeOfRawData节表项16文件中的真实数据长度为 0 的节不占文件空间资源目录数据目录索引 2定位.rsrc类资源节名可能被改不能只看名字上表里最值得盯的是PointerToRawData和SizeOfRawData。解压工具从文件里取数据用的是原始偏移和原始大小调试器看内存布局用的是虚拟地址和虚拟大小。把这两组值搞混写出来的提取脚本就会读错段轻则产物缺文件重则把 PE 头当成资源导出。我的习惯是先把每个节区的raw_ptr、raw_size、vaddr、vsize打成表看到.rsrc的raw_size明显大于普通资源节再重点看资源目录。2.2 资源段不是唯一目标overlay 与嵌套载荷资源段是结构化资源图标、位图、版本信息、清单文件、对话框模板都可能在里面图形化解压工具通常能把它们导成独立文件。但安装包和单文件打包器经常把真正的载荷放在 overlay最后一个节区的原始数据结束到文件末尾之间的附加数据。overlay 不是垃圾很多自解压包把压缩流、脚本段、校验信息都放在这里。你如果把 overlay 当填充字节截掉外层 EXE 可能还能打开但解出来的安装器会缺载荷运行到一半报错。另一个常见位置是资源段里的RCDATA或自定义资源类型。它们可能是压缩流也可能是嵌套的 PE 文件。判断方法不复杂先看资源大小和头部字节如果前两个字节是MZ大概率是嵌套 EXE 或 DLL如果是一段高熵数据可能是压缩流或加密载荷需要工具包里的二级识别入口继续处理。这里不要迷信字符串扫描字符串只能提供线索不能当结论。安装器标识、临时目录名、压缩库名这些字符串很有用但误报也高最后还是要回到偏移和大小。2.3 用一段 Python 把节区、overlay 和可疑字符串列出来下面这段脚本不依赖第三方库直接按 PE 结构读节表算 overlay 偏移并抽样打印可打印字符串。把它存成check_pe.py作为解压前的固定检查。import struct, pathlib, re, json, sys def u16(b, o): return struct.unpack_from(H, b, o)[0] def u32(b, o): return struct.unpack_from(I, b, o)[0] def inspect_pe(path): p pathlib.Path(path) data p.read_bytes() if len(data) 0x40 or data[:2] ! bMZ: raise ValueError(不是 MZ 开头) e_lfanew u32(data, 0x3c) if data[e_lfanew:e_lfanew 4] ! bPE\0\0: raise ValueError(PE 签名不在 e_lfanew 指向的位置) coff e_lfanew 4 num_sections u16(data, coff 2) opt_size u16(data, coff 16) opt coff 20 magic u16(data, opt) sections [] sec_off opt opt_size for i in range(num_sections): o sec_off i * 40 sections.append({ name: data[o:o 8].rstrip(b\0).decode(latin1, replace), vsize: u32(data, o 8), vaddr: u32(data, o 12), raw_size: u32(data, o 16), raw_ptr: u32(data, o 20), }) # overlay 从最后一个节区的原始数据末尾开始 end max((s[raw_ptr] s[raw_size] for s in sections), default0) overlay data[end:] text data.decode(latin1, ignore) strings sorted(set(re.findall(r[ -~]{6,120}, text)))[:80] return { file: str(p), size: len(data), pe_offset: e_lfanew, optional_magic: hex(magic), sections: sections, overlay_offset: end, overlay_size: len(overlay), overlay_head_hex: overlay[:32].hex(), strings_sample: strings, } if __name__ __main__: print(json.dumps(inspect_pe(sys.argv[1]), ensure_asciiFalse, indent2))脚本里的e_lfanew用来跳转到 PE 签名num_sections和opt_size用来算节表起点避免把 PE32 和 PE32 的可选头长度写死。raw_ptr raw_size取最大值得到 overlay 起点overlay_head_hex打印前 32 字节方便判断它是MZ头、压缩流头还是纯填充。strings_sample限制 80 条、每条 6 到 120 个可打印字符目的是抽样看安装器标识和临时目录名不是做完整字符串导出。运行时传一个 EXE 路径即可输出 JSON 可以直接贴进样本登记清单。看到overlay_size大于 0 时不要急着截掉先单独复制一份 overlay 再继续。3. 解压流程落地从只读副本到产物清单真正开始解压时最怕的不是工具报错而是工具“成功”了但产物对不上。我的流程固定成三步原始样本只读备份工作目录按哈希命名解压入口和产物目录分离。图形入口适合看单个文件的资源树命令行入口适合批量跑和留日志。不管用哪个入口都先假设它会写出不可预期的东西所以工作目录要独立样本要只读产物要带清单。3.1 只读副本与样本登记先把后悔药备好原始样本不要直接扔进解压工具。先复制到工作目录的input子目录改成只读权限再算一次 SHA-256把文件大小、修改时间、来源备注写进manifest.json。工作目录名用文件名_哈希前8位这样同一批样本不会互相覆盖出了问题也能回到原始副本。只读权限不是为了防病毒是为了防手滑有些工具会回写样本、改时间戳、在原目录释放临时文件源文件一旦被改后面所有对比都失去基准。样本登记还要记录工具入口和参数。图形入口至少记下工具名称、版本、勾选项命令行入口把完整命令、退出码、标准输出和标准错误落到run.log。不要只留一句“解压成功”因为后面发现缺文件时你需要知道当时用的是自动识别还是强制模式有没有禁止执行载荷临时目录设在哪里。这个动作看着笨但它是后面排查的基准。3.2 图形入口和命令行入口什么时候别用“双击”图形入口的优势是直观资源树、图标预览、版本信息、导出按钮都在一个界面里适合单文件排查和资源复用。但图形入口不适合批量也不适合审计因为你很难记录每一次点击。命令行入口更适合批量输入、输出、模式、超时、日志都能参数化跑完一批还能生成失败列表。这里有一个边界要提前说清不要用“双击运行”代替静态解包。双击自解压包程序可能先释放到临时目录运行完再删掉你看到的只是它想让你看到的界面。正确做法是把样本复制到只读工作目录用工具包的静态解包模式把临时目录指向工作目录下的temp并且关闭“解压后自动打开产物”这类选项。工具入口的参数名可能不同但逻辑一样输入只读副本输出独立目录禁止执行载荷保留临时文件。3.3 批量解压脚本参数、产物命名与失败重试批量脚本不要直接拼命令行字符串路径里有空格和中文时很容易翻车。用列表传参工作目录用pathlib管理每个样本一个任务目录失败时保留现场。下面是一个可抄的骨架把entry换成工具包实际命令行入口即可。import hashlib, json, shutil, subprocess, sys, pathlib, datetime def sha256(path): h hashlib.sha256() with open(path, rb) as f: for chunk in iter(lambda: f.read(1024 * 1024), b): h.update(chunk) return h.hexdigest() def prepare(sample, workroot): sample pathlib.Path(sample) workroot pathlib.Path(workroot) digest sha256(sample) job workroot / f{sample.stem}_{digest[:8]} input_dir job / input out_dir job / out temp_dir job / temp for d in (input_dir, out_dir, temp_dir): d.mkdir(parentsTrue, exist_okTrue) dst input_dir / sample.name shutil.copy2(sample, dst) dst.chmod(0o444) # 只读副本防止工具回写源文件 manifest { source: str(sample), copy: str(dst), sha256: digest, size: dst.stat().st_size, started_at: datetime.datetime.now().isoformat(timespecseconds), } (job / manifest.json).write_text( json.dumps(manifest, ensure_asciiFalse, indent2), encodingutf-8 ) return job, dst, out_dir, temp_dir def run_unpack(entry, sample, out_dir, temp_dir, modeauto): cmd [ sys.executable, str(entry), --input, str(sample), --out, str(out_dir), --temp, str(temp_dir), --mode, mode, --no-exec, # 只解包不执行载荷 --keep-temp, # 失败时保留临时目录 ] p subprocess.run( cmd, capture_outputTrue, textTrue, timeout600, ) return { cmd: cmd, returncode: p.returncode, stdout: p.stdout[-4000:], stderr: p.stderr[-4000:], } if __name__ __main__: sample sys.argv[1] entry pathlib.Path(sys.argv[2]) job, dst, out_dir, temp_dir prepare(sample, pathlib.Path(work)) result run_unpack(entry, dst, out_dir, temp_dir) (job / run.json).write_text( json.dumps(result, ensure_asciiFalse, indent2), encodingutf-8 ) print(job)prepare负责复制只读副本、算哈希、建input/out/temp三个目录manifest.json记录样本来源和起始时间。run_unpack用列表传参避免空格和中文路径被 shell 拆坏timeout600是单样本上限批处理时可以按文件大小调整--mode auto让工具自动识别容器类型识别失败时再换强制模式--no-exec是关键参数表示只做静态解包不运行解出来的载荷--keep-temp用于失败现场保留。脚本只截取标准输出和标准错误的最后 4000 字符防止日志把磁盘打满。跑完先看returncode非 0 不要急着重跑先看run.json里的stderr和temp目录。4. 安装包与单文件打包识别信号与分层提取EXE 文件解压的难点不在“能不能打开”而在“打开的是哪一层”。同一个 EXE 可能是安装器、自解压包、单文件打包器、资源封装器也可能是普通程序加了一层压缩壳。不同层对应的提取策略不一样安装器要找脚本段和压缩载荷自解压包要看 overlay 和临时目录单文件打包器要看资源段里的嵌套 PE。识别信号比盲试更省时间。4.1 安装包类资源段、脚本段与压缩载荷的信号安装包类 EXE 通常有几个信号资源段里有安装器标识字符串节区里出现脚本段或压缩库名文件尾部 overlay 明显大于普通程序解压后能看到.tmp、.dat、.cab之类的中间文件。图形入口一般能直接展开资源树但脚本段和压缩载荷不一定在资源树里需要命令行入口的“深度扫描”或“载荷识别”模式。我的做法是先看overlay_size再看资源段里最大的几个条目最后用字符串样本交叉验证。观察点可能信号说明建议动作overlay 大于 1MB自解压或安装载荷不是填充数据单独复制 overlay记录偏移和大小资源段异常大图标、脚本、压缩资源资源目录可能有多层导出资源树按类型和大小排序字符串含安装器标识安装包构建器可能有脚本段找脚本段和压缩流不要只看资源解出文件含临时后缀运行时释放静态解包可能不完整在只读沙箱观察临时目录解出内层 MZ 头嵌套 EXE/DLL需要递归设置深度上限记录层级这张表不能替代实际分析但能帮你决定先看哪一层。比如 overlay 很大、资源段很小优先把 overlay 单独拿出来识别资源段很大、overlay 很小优先导出资源树两边都不大但解出内层 PE说明是嵌套结构进入递归流程。不要一上来就把所有模式都勾上模式越多产物越难对账。4.2 单文件打包与自解压临时目录和 overlay 的配合单文件打包器常把依赖和资源压进一个 EXE运行时释放到临时目录再加载。静态解包时如果工具只导出资源段可能会漏掉 overlay 里的压缩流如果只导 overlay又可能漏掉资源段里的配置。正确做法是两层都看先导出资源树再单独保存 overlay最后用文件头识别 overlay 是不是压缩流或嵌套 PE。自解压包还要注意临时目录工具包里如果有“模拟运行”或“临时目录捕获”模式输出目录一定要指向工作目录下的temp不要让它写到系统临时目录否则清理时找不到现场。这里有一个容易忽略的点解压出来的文件权限和时间戳可能被工具改写。做资产迁移时时间戳影响不大做完整性对比时时间戳和权限就要单独记录。我的习惯是产物目录只读保存另建一个report目录放清单清单里同时记录原始样本哈希、产物路径、产物大小和产物哈希。这样即使工具改写了时间戳也能用哈希判断文件内容有没有变。4.3 递归扫描嵌套 EXE深度上限与产物树嵌套 EXE 很常见外层安装器解出内层安装器内层再解出实际程序。只解一层就以为完整后面运行缺 DLL 时又要回头补。递归扫描的关键是限制深度防止压缩炸弹和无限嵌套。下面这段脚本只做识别和标记不自动执行适合挂在批量解压之后。import pathlib, struct def is_pe(path): with open(path, rb) as f: head f.read(0x40) if len(head) 0x40 or head[:2] ! bMZ: return False e_lfanew struct.unpack_from(I, head, 0x3c)[0] if e_lfanew 0 or e_lfanew 0x10000000: return False f.seek(e_lfanew) return f.read(4) bPE\0\0 def walk_pe(root, depth0, max_depth3): root pathlib.Path(root) for p in sorted(root.rglob(*)): if not p.is_file(): continue if p.suffix.lower() not in {.exe, .dll, .bin, .dat, .tmp}: continue if is_pe(p): print( * depth f{p} size{p.stat().st_size}) if depth max_depth: # 这里调用工具入口把 p 解到 p.parent / (p.stem _unpack) # 不要在这段脚本里执行 p pass if __name__ __main__: walk_pe(work, max_depth3)is_pe只读文件头先看MZ再跳到e_lfanew检查PE\0\0不会把整个文件读进内存。walk_pe只扫描指定后缀max_depth3是递归上限超过就只记录不展开。注释里的调用入口要换成工具包实际命令行入口并且保持“只解包、不执行”。产物树建议按layer0/、layer1/、layer2/分层存放每层附一个layer.json记录父文件、偏移、工具参数和退出码。这样后面发现缺文件时能快速定位是哪一层漏了。5. 避坑与排查五个让产物对不上的现场这一章是我自己踩过的血泪经验。EXE 文件解压的失败很少表现为“工具崩溃”更多是“工具说成功但产物对不上”。下面五个现场按出现频率排序每条按现象、原因、解决写方便你直接对照日志。5.1 现象解压后目录几乎是空的现象是工具返回成功out目录只有几个图标和版本信息没有安装载荷。原因通常是把自解压包当普通资源文件处理了真正的载荷在 overlay 或临时目录里工具只导出了资源段。解决方法是先看overlay_size把 overlay 单独复制出来再用深度扫描模式识别同时检查工具是否开启了“仅资源”模式改成“自动识别”或“容器解包”。如果样本是自解压包不要双击运行双击释放到系统临时目录后可能自动清理现场就没了。把临时目录参数指向工作目录下的temp失败时保留。5.2 现象安装器被“瘦身”后无法启动现象是解出一个安装器 EXE大小比原样本小很多双击报缺少载荷。原因是为了减小产物把 overlay 截掉了或者只导出了资源段里的脚本没有把压缩流一起带出来。解决方法是保留完整 overlay记录overlay_offset和overlay_size产物目录里单独放一份overlay.bin如果工具支持“重组”模式按原偏移写回再校验文件大小和哈希。不要用“删除填充”这种选项处理安装包填充和载荷在文件尾部经常挨在一起删错一段就少一块。5.3 现象批量任务一半成功一半 0 字节现象是同一批样本有的产物正常有的文件 0 字节日志里只有一句权限错误。原因常见于路径拼接样本名带空格或中文命令行被 shell 拆成多个参数工具把后半段当成了输出目录也可能是只读副本权限设置过早工具需要写临时文件时被拒绝。解决方法是改用列表传参不要拼字符串只读权限只加在input副本上out和temp保持可写批量脚本里给每个任务独立目录失败后不要直接覆盖保留run.json和temp。看到 0 字节先查命令参数再查磁盘空间和文件句柄。5.4 现象只解出一层运行仍缺 DLL现象是外层解压成功内层 EXE 也能打开但运行时报缺少 DLL 或配置文件。原因是嵌套结构只解了一层内层 EXE 的资源段或 overlay 里还有一层载荷。解决方法是跑递归扫描按layer0/layer1/layer2分层存放每层记录父文件和偏移递归深度不要无限放大max_depth3足够覆盖大多数安装器超过上限的先人工看文件头和字符串。不要用“自动执行解出来的 EXE”来验证完整性那会把静态分析变成运行分析风险和工作量都上一个台阶。5.5 现象哈希对不上、时间戳全变现象是产物内容和预期一致但哈希清单对不上时间戳也全变了。原因是解压工具在写出文件时重写了时间戳、权限或换行符部分工具还会给资源文件加头部。解决方法是把“内容哈希”和“元数据”分开记录内容哈希用 SHA-256元数据单独记原始修改时间、产物修改时间、权限位对比时先比大小和哈希再比时间戳。如果工具支持“保留原始时间戳”就打开不支持就接受元数据差异不要为了时间戳一致去改产物内容。源文件哈希在解压前后各算一次确认只读副本没有被回写。6. 完整性验证哈希、字符串与时间线三件套解压完不要只看目录里有没有文件我一般做三件套验证哈希、文件树、时间线。哈希解决“内容有没有变”文件树解决“层级有没有漏”时间线解决“失败能不能复现”。这三件事都不复杂但能把很多“感觉解压成功了”变成可对账的结果。验证项做法判定源文件哈希解压前后各算一次 SHA-256两次一致说明只读副本没被改写产物清单记录路径、大小、SHA-256、层级与manifest.json和layer.json对得上overlay记录偏移、大小单独保存不为空时不能丢必要时参与重组字符串样本扫描安装器标识、临时目录名与解压日志交叉验证不单独下结论时间线记录开始、结束、工具入口、参数、退出码失败时能按同样参数复跑我现在的固定动作是原始样本先复制只读副本算一次哈希解压前再算一次确认副本没变解压后生成产物文件树和哈希清单和run.json放在同一个任务目录overlay 不为空就单独留一份并在清单里写清偏移和大小。这个习惯看着笨但能省掉很多后悔药。那次批量任务里有一个安装器解出来能打开、安装到一半才报缺文件最后就是靠 overlay 偏移和分层清单定位到第二层漏解。从那以后我每次拿到 EXE 都先走一遍只读副本、哈希、overlay 检查再决定用图形入口还是命令行入口。资源包和示例脚本放在文末获取入口拿到后先跑只读副本流程再按自己的样本调整递归深度和超时参数。希望帮到你。本文还有配套的精品资源点击获取