首页 > 系统相关 >Windows编程系列:PE文件结构

Windows编程系列:PE文件结构

时间:2024-05-13 16:07:58浏览次数:16  
标签:WORD 字节 Windows 编程 PE DOS NULL hFile

最近在参考OpenShell为任务栏设置图片背景时,发现里面使用了IAT Hook,这一块没有接触过,去查资料的时候发现IAT Hook需要对PE文件结构有一定的了解,索性将PE文件结构的资料找出来,系统学习一下。

 

PE文件结构

Portable Executable (PE),可移植的可执行文件。在Windows平台下,所有的可执行文件(包括.exe, .dll, .sys, .ocx, .com等)均使用PE文件结构。这些使用了PE文件结构的可执行文件也称为PE文件。

 

PE结构包含的结构体有DOS头,PE标识 、文件头、可选头、目录头、目录结构、节表等。

整体结构如下

 

从上图可以看出PE结构分为4大部分,其中每个部分又进行了细分。

从数据管理的角度来看,可以把PE文件大致分为两部分,

1、DOS头、PE头和节表属于PE文件的数据管理结构或数据组织结构部分,

2、节表数据才是PE文件真正的数据部分,其中包含着代码、数据、资源等内容。

 

DOS头

DOS头分为“MZ头部”和"DOS存根“。

”MZ头部“是真正的DOS头部,由于其开始处的两个字节为"MZ",因此DOS头也可以叫作MZ头部。

这个我们用十六进制编辑器随便打开一个exe就可以看到

该部分用于程序在DOS系统下加载,它的结构被定义为IMAGE_DOS_HEADER

 

IMAGE_DOS_HEADER定义如下所示:

 1 //大小为: 0x40(64)字节
 2  #define IMAGE_DOS_SIGNATURE                 0x5A4D      // MZ
 3  
 4 typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
 5     WORD   e_magic;                     // MZ标记 0x5a4d 
 6     WORD   e_cblp;                      // 最后(部分)页中的字节数
 7     WORD   e_cp;                        // 文件中的全部和部分页数
 8     WORD   e_crlc;                      // 重定位表中的指针数
 9     WORD   e_cparhdr;                   // 头部尺寸以段落为单位
10     WORD   e_minalloc;                  // 所需的最小附加段
11     WORD   e_maxalloc;                  // 所需的最大附加段
12     WORD   e_ss;                        // 初始的SS值(相对偏移量)
13     WORD   e_sp;                        // 初始的SP值
14     WORD   e_csum;                      // 补码校验值
15     WORD   e_ip;                        // 初始的IP值
16     WORD   e_cs;                        // 初始的SS值
17     WORD   e_lfarlc;                    // 重定位表的字节偏移量
18     WORD   e_ovno;                      // 覆盖号
19     WORD   e_res[4];                    // 保留字
20     WORD   e_oemid;                     // OEM标识符(相对m_oeminfo)
21     WORD   e_oeminfo;                   // OEM信息
22     WORD   e_res2[10];                  // 保留字
23     LONG   e_lfanew;                    // NT头(PE标记)相对于文件的偏移地址
24   } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

 

DOS存根是一段简单的程序,主要用于输出“This program cannot be run in DOS mode.”类似的提示字符串。

 

为什么PE结构的最开始位置有这样一段DOS头部呢?

为了该可执行程序可以兼容DOS系统。通常情况下,Win32下的PE程序不能在DOS下运行,因此保留了这样一个简单的DOS程序用于提示“不能运行于DOS模式下”。

 

DOS头部IMAGE_DOS_HEADER详解

IMAGE_DOS_HEADER的定义在前面我们列出来了,该结构体中需要掌握的字段 只有两个分别是第一个字段 e_magic和最后一个字段e_lfanew。

e_magic:DOS可执行文件的标识,占用2字节,该位置保存着字符是“MZ",该标识符在Winnt.h头文件中有一个宏定义,如下所示:

1 #define IMAGE_DOS_SIGNATURE 0x5A4D

 

我们创建一个简单的控制台程序

1 #include <iostream>
2 
3 int main()
4 {
5     std::cout << "Hello World!\n";
6 }

 

使用16进制编辑器(我这里用的是ImHex,使用个人习惯的软件即可)打开编译出来的二进制文件(.exe)。

可以看到在0x00000000的位置保存着2字节的内容0x5A4D(ASCII的MZ)这里使用的是小尾(小端)方式存储,即高位保存高字节,低位保存低字节,所以上图中写的是4D 5A,这也是适合阅读顺序。

 

说明:

  • 大端模式(Big-Endian)。在内存中,多字节数据类型的高位字节存储在低地址处,而低位字节存储在高地址处。这种模式与我们阅读数字的方式相似,即先读高位,后读低位。

  • 小端模式(Little-Endian)。在内存中,多字节数据的低位字节存储在低地址处,而高位字节存储在高地址处。这种模式与我们阅读数字的方式相反,即先读低位,后读高位。

如0x0102这样一个数据,

使用大端方式存储,存储方式为:01 02

使用小端方式存储,存储方式为:02 01

 

我们可以看到下面这样一段程序

 1 #include <iostream>
 2 #include<Windows.h>
 3 #include<winnt.h>
 4 
 5 int main()
 6 {
 7     
 8     HANDLE hFile = CreateFile(L"a.bin", GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
 9 
10     BYTE buffer[4] = { 1,2,3,4 };  //写入 1 2 3 4 4个字节
11 
12     DWORD dwHexValue = 0x5D4A; //写入0x5D4A(实际存储是4A 5D)
13     DWORD dwDecValue = 1234;   //写入1234  (0x04D2 实际存储D2 04)
14 
15     LPOVERLAPPED lv{};
16     if (hFile)
17     {
18         WriteFile(hFile, buffer, 4, NULL, NULL);
19         WriteFile(hFile, (PVOID)&dwHexValue, 4, NULL, NULL);
20         WriteFile(hFile, (PVOID)&dwDecValue, 4, NULL, NULL);
21         CloseHandle(hFile);
22     }
23 }

用十六进制编辑器打开可以看到

为什么是这种情况,因为对于 Microsoft Visual C++ 的目标平台(x86、x64、ARM、ARM64),所有本机标量类型都是小字节序。

-------------------------

好的,让我们继续回归主题,在0x0000003C位置处,也就是IMAGE_DOS_HEADER的e_lfanew字段,该字段保存着PE头部的起始位置。

e_lfanew字段是LONG类型,所以这里是4个字节,F8 00 00 00,因为是使用的小端字节序,所以我们可以在0x000000F8位置,看到50 45 00 00,与之对应的ASCII字符为”PE\0\0“,这里就是PE头部开始的位置。

 

IMAGE_DOS_HEADER(e_lfanew字段之后)到"PE\0\0"之间的内容就是DOS存档,可以将该部分删除,然后将PE头部整体向前移动,也可以将一些配置数据保存在此处等。

我们将这里全部填充为0,程序也是可以正常执行的。

 

我写了下面这样一段测试程序:

 1 #include <iostream>
 2 #include<Windows.h>
 3 #include<winnt.h>
 4 
 5 int main()
 6 {
 7     
 8     HANDLE hFile = CreateFile(L"exepath", GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
 9 
10     if (hFile)
11     {
12         SetFilePointer(hFile, 0x00000040, NULL, FILE_BEGIN);
13 
14         BYTE buffer[8] = { 1,2,3,4,5,6,7,8 };
15         WriteFile(hFile, buffer, 8, NULL, NULL);
16 
17         CloseHandle(hFile);
18     }    
19 }

在e_lfanew字段之后写入了8个字节的数据,程序也是可以照常执行的。

 

 

参考资料:

pe format

https://learn.microsoft.com/en-us/windows/win32/debug/pe-format

endian

https://learn.microsoft.com/en-us/cpp/standard-library/bit-enum?view=msvc-170

标签:WORD,字节,Windows,编程,PE,DOS,NULL,hFile
From: https://www.cnblogs.com/zhaotianff/p/18186676

相关文章

  • c++ true_type与false_type
    std::true_type和std::false_type实际上是类型别名是两个类型(类模板)注意区分true_type与false_type与true和false区别true_type,false_type代表类型true,false代表值nmsp1::FalseTypemyfunc1();//返回假这种含义nmsp1::TrueTypemyfunc2();//返回真这种含......
  • 使用python在windows系统操作快捷方式
    其实问题是由上一篇文章(https://www.cnblogs.com/anpengapple/p/18179353)的结尾引出来的。不需要了解背景的话,我现在需要做的是,右键打开桌面上的chrome快捷方式的属性,在目标的后面增加一个参数。我不想傻傻地手动添加,想交给程序来处理。 首先需要简单来说一下,windows的快捷方式......
  • Laravel Model中的$appends
    protected$appends是Laravel模型中的一个属性,用于指定哪些虚拟属性(Accessor)应该被包含在模型的数组或JSON表示中。虚拟属性是在模型中定义的,通过使用Accessors和Mutators来访问和修改模型属性的值。这些虚拟属性不会存储在数据库中,但可以通过模型实例进行访问和操作......
  • spring之AOP(面向切面编程)
    什么是AOP?AOP(AspectOrientedProgramming)意为:面向切面编程,体现了横切的思想,意思是在添加某项功能的时候,是以切面插入的方式实现的,对原有的代码不会产生改变。通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP在spring中的作用:在不改变原有代码的情况......
  • OSS_PIPE:Rust编写的大规模文件迁移工具| 京东云技术团队
    文盘rust好久没有更新了。这段时间笔者用rust写了个小东西,跟各位分享一下背景随着业务的发展,文件数量和文件大小会急剧增加,文件迁移的数量和难度不断攀升。oss_pipe是rust编写的文件迁移工具,旨在支撑大规模的文件迁移场景。编写oss_pipe的初衷•同类产品面临的问题•rust......
  • openGauss 开启RemoveIPC引起的core问题
    开启RemoveIPC引起的core问题问题现象操作系统配置中RemoveIPC参数设置为yes,数据库运行过程中出现宕机,并显示如下日志消息。FATAL:semctl(1463124609,3,SETVAL,0)failed:Invalidargument原因分析当RemoveIPC参数设置为yes时,操作系统会在对应用户退出时删除IPC资源(共......
  • openGauss 例行维护表
    例行维护表为了保证数据库的有效运行,数据库必须在插入/删除操作后,基于客户场景,定期做VACUUMFULL和ANALYZE,更新统计信息,以便获得更优的性能。相关概念使用VACUUM、VACUUMFULL和ANALYZE命令定期对每个表进行维护,主要有以下原因:VACUUMFULL可回收已更新或已删除的数据所占据的......
  • openGauss 例行维护
    例行维护检查时间一致性检查应用连接数例行维护表例行重建索引数据安全维护建议为保证openGauss数据库中的数据安全,避免丢失数据,非法访问数据等事故发生,请仔细阅读以下内容。慢SQL诊断......
  • openGauss 快速设置
    快速设置首先在postgresql.conf中设置配置选项:wal_level=logical对于一个基础设置来说,其他所需的设置使用默认值就足够了。需要调整pg_hba.conf以允许复制(这里的值取决于实际的网络配置以及用于连接的用户):hostallrepuser0.0.0.0/0sha256然后在发布......
  • openGauss 逻辑复制
    逻辑复制逻辑解码使用逻辑复制工具复制数据发布订阅详情查看:https://opengauss.org详情查看:https://docs-opengauss.osinfra.cn......