首页
社区
课程
招聘
[原创]从Windows文件执行流程中来解析PE头
发表于: 2025-9-24 11:02 546

[原创]从Windows文件执行流程中来解析PE头

2025-9-24 11:02
546

文件验证

MZ签名检查,检查文件开头两字节是否为4D 5A,失败则报错“Not a valid Win32 application”。

定位NT头,读取e_lfanew值(IMAGE_DOS_HEADER -> e_lfanew)并检验地址有效性,失败则报错“Invalid PE header offset”。

检验NT头签名(IMAGE_NT_HEADERS -> Signature)是否为00 00 45 50,失败则报错“Invalid PE signature”。

检验CPU架构,验证Machine值(IMAGE_NT_HEADERS -> IMAGE_FILE_HEADER -> Machine)是否和CPU架构兼容,失败则报错“Unsupported machine type”。

检验可选头大小,验证SizeOfOptionalHeader(IMAGE_NT_HEADERS -> IMAGE_FILE_HEADER -> SizeOfOptionalHeader)是否与IMAGE_OPTIONAL_HEADER长度匹配,失败则报错“Invalid optional header size”。

检验文件头魔术字,验证Magic值(IMAGE_NT_HEADERS -> IMAGE_OPTIONAL_HEADER -> Magic)有效性,失败则报错“Invalid PE format”。

检验入口点有效性,检查AddressOfEntryPoint(IMAGE_NT_HEADERS -> IMAGE_OPTIONAL_HEADER -> AddressOfEntryPoint是否落在任一节的VirtualAddress 范围内,失败则报错“Invalid entry point”。

检验内存对齐粒度,确认 SectionAlignment ≥ FileAlignment(IMAGE_NT_HEADERS -> IMAGE_OPTIONAL_HEADER -> SectionAlignment/FileAlignment),失败则报错“Invalid alignment values”。

校验映像大小,计算 SizeOfImage(IMAGE_NT_HEADERS -> IMAGE_OPTIONAL_HEADER -> SizeOfImage)是否 ≥ 所有头 + 节的内存总和,失败则报错“Corrupt image size”。

 

内存映射

遍历节表,按NumberOfSections(IMAGE_NT_HEADERS -> IMAGE_FILE_HEADER -> NumberOfSections)循环处理每个IMAGE_SECTION_HEADER,处理中检验PointerToRawData(IMAGE_SECTION_HEADER -> PointerToRawData)是否超出文件大小和Characteristics(IMAGE_SECTION_HEADER -> Characteristics)权限是否合法(如可执行节不可写),失败则报错 “Invalid section table”。

分配虚拟内存,调用VirtualAlloc(Windows API中用于操作虚拟内存的核心函数)按ImageBase(IMAGE_NT_HEADERS -> IMAGE_OPTIONAL_HEADER -> ImageBase)和SizeOfImage(IMAGE_NT_HEADERS -> IMAGE_OPTIONAL_HEADER -> SizeOfImage)保留地址空间,失败则报错“Memory allocation failed”。

映射头部数据,复制 DOS头 + NT头 + 节表到 ImageBase。

加载节数据,按PointerToRawData和SizeOfRawData(IMAGE_SECTION_HEADER -> PointerToRawData/SizeOfRawData)将文件内容写入VirtualAddress(IMAGE_SECTION_HEADER -> VirtualAddress)对应内存。

设置内存保护,根据Characteristics(IMAGE_SECTION_HEADER -> Characteristics)调用VirtualProtect(Windows API 中用于动态修改内存页保护属性的关键函数)。

 

动态链接处理

解析导入表,从 DataDirectory[1]定位IMAGE_IMPORT_DESCRIPTOR数组关键数据(IMAGE_OPTIONAL_HEADER -> IMAGE_DATA_DIRECTORY[1] -> VirtualAddress -> IMAGE_IMPORT_DESCRIPTOR -> Name/OriginalFirstThunk/FirstThunk),对每个DLL:按 Name 字段加载依赖库(LoadLibrary),遍历 OriginalFirstThunk 解析函数名/序号,填充 FirstThunk 指向的 IAT。

应用重定位(ASLR),若实际加载地址 ≠ ImageBase则从 DataDirectory[5] 读取 IMAGE_BASE_RELOCATION(IMAGE_OPTIONAL_HEADER -> IMAGE_DATA_DIRECTORY[5] -> VirtualAddress -> IMAGE_BASE_RELOCATION),按重定位项修正代码/数据中的绝对地址。

 

执行准备

初始化线程局部存储(TLS),若存在 DataDirectory[9],调用 TLS 回调函数

调用入口点,跳转至 ImageBase + AddressOfEntryPoint,是EXE → 进入 mainCRTStartup,是DLL → 调用 DllMain(若存在)。


这个执行流程主要就是可以了解文件结构一些关键字段的使用。比如加壳,一些壳会对文件头部进行扭曲,也就是说,某一些值是调试器严重依赖而执行器并不依赖的。文件无法被调试,但是又能成功执行,这就是因为那些执行中需要被用到的值并没有被改变,这里列出的执行过程中遇到的一些值就是执行中需要依赖的值,理论上来说,除这些值以外修改后不影响文件执行,不过实际情况好像要复杂一些。

这个是我修改的一个Helloworld程序的头部(突然感觉能利用这个在程序里加一点彩蛋啥的),里面修改了一些执行中不需要依赖的值,所以尝试运行完全不会有问题,可以用工具扫描一下对比看看:

第一张是正常的程序,第二张是修改过的程序,虽然不足以干扰到调试器,但是其实可以看到有一些区别了。


一点个人学习经验,欢迎交流、指正。


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 2
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回