目录
[壳(Packer / Protector)的定义:](#壳(Packer / Protector)的定义:)
[(0)安全开发方面](#(0)安全开发方面)
[(1)免杀 - 最重要利用](#(1)免杀 - 最重要利用)

PE文件的基本结构(回顾)
PE(Portable Executable)文件是Windows操作系统下可执行文件(如.exe、.dll等)的标准格式。其结构从头到尾依次包括以下主要部分:
-
DOS头(DOS Header):位于文件最开始,用于兼容旧的DOS系统。包含一个小的DOS存根程序,当在DOS环境下运行时会提示"This program cannot be run in DOS mode."。DOS头中最重要的字段是
e_lfanew,它是一个偏移量,指向NT头的起始位置。 -
NT头(NT Headers):包含文件签名("PE\0\0")以及两个重要子结构:
-
文件头(File Header):记录机器类型、节的数量、时间戳等基本信息。
-
可选头(Optional Header):包含入口点(AddressOfEntryPoint)、镜像基址(ImageBase)、节对齐、文件对齐等关键运行时信息。
-
-
节表(Section Table):也称为节头,是一个数组,每个节头描述一个节的位置、大小、属性(可读、可写、可执行等)。
-
节数据(Sections):实际存放文件内容,包括:
-
.text或.code:代码段 -
.data:已初始化数据 -
.rdata:只读数据 -
.rsrc:资源 -
.reloc:重定位表等
-
通俗比喻: PE文件就像一栋大楼:
-
DOS头 是旧时代的大门(为了兼容老系统)
-
NT头 是整栋楼的总蓝图和设计图纸
-
节头 是房间目录表,告诉你每个房间在哪、有多大、做什么用
-
节数据 则是实际的房间,里面放着代码、数据等真实内容
案例: 以Windows自带的notepad.exe为例,使用PEView或CFF Explorer等工具打开后,可以看到:
-
DOS头偏移
0x3C处存放着指向NT头的偏移值 -
NT头起始位置有明显的签名字符串 "PE\0\0"
什么是壳?壳在PE文件中的作用
【未加壳的PE文件结构】
+-------------------+
| DOS Header | ← 兼容DOS,e_lfanew指向NT头
+-------------------+
| NT Headers | ← "PE\0\0" + FileHeader + OptionalHeader
| (File Header) |
| (OptionalHeader)|
+-------------------+
| Section Table | ← 节头数组(例如3个节)
| .text header |
| .rdata header |
| .data header |
+-------------------+
| |
| .text | ← 原始代码(正常指令)
| |
+-------------------+
| .rdata | ← 只读数据、导入表
+-------------------+
| .data | ← 已初始化数据
+-------------------+
...(其他节)
未加壳:入口点指向 .text 节,节名正常,代码可直接阅读
壳(Packer / Protector)的定义:
-
壳是软件加壳技术,用于保护PE可执行文件。
-
原理是附加一段自执行代码,控制原始代码的加载和执行。以下详细输出原理逻辑,补充缺失的部分如内存分配细节,并以文本模式画出通讯流程图和调用逻辑图。
壳的定义:
-
壳(Packer/Shell)是一种程序,附加到目标PE上,形成新PE,运行时先运行壳代码,处理原始内容后转移控制。
-
通俗:壳像保镖,先检查环境,解开包裹,然后让主人(原始代码)工作。
+---------------+ +---------------+
| 系统加载器 | ----> | PE头部检查 |
+---------------+ +---------------+
| |
v v
+---------------+ +---------------+
| 执行壳入口 | <---- | 解密/解压 |
+---------------+ +---------------+
| |
v v
+---------------+ +---------------+
| 修复导入表 | ----> | 跳转OEP |
+---------------+ +---------------+
壳的原理(详细逻辑):
-
附加壳代码到PE新节或覆盖。
-
修改PE头ImageBase、EntryPoint指向壳。
-
运行时,壳分配内存,解压/解密原始镜像。
-
修复重定位、导入表(补充:使用GetProcAddress动态加载API)。
-
跳转到原始OEP执行。
【加壳后的PE文件结构】
+-------------------+
| DOS Header | ← 未变
+-------------------+
| NT Headers | ← 入口点被修改!指向壳代码
| (修改后) |
+-------------------+
| Section Table | ← 节数量增加,节头信息变更
| .text header | ← 原始节可能被加密/压缩
| .rdata header |
| .data header |
| .UPX0 header | ← 新增壳节
| .UPX1 header | ← 新增壳节
+-------------------+
| |
| .text (加密) | ← 原始代码被加密
| |
+-------------------+
| .rdata (加密) |
+-------------------+
| .UPX0 | ← 壳代码 + 解压/解密例程
| (壳代码) |
+-------------------+
| .UPX1 | ← 压缩/加密后的原始数据
+-------------------+加壳后:入口点指向壳节(如 .UPX1),出现异常节名,原始代码被加密/压缩,静态查看几乎无法理解
壳在PE文件中的作用:
-
压缩:显著减小文件体积(常见于UPX、ASPack等)
-
加密/混淆:保护原始代码不被静态分析,防止逆向工程
-
反调试/反虚拟机:增加分析难度
-
隐藏导入表:部分壳会动态重建IAT(导入地址表),隐藏原始API调用
-
防篡改:检测文件是否被修改
工作流程:
-
壳代码被附加到PE文件末尾或新增的节中
-
修改PE文件的入口点(Entry Point),指向壳代码
-
程序运行时,壳代码先执行 → 解密/解压原始节数据 → 修复PE结构 → 跳转到原始入口点(OEP)
通俗比喻:壳像保镖,先检查环境,解开包裹,然后让主人(原始代码)工作。
+---------------+ +---------------+
| 系统加载器 | ----> | PE头部检查 |
+---------------+ +---------------+
| |
v v
+---------------+ +---------------+
| 执行壳入口 | <---- | 解密/解压 |
+---------------+ +---------------+
| |
v v
+---------------+ +---------------+
| 修复导入表 | ----> | 跳转OEP |
+---------------+ +---------------+
案例:
- 使用UPX对一个普通exe文件进行压缩加壳后,文件体积明显变小。
- 运行时,UPX壳会自动在内存中解压原始代码,然后跳转执行。
加壳过程对PE结构的修改
加壳本质上是对原始PE文件进行"改造",主要修改如下:
加壳对PE结构的修改:
-
增加新节:壳代码通常被放在新增的节中(如
.UPX0、.UPX1、.aspack等) -
修改节表:节的数量(NumberOfSections)增加,节头信息被更新
-
更改入口点:将
AddressOfEntryPoint修改为壳代码的起始地址 -
加密/压缩原始节:原始的
.text、.data等节内容被加密或压缩,虚拟大小与原始大小可能发生变化 -
修改/隐藏导入表:部分壳会破坏或加密IAT,运行时动态重建
-
更新文件头和可选头:如SizeOfImage、SizeOfHeaders等字段可能被调整
-
添加新数据目录:部分高级壳会修改数据目录表
通俗比喻:
- 加壳就像给房子重新装修:加了一层防护外墙(新节),调整了门的位置(入口点),把原来的房间内容都加密锁起来(原始节加密),并重新制作了房间目录(节表)。
案例:
一个未加壳的简单 hello.exe 通常只有 3~4 个节(.text、.rdata、.data 等)。 加壳(UPX)后,节数量增加到 4~5 个,出现 .UPX0、.UPX1 等异常节名,且入口点指向新节。
壳的分类
- 壳主要分为压缩壳、加密壳和保护壳三种,每类原理不同,但核心是修改PE执行流。
壳分类:
-
压缩壳: 如UPX、ASPack,用于减小文件大小。
-
加密壳: 如Themida、VMProtect,加密代码防逆向。
-
保护壳: 如Armadillo,添加反调试、虚拟机保护。
原理逻辑
-
压缩壳原理:
- 使用算法如LZMA压缩原始节,运行时在内存解压,恢复虚拟地址。
-
加密壳原理:
- 用AES等加密原始代码,壳含密钥,运行时解密到内存,避免静态分析。
-
保护壳原理:
- 注入反调试代码,如IsDebuggerPresent检查,或虚拟化代码成字节码,补充逻辑:如果检测调试,则自毁或误导。
通俗解释:
- 压缩壳像zip文件,加密壳像密码箱,保护壳像带警报的保险柜。
案例:
- UPX压缩壳:原始PE大小1MB,加壳后500KB,运行时解压占用1MB内存。
如何加壳
-
PE 文件格式基于 Windows NT 内核,由 DOS 头、NT 头、节表和节数据组成。
-
加壳本质是修改这些结构,插入"壳"来包装原内容。
- 文件结构修改:
-
原 PE 文件有多个节(如 .text 代码节、.data 数据节)。
-
加壳工具扫描文件,压缩/加密原节内容,然后将壳代码(一段自解压逻辑)和打包数据追加到末尾节或新节中。
-
同时,更新 NT 头的 AddressOfEntryPoint(入口点)字段,指向壳代码起始地址。
-
- 壳代码的功能:
-
壳代码不是单纯的"加密混淆",而是运行时解包器。
-
它负责分配内存、解压/解密原数据、修复 PE 结构(如导入表 IAT 和重定位表),然后跳转到原入口点(OEP)。
-
如果仅压缩,无需加密;如果加密,则用算法如 XOR 或 AES 处理。
-
-
运行时流程:
-
Windows 加载器读取 PE 文件,先执行壳代码。
-
壳代码完成后,程序无缝切换到原逻辑,用户无感知。
-
-
保护机制扩展:
- 高级壳(如 VMProtect)会加入虚拟机(VM)混淆,将原代码转为字节码,在运行时解释执行。这超出简单加密,旨在反调试。
-
潜在风险与变体:
-
不所有壳都"干净"追加:
-
有些覆盖原有节以节省空间;
-
边界情况如多层加壳(壳中套壳),脱壳需层层剥离。
[原 PE 文件] --> [加壳工具扫描结构]
|
v
[压缩/加密原节] <-- [插入壳代码 + 修改入口点] --> [追加到末尾/新节]
|
v
[输出加壳 PE 文件]运行时:
[Windows 加载器] --> [执行壳代码 (分配内存)]
|
v
[解压/解密原数据] --> [修复 IAT/重定位] --> [跳转到 OEP]
|
v
[执行原程序逻辑] --> [程序正常运行] -
加壳案例
-
以 UPX(开源压缩壳)为例,说明原理(通俗易懂 + 底层细节):
-
场景:假设一个简单 C++ 程序 hello.exe(输出 "Hello World"),大小 100KB。
-
加壳步骤:
-
UPX 读取 hello.exe,压缩 .text 等节为数据块(用 LZMA 算法,压缩率可达 50%)。
-
添加壳代码(约 10KB),修改入口点指向壳。
-
输出 packed_hello.exe,大小减至 60KB。
-
运行时底层:
-
加载 packed_hello.exe,CPU 先跳到壳代码。
-
壳代码申请内存,解压原节,复制到正确地址。
-
修复导入(如 kernel32.dll 的 GetProcAddress),避免地址冲突。
-
跳转 OEP,执行 "Hello World"。
cpp
// 壳代码入口:运行时先执行这里
void shell_entry() {
// 分配虚拟内存,用于存放解压后的原代码
void* mem = VirtualAlloc(NULL, ORIGINAL_SIZE, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// 解压/解密原数据(假设用 XOR 简单加密)
decrypt_and_copy(original_packed_data, mem);
// 修复导入表:遍历 IAT,动态加载 DLL 函数地址
fix_IAT(mem);
// 修复重定位:调整地址偏移(因为加载基址可能变)
fix_relocations(mem);
// 跳转到原入口点,继续原程序
jump_to_OEP(mem + ORIGINAL_EP_OFFSET);
}
// 辅助函数:简单 XOR 解密
void decrypt_and_copy(char* packed, void* dest) {
// 循环处理每个字节
for (int i = 0; i < PACKED_SIZE; i++) {
// 逐字节 XOR 密钥(示例密钥 0xAA)
((char*)dest)[i] = packed[i] ^ 0xAA;
}
}
加了壳有啥用(加壳利用)
(0)安全开发方面
-
从安全开发角度,壳用于保护合法软件知识产权,防止逆向工程或破解。
-
开发者通过壳加密核心算法,维护软件安全性。
-
底层原理:壳隐藏代码逻辑,增加反汇编成本;虚拟机混淆使调试器崩溃。
利用方式
-
代码保护:加密敏感节,防 IDA Pro 等工具分析。
-
反调试集成:壳添加检查(如 IsDebuggerPresent),终止逆向。
-
许可验证:壳内嵌入 DRM,运行时校验 license。
案例:
-
游戏引擎如 Unity 加壳保护 anti-cheat 模块。
-
破解者尝试调试,壳检测虚拟机环境,直接退出。
(1)免杀 - 最重要利用
杀毒软件的静态检测主要依赖特征码(Signature)匹配。一旦文件被加壳,原始恶意代码被加密或压缩,文件整体的二进制特征发生巨大变化,导致杀软无法匹配已知病毒库,从而大幅降低检测率。
底层原理:
-
静态扫描失效:杀软扫描时只能看到壳代码(通常是无害的压缩/解密循环),而真正的恶意负载被隐藏在加密数据中。
-
动态检测困难:虽然程序运行时会脱壳暴露原始代码,但许多壳带有反调试、反沙箱、反虚拟机机制,可有效对抗杀软的动态行为检测。
具体利用方式:
-
签名混淆:通过加密原始代码,彻底改变病毒特征码,使杀软静态查杀失败。
-
多层壳叠加:先用UPX压缩,再套Themida、VMProtect等强壳,层层保护,极大增加自动脱壳和检测难度。
-
自定义壳:高级攻击者会编写私人壳或高度变形的壳,避免被通用解壳器(Unpacker)识别和处理。
-
壳+代码混淆结合:在加壳基础上再进行代码虚拟化、控制流平坦化等,进一步提升免杀效果。
真实案例:
一个已知病毒文件 virus.exe,被杀软准确识别并拦截。使用UPX加壳后,同一杀软扫描时提示"未发现威胁"。运行时,壳代码先在内存中解密原始病毒负载,然后执行恶意行为(如注入进程、窃取信息)。
(2)保护恶意代码逻辑
防止安全研究员或竞争对手通过静态逆向分析快速理解恶意代码的功能和目的。
(3)对抗逆向工程与取证分析
增加分析成本和时间。安全人员必须先成功脱壳,才能看到真实恶意代码,这为恶意软件的传播争取了宝贵时间。
(4)体积压缩与传播便利
压缩后的文件体积更小,更容易通过邮件、U盘、下载站等方式传播。
如何快速判断一个PE文件是否被加壳?
判断思路:
-
使用PEiD、Detect It Easy(DIE)等工具直接扫描壳类型
-
手动查看:入口点是否落在异常节中?节名是否出现
.UPX、.aspack、.Themida等? -
查看入口点处反汇编代码:如果是大量循环、解密指令、PUSH大量寄存器保护等,而非正常程序初始化代码,则高度疑似加壳
-
对比文件大小与功能:体积异常小但功能完整,通常被压缩加壳
掌握了PE基本结构和加壳原理后,你就已经打开了Windows可执行文件逆向工程的大门。后续可以继续学习脱壳技术(如使用OllyDbg/ x64dbg调试dump、修复IAT等)。
总结:
- PE文件就像一栋有明确蓝图(NT头 + 节表)和房间(各节)的大楼。
- 壳则是给这栋大楼加了一层"智能外壳":它修改入口点、新增节(或覆盖原有节)、对原始代码/数据进行压缩/加密/混淆。
- 程序运行时,壳代码先执行,负责在内存中分配空间 → 解压/解密 → 修复关键结构 → 跳转到原始OEP,让用户感觉不到任何差异。
壳的核心价值有两方面:
-
合法保护:保护知识产权、防逆向、防篡改。
-
恶意利用(尤其是免杀):彻底改变文件静态特征,让杀毒软件的签名检测失效,同时通过反调试、反VM等机制对抗动态分析。
涉及的关键PE结构/表:
-
入口点(AddressOfEntryPoint):被重定向到壳代码。
-
节表(Section Table):节数量增加,节属性/大小变更。
-
导入表(Import Directory / IAT):常被破坏或隐藏,运行时动态重建(使用
LoadLibrary+GetProcAddress)。 -
重定位表(Relocation Directory):壳解包后需要修复基址重定位。
-
数据目录表(Data Directory):部分壳会修改多个目录项。
免杀中壳的重点
免杀中壳的重点在于"特征隐藏 + 动态还原 + 反分析":
-
静态特征彻底改变:原始恶意代码的签名、字符串、指令序列被加密/压缩后,杀软静态扫描只能看到"无害"的壳代码(解压循环、PUSHAD/POPAD等常见stub指令),导致特征码匹配失败。
-
动态行为延迟暴露 :只有运行到壳的后期(OEP附近)才会把原始恶意负载解开到内存。这时如果壳带有反调试 (
IsDebuggerPresent、CheckRemoteDebuggerPresent、NtQueryInformationProcess等)、反沙箱 、反虚拟机检测,就能提前退出或自我销毁,极大增加杀软动态检测的难度。 -
IAT隐藏与动态重建 :很多强壳不使用标准的导入表,而是在运行时手动
LoadLibrary+GetProcAddress填充IAT,隐藏了原始调用的DLL和API,进一步绕过行为监控。 -
多层/自定义壳:UPX(压缩)+ Themida/VMProtect(加密+VM)叠加,或自己写变形壳,能让通用解壳器失效。
-
体积压缩:便于传播,同时小文件更容易躲过部分启发式检测。
对初级人员:
-
入门(UPX这类简单压缩壳):中等偏低。有公开源码和一键解壳工具,跟着教程手动脱壳(ESP定律、单步到OEP、Scylla修复IAT)就能上手,1-2周可有感觉。
-
中级(ASPack、PECompact等):中等,需要手动找OEP、理解更多反调试技巧。
-
高级(Themida、VMProtect等保护壳):难度很高。涉及虚拟机字节码解释、大量反调试、多层保护、进程注入等,初学者容易卡住,可能需要几个月持续练习 + 阅读大量源码/论文。
-
总体来说:学壳的难度主要不在"壳本身",而在PE格式 + 动态调试 + Windows internals的功底。没有扎实PE基础,直接学壳会非常吃力,经常"看不懂为什么在这里跳转",而且还有知识点不明白的要反反复复的哟。
后续:
- 后续我会写一个个人的加壳程序实战
- 常规加密壳脱壳 IAT修复 重定位表修复
- 讲一下VMP脱壳 相关知识点 一个小小的案例 讲清楚vmp的 hander jump 虚拟化指令集 vm_context
- 以上慢慢会更新的 最近有点忙
- 反正以上是基于windows的PE结构对于懂的人简单,但是简单也是基于反反复复的实践以及理解,不然PE里面很多理论知识点搞不明白的,搞不明白后面的开发或者安全开发也就是搞不明白。