主机恶意软件防护的异常PE文件结构混淆(Anomalous PE File Structure Obfuscation)检测与防护
我们已经讨论了许多运行时和内存层面的恶意软件对抗技术。现在,让我们进入一个更基础的领域:文件本身。恶意软件为了逃避静态扫描和分析,常常会畸形地修改Windows可执行文件(PE文件,即Portable Executable格式)的结构,这种行为就是“异常PE文件结构混淆”。
下面,我将分五步,由浅入深地为你讲解这个技术。
第一步:理解正常PE文件的结构——我们的“参照物”
PE文件是Windows下可执行文件(.exe, .dll, .sys等)的标准格式。它就像一本结构严谨的书,分为以下几个关键章节:
- DOS头:兼容老DOS系统的“古老序言”,总是以
MZ开头。它指向真正的PE头位置。 - PE头签名:一个标志,内容是
PE\0\0,宣告“这是一个PE文件”。 - 文件头:记录了基本信息,如文件的机器类型(x86, x64)、节区(Section)数量、时间戳等。
- 可选头:虽叫“可选”,但很关键。包含程序的入口点地址、首选加载基址、代码/数据大小、以及数据目录。
- 数据目录:一个表格,记录了文件内重要结构(如导入表、导出表、资源表、重定位表)的“起始位置”和“大小”。操作系统加载器主要依赖这个目录来初始化程序。
- 节区头:每个节区(比如
.text放代码,.data放数据,.rdata放只读数据)都有自己的“头部”,描述它的虚拟地址、文件偏移、原始数据大小、内存属性(如可读、可写、可执行)等。 - 节区数据:实际的内容,比如机器码指令、字符串常量、图标图片等。
正常情况下,这些部分必须逻辑清晰、首尾对齐、大小合理,Windows加载器才能正确运行它。
第二步:理解恶意行为——为什么要“混淆”结构?
恶意软件编写者很清楚,大多数杀毒软件首先会解析PE文件,检查其特征码或提取行为特征。如果文件结构完全标准,就容易被快速匹配签名。
因此,他们故意制造结构异常,达到两个目的:
- 逃避静态扫描:许多静态扫描器假设PE文件格式规范。一旦遇到非预期结构,扫描器自身可能崩溃、跳过扫描、或者解析出错,从而漏过恶意内容。
- 对抗分析工具:导致调试器、反汇编器或沙箱无法正确加载或解析文件,增加分析人员的分析时间。
第三步:深入技术细节——常见的异常PE结构混淆手法
恶意软件作者并不只是简单破坏文件,因为破坏过度会导致程序无法运行。他们采用“精确破坏”,使得Windows加载器(它比许多分析工具更容错)仍能运行,而安全工具却出错。
-
畸形校验和:
- 正常:PE可选头中的
CheckSum字段应为文件实际计算出的校验和。系统加载内核驱动时会验证它。 - 异常:恶意软件故意填入一个错误的值。许多扫描器会严格校验,发现不一致就报可疑或不处理;但Windows加载器在普通用户态EXE文件中往往忽略此值,恶意软件依然可运行。
- 正常:PE可选头中的
-
无效的节区大小与偏移混淆:
- 手法:将节区的
PointerToRawData(在文件中的偏移)设置为一个远超文件本身大小的值,或将SizeOfRawData(在文件中占用的字节数)设为0,但VirtualSize(内存中的大小)却很大。 - 目的:当扫描器试图去偏移处读取节区内容时,会读到文件末尾的空白或直接出错。而Windows加载器在映射节区到内存时,会基于
VirtualAddress和VirtualSize来处理,忽略文件偏移的异常,仍能正确分配内存并初始化零填充的节区。
- 手法:将节区的
-
重叠节区:
- 手法:定义两个不同的节区头,使它们在磁盘文件上指向完全相同的区域,但在内存中被映射到不同的虚拟地址。
- 效果:扫描器解析时可能陷入循环或误判两节区的权限。加载器则按顺序处理,最终内存中会形成具有不同权限的两块区域,代码可以跨引用,增加静态分析难度。
-
数据目录项滥用:
- 手法:在某些不常用的数据目录项(如
LoadConfig、DelayImport)中填入非法或指向节区中间的值。或者将导入表的VirtualAddress指向.text代码节区内部。 - 效果:标准工具解析导入表时会失败。但恶意软件可能通过手工方式自行加载API,根本不依赖标准导入表。
- 手法:在某些不常用的数据目录项(如
-
可选头中的
SizeOfImage不匹配:- 手法:
SizeOfImage本应等于所有节区在内存中占用的总大小。恶意软件将其设置成一个比实际所需大得多的值。 - 目的:欺骗扫描器的内存模拟器,导致其分配巨大内存而性能下降或失败。Windows加载器仍会根据节区计算实际需要的映像大小,并修正逻辑。
- 手法:
第四步:检测方法——如何发现这些混淆?
防御方不能只依赖标准库解析。检测核心是 “感知偏差”——对比文件宣称的结构与真实存在的数据。
-
深度解析与边界检查:
- 自研PE解析器,在读取每个字段前,都严格校验偏移量、长度是否在文件实际大小范围内。
- 一旦发现
PointerToRawData + SizeOfRawData > 文件总大小,或VirtualAddress + VirtualSize导致整数溢出,立即标记为异常。
-
多视角一致性校验:
- 视角A:基于文件偏移的解析:尝试按标准方法读取节区、表、目录。
- 视角B:基于加载器行为的模拟:模拟Windows加载器的容错逻辑,计算实际的映像布局、导入项等。
- 检测点:对比两个视角的结果。如果A失败而B成功,或者两者解析出的导入表、节区权限差异巨大,则是混淆的可疑信号。
-
启发式规则:
- 检测
SizeOfRawData为0但内存中VirtualSize大于0的节区(常见于注入代码的节区)。 - 检测重叠节区:即两个不同节区头的文件偏移区间
[PointerToRawData, PointerToRawData+SizeOfRawData)存在重叠。 - 检测可疑的数据目录项,例如导入表地址指向代码段(
.text)或数据段内。
- 检测
-
熵值分析:
- 对于声明为大尺寸但实际数据很小的节区,计算其真实数据的熵值。如果熵值极高(接近8),说明数据很可能已压缩或加密,这也是可疑点。
第五步:防护策略——如何阻断与修复?
检测到异常PE结构后,我们不是仅仅报告,而要采取行动:
-
深度清理性扫描:
- 使用健壮的、基于加载器模拟的解析器,重建一个“标准化”的PE视图,再提取特征进行匹配。这可以穿透许多基于偏移的混淆。
-
动态转换:
- 在安全沙箱中,尝试修复异常结构(如修正错误的
SizeOfImage,合并重叠节区),生成一个“规范化”的PE文件副本,再对其进行深度分析或提取特征。
- 在安全沙箱中,尝试修复异常结构(如修正错误的
-
阻止执行:
- 如果文件在网关或主机代理层被检测到,直接阻止其落盘或执行。利用系统策略(如Windows的
Device Guard/WDAC)仅允许签名完整、结构规范的PE文件运行。
- 如果文件在网关或主机代理层被检测到,直接阻止其落盘或执行。利用系统策略(如Windows的
-
行为强制联动:
- 对于这类混淆文件,提高其动态分析优先级。强制在严格的内存模拟环境中运行,监控其是否尝试展开自身、修改内存权限、连接外部C2等行为。因为结构混淆的最终目的是掩盖恶意行为,直接观察行为是最可靠的验证。
总结来说,异常PE文件结构混淆攻击的是文件静态分析的基础设施。防御的核心在于不再盲目信任文件头中的声明,而是通过多视角一致性校验和加载器行为模拟,去伪存真,再结合动态行为分析,让恶意软件无处遁形。