ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

PEiD查壳识别原理与未知壳侦测:从特征匹配到启发式判断的完整实战指南

PEiD查壳识别原理与未知壳侦测:从特征匹配到启发式判断的完整实战指南 简介PEiD侦测未知壳加强版是一套面向逆向工程与安全分析的可执行文件查壳工具包主要帮助用户识别程序是否被加壳以及壳的种类尤其强化了未知壳侦测能力。它在原版PEiD基础上扩充了特征签名库和插件接口可通过特征匹配与辅助插件应对UPX、ASPack等常见壳及部分未知变种适用于病毒分析、软件调试和恶意代码研究场景。压缩包共45个文件包括36个dll插件、exe主程序、bpl运行库、txt文档、ini配置以及defs.h和null.c等C源码整体体积仅2.48MB轻量易用。目前已有234人学习下载。包内除PEiD主程序外还集成FileInfo、Morphine、GenOEP、ImpREC等常用查壳/脱壳辅助插件并提供userdb.txt签名库和插件开发示例用户可自行更新特征或扩展功能在处理各类加壳样本时更灵活高效。1. PEiD 到底是什么一个查壳工具为什么能决定你下一步往哪走拿到一个来路不明的 PE 样本先别急着扔进调试器。动态调试能看到结果但看不到入口点做过什么更猜不到后面还有几层壳。恶意代码分析最忌在未知加壳状态下直接上线性分析你看着像入口函数的位置其实是壳的解压 stub真正的主程序在内存里解出来之前IDA 里全是花指令。PEiD 就是干这个的把 PE 文件拆开对照内置特征库几毫秒告诉你它有没有壳、什么壳、入口点偏移在哪个段。侦测未知壳加强版的意义不只是多几个特征而是把「特征匹配」和「启发式判断」揉在一起让查壳从依赖运气变成依赖流程。这套流程做分析的、做应急响应的、做游戏保护的都离不开。前两者靠它筛查恶意样本的加壳类型后者靠它确认自己的保护方案有没有被识别。上手成本低到几乎没有学习曲线但能发挥多大作用取决于你到底理解了多少参数含义和边界条件。这篇文章顺着排查思路走一遍先讲识别原理再讲怎么跑通最小流程然后是未知壳的实判定方法最后是踩坑和如何把自己遇见的新壳固化成特征。为什么不直接拖进在线沙箱因为沙箱跑的是行为是野样本才有参考价值你手上的样本可能是一种没见过的新壳沙箱只会告诉你「该文件正在访问注册表」。判断壳决定了你要在哪个阶段下断点比什么都重要。2. PEiD 的识别机制靠什么认出壳以及加强版改进了什么2.1 经典三件套特征码、入口点校验和、段信息表PEiD 的第一层识别逻辑是对文件字节做模糊匹配。传统特征码是拿已知壳的入口代码片段算一个首字节序列存放在签名库里。扫描时 PEiD 从 PE 头里读出入口点AddressOfEntryPoint指向的 RVA换算成文件偏移然后读取那一段字节去和签名库比对。只要前十六到二十四个字节对得上就判定命中。这个方案快十年前的机子上也是毫秒级缺陷是只认入口位置的精确定位只需要在入口前偏移几个字节伪装一下匹配就落空。增强版在保留特征匹配的基础上加了一层入口点校验和的判定。入口点校验和EntryPoint Checksum先把入口代码的前若干个字节取出来做一个简单的累加运算再去对照算法库。特征码比对的是冷数据校验和比对的是运算特性。两者各有侧重特征码对指令的精确形态敏感校验和对指令序列的统计特性敏感。实践里的典型情况是一个壳压缩了入口区块的代码长度特征码长短对不齐校验和却不受长度影响依然能识别出熟悉的壳族。段信息表是第三道保险。PE 文件里每个段都有自己的名字壳为了减少暴露通常会把段名改成 UPX0、UPX1 这类特征名但很多自制壳根本没改段名这一步。PEiD 会把段名、段的虚拟大小与原始大小比值、段的熵值等维度的信息汇总起来对壳的类型做一个旁路判定。三个维度互相印证不是非要三条全中才出结论而是按置信度排序给结果。这也是加强版的核心改进——老版本是单一命中即返回新思路是收集证据链再下结论。2.2 启发式识别如何补齐特征库的空缺特征库再全也追不上每天冒出来的新壳。PEiD 的启发式识别不依赖精确字节匹配而是看结构上的反常现象。最常见的一条规则是大量 PE 样本的入口不在第一个段但绝大多数合法程序入口都在代码段里偏移不大。如果入口点出现在一个名为.themida或.MPRESS1的短段里而文件头又显示这个段的原始大小远小于虚拟大小几乎可以确定这是个加壳程序在准备解压前的桩代码。这种判断维度的价值在于不需要精确知道这是一个什么品牌的壳也能得出「这个 PE 是加壳的」结论。对于恶意软件分析场景知道类型是加分项确认加壳状态就是决策项了。接下来你决定是在入口点下硬件断点还是去内存里翻解压结果思路完全不同。2.3 加强版和原版的实用差异单纯说「增强」有点虚实际操作上差别集中在三个点。第一特征库覆盖范围明显变大了对近几年流行的壳族变种有更新第二界面上能看到匹配记录的命中细节不再只是弹一行「什么都没找到」就完事第三增加了导出签名库的路径你可以把分析过程中确认过的自定义壳特征写进 userdb.txt下次再遇到同一个壳就能直接命中。老版本没有这个模块第三方整理的特征文件加载也麻烦。特别提醒一句PEiD 的上一个官方版本是 0.95这已经是很多年前的东西了。现在网上流通的带「加强版」字样的都是社区维护分支不同发布者合并的特征库不一样实际识别偏向也不同。所以 2.1 节说的三维判定不同版本权重也有差异有的启发式灵敏度高一些对正常编译的 VC 程序也会偶尔报一个「未知壳」的询问式结果。这不是工具坏了是启发式阈值设得偏保守的策略需要人工排除。3. 跑通一次查壳流程命令行、界面选项与输出解读3.1 用命令行完成批量侦测窗口版一次拖一个文件适合手工精查。分析大量样本时还是走命令行更快。PEiD 带一个命令行模式虽然原始发布包里没有单独放 CLI 帮助但传参数就能在控制台里直接输出识别结果不需要打开图形界面。peid.exe -scan C:\samples\2010_malware.exe -log C:\work\scan_result.txt-scan参数后面跟的是待扫描文件路径-log指定结果输出文件。屏幕上能看到带时间戳的扫面进度结果会包含文件大小、入口点地址、命中签名名称。参数说明命令行模式下 PEiD 不会弹出图形界面跑完即退出适合脚本批量循环。批量循环里可以把文件路径作为变量传入把输出日志统一收集到一个目录再定时归档。for /r C:\samples\raw %%i in (*.exe) do peid.exe -scan %%i -log C:\samples\log\%%~ni.txt这段批处理会把 samples\raw 目录下所有 exe 依次扫描输出文件按原始文件名一一对应。跑完看一遍日志文件的大小分布如果有一批结果文件全部是 0 字节优先检查这批样本是不是都是 DLL命令行模式对 DLL 支持不如 GUI 模式稳定遇到 DLL 用/ -scan参数直接传可能直接跳过不扫此时排队清单里要把 DLL 单独拎出来用界面版处理。3.2 界面版的核心操作与三个必看字段界面版打开之后只有一个文件选择框把样本拖进去下半部分列出 EP 段、入口点、文件偏移、命中签名等字段。这里容易被忽略的是「EP 段EntryPoint Section」和「文件偏移File Offset」两列。EP 段告诉你入口点位于哪个节区它比签名结果更能说明问题——如果签名显示「什么都没找到」但 EP 段是.vmp0或者一个非标准短段名这就是一个启发性疑点值得当加壳文件处理。文件偏移的含义是把入口点的 RVA 转成磁盘上的物理位置这个位置就是调试器里入口断点的落点位置。你后面开 x64dbg 时直接在偏移处下断即可。看这三个字段的顺序建议是先看命中签名再看 EP 段名最后对照文件偏移验证签名是否可信。签名可信的标准是偏移位置和 EP 段算术上互相吻合如果签名叫 UPX 但 EP 段显示的是.data这个命中大概率有误。3.3 批量任务在分析流程中的站位实际恶意样本分析流程里查壳只是第一步后面通常跟着脱壳、静态提取配置、动态行为分析。PEiD 得到的结论不需要绝对精确它只需要帮你完成一个关键决策这个样本要不要进入脱壳环节。跑批量样例时我习惯在每批结果后面加一行备注把启发式怀疑对象标记出来这些样本会优先进入动态分析队列。批量结果里最危险的不是「什么都没找到」而是 9 成样本报同一个壳而其中 1 个报别的壳——那个离群值往往才是真正需要重点关注的对象因为恶意代码作者会用同一种合法壳打包钓鱼程序偶尔混进来一个不同壳的更可能是定向攻击载荷。4. 侦测未知壳加强版没有特征库命中时怎么继续查4.1 把「未识别」拆开熵值、段表和导入函数三条线索PEiD 报告「什么都没找到」不代表文件干净。手工继续往下推时第一眼要看向文件熵值。用一个小脚本快速算整个文件的 Shannon 熵import math import binascii def file_entropy(path): with open(path, rb) as f: data f.read() if not data: return 0.0 freq [0] * 256 for byte in data: freq[byte] 1 entropy 0.0 for count in freq: if count: p count / len(data) entropy - p * math.log2(p) return entropy sample_path rC:\samples\unknown.bin ent file_entropy(sample_path) print(f整体熵值: {ent:.4f})逻辑说明函数逐个字节统计频率再按香农公式算熵。整体熵值高于 7.0 通常意味着文件内容经过压缩或加密这是加壳的典型特征。纯文本或未压缩的原生 PE 熵值一般在 5.0 到 6.5 之间。参数说明阈值不是死的。编译器自带优化会把代码段排得比较紧密也可能到 6.8 左右。判定时不要把整体熵当唯一标准最好分段看——PE 文件里代码段的熵和资源段的熵相差很大如果资源段的熵高到 7.5 以上更要怀疑资源有没有被加密隐藏。第二眼看段表。工具自带的段表视图会把每个段名、虚拟大小、原始大小、熵值列成一张表。正常程序的段名规律是.text、.data、.rdata、.rsrc大小关系基本合理。加壳程序要么段名怪异要么虚拟大小和原始大小的比值失衡——虚拟大小远大于原始大小说明这个段在镜像里需要占很大空间但磁盘上只有一小团数据典型的内存解压预留区。第三眼看导入表。壳为了减小体积把原程序的导入函数拆散重排但不会完全消灭导入函数。如果导入表里出现LoadLibrary、GetProcAddress、VirtualAlloc这组特征组合而其他正常业务 API 寥寥无几基本可以判定这是一个壳的装载器。4.2 手工取样从入口点首字节提炼签名不明白壳是怎么实现加载的先看入口点那几十个字节的反汇编结果。用十六进制编辑器跳转到入口偏移处把后面 64 字节复制出来对照常见壳的起始指令模式做逐条比对。UPX 的入口通常是pushsub esp, imm结构然后就是循环解压指令。VMProtect 的入口往往先跳到一个间接地址再进入虚拟机解释循环。Themida 的入口会包含大量的pushfd与call组合且代码块之间有明显的花指令填充。有经验的逆向工程师能靠入口处指令是「推栈、解密循环、写回内存、跳转」还是「直接进入业务逻辑」来判断壳的大致类型。这一步不依赖任何特征库是纯手工行为模式匹配。加强版里有一个功能可以帮你把这组字节直接存成一个新特征而不是只给你一个「未识别」的结论。取样的字节数不宜太短太短容易和其他二进制撞车也不宜太长太长会把壳版本差异也绑进来导致匹配过窄。64 字节是一个实用平衡点。下面是提取入口字节并计算校验值的 Python 片段用来给新壳生成稳定指纹import pefile import hashlib pe pefile.PE(rC:\samples\unknown.bin) ep_rva pe.OPTIONAL_HEADER.AddressOfEntryPoint ep_va pe.OPTIONAL_HEADER.ImageBase ep_rva ep_file_offset pe.get_offset_from_rva(ep_rva) with open(rC:\samples\unknown.bin, rb) as f: f.seek(ep_file_offset) entry_bytes f.read(64) print(f入口点 VA: 0x{ep_va:08X}) print(f文件偏移: 0x{ep_file_offset:08X}) print(f首包字节: {entry_bytes[:16].hex( )}) print(fSHA256: {hashlib.sha256(entry_bytes).hexdigest()})代码逻辑先实例化 pefile 读取 PE 头拿到入口点 RVA再用get_offset_from_rva换算成文件偏移。随后读取 64 字节入口代码打印首 16 字节和整段 SHA256。首 16 字节用于和已知壳快速对照SHA256 用于给这个壳的唯一形态留档。参数说明entry_bytes的长度按需调整。如果是分析一个疑似很久以前就存在的旧壳32 字节足够新壳变种多取 64 字节更稳妥。SHA256 在这里只服务于记录不参与匹配因为哪怕有一个字节变化 hash 就完全不同。4.3 用内存特征补足磁盘特征跑起来看动态行为磁盘分析做到这里通常只剩最后一步才能盖章跑起来看。加壳程序的磁盘版本只是一坨压缩数据真正的内容在运行解压后才会出现在内存里。用调试器加载样本在VirtualAlloc或WriteProcessMemory上下断大概率能落到解压函数附近。这个方法对分析人员来说不需要完整脱壳看到内存里出现了原始 PE 头MZ 标记就算到达胜利点。平时做完这一步我会把内存 dump 出来再交给 PEiD 扫一遍——很多壳的磁盘形态识别不了但内存形态因为解压到了原始代码特征码会直接命中。这是实践里对付未知壳最可靠的一条路径先把壳跑起来再把内存里的真东西喂给特征库。虚拟机里跑需要留意反调试机制。常见的一批新壳会检测IsDebuggerPresent、NtQueryInformationProcess甚至尝试读取调试寄存器。常规做法是先把 VM 的硬件辅助虚拟化特性关掉再用 x64dbg 的 ScyllaHide 插件做基础反反调试。如果一运行就退出且没有任何报错弹窗先怀疑是反虚拟机检测而不是分析姿势不对。5. 实战避坑PEiD 带崩分析结论的 5 个典型场景5.1 误报「Microsoft Visual C」就以为没壳现象PEiD 识别结果为Microsoft Visual C 6.0后续分析步骤直接跳过查壳按普通样本处理。分析走到一半发现入口点跳转逻辑和正常 VC 运行库完全不同才意识到有大问题。原因很多壳为了伪装成自然编译的二进制会在入口点之前伪造 VC 运行库的特征字节。PEiD 的特征匹配只看字节片段完全没在意这些字节位于哪个段、周围是什么代码。只要特征对得上就报结果。解决命中签名后不要着急下结论。看一下签名名和入口段名是否搭配VC 运行库签名的入口段应该是.text如果 EP 段是.themida或者一段无名代码块就有伪装嫌疑。此时越过 PEiD 直接看导入表原生 VC 程序的导入表必含kernel32.dll的一组常见 API 且没有大量动态调用。5.2 同一个 exe 多引擎扫描结果不一致现象同一份样本用不同版本的 PEiD 扫一个报 UPX另一个报未知。原因网上流通的加强版维护分支合并了不同来源的特征集有的偏重老壳覆盖有的偏重新壳启发性规则。同一字节序列在不同版本里走的解析分支不一样。解决搭建分析环境时固定一个 PEiD 版本不要频繁升级。跨版本对比结果时要看特征库版本号是否一致。不一致的结论不能直接拿来当证据链。5.3 报壳正确但脱壳脚本不兼容现象PEiD 明确识别为 ASPack 2.12加载对应脱壳脚本后 OEP 定位错误dump 出来跑不起来。原因PEiD 的特征库粒度只到「壳的某个版本区间」脚本则是按精确版本偏移写的。同品牌壳在编译选项差异下会产生指令序列微偏移导致脚本的硬编码结构判断崩掉。解决把识别结果当成初筛而不是定理拖进调试器先确认入口点周围代码结构是否符合已知壳的典型框架再决定用哪个版本的脚本。明知有 10 个 ASPack 变种就不要只准备一个脚本。5.4 DLL 扫描结果莫名为空现象GUI 界面拖入 DLLPEiD 窗口内不出结果像卡住了一样。原因部分分支版本对 DLL 及驱动文件支持不完整默认按 exe 的处理逻辑去扫描碰到 PE 头解析异常直接吞异常。解决DLL 样本优先看导出表而不是入口点。直接检查导出表里是否有与加壳工具相关的导出函数UPX1、Themida等常见壳名再用脚本计算熵值辅助确认。界面扫描结果的缺陷不影响这个替代方案。5.5 32 位壳识别正常但换 64 位就失灵现象同一套流程处理 32 位样本没有问题换成 64 位样本 PEiD 全部报未知或直接崩溃。原因很多特征是以 32 位指令模式提取的64 位入口指令形态完全不同匹配模式自然失效。老版本没有为 x64 维护独立的入口特征集。解决64 位样本交给另一个专用工具处理PEiD 重点盘 32 位样本。需要查 64 位时用「解析 PE 头 熵值 段名」的组合跳过高精度匹配直接看结构疑点。这一环节做充分了64 位壳有没有加其实也能判断个八九不离十。6. 把新壳特征固化成签名自己维护一份可用特征库加强版支持绕过 GUI 直接修改 userdb.txt 添加自定义特征把识别未知壳的经验保存成可复用资产。userdb.txt 存放在 PEiD 根目录下格式是每行一条签名由签名名称、入口点特征字节、匹配掩码组成。[MyNewPackerv1] EP 55 8BEC 83C4 F0 33 C0 56 57新建一条签名名字叫MyNewPackerv1入口特征字节是反向推导出来的真实入口指令片段。中间的空格允许有通配符?表示该字节不参与匹配。保存后重启 PEiD就能在特征列表里看到这条自定义规则。写特征之前有一步验证不能跳把 5 个以上同源样本的入口字节对齐确认特征片段在它们之间完全一致。如果只有 1 个样本做出特征下次碰到同壳但不同编译版本时大概率匹配失败反而让你对工具丧失信心。这段验证可以写个小脚本辅助完成samples [a.bin, b.bin, c.bin, d.bin, e.bin] base None for path in samples: with open(path, rb) as f: data f.read(32) if base is None: base data continue common bytes(x if x y else 0x3F for x, y in zip(base, data)) base common print(通用字节:, base.hex( ))这个 script 把多个样本的入口前 32 字节逐字节对齐相同的字节保留不同的字节输出为3F即特征语法里的?通配符这样自动生成的特征片段天然兼容同壳不同变种。参数说明zip后长度取最短的那个样本的 32 字节所以样本之间入口偏移一致性是前提。不一致时先手动对齐再跑脚本。再往后走新壳出现得多了userdb 内部会越积越长建议维护时按壳族分组加注释行说明特征来源和验证日期。某条特征如果出现误报把对应误报文件的入口字节导出来合并进特征推导样本集再重新生成。没有对比就没有修正依据只加不删的特征库迟早变成误报制造机。最后留一个重复出现的教训查壳结论必须结合调试器里的实际表现互证PEiD 给的只是概率不是定理。如果壳的特征匹配和运行行为互相矛盾宁可信行为也不要轻信签名。查壳工具是来帮我们缩小范围省时间的但真正敢下结论的永远是把自己当成那个会逆向的人——先怀疑再验证。希望这些思路对你接下来的分析工作有一点点帮助。本文还有配套的精品资源点击获取
返回列表