简介这是一款面向逆向工程与安全分析人员的自动查壳脱壳工具可检测程序的编译器信息、是否加壳、入口点地址、输出表与输入表等PE结构信息并支持提取PE文件中的图片、EXE、压缩包、MSI、SWF等资源还能识别bmp、jpg、mp4、7z、rar、tar、vhd等大量文件格式为破解与脱壳学习提供引导。资源包共180个文件以75个dll动态库、27个eis、19个jpg、14个lng语言文件、8个txt说明及5个exe主程序为主另含bpl、ini、cfg、dpr、bat、asm、c、h等源码与配置压缩包约13.1MB结构完整便于直接运行与二次研究。目前已有120人学习下载适合想快速了解加密程序所用加密方式、掌握脱壳思路并加以实践利用的开发者参考。1. 从一次崩溃说起为什么我重新捡起了查壳脱壳工具上周排查一个客户现场崩溃的 exe符号表被剥得干干净净IDA 打开一片红。我第一反应不是上调试器而是先看它到底加没加壳——因为如果壳没脱后面所有静态分析都是白费功夫。这就是查壳脱壳工具存在的意义它不负责帮你逆向出业务逻辑但它负责把「能不能逆向」这个前置问题回答清楚。这类工具能读取 PE 文件的编译器信息、入口点地址、输出表、输入表、节区特征判断程序是否被加壳并尝试脱掉常见壳让后续分析工具能正常识别代码。适合做安全分析、恶意样本排查、软件兼容性诊断的从业者也适合刚接触 PE 结构、想找个能直接上手的工具练手的人。它解决的不是「破解」问题而是「看清程序结构」的问题。2. PE 信息到底看什么从入口点到输入表的判读逻辑2.1 编译器信息与入口点地址的判读拿到一个 exe工具第一屏通常会给出编译器类型MSVC、GCC、Borland Delphi、Go 等和入口点地址Entry Point简称 EP。很多人扫一眼就过但这两个字段是判断加壳的第一线索。正常编译出来的程序入口点一般落在.text节区地址不会太离谱如果 EP 指向.aspack、.upx0、.themida这类明显非标准节区基本可以确定加壳。编译器信息则用来交叉验证一个声称是 VC 编译的程序如果节区名全是随机字符串那编译器信息很可能是壳伪造的。我一般会按这个顺序看先看节区表Section Table正常程序节区名是.text、.rdata、.data、.rsrc这几个。再看入口点所在节区如果 EP 不在.text而在某个陌生节区标记可疑。最后看编译器信息是否和节区特征自洽。工具里通常有「PE 信息」面板把这些字段集中展示。下面是一段用 Python 读取 PE 头部基础信息的示例方便你理解工具底层在做什么import pefile # 加载目标 exepefile 是常见的 PE 解析库 pe pefile.PE(target.exe) # 打印编译器时间戳和入口点地址 print(TimeDateStamp:, hex(pe.FILE_HEADER.TimeDateStamp)) print(EntryPoint:, hex(pe.OPTIONAL_HEADER.AddressOfEntryPoint)) print(ImageBase:, hex(pe.OPTIONAL_HEADER.ImageBase)) # 遍历节区输出名称、虚拟地址、大小和特征 for section in pe.sections: name section.Name.decode(errorsignore).strip(\x00) print(fSection: {name} VA: {hex(section.VirtualAddress)} fVSize: {hex(section.Misc_VirtualSize)} fCharacteristics: {hex(section.Characteristics)})这段代码的逻辑很直接pefile把 PE 头部解析成对象AddressOfEntryPoint是相对虚拟地址RVA要加上ImageBase才是实际虚拟地址。节区的Characteristics字段里可执行节区通常带0x20000000MEM_EXECUTE和0x80000000MEM_READ。如果某个节区同时可写又可执行0xE0000000那在正常编译器产物里几乎不会出现是加壳的强信号。参数上pefile默认按文件路径加载遇到损坏的 PE 可以传fast_loadTrue只读头部避免解析导入表时卡住。2.2 输出表与输入表的差异诊断输入表Import Table和输出表Export Table是判断程序行为的另一组关键信息。输入表告诉你程序依赖哪些 DLL 和函数输出表告诉你这个 DLL/EXE 对外暴露了哪些函数。加壳程序的一个典型特征是输入表被压缩到极少条目甚至只剩LoadLibrary和GetProcAddress两个函数——因为壳需要在运行时动态解析真正的 API。工具里看输入表重点看三件事导入的 DLL 数量是否异常少正常 MFC 程序至少导入十几个 DLL。是否存在LoadLibraryA/WGetProcAddress组合这是壳的典型动态解析模式。导入函数名是否被序号Ordinal替代序号导入常见于加壳或手工优化过的程序。输出表则主要看导出函数的地址是否落在可疑节区。如果一个 DLL 的导出函数地址全部指向.text之外的节区说明导出表被壳改写过。下面这段代码演示如何枚举输入表并统计 DLL 数量import pefile pe pefile.PE(target.exe) # 检查是否存在导入表目录 if hasattr(pe, DIRECTORY_ENTRY_IMPORT): dll_count len(pe.DIRECTORY_ENTRY_IMPORT) print(fImported DLL count: {dll_count}) for entry in pe.DIRECTORY_ENTRY_IMPORT: dll_name entry.dll.decode(errorsignore) func_count len(entry.imports) print(f {dll_name}: {func_count} functions) # 检查是否出现动态解析组合 for imp in entry.imports: if imp.name in (bLoadLibraryA, bLoadLibraryW, bGetProcAddress): print(f [SUSPICIOUS] {imp.name.decode()}) else: print(No import table found - likely packed or stripped)逻辑说明DIRECTORY_ENTRY_IMPORT是pefile解析出的导入目录每个元素对应一个 DLL。entry.imports是该 DLL 下的函数列表。如果dll_count小于 5 且出现LoadLibrary/GetProcAddress基本可以判定加壳。参数上imp.name可能是None序号导入所以判断前要先确认非空。这段代码可以直接嵌进你自己的批量扫描脚本配合os.walk遍历目录。提示输入表为空不一定是加壳也可能是程序用了延迟导入Delay Import或纯资源 DLL。要结合节区特征一起判断别单凭一个字段下结论。3. 脱壳实操从识别壳类型到 dump 出可分析文件3.1 常见壳的识别特征与工具选择查壳工具识别出壳类型后脱壳方式取决于壳的种类。常见壳分两类压缩壳UPX、ASPack、FSG和加密壳Themida、VMProtect、Enigma。压缩壳的目的是减小体积脱起来相对容易工具通常能自动完成加密壳的目的是防逆向脱壳难度高很多需要手动 dump 加修复导入表。识别特征我一般记这几条壳类型典型节区名入口点特征脱壳难度UPXUPX0 / UPX1EP 在 UPX1低工具自动ASPack.aspack / .adataEP 在 .aspack低工具自动FSG无标准节区名EP 在随机节区中需手动修复Themida随机字符串EP 在最后节区高需专用脚本VMProtect.vmp0 / .vmp1EP 在 .vmp0高虚拟化代码难还原工具自动脱壳的流程一般是识别壳 → 在内存中等待壳解压完成 → dump 内存镜像 → 修复导入表 → 生成可执行文件。对于 UPX 这类甚至可以直接用upx -d命令行脱# 确认是 UPX 壳后直接用 upx 解压 upx -d packed.exe -o unpacked.exe # 如果 upx 报错 not packed by UPX说明壳被改过 # 需要手动在调试器里找到 OEP 后 dump-d是解压模式-o指定输出文件名。如果报错说不是 UPX 壳但节区名又是 UPX那说明壳头被修改过工具自动脱会失败得走手动流程。3.2 手动 dump 与导入表修复步骤自动脱壳失败时手动流程分四步找 OEP、dump 内存、修复导入表、验证文件。找 OEPOriginal Entry Point的常见做法是在调试器里对代码段下内存访问断点壳解压完跳回原程序时会触发。具体步骤用调试器加载目标 exe在.text节区下内存写入断点。运行到断点触发此时栈顶或跳转目标就是 OEP。用调试器的 dump 插件把当前内存镜像保存成文件。用导入表修复工具如 Scylla重建导入表。用查壳工具重新扫描 dump 出的文件确认壳已去除。修复导入表时工具需要你填入 OEP 的 RVA。这个值就是第 2 步记录的地址减去 ImageBase。填错会导致修复后的文件无法运行表现是双击无反应或报「不是有效的 Win32 应用程序」。import pefile # 验证 dump 出的文件是否还有壳特征 pe pefile.PE(dumped.exe) # 检查节区是否恢复正常 for section in pe.sections: name section.Name.decode(errorsignore).strip(\x00) chars section.Characteristics writable bool(chars 0x80000000) executable bool(chars 0x20000000) print(f{name}: W{writable} X{executable}) # 检查导入表是否被修复 if hasattr(pe, DIRECTORY_ENTRY_IMPORT): print(fImport DLLs: {len(pe.DIRECTORY_ENTRY_IMPORT)}) else: print(Import table still missing - repair failed)这段验证代码的逻辑是脱壳成功的文件节区应该恢复成正常的可读可执行但不可写.text的 W0 X1导入表 DLL 数量应该恢复到合理范围通常 5 个以上。如果导入表仍然缺失说明修复步骤没做对需要回到 Scylla 重新填 OEP。参数上Characteristics的位判断和前面一致0x80000000是可写位0x20000000是可执行位。注意脱壳后的文件不要直接用于生产环境只用于分析。dump 出的内存镜像可能包含未初始化的数据直接运行有风险。4. 避坑与排查脱壳过程中最容易翻车的五个点4.1 入口点地址读出来是 0 或异常值现象工具显示 EntryPoint 为0x00000000或一个明显超出镜像范围的地址。原因通常是 PE 头部被壳破坏或者文件本身不是标准 PE比如是 DOS 扩展或损坏文件。解决方法是先用pefile的fast_load模式只读头部确认OptionalHeader是否完整如果AddressOfEntryPoint为 0说明壳把入口点重定向到了 TLS 回调或异常处理里这种情况要去看 TLS 目录而不是 EP。4.2 自动脱壳后文件变大但无法运行现象工具提示脱壳成功生成的文件体积正常但双击报错。原因多半是导入表没修复完整或者 dump 时内存页没对齐。解决方法是检查 dump 工具的「重建导入表」选项是否勾选以及 OEP 的 RVA 是否填对。我一般会用pefile重新扫一遍导入表确认 DLL 数量恢复到正常水平再收工。4.3 壳识别为「未知」但节区明显异常现象工具无法识别壳类型但节区名是随机字符串EP 也不在.text。原因是壳被定制修改过特征库匹配不上。解决方法是不要依赖自动识别直接看节区的熵值——加密壳的节区熵值通常接近 8.0满熵压缩壳在 6.5 到 7.5 之间。用pefile配合math算熵值超过 7.5 的基本可以判定加密壳需要走手动 dump。4.4 脱壳后字符串全部乱码现象dump 出的文件用字符串工具扫全是乱码。原因是壳在内存中解密了字符串但 dump 的时机太早解密还没完成。解决方法是在调试器里让程序多跑几秒等壳的初始化代码执行完再 dump。判断时机的一个技巧是看输入表是否已经解析出真实 DLL 名如果还是空的说明没到 dump 点。4.5 工具报「不是有效的 PE 文件」现象加载目标文件时直接报错。原因是文件可能是 .NET 程序PE 结构但入口点指向 CLR 头或者 64 位程序被 32 位工具加载。解决方法是先确认文件架构用file命令或看 PE 头的Machine字段0x14c是 32 位0x8664是 64 位。32 位工具打不开 64 位文件是常见翻车点换对应版本的工具即可。5. 批量扫描与自动化把查壳嵌进你的分析流水线单个文件手动查壳效率太低实际工作中我经常要面对几百个样本。这时候把查壳逻辑写成脚本批量输出结果比一个个拖进工具快得多。核心思路是用pefile遍历目录对每个文件输出编译器信息、入口点、节区特征和壳判定结果最后汇总成 CSV。import os import csv import pefile def scan_pe(filepath): 扫描单个 PE 文件返回关键字段字典 result {file: filepath, packed: unknown, compiler: , ep: , sections: } try: pe pefile.PE(filepath, fast_loadTrue) # 入口点 RVA ep pe.OPTIONAL_HEADER.AddressOfEntryPoint result[ep] hex(ep) # 节区名拼接 names [] for s in pe.sections: n s.Name.decode(errorsignore).strip(\x00) names.append(n) result[sections] |.join(names) # 简单壳判定节区名含 UPX/aspack 等关键词 packed_kw [UPX, aspack, adata, vmp, themida] if any(kw.lower() in result[sections].lower() for kw in packed_kw): result[packed] yes else: result[packed] no pe.close() except Exception as e: result[packed] ferror: {e} return result # 遍历目录批量扫描 rows [] for root, _, files in os.walk(./samples): for f in files: if f.lower().endswith((.exe, .dll)): rows.append(scan_pe(os.path.join(root, f))) # 输出 CSV with open(scan_result.csv, w, newline) as fp: writer csv.DictWriter(fp, fieldnamesrows[0].keys()) writer.writeheader() writer.writerows(rows) print(fScanned {len(rows)} files)这段脚本的逻辑分三层scan_pe负责单文件解析os.walk负责目录遍历csv负责结果落盘。参数上fast_loadTrue让pefile只读头部不解析导入表速度能快好几倍适合大批量扫描。壳判定这里用的是节区名关键词匹配简单但有效如果要更准可以加上熵值计算和 EP 节区判断。rows[0].keys()取字段名要求目录里至少有一个文件空目录会报错实际用的时候加个判空。跑完 CSV 后我会按packed列排序把标记为 yes 的文件单独拎出来走脱壳流程no 的直接进静态分析。这样一轮下来几百个样本的初筛能在几分钟内完成。验证脚本是否可靠的方法很简单拿一个已知的 UPX 壳文件和一个正常编译的文件各跑一遍看packed列是否分别输出 yes 和 no。如果 UPX 文件被判成 no检查节区名是否被改过——有些壳会把UPX0改成UPX0加随机后缀关键词匹配要相应放宽。从那以后我每次拿到新样本都强制先跑一遍批量扫描再动手省得在没加壳的文件上浪费调试时间。希望帮到你。本文还有配套的精品资源点击获取