利用未公开特性伪造PE节区头部的技术分析

本文详细分析了通过修改PE文件SectionAlignment值触发Windows加载器特殊行为的漏洞利用技术,可实现无节区或伪造节区的可执行文件加载,并提供了32/64位通用shellcode的实践示例。

滥用未公开特性伪造PE节区头部

引言

在调试其他项目时,我偶然发现PE文件的特殊行为:当NT头中的SectionAlignment值小于页面大小(4096)时,镜像的内存映射方式会出现显著差异。加载器不会按常规方式解析节区表构造内存镜像,而是将整个文件(包括头部)以RWX权限映射到内存中 - 完全忽略单独的节区头部。

这种特性使得我们可以创建不含任何节区但仍能执行自身代码的PE可执行文件。由于默认具有写权限,这些代码甚至可以实现自我修改。

Windows内核机制

在MiCreateImageFileMap函数中可见PE头解析逻辑:当SectionAlignment小于0x1000时,系统会在映射镜像前设置未公开标志位(0x200000):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
if(v29->SectionAlignment < 0x1000)
{
    if((SectionFlags & 0x80000) != 0)
    {
        v17 = 0xC000007B;
        MiLogCreateImageFileMapFailure(v36, v39, *(unsigned int *)(v29 + 64), DWORD1(v99));
        ImageFailureReason = 55;
        goto LABEL_81;
    }
    SectionFlags |= 0x200000;
}

当该标志置位时,MiBuildImageControlArea将整个文件视为单个节区:

1
2
3
4
if((SectionFlags & 0x200000) != 0)
{
    SectionCount = 1;
}

技术验证

示例1:无节区可执行PE

通过手工构造的PE头部演示该技术(关键字段):

1
2
3
4
5
6
7
8
(DOS Header)
   e_magic: 0x5A4D
   e_lfanew: 0x40
(NT Header)
   SectionAlignment: 0x200  // 关键值
   SizeOfImage: 0x100000
   NumberOfSections: 0x0    // 无节区
   Characteristics: 0x22

附加的位置无关代码通过动态加载user32.dll调用MessageBoxA,该payload同时兼容32/64位环境:

1
2
3
4
5
6
mov eax, esp   ; 架构检测机制
push 0
sub eax, esp
pop ecx
cmp eax, 8
je 64bit_code

示例2:带伪造节区的可执行PE

更有趣的是可以创建虚假的只读节区。虽然节区标记为只读,但实际内存仍具有RWX权限:

1
2
3
(伪造的节区头部)
   VirtualSize: 0x1000
   Characteristics: 0x40000040 // 只读标志

技术细节

  1. 有效载荷可以嵌入NT头部内部(SizeOfHeaders值可设为0)
  2. 从Vista到Win10所有版本均受影响
  3. 现代反汇编工具已能识别此类文件
  4. 可能最初设计用于超小镜像(整镜像小于内存页)

示例EXE文件下载

特别说明:该技术通过比较栈指针变化检测运行架构,虽非最优但足够用于概念验证。实际攻击中可采用更精细的检测方法。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计