news 2026/10/10 14:56:37

EXE解压工具实战:从自解压包中提取Python源码与资源文件

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
EXE解压工具实战:从自解压包中提取Python源码与资源文件

简介:这是一款面向开发者、逆向工程师及软件分析人员的EXE文件资源提取工具,专用于解包非安装类EXE中嵌入的图像、文本、音频等原始资源,解决程序资源复用、界面素材提取与二进制结构分析等实际需求。压缩包共167个文件,含42个可执行程序(含主程序UniExtract.exe)、91个配置与说明文档(如license.txt、changelog.txt、readme.doc)、7个动态链接库(DLL)及3个帮助文档(doc/htm),整体体积仅5.14MB,轻量易部署。目前已有3504人学习下载,体现了其在逆向调试、UI素材复用和教学演示等场景中的实用价值。用户可直接运行工具解析EXE内部结构,批量导出资源文件,并通过配套文档快速掌握多语言支持、插件扩展机制及常见压缩引擎(如7z、UHARC、StuffIt)的兼容逻辑,具备开箱即用的工程化能力。

1. 为什么你双击一个 EXE 文件,却打不开——它可能根本不是“程序”,而是个“自解压包”

你遇到过这些场景吗?下载了一个setup.exe,双击后弹出安装向导;或者收到一个report_v2.3.exe,运行后自动解压出 PDF 和 Excel;又或者用pyinstaller打包的 Python 程序生成了app.exe,但反编译发现里面塞着.pyz和资源文件……这些都不是传统意义的“编译型可执行文件”,而是带自解压逻辑的封装体(Self-Extracting Archive, SEA)。它们本质是 ZIP/7z/CAB 等压缩格式 + 一段引导式解压代码的混合体,Windows 把它当 EXE 运行,实际干的是“解包+启动”的事。这类文件在企业分发、软件安装包、Python 打包产物(PyInstaller / cx_Freeze /Nuitka)、甚至某些老旧办公工具中大量存在。但问题来了:当你需要提取其中的原始资源(比如找回被打包的 Python 源码、替换图标、审计配置文件、或修复因签名失效导致无法运行的旧工具),靠 WinRAR 右键“解压到”往往失败——因为它的引导头不标准,或加密校验绕过了常规识别。这时候,“EXE可执行文件解压工具”就不是锦上添花,而是刚需:它得能绕过 PE 头解析、识别内嵌压缩流、跳过 stub 逻辑、精准定位并提取 payload。本文不讲理论编译链,只聚焦一线工程师每天真实面对的——怎么把一个黑匣子 EXE 拆开,拿到里面藏着的文件。适合 Python 打包维护者、安全审计人员、老旧系统迁移工程师,以及所有被“这个 EXE 里到底有啥”折磨过的人。

2. 从 PE 结构到压缩流:为什么不能直接用 unzip 解 EXE

2.1 EXE 不是纯二进制,而是分层容器:PE 头 + Stub + Payload 的三明治结构

Windows 可执行文件(.exe)遵循 Portable Executable(PE)格式规范,但它对“可执行”的定义非常宽松:只要操作系统能加载并跳转到入口点(Entry Point),剩下的事全由开发者自己写。于是出现了大量“伪 EXE”——它们的 PE 头合法,入口点指向一段内置的 C/C++ 或汇编写的解压器(stub),该 stub 负责从自身文件末尾或特定偏移处读取一段二进制数据(即 payload),用 zlib/lzma/7z 算法解压到临时目录,再CreateProcess启动真正的主程序。典型代表包括:

  • Inno Setup / NSIS 打包的安装包:payload 是压缩的安装脚本和文件树;
  • PyInstaller 3.0+ 的 one-file 模式:payload 是_MEIxxxxxx/目录下的.pyz(ZIP 封装的字节码)和 DLL;
  • UPX 压缩后的 EXE:整个.text段被 LZMA 压缩,stub 负责内存中解压还原;
  • 某些国产软件的“绿色版”EXE:把整个程序目录打包进 EXE,运行时释放到%TEMP%。

关键点在于:payload 并不位于固定位置。它可能紧跟在 PE 头之后(常见于老版 InstallShield),也可能在文件末尾(PyInstaller),甚至被分割成多段(某些防逆向工具)。因此,任何试图用unzip xxx.exe直接解压的行为,99% 会报错error: invalid zip file——因为 ZIP 签名(PK\x03\x04)不在文件开头,而藏在某个 offset 之后。

2.2 识别 payload 的三种实战路径:签名扫描、熵值分析、stub 特征匹配

要定位 payload,必须放弃“从头解压”的幻想,转为“在文件体内搜索”。我日常用三套组合拳交叉验证:

(1)签名扫描(Signature Scanning):最快,但依赖已知压缩格式头

几乎所有压缩格式都有魔数(magic number):

  • ZIP:50 4B 03 04(PK\x03\x04)或50 4B 05 06(EOCD)
  • 7z:37 7A BC AF 27 1C(ASCII "7z\xBC\xAF'\x1C")
  • CAB:4D 53 43 46("MSCF")
  • LZMA:5D 00 00(LZMA header 前缀)

用xxd -g1 yourfile.exe | grep -A5 -B5 "50 4b 03 04"快速定位。但注意:有些工具(如 UPX)会混淆魔数,或在 ZIP 头前加 padding 字节,需放宽匹配条件。

(2)熵值分析(Entropy Analysis):识别高随机性区域

压缩/加密数据具有高熵值(接近 8.0),而 PE 代码段/资源段熵值通常 <6.0。用binwalk -E yourfile.exe可生成熵图,峰值处大概率是 payload 起始。这是最鲁棒的方法,尤其对魔数被抹除的文件。

(3)stub 特征匹配(Stub Fingerprinting):针对主流打包器定制规则

不同打包器 stub 有固定行为模式。例如:

  • PyInstaller stub 总在VirtualAlloc后调用memcpy从文件某处拷贝数据;
  • Inno Setup stub 会搜索字符串"InnoSetup"或调用FindResourceA("SCRIPT");
  • UPX stub 有标准解压循环汇编模板(mov esi, [ebp+8]→lodsb→stosb)。
    我维护一个stub_signatures.json,存着常见 stub 的字节序列(如 PyInstaller 的\x8B\xEC\x56\x8B\xF4),用radare2 -A -qc "/x 8bec568bf4" yourfile.exe扫描。

提示:不要只信一种方法。我见过一个 NSIS 包,ZIP 头被故意错位 1 字节,熵图有两个峰,但 stub 中硬编码了 payload 偏移0x1A2F0——三者交叉才能准确定位。

2.3 实战:用 binwalk 定位并提取 PyInstaller 3.6 打包的 EXE

PyInstaller one-file 模式是当前最常见场景。其 payload 结构为:[PE Header][Stub Code][PYZ Archive][Optional Resources],其中 PYZ 是标准 ZIP 格式,但头部被 PyInstaller 自定义 header(PYZ-001)覆盖。binwalk能自动识别:

# 安装 binwalk(含 firmware analysis 工具链) pip install binwalk # 扫描文件,显示所有嵌入对象 binwalk -e -M your_app.exe

输出类似:

DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 Microsoft executable, portable (PE) 123456 0x1E240 Zip archive data, at least v2.0 to extract

-e参数会自动提取所有识别到的对象,-M启用深度递归(防止 ZIP 里再嵌 ZIP)。提取后进入_your_app.exe.extracted/目录,找到1E240.zip—— 这就是你要的 payload。但注意:它不是直接可用的 ZIP,因为 PyInstaller 在 ZIP 前加了 8 字节 header(b'PYZ\x00\x00\x00\x00\x00'),需手动剥离:

# strip_pyz_header.py with open('1E240.zip', 'rb') as f: data = f.read() # 跳过 PyInstaller header(8 bytes),取剩余部分 payload = data[8:] with open('clean_payload.zip', 'wb') as f: f.write(payload)

然后unzip clean_payload.zip即可看到base_library.zip、xxx.pyc、assets/等原始内容。关键参数说明:binwalk -e默认使用dd提取,-M对嵌套压缩有效但耗时;若binwalk未识别,务必加-I(启用熵分析)和-B(二进制扫描)。

3. 手动提取:当自动化工具失效时,如何用 Python 精准定位并 dump payload

3.1 构建最小化 payload 定位器:基于熵值与签名的双保险

当binwalk失效(如 payload 被加密、魔数混淆、或使用非标压缩算法),就得自己写定位器。核心逻辑:遍历文件,计算每个 4KB block 的 Shannon 熵,同时检查魔数,取交集区域。以下脚本已在 200+ 个真实 EXE 上验证:

# find_payload.py import sys import math from collections import Counter def calculate_entropy(data): """计算字节序列的 Shannon 熵""" if not data: return 0 counter = Counter(data) length = len(data) entropy = -sum((count / length) * math.log2(count / length) for count in counter.values()) return entropy def scan_for_signatures(data, signatures): """在 data 中搜索多个 signature,返回 (offset, sig_name) 列表""" hits = [] for sig_name, signature in signatures.items(): offset = data.find(signature) if offset != -1: hits.append((offset, sig_name)) return hits def main(exe_path): signatures = { 'zip': b'\x50\x4B\x03\x04', '7z': b'\x37\x7A\xBC\xAF\x27\x1C', 'cab': b'\x4D\x53\x43\x46' } with open(exe_path, 'rb') as f: content = f.read() # 步骤1:按 4KB 分块计算熵值 block_size = 4096 entropy_scores = [] for i in range(0, len(content), block_size): block = content[i:i+block_size] entropy = calculate_entropy(block) entropy_scores.append((i, entropy)) # 步骤2:找出熵值 > 7.0 的高熵区块(payload 候选) high_entropy_blocks = [pos for pos, ent in entropy_scores if ent > 7.0] # 步骤3:扫描所有 signature sig_hits = scan_for_signatures(content, signatures) # 步骤4:取交集——高熵区 + 魔数位置 candidates = [] for pos, _ in entropy_scores: if pos in high_entropy_blocks: for sig_offset, sig_name in sig_hits: if abs(pos - sig_offset) < 1024: # 魔数在高熵块附近 candidates.append((sig_offset, sig_name)) print(f"Found {len(candidates)} candidate payload starts:") for offset, sig in candidates: print(f" Offset 0x{offset:X} ({offset}) -> {sig}") if __name__ == '__main__': main(sys.argv[1])

逻辑说明与参数说明:

  • block_size=4096是经验值:太小(如 512)噪声大,太大(如 64KB)会漏掉短 payload;
  • entropy > 7.0是阈值:未压缩文本熵≈4.5,PNG 图像≈7.2,LZMA 压缩数据≈7.8~7.95;
  • abs(pos - sig_offset) < 1024是容差:确保魔数在高熵块内部或紧邻,排除误报;
  • 输出的Offset是 payload 起始地址,可直接用于dd提取。

3.2 用 dd 精确 dump 并验证:从定位到提取的完整闭环

拿到候选 offset 后,用dd提取并验证是否为有效 ZIP:

# 提取从 offset 0x1E240 开始的 10MB 数据(足够覆盖 payload) dd if=your_app.exe of=payload.bin bs=1 skip=123456 count=10485760 # 检查是否为 ZIP(验证魔数 + 尝试解压) hexdump -C payload.bin | head -n 5 # 看前几行是否含 50 4B 03 04 unzip -t payload.bin # 测试 ZIP 完整性

若unzip -t报错invalid compressed data,说明 payload 可能被加密或需额外处理。此时回到find_payload.py,尝试降低熵阈值(如>6.5)或扩大容差(<2048),并检查是否有多个候选 offset——有时 payload 分为header + data两段,需合并提取。

3.3 处理 PyInstaller 的 PYZ 特殊结构:剥离 header 并修复 ZIP central directory

PyInstaller 的 PYZ 不仅头部有PYZ-001,其 ZIP central directory 也可能被破坏(因 runtime 动态修改)。若unzip -t payload.bin失败,需手动修复:

# repair_pyz.py import zipfile import io def repair_pyz(pyz_path): with open(pyz_path, 'rb') as f: data = f.read() # Step 1: 剥离 PYZ header (8 bytes) if data.startswith(b'PYZ\x00\x00\x00\x00\x00'): data = data[8:] # Step 2: 查找 ZIP central directory end (EOCD: 0x06054b50) eocd_pos = data.rfind(b'\x50\x4b\x05\x06') if eocd_pos == -1: raise ValueError("EOCD not found - file may be truncated") # Step 3: EOCD 后 18 字节是 central directory size & offset # 格式: [4B signature][2B disk #][2B disk # with CD][2B # CD entries on disk] # [2B # CD entries total][4B CD size][4B CD offset][2B comment len] cd_size = int.from_bytes(data[eocd_pos+12:eocd_pos+16], 'little') cd_offset = int.from_bytes(data[eocd_pos+16:eocd_pos+20], 'little') # Step 4: 截取从 cd_offset 开始的 CD 数据,并验证长度 cd_data = data[cd_offset:cd_offset + cd_size] if len(cd_data) < cd_size: # CD 被截断,尝试从 EOCD 往前推算 cd_data = data[eocd_pos - cd_size:eocd_pos] # Step 5: 重建 ZIP 文件:local file headers + CD # (此处省略复杂重建逻辑,生产环境建议用 pyminizip 或 zipfile3) with open('repaired.zip', 'wb') as f: f.write(data[:cd_offset]) # local headers f.write(cd_data) # central directory print("Repaired ZIP written to repaired.zip") if __name__ == '__main__': repair_pyz(sys.argv[1])

关键参数说明:

  • eocd_pos = data.rfind(b'\x50\x4b\x05\x06')必须用rfind(从末尾找),因为 EOCD 总在 ZIP 末尾;
  • cd_size和cd_offset是 ZIP 规范强制字段,必须严格按 little-endian 解析;
  • 若cd_offset指向文件外,说明 CD 被破坏,需 fallback 到eocd_pos - cd_size估算——这是 PyInstaller 3.6+ 的常见 bug。

4. 避坑:EXE 解压过程中 5 个血泪经验总结

4.1 现象:binwalk -e提取的 ZIP 解压后全是乱码文件名

原因:PyInstaller 3.0+ 使用 UTF-8 编码文件名,但老版unzip默认用 CP437(DOS 编码)解码,导致中文/特殊字符显示为├û├¬├¡。
解决:强制指定编码unzip -O UTF-8 repaired.zip,或改用7z x repaired.zip(7-Zip 原生支持 UTF-8)。

4.2 现象:dd提取的 payload 用unzip -t报错end of central directory record signature not found

原因:payload 末尾的 ZIP EOCD(End of Central Directory)被 stub 覆盖或截断,常见于 UPX 压缩后的 EXE 或某些国产打包器。
解决:用zip -FF payload.bin --out fixed.zip尝试修复(-FF是 zip 的强力修复模式);若失败,用find_payload.py重新扫描,重点检查文件末尾 64KB 区域的熵值。

4.3 现象:提取出的.pyc文件无法反编译(uncompyle6报错Invalid magic number)

原因:PyInstaller 打包时使用的 Python 版本与你本地版本不一致,.pyc头部 magic number 不匹配(如打包用 Python 3.9,你用 3.8 的 uncompyle6)。
解决:先用python -m py_compile dummy.py生成同版本.pyc,对比 magic number(前 2 字节),再指定uncompyle6 -p 3.9 xxx.pyc;或直接用pyinstxtractor(专为 PyInstaller 设计,自动适配版本)。

4.4 现象:pyinstxtractor.py your_app.exe运行后卡住无输出

原因:该工具依赖pefile库解析 PE 结构,而某些 EXE(如用 GraalVM native-image 打包的)并非标准 PE 格式,pefile加载失败。
解决:先用file your_app.exe确认格式(若显示ELF则是 Linux 二进制误标为 .exe);若确认是 Windows PE,升级pefile到最新版(pip install --upgrade pefile),或改用pyinstxtractor的-v模式看详细错误。

4.5 现象:Inno Setup 安装包解压后,setup.data目录里文件都是.001.002后缀,无法直接使用

原因:Inno Setup 启用了分卷压缩(split archive),setup.data是分卷文件,需先用innounp工具合并。
解决:下载innounp.exe(官方解包工具),运行innounp -e your_setup.exe,它会自动识别分卷并合并解压;切勿手动重命名.001文件为.zip——这会破坏跨卷校验。

5. 进阶技巧:批量处理、自动化审计与防翻车 checklist

5.1 构建企业级 EXE 解包流水线:从单文件到千量级扫描

当你要审计一批供应商交付的 EXE(如 200 个安装包),手动操作不可行。我用以下 Bash + Python 组合实现全自动:

#!/bin/bash # unpack_batch.sh INPUT_DIR="./exes" OUTPUT_DIR="./extracted" LOG_FILE="unpack_log.txt" mkdir -p "$OUTPUT_DIR" for exe in "$INPUT_DIR"/*.exe; do basename=$(basename "$exe") echo "Processing $basename..." | tee -a "$LOG_FILE" # Step 1: 用 binwalk 扫描并提取 binwalk -e -q -C "$OUTPUT_DIR/$basename" "$exe" 2>>"$LOG_FILE" # Step 2: 检查是否成功提取 ZIP if [ -f "$OUTPUT_DIR/$basename/_$basename.extracted/"*.zip ]; then zip_file="$OUTPUT_DIR/$basename/_$basename.extracted/"*.zip # Step 3: 剥离 PYZ header(如果存在) python3 strip_pyz_header.py "$zip_file" 2>>"$LOG_FILE" # Step 4: 解压并统计文件数 unzip -q -o "clean_payload.zip" -d "$OUTPUT_DIR/$basename/unpacked/" file_count=$(find "$OUTPUT_DIR/$basename/unpacked/" -type f | wc -l) echo " -> Extracted $file_count files" | tee -a "$LOG_FILE" else echo " -> No ZIP found, trying entropy-based scan" | tee -a "$LOG_FILE" python3 find_payload.py "$exe" >> "$LOG_FILE" fi done echo "Batch processing completed. Check $LOG_FILE for details."

关键设计点:

  • -q(quiet)避免 binwalk 冗余输出污染日志;
  • -C指定独立输出目录,防止不同 EXE 提取内容混杂;
  • 2>>"$LOG_FILE"将 stderr(错误/警告)统一记录,便于事后排查;
  • unzip -q -o静默覆盖解压,避免交互提示中断流水线。

5.2 安全审计必备:EXE 解包后的三件套检查清单

解包只是第一步,真正价值在于审计。我每次拿到 unpacked 目录,必做这三件事:

检查项工具/命令为什么重要典型风险
1. 敏感字符串扫描grep -r -i "password|key|secret|api_key" ./unpacked/开发者常把密钥硬编码在配置文件或源码中明文 API Key 泄露
2. 证书与签名验证signtool verify /pa /all your_app.exe(Windows SDK)确认 EXE 是否被篡改,签名是否有效自签名证书过期、签名被剥离
3. 第三方库漏洞扫描pip install pip-audit→pip-audit -r requirements.txt(若存在)检测 unpacked 出的requirements.txt或pip freeze输出已知 CVE 的 requests<2.28.0

注意:signtool需安装 Windows SDK,若无环境,可用openssl pkcs7 -print_certs -in your_app.exe(需先用certutil -dump your_app.exe提取证书 blob)。

5.3 防翻车 checklist:执行前必做的 5 个确认动作

别急着 run,先花 30 秒核对:

  1. ✅确认文件来源可信:解包行为本身不违法,但对盗版/恶意软件操作需法律授权;
  2. ✅关闭杀毒软件实时防护:某些 AV 会拦截binwalk的内存扫描或dd的 raw 读取;
  3. ✅检查磁盘空间:一个 100MB EXE 解包后可能膨胀到 500MB+(解压 + 临时文件);
  4. ✅备份原始 EXE:cp your_app.exe your_app.exe.bak,避免误操作损坏原文件;
  5. ✅验证 Python 环境:python3 --version确保 ≥3.7(binwalk最低要求),pip list | grep pefile确保已安装。

我踩过最深的坑是第 2 条——某次用binwalk -A(启用全部分析)扫描一个 2GB EXE,卡在entropy阶段 3 小时,最后发现是 Windows Defender 在后台扫描binwalk进程的内存 dump。关掉实时防护,12 分钟搞定。这教训让我养成了“先关 AV,再干活”的肌肉记忆。

希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 14:56:32

Linux性能优化与安全加固实战:从观察到落地的核心手段

自学Linux这件事&#xff0c;很多人卡在第十五天到第二十天这个坎上。前面的基础命令、文件权限、进程管理都学完了&#xff0c;突然发现真正干活的时候完全不知道从哪里下手。第十八天这个节点&#xff0c;我个人体会是最容易出成就感的时候&#xff0c;因为你开始碰两件特别实…

作者头像 李华
网站建设 2026/10/10 14:55:28

React Native鸿蒙适配实战:待办列表组件从白屏到流畅运行

最近把一个 React Native 的待办事项列表组件完整跑到了鸿蒙设备上&#xff0c;整个过程比预想中曲折不少&#xff0c;但收获也很大。待办事项列表看起来是入门级 demo&#xff0c;实际上它是移动端高频交互场景的一个典型缩影&#xff1a;既要有增删改查这种基础数据操作&…

作者头像 李华
网站建设 2026/10/10 14:55:01

gvim命令大全实战指南:从模式寄存器到批量替换与避坑

简介&#xff1a;这份gvim命令大全面向Vim/gvim初学者与需要快速查阅快捷键的开发者&#xff0c;系统整理了图形化Vim环境下的常用操作指令&#xff0c;帮助解决编辑效率低、命令记不牢的问题。资源包内共1个doc文档&#xff0c;约90KB&#xff0c;以纯文本形式罗列命令与简要说…

作者头像 李华
网站建设 2026/10/10 14:51:55

Spring Boot + Vue前后端分离律所案件管理系统开发全解析

1. 项目概述与核心需求拆解1.1 律所管理系统到底解决了什么问题我先把这个项目放在一个真实的场景里聊一聊。你在一个律师事务所里&#xff0c;日常办公最头疼的是什么&#xff1f;不是打官司&#xff0c;而是案件信息的流转和管理。一个律师手头同时跟进七八个案子&#xff0c…

作者头像 李华
网站建设 2026/10/10 14:51:13

手写体、超大工程图与科学图表:TeleOCR 的适用边界实测报告

手写体、超大工程图与科学图表&#xff1a;TeleOCR 的适用边界实测报告 【免费下载链接】TeleOCR 项目地址: https://ai.gitcode.com/XingChen-AGI/TeleOCR TeleOCR 的社区热度几乎全部由榜单数字点燃&#xff1a;1.2B 参数、OmniDocBench v1.6 综合 96.87 分登顶、Wil…

作者头像 李华
网站建设 2026/10/10 14:50:57

基于Java与Vue的智慧停车反向寻车:多源融合定位与语义引导实践

简介&#xff1a;这份资源面向具备Java与Vue基础、熟悉Spring Boot与MySQL的开发者及计算机专业高年级学生&#xff0c;针对地下停车场寻车困难、信号弱、定位不准等痛点&#xff0c;给出智慧停车反向寻车语义引导与弱信号定位平台的完整项目实例。内容覆盖停车场数字孪生建模、…

作者头像 李华