【翻译】Abusing undocumented features to spoof PE section headers
引言
在一次调试无关项目时,我偶然发现了 PE 文件的有趣行为。当将 NT 头中的 SectionAlignment
值设置为小于页面大小(4096)时,内存映射方式会发生显著变化。加载器不再通过解析节区表来构建内存镜像,而是直接将整个文件(包括头部)以读写执行(RWX)权限映射到内存中——所有的节区头都被完全忽略。
这种特性使得我们可以创建不含任何节区但仍能执行自身代码的 PE 可执行文件。由于默认存在写权限,这些代码甚至可以实现自修改功能。
这种模式的潜在滥用场景包括创建伪造的节区表。表面上看是包含读写/只读数据段的正常 PE 模块,但当实际执行时,看似具有 NX(不可执行)属性的数据段会获得执行权限。
尽管我确信该技术可能已被前人发现(甚至滥用),但尚未找到任何公开文档记载。MSDN 确实简要提及 SectionAlignment
值可以小于页面大小,但未进一步说明其安全影响。
Windows 内核机制分析
通过分析内核函数 MiCreateImageFileMap
可以发现:当 SectionAlignment
值小于 0x1000 时,系统会在内存映射前设置未公开的标志位(0x200000):
if(v29->SectionAlignment < 0x1000)
{
if((SectionFlags & 0x80000) != 0)
{
v17 = 0xC000007B;
MiLogCreateImageFileMapFailure(v36, v39, *(unsigned int *)(v29 + 64), DWORD1(v99));
ImageFailureReason = 55;
goto LABEL_81;
}
if(!MiLegacyImageArchitecture((unsigned __int16)v99))
{
v17 = 0xC000007B;
ImageFailureReason = 56;
goto LABEL_81;
}
SectionFlags |= 0x200000;
}
v40 = MiBuildImageControlArea(a3, v38, v29, (unsigned int)&v99, SectionFlags, (__int64)&FileSize, (__int64)&v93);
当检测到该标志位时,MiBuildImageControlArea 会将整个文件视为单个节区(section)进行处理:
if((SectionFlags & 0x200000) != 0)
{
SectionCount = 1;
}
else
{
SectionCount = a4->NumberOfSections + 1;
}
v12 = MiAllocatePool(64, 8 * (7 * SectionCount + (((unsigned __int64)(unsigned int)MiFlags >> 13) & 1)) + 184, (SectionFlags & 0x200000) != 0 ? 0x61436D4D : 0x69436D4D);
最终,原始镜像会被完整映射到内存中,所有页表项(PTE)均被赋予 MM_EXECUTE_READWRITE
(可读可写可执行)权限。如前所述,此时 IMAGE_SECTION_HEADER
节区表会被完全忽略,这意味着采用此模式的 PE 模块可以将 NumberOfSections
(节区数量)值设为 0。该模式下的 PE 模块也没有明显的尺寸限制——加载器会根据 SizeOfImage
(镜像大小)字段分配内存并复制文件内容,超出文件大小的内存区域将保持空白。
演示案例 #1 - 无节区的可执行 PE 文件
该技术最简单的实现方式是创建适用于位置无关代码(Position-Independent Code)的通用 "loader"。笔者通过手工构造了以下测试用头部结构:
// (64-bit EXE headers)
BYTE bHeaders64[328] =
{
0x4D, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x64, 0x86, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xF0, 0x00, 0x22, 0x00, 0x0B, 0x02, 0x0E, 0x1D, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x10, 0x00, 0x48, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x60, 0x81, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// (code goes here)
};
BYTE bHeaders32[304] =
{
0x4D, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x4C, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xE0, 0x00, 0x02, 0x01, 0x0B, 0x01, 0x0E, 0x1D, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x10, 0x00, 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x40, 0x81, 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00,
0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
// (code goes here)
};
这些 PE 头结构包含以下特殊配置:SectionAlignment
值为 0x200(而非标准的 0x1000),SizeOfImage
值为 0x100000(1MB),空白节表(section table),以及紧接头部的入口点(entry-point)。除上述关键参数外,其余字段均保持常规配置:
(DOS Header)
e_magic : 0x5A4D
...
e_lfanew : 0x40
(NT Header)
Signature : 0x4550
Machine : 0x8664
NumberOfSections : 0x0
TimeDateStamp : 0x0
PointerToSymbolTable : 0x0
NumberOfSymbols : 0x0
SizeOfOptionalHeader : 0xF0
Characteristics : 0x22
Magic : 0x20B
MajorLinkerVersion : 0xE
MinorLinkerVersion : 0x1D
SizeOfCode : 0x0
SizeOfInitializedData : 0x0
SizeOfUninitializedData : 0x0
AddressOfEntryPoint : 0x148
BaseOfCode : 0x0
ImageBase : 0x140000000
SectionAlignment : 0x200
FileAlignment : 0x200
MajorOperatingSystemVersion : 0x6
MinorOperatingSystemVersion : 0x0
MajorImageVersion : 0x0
MinorImageVersion : 0x0
MajorSubsystemVersion : 0x6
MinorSubsystemVersion : 0x0
Win32VersionValue : 0x0
SizeOfImage : 0x100000
SizeOfHeaders : 0x148
CheckSum : 0x0
Subsystem : 0x2
DllCharacteristics : 0x8160
SizeOfStackReserve : 0x100000
SizeOfStackCommit : 0x1000
SizeOfHeapReserve : 0x100000
SizeOfHeapCommit : 0x1000
LoaderFlags : 0x0
NumberOfRvaAndSizes : 0x10
DataDirectory[0] : 0x0, 0x0
...
DataDirectory[15] : 0x0, 0x0
(Start of code)
出于演示目的,我们将使用调用 MessageBoxA
的位置无关代码 (Position-Independent Code, PIC)。由于基础头文件缺少导入表 (import table),该代码必须手动定位并加载所有依赖项——本例中为 user32.dll。该载荷 (payload) 可同时适用于 32 位和 64 位环境:
BYTE bMessageBox[939] =
{
0x8B, 0xC4, 0x6A, 0x00, 0x2B, 0xC4, 0x59, 0x83, 0xF8, 0x08, 0x0F, 0x84,
0xA0, 0x01, 0x00, 0x00, 0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x3C, 0x64, 0xA1,
0x30, 0x00, 0x00, 0x00, 0x33, 0xD2, 0x53, 0x56, 0x57, 0x8B, 0x40, 0x0C,
0x33, 0xDB, 0x21, 0x5D, 0xF0, 0x21, 0x5D, 0xEC, 0x8B, 0x40, 0x1C, 0x8B,
0x00, 0x8B, 0x78, 0x08, 0x8B, 0x47, 0x3C, 0x8B, 0x44, 0x38, 0x78, 0x03,
0xC7, 0x8B, 0x48, 0x24, 0x03, 0xCF, 0x89, 0x4D, 0xE8, 0x8B, 0x48, 0x20,
0x03, 0xCF, 0x89, 0x4D, 0xE4, 0x8B, 0x48, 0x1C, 0x03, 0xCF, 0x89, 0x4D,
0xF4, 0x8B, 0x48, 0x14, 0x89, 0x4D, 0xFC, 0x85, 0xC9, 0x74, 0x5F, 0x8B,
0x70, 0x18, 0x8B, 0xC1, 0x89, 0x75, 0xF8, 0x33, 0xC9, 0x85, 0xF6, 0x74,
0x4C, 0x8B, 0x45, 0xE8, 0x0F, 0xB7, 0x04, 0x48, 0x3B, 0xC2, 0x74, 0x07,
0x41, 0x3B, 0xCE, 0x72, 0xF0, 0xEB, 0x37, 0x8B, 0x45, 0xE4, 0x8B, 0x0C,
0x88, 0x03, 0xCF, 0x74, 0x2D, 0x8A, 0x01, 0xBE, 0x05, 0x15, 0x00, 0x00,
0x84, 0xC0, 0x74, 0x1F, 0x6B, 0xF6, 0x21, 0x0F, 0xBE, 0xC0, 0x03, 0xF0,
0x41, 0x8A, 0x01, 0x84, 0xC0, 0x75, 0xF1, 0x81, 0xFE, 0xFB, 0xF0, 0xBF,
0x5F, 0x75, 0x74, 0x8B, 0x45, 0xF4, 0x8B, 0x1C, 0x90, 0x03, 0xDF, 0x8B,
0x75, 0xF8, 0x8B, 0x45, 0xFC, 0x42, 0x3B, 0xD0, 0x72, 0xA9, 0x8D, 0x45,
0xC4, 0xC7, 0x45, 0xC4, 0x75, 0x73, 0x65, 0x72, 0x50, 0x66, 0xC7, 0x45,
0xC8, 0x33, 0x32, 0xC6, 0x45, 0xCA, 0x00, 0xFF, 0xD3, 0x8B, 0xF8, 0x33,
0xD2, 0x8B, 0x4F, 0x3C, 0x8B, 0x4C, 0x39, 0x78, 0x03, 0xCF, 0x8B, 0x41,
0x20, 0x8B, 0x71, 0x24, 0x03, 0xC7, 0x8B, 0x59, 0x14, 0x03, 0xF7, 0x89,
0x45, 0xE4, 0x8B, 0x41, 0x1C, 0x03, 0xC7, 0x89, 0x75, 0xF8, 0x89, 0x45,
0xE8, 0x89, 0x5D, 0xFC, 0x85, 0xDB, 0x74, 0x7D, 0x8B, 0x59, 0x18, 0x8B,
0x45, 0xFC, 0x33, 0xC9, 0x85, 0xDB, 0x74, 0x6C, 0x0F, 0xB7, 0x04, 0x4E,
0x3B, 0xC2, 0x74, 0x22, 0x41, 0x3B, 0xCB, 0x72, 0xF3, 0xEB, 0x5A, 0x81,
0xFE, 0x6D, 0x07, 0xAF, 0x60, 0x8B, 0x75, 0xF8, 0x75, 0x8C, 0x8B, 0x45,
0xF4, 0x8B, 0x04, 0x90, 0x03, 0xC7, 0x89, 0x45, 0xEC, 0xE9, 0x7C, 0xFF,
0xFF, 0xFF, 0x8B, 0x45, 0xE4, 0x8B, 0x0C, 0x88, 0x03, 0xCF, 0x74, 0x35,
0x8A, 0x01, 0xBE, 0x05, 0x15, 0x00, 0x00, 0x84, 0xC0, 0x74, 0x27, 0x6B,
0xF6, 0x21, 0x0F, 0xBE, 0xC0, 0x03, 0xF0, 0x41, 0x8A, 0x01, 0x84, 0xC0,
0x75, 0xF1, 0x81, 0xFE, 0xB4, 0x14, 0x4F, 0x38, 0x8B, 0x75, 0xF8, 0x75,
0x10, 0x8B, 0x45, 0xE8, 0x8B, 0x04, 0x90, 0x03, 0xC7, 0x89, 0x45, 0xF0,
0xEB, 0x03, 0x8B, 0x75, 0xF8, 0x8B, 0x45, 0xFC, 0x42, 0x3B, 0xD0, 0x72,
0x89, 0x33, 0xC9, 0xC7, 0x45, 0xC4, 0x54, 0x65, 0x73, 0x74, 0x51, 0x8D,
0x45, 0xC4, 0x88, 0x4D, 0xC8, 0x50, 0x50, 0x51, 0xFF, 0x55, 0xF0, 0x6A,
0x7B, 0x6A, 0xFF, 0xFF, 0x55, 0xEC, 0x5F, 0x5E, 0x5B, 0xC9, 0xC3, 0x90,
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
0x48, 0x89, 0x5C, 0x24, 0x08, 0x48, 0x89, 0x6C, 0x24, 0x10, 0x48, 0x89,
0x74, 0x24, 0x18, 0x48, 0x89, 0x7C, 0x24, 0x20, 0x41, 0x54, 0x41, 0x56,
0x41, 0x57, 0x48, 0x83, 0xEC, 0x40, 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60,
0x00, 0x00, 0x00, 0x33, 0xFF, 0x45, 0x33, 0xFF, 0x45, 0x33, 0xE4, 0x45,
0x33, 0xC9, 0x48, 0x8B, 0x48, 0x18, 0x48, 0x8B, 0x41, 0x30, 0x48, 0x8B,
0x08, 0x48, 0x8B, 0x59, 0x10, 0x48, 0x63, 0x43, 0x3C, 0x8B, 0x8C, 0x18,
0x88, 0x00, 0x00, 0x00, 0x48, 0x03, 0xCB, 0x8B, 0x69, 0x24, 0x44, 0x8B,
0x71, 0x20, 0x48, 0x03, 0xEB, 0x44, 0x8B, 0x59, 0x1C, 0x4C, 0x03, 0xF3,
0x8B, 0x71, 0x14, 0x4C, 0x03, 0xDB, 0x85, 0xF6, 0x0F, 0x84, 0x80, 0x00,
0x00, 0x00, 0x44, 0x8B, 0x51, 0x18, 0x33, 0xC9, 0x45, 0x85, 0xD2, 0x74,
0x69, 0x48, 0x8B, 0xD5, 0x0F, 0x1F, 0x40, 0x00, 0x0F, 0xB7, 0x02, 0x41,
0x3B, 0xC1, 0x74, 0x0D, 0xFF, 0xC1, 0x48, 0x83, 0xC2, 0x02, 0x41, 0x3B,
0xCA, 0x72, 0xED, 0xEB, 0x4D, 0x45, 0x8B, 0x04, 0x8E, 0x4C, 0x03, 0xC3,
0x74, 0x44, 0x41, 0x0F, 0xB6, 0x00, 0x33, 0xD2, 0xB9, 0x05, 0x15, 0x00,
0x00, 0x84, 0xC0, 0x74, 0x35, 0x0F, 0x1F, 0x00, 0x6B, 0xC9, 0x21, 0x8D,
0x52, 0x01, 0x0F, 0xBE, 0xC0, 0x03, 0xC8, 0x42, 0x0F, 0xB6, 0x04, 0x02,
0x84, 0xC0, 0x75, 0xEC, 0x81, 0xF9, 0xFB, 0xF0, 0xBF, 0x5F, 0x75, 0x08,
0x41, 0x8B, 0x3B, 0x48, 0x03, 0xFB, 0xEB, 0x0E, 0x81, 0xF9, 0x6D, 0x07,
0xAF, 0x60, 0x75, 0x06, 0x45, 0x8B, 0x23, 0x4C, 0x03, 0xE3, 0x41, 0xFF,
0xC1, 0x49, 0x83, 0xC3, 0x04, 0x44, 0x3B, 0xCE, 0x72, 0x84, 0x48, 0x8D,
0x4C, 0x24, 0x20, 0xC7, 0x44, 0x24, 0x20, 0x75, 0x73, 0x65, 0x72, 0x66,
0xC7, 0x44, 0x24, 0x24, 0x33, 0x32, 0x44, 0x88, 0x7C, 0x24, 0x26, 0xFF,
0xD7, 0x45, 0x33, 0xC9, 0x48, 0x8B, 0xD8, 0x48, 0x63, 0x48, 0x3C, 0x8B,
0x94, 0x01, 0x88, 0x00, 0x00, 0x00, 0x48, 0x03, 0xD0, 0x8B, 0x7A, 0x24,
0x8B, 0x6A, 0x20, 0x48, 0x03, 0xF8, 0x44, 0x8B, 0x5A, 0x1C, 0x48, 0x03,
0xE8, 0x8B, 0x72, 0x14, 0x4C, 0x03, 0xD8, 0x85, 0xF6, 0x74, 0x77, 0x44,
0x8B, 0x52, 0x18, 0x0F, 0x1F, 0x44, 0x00, 0x00, 0x33, 0xC0, 0x45, 0x85,
0xD2, 0x74, 0x5B, 0x48, 0x8B, 0xD7, 0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00,
0x0F, 0xB7, 0x0A, 0x41, 0x3B, 0xC9, 0x74, 0x0D, 0xFF, 0xC0, 0x48, 0x83,
0xC2, 0x02, 0x41, 0x3B, 0xC2, 0x72, 0xED, 0xEB, 0x3D, 0x44, 0x8B, 0x44,
0x85, 0x00, 0x4C, 0x03, 0xC3, 0x74, 0x33, 0x41, 0x0F, 0xB6, 0x00, 0x33,
0xD2, 0xB9, 0x05, 0x15, 0x00, 0x00, 0x84, 0xC0, 0x74, 0x24, 0x66, 0x90,
0x6B, 0xC9, 0x21, 0x8D, 0x52, 0x01, 0x0F, 0xBE, 0xC0, 0x03, 0xC8, 0x42,
0x0F, 0xB6, 0x04, 0x02, 0x84, 0xC0, 0x75, 0xEC, 0x81, 0xF9, 0xB4, 0x14,
0x4F, 0x38, 0x75, 0x06, 0x45, 0x8B, 0x3B, 0x4C, 0x03, 0xFB, 0x41, 0xFF,
0xC1, 0x49, 0x83, 0xC3, 0x04, 0x44, 0x3B, 0xCE, 0x72, 0x92, 0x45, 0x33,
0xC9, 0xC7, 0x44, 0x24, 0x20, 0x54, 0x65, 0x73, 0x74, 0x4C, 0x8D, 0x44,
0x24, 0x20, 0xC6, 0x44, 0x24, 0x24, 0x00, 0x48, 0x8D, 0x54, 0x24, 0x20,
0x33, 0xC9, 0x41, 0xFF, 0xD7, 0xBA, 0x7B, 0x00, 0x00, 0x00, 0x48, 0xC7,
0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0x41, 0xFF, 0xD4, 0x48, 0x8B, 0x5C, 0x24,
0x60, 0x48, 0x8B, 0x6C, 0x24, 0x68, 0x48, 0x8B, 0x74, 0x24, 0x70, 0x48,
0x8B, 0x7C, 0x24, 0x78, 0x48, 0x83, 0xC4, 0x40, 0x41, 0x5F, 0x41, 0x5E,
0x41, 0x5C, 0xC3
};
需要特别说明的是,多位读者曾询问这个同时兼容 32 位和 64 位模式(此前用于其他项目)的示例代码是如何实现的。其原理非常简单:首先存储原始栈指针(stack pointer)值,然后将一个值压入栈(push),最后比较新旧栈指针的差值。若差值为 8 则执行 64 位代码(64-bit code),否则执行 32 位代码(32-bit code)。虽然存在更高效的实现方式,但当前方法已足够满足演示需求:
mov eax, esp ; store stack ptr
push 0 ; push a value onto the stack
subeax, esp; calculate difference
pop ecx ; restore stack
cmp eax, 8 ; check if the difference is 8
je 64bit_code
32bit_code:
xxxx
64bit_code:
xxxx
通过将载荷附加到原始 PE 头(Portable Executable headers)后,我们可以生成完整可用的 EXE 文件。提供的 PE 头中硬编码了 SizeOfImage
值为 0x100000,允许最大载荷容量接近 1MB,必要时可进行扩容。运行该程序仍能正常显示消息框,尽管其 PE 头中完全不存在任何可执行区段(executable sections),甚至没有任何区段定义:
演示案例二 - 含伪造区段的可执行 PE 文件
更值得关注的是,我们还可以利用该模式创建伪造的区段表(section table)。笔者构建了另一个 EXE 文件,其格式与先前样本类似,但包含单个只读区段(read-only section):
主载荷存储在此只读区段内,入口点(entry-point)已更新为 0x1000。正常情况下,由于尝试执行只读内存,程序本应立即触发访问违规异常(access-violation exception)。但实际运行中,目标内存区域具有 RWX(读 - 写 - 执行)权限,载荷得以成功执行:
技术说明
上述概念验证(POC)方法将载荷附加在 NT 头(NT headers)末端,但该技术同样支持在头结构内部嵌入可执行代码。当 AddressOfEntryPoint
值小于 SizeOfHeaders
值时模块会加载失败,但由于 SizeOfHeaders
值未严格校验,该限制可轻易绕过。甚至可将其设为 0,使入口点可定位在文件任意位置。
该特性最初可能旨在支持超小型镜像(small images),使头结构、代码和数据能容纳在单个内存页(memory page)中。由于内存保护以页为单位实施,当虚拟区段(virtual section)尺寸小于页大小时,为所有页表项(PTEs)应用 RWX 权限具有合理性——若多个区段共存于同一页内,将无法正确管理内存保护。
经测试,这些 EXE 文件在 Windows Vista 至 Windows 10 等各版本系统均可正常运行。尽管该技术无法欺骗现代反汇编工具,实际应用价值有限,但其技术原理仍具有研究价值。
原文始发于微信公众号(securitainment):利用未公开特性伪造 PE 节区头
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论