请选择 进入手机版 | 继续访问电脑版

DecoyMini 技术交流社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 1712|回复: 1

[技术前沿] [原创] PE 文件结构剖析之 NT 头

[复制链接]

5

主题

0

回帖

0

荣誉

Rank: 1

UID
1625
积分
22
精华
0
沃币
3 枚
注册时间
2024-3-8
发表于 2024-3-12 15:25:53 | 显示全部楼层 |阅读模式
紧跟 Rich Header 后面就是 NT 头了,在说 NT 头之前,先说下 RVA 这个概念,RVA 意为相对虚拟地址,这个相对是相对于文件载入到内存后的基地址,后续提到某个字段的 RVA,表示这个字段在内存中相对于载入的基地址偏移大小,可计算出这个字段的虚拟地址为载入的内存基地址加上 RVA。NT 头结构包含 3 个字段:

  1. // 32 位
  2. typedef struct _IMAGE_NT_HEADERS {
  3.     DWORD Signature;                            // [0x00] PE 标识
  4.     IMAGE_FILE_HEADER FileHeader;               // [0x04] 文件头
  5.     IMAGE_OPTIONAL_HEADER32 OptionalHeader;     // [0x18] 可选头
  6. } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

  7. // 64 位
  8. typedef struct _IMAGE_NT_HEADERS64 {
  9.     DWORD Signature;                            // [0x00] PE 标识
  10.     IMAGE_FILE_HEADER FileHeader;               // [0x04] 文件头
  11.     IMAGE_OPTIONAL_HEADER64 OptionalHeader;     // [0x18] 可选头
  12. } IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
复制代码

文件布局 (32 位),后面的解析以 32 位讲解



一、Signature


Signature 字段是一个 DWORD 型字段,占用 4 个字节,为固定值 0x50450000 (PE00),用 IMAGE_NT_SIGNATURE 宏表示,可用于进一步判断是否为 PE 文件,在这里先初步用 C++ 简单实现读取并判断:

  1. #include <iostream>
  2. #include <Windows.h>

  3. int main(void)
  4. {
  5.         // 1.以只读方式打开一个 PE 文件
  6.         HANDLE hFile = CreateFile(
  7.                 TEXT("D:\\test"),
  8.                 GENERIC_READ,
  9.                 0,
  10.                 NULL,
  11.                 OPEN_EXISTING,
  12.                 FILE_ATTRIBUTE_NORMAL,
  13.                 NULL
  14.         );

  15.         // 2.判断文件句柄是否有效,若无效提示打开文件失败并退出
  16.         if (INVALID_HANDLE_VALUE == hFile)
  17.         {
  18.                 std::cout << "Open file failed!" << std::endl;
  19.                 exit(EXIT_SUCCESS);
  20.         }

  21.         // 3.获取 PE 文件大小
  22.         DWORD dwFileSize = GetFileSize(hFile, NULL);
  23.        
  24.         // 4.申请一块 PE 文件大小的缓冲区
  25.         BYTE* pFileBuffer = new BYTE[dwFileSize];

  26.         // 此变量仅做接收用
  27.         DWORD dwReadFile = 0;
  28.         // 5.将文件内容读到申请的缓冲区中
  29.         if (!ReadFile(hFile, pFileBuffer, dwFileSize, &dwReadFile, NULL))
  30.         {
  31.                 std::cout << "Read file failed!" << std::endl;
  32.                 CloseHandle(hFile);
  33.                 delete[] pFileBuffer;
  34.                 pFileBuffer = NULL;
  35.                 exit(EXIT_SUCCESS);
  36.         }

  37.         // 6.判断这个文件是不是一个有效的 PE 文件
  38.         // 6.1 先检查 DOS 头中的 MZ 标志,判断 e_magic 字段是否为 0x5A4D (IMAGE_DOS_HEADER)
  39.         DWORD dwFileAddr = (DWORD)pFileBuffer;
  40.         PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwFileAddr;
  41.         if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
  42.         {
  43.                 std::cout << "This is a invalid PE file." << std::endl;
  44.                 CloseHandle(hFile);
  45.                 delete[] pFileBuffer;
  46.                 pFileBuffer = NULL;
  47.                 exit(EXIT_SUCCESS);
  48.         }
  49.         // 6.2 若通过的话再获取 NT 头所在的位置,并判断 e_lfanew 字段是否为 0x50450000 (IMAGE_NT_SIGNATURE)
  50.         PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(dwFileAddr + pDosHeader->e_lfanew);
  51.         if (pNtHeader->Signature != IMAGE_NT_SIGNATURE)
  52.         {
  53.                 std::cout << "This is a invalid PE file." << std::endl;
  54.                 CloseHandle(hFile);
  55.                 delete[] pFileBuffer;
  56.                 pFileBuffer = NULL;
  57.                 exit(EXIT_SUCCESS);
  58.         }

  59.         // 7.若以上都通过则为一个有效的 PE 文件
  60.         std::cout << "This is a valid PE file." << std::endl;

  61.         delete[] pFileBuffer;
  62.         pFileBuffer = NULL;

  63.         return 0;
  64. }
复制代码

运行后如下:



二、文件头


第 2 个字段为文件头,是一个结构体,包含 7 个字段,后面只说相对重要字段:

  1. typedef struct _IMAGE_FILE_HEADER {
  2.     WORD    Machine;                    //[0x04] 1)运行平台
  3.     WORD    NumberOfSections;           //[0x06] 2)区段的数量
  4.     DWORD   TimeDateStamp;              //[0x08] 3)文件的创建时间
  5.     DWORD   PointerToSymbolTable;       //[0x0C] 4)符号表指针 (0,已弃用)
  6.     DWORD   NumberOfSymbols;            //[0x10] 5)符号的数量 (0,已弃用)
  7.     WORD    SizeOfOptionalHeader;       //[0x14] 6)扩展头大小
  8.     WORD    Characteristics;            //[0x16] 7)文件属性
  9. } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
复制代码

IMAGE_FILE_HEADER 结构体包含了 PE 文件的概览信息。Machine 字段表示此 PE 文件可以运行在哪种结构类型的 CPU 上,在给出的示例中为 0x14C,表示的是 Intel386,用 IMAGE_FILE_MACHINE_I386 宏表示,关于其它值可参考下面这张图 (具体类型可参看文档 https://learn.microsoft.com/zh-c ... ormat#machine-types):



NumberOfSections 字段表示的区段的数量,此示例为 5,表示有 5 个区段 (区段也称为节):



SizeOfOptionalHeader 字段表示扩展头的大小,一般为固定值,32 位为 0x00E0,64 位为 0x00F0。

TimeDateStamp 字段表示文件创建的时间,此示例为 0x65E7FA12,需要将其转换为友好可读形式:

  1. // 8.获取文件头
  2. IMAGE_FILE_HEADER FileHeader = pNtHeader->FileHeader;
  3. std::cout << "run target:0x" << std::hex << FileHeader.Machine << std::endl;
  4. std::cout << "section number:0x" << std::hex << FileHeader.NumberOfSections << std::endl;
  5. // 进行时间转换
  6. tm FileCreateTime;
  7. errno_t ret = gmtime_s(&FileCreateTime, (time_t*)&FileHeader.TimeDateStamp);
  8. if (ret == 0)
  9. {
  10.         std::cout << "file createtime:" << std::dec << FileCreateTime.tm_year + 1900 << "-"
  11.                 << FileCreateTime.tm_mon + 1 << "-"
  12.                 << FileCreateTime.tm_mday << " "
  13.                 << FileCreateTime.tm_hour + 8 << ":"
  14.                 << FileCreateTime.tm_min << ":"
  15.                 << FileCreateTime.tm_sec << std::endl;
  16. }
  17. std::cout << "file Characteristics: 0x" << std::hex << FileHeader.Characteristics << std::endl;
复制代码

运行结果 (参照 DIE):



Characteristics 字段表示 PE 文件的属性,一般情况下,普通 exe 为 0x010F,DLL 文件为 0x0210,可参考如下图:



在此示例中为 0x102,可看作是 0x0100 | 0x0002 组合成的值,意为此示例为 32 位且可执行的。具体更加详细说明参考:https://learn.microsoft.com/zh-c ... mat#characteristics

三、可选头


第 3 个字段为可选头 IMAGE_OPTIONAL_HEADER,但很多人称它为扩展头,意为对 PE 属性的一个扩展说明,但个人觉得叫可选头也没错,因为某些文件类型没有它,比如 obj 文件,但此头 是 NT 头里面最重要的头,PE 加载器查找该头提供的特定信息,以便能够加载和运行可执行文件。

有很多人有一个疑问,为什么文件头里有一个可选头大小字段说明 (IMAGE_FILE_HEADER.SizeOfOptionalHeader),原因也很简单,就是可选头的大小不固定。可选头有两个版本,一个 32 位的,一个 64 位的,这两种版本有两个不同之处:

  • 结构体本身的大小,IMAGE_OPTIONAL_HEADER32 有 31 个成员,而 IMAGE_OPTIONAL_HEADER64 只有 30 个成员,少掉的一个字段为 32 位版本中的 BaseOfData;
  • 部分成员的数据类型:以下 5 个字段在 32 位中定义为 DWORD ,在 64 位中定义为 ULONGLONG (ImageBase、SizeOfStackReserve、SizeOfStackCommit、SizeOfHeapReserve、SizeOfHeapCommit);

结构如下:

  1. // 32 位
  2. typedef struct _IMAGE_OPTIONAL_HEADER {
  3.     // 标准域
  4.     WORD    Magic;                              //[0x18] 1) 标志位
  5.     BYTE    MajorLinkerVersion;                 //[0x1A] 2) 链接器主版本号
  6.     BYTE    MinorLinkerVersion;                 //[0x1B] 3) 链接器子版本号
  7.     DWORD   SizeOfCode;                         //[0x1C] 4) 所有代码段 (注:本段代码里的 "段" 应为 "区段",简写为 "段" 是为了保证格式的整齐) 的总大小
  8.     DWORD   SizeOfInitializedData;              //[0x20] 5) 所有已初始化段总大小
  9.     DWORD   SizeOfUninitializedData;            //[0x24] 6) 所有未初始化段总大小
  10.     DWORD   AddressOfEntryPoint;                //[0x28] 7) 程序执行入口 RVA
  11.     DWORD   BaseOfCode;                         //[0x2C] 8) 代码段起始 RVA
  12.     DWORD   BaseOfData;                         //[0x30] 9) 数据段起始 RVA
  13.    
  14.     // NT 附加域
  15.     DWORD   ImageBase;                          //[0x34] 10) 程序默认载入基地址
  16.     DWORD   SectionAlignment;                   //[0x38] 11) 内存中的段对齐值
  17.     DWORD   FileAlignment;                      //[0x3C] 12) 文件中的段对齐值
  18.     WORD    MajorOperatingSystemVersion;        //[0x40] 13) 系统主版本号
  19.     WORD    MinorOperatingSystemVersion;        //[0x42] 14) 系统子版本号
  20.     WORD    MajorImageVersion;                  //[0x44] 15) 自定义的主版本号
  21.     WORD    MinorImageVersion;                  //[0x46] 16) 自定义的子版本号
  22.     WORD    MajorSubsystemVersion;              //[0x48] 17) 所需子系统主版本号
  23.     WORD    MinorSubsystemVersion;              //[0x4A] 18) 所需子系统子版本号
  24.     DWORD   Win32VersionValue;                  //[0x4C] 19) 保留,通常为 0x00
  25.     DWORD   SizeOfImage;                        //[0x50] 20) 内存中映像总尺寸
  26.     DWORD   SizeOfHeaders;                      //[0x54] 21) 各个文件头的总尺寸
  27.     DWORD   CheckSum;                           //[0x58] 22) 映像文件校验和
  28.     WORD    Subsystem;                          //[0x5C] 23) 文件子系统
  29.     WORD    DllCharacteristics;                 //[0x5E] 24) DLL 标志位
  30.     DWORD   SizeOfStackReserve;                 //[0x60] 25) 初始化栈大小
  31.     DWORD   SizeOfStackCommit;                  //[0x64] 26) 初始化实际提交栈大小
  32.     DWORD   SizeOfHeapReserve;                  //[0x68] 27) 初始化保留栈大小
  33.     DWORD   SizeOfHeapCommit;                   //[0x6C] 28) 初始化实际保留栈大小
  34.     DWORD   LoaderFlags;                        //[0x70] 29) 调试相关,默认 0x00
  35.     DWORD   NumberOfRvaAndSizes;                //[0x74] 30) 数据目录表的数量
  36.     IMAGE_DATA_DIRECTORY DataDirectory[0x10];   //[0x78] 31) 数据目录表
  37. } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

  38. // 64 位
  39. typedef struct _IMAGE_OPTIONAL_HEADER64 {
  40.     // 标准域
  41.     WORD    Magic;                              //[0x18] 1) 标志位
  42.     BYTE    MajorLinkerVersion;                 //[0x1A] 2) 链接器主版本号
  43.     BYTE    MinorLinkerVersion;                 //[0x1B] 3) 链接器子版本号
  44.     DWORD   SizeOfCode;                         //[0x1C] 4) 所有代码段 (注:本段代码里的 "段" 应为 "区段",简写为 "段" 是为了保证格式的整齐) 的总大小
  45.     DWORD   SizeOfInitializedData;              //[0x20] 5) 所有已初始化段总大小
  46.     DWORD   SizeOfUninitializedData;            //[0x24] 6) 所有未初始化段总大小
  47.     DWORD   AddressOfEntryPoint;                //[0x28] 7) 程序执行入口 RVA
  48.     DWORD   BaseOfCode;                         //[0x2C] 8) 代码段起始 RVA
  49.    
  50.     // DWORD   BaseOfData;                         //[0x30] 9) 数据段起始 RVA
  51.    
  52.     // NT 附加域
  53.     ULONGLONG   ImageBase;                      //[0x34] 10) 程序默认载入基地址
  54.     DWORD   SectionAlignment;                   //[0x38] 11) 内存中的段对齐值
  55.     DWORD   FileAlignment;                      //[0x3C] 12) 文件中的段对齐值
  56.     WORD    MajorOperatingSystemVersion;        //[0x40] 13) 系统主版本号
  57.     WORD    MinorOperatingSystemVersion;        //[0x42] 14) 系统子版本号
  58.     WORD    MajorImageVersion;                  //[0x44] 15) 自定义的主版本号
  59.     WORD    MinorImageVersion;                  //[0x46] 16) 自定义的子版本号
  60.     WORD    MajorSubsystemVersion;              //[0x48] 17) 所需子系统主版本号
  61.     WORD    MinorSubsystemVersion;              //[0x4A] 18) 所需子系统子版本号
  62.     DWORD   Win32VersionValue;                  //[0x4C] 19) 保留,通常为 0x00
  63.     DWORD   SizeOfImage;                        //[0x50] 20) 内存中映像总尺寸
  64.     DWORD   SizeOfHeaders;                      //[0x54] 21) 各个文件头的总尺寸
  65.     DWORD   CheckSum;                           //[0x58] 22) 映像文件校验和
  66.     WORD    Subsystem;                          //[0x5C] 23) 文件子系统
  67.     WORD    DllCharacteristics;                 //[0x5E] 24) DLL 标志位
  68.     ULONGLONG   SizeOfStackReserve;             //[0x60] 25) 初始化栈大小
  69.     ULONGLONG   SizeOfStackCommit;              //[0x68] 26) 初始化实际提交栈大小
  70.     ULONGLONG   SizeOfHeapReserve;              //[0x70] 27) 初始化保留栈大小
  71.     ULONGLONG   SizeOfHeapCommit;               //[0x78] 28) 初始化实际保留栈大小
  72.     DWORD   LoaderFlags;                        //[0x80] 29) 调试相关,默认 0x00
  73.     DWORD   NumberOfRvaAndSizes;                //[0x84] 30) 数据目录表的数量
  74.     IMAGE_DATA_DIRECTORY DataDirectory[0x10];   //[0x88] 31) 数据目录表
  75. } IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
复制代码

同样只提下相对比较重要的字段,Magic 字段表示文件的类型,普通可执行镜像为 0x010B,ROM 镜像为  0x0170,PE32+ 可执行文件为 0x20B,这个字段决定了可执行文件是 32 位的还是 64 位的,Windows 加载器将忽略 IMAGE_FILE_HEADER.Machine。

SizeOfCode 字段表示所有 IMAGE_SCN_CNT_CODE 属性的区段总大小,此大小在计算时按照磁盘扇区字节数的整数倍计算。

SizeOfInitializedData 字段保存初始化数据 (.data) 大小,如果有多个部分,则保存所有初始化数据部分的总和。

SizeOfUninitializedData 字段保存未初始数据 (.bss) 大小,如果有多个部分,则保存所有未初始化数据部分的总和。

AddressOfEntryPoint 字段表示程序执行入口的 RVA,在大多数可执行文件中,入口点并不指向 main、winmain 和 dllmain 等函数的入口,而是指向运行库代码,再由其调用这些函数。对于程序映像,此相对地址指向起始地址,对于设备驱动程序,它指向初始化函数。对于 DLL,入口点是可选的,如果没有入口点,则 AddressOfEntryPoint 字段设置为 0。

BaseOfCode 字段表示代码段起始 RVA



BaseOfData 字段 (仅 32 位) 表示数据段的起始 RVA,这个值在 64 位文件中是无效的。



ImageBase 字段表示文件在内存中的首选装入的基地址 (默认为 0x400000),加载器将试图在此地址载入,如果载入成功,则跳过基址重定位步骤,如果此地址被占用,则会重新在正确对齐的合法地址中选择一个作为实际载入地址,由于 ASLR 等内存保护 (VS 中默认开启,ASLR 需要操作系统支持及程序本身支持,两者满足才能生效,xp 系统不支持 ASLR) 以及许多其他原因该字段指定的地址几乎从未被使用过,在这种情况下,PE 加载器会选择一个未使用的内存范围来加载映像,将映像加载到该地址后加载器进入一个称为重定位的过程,有一个特殊的部分保存有关如果需要重定位则需要修复的位置的信息,该部分称为重定位表 (.reloc)。

ASLR (Address space layout randomization,地址空间布局随机化) 是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。据研究表明 ASLR 可以有效的降低缓冲区溢出攻击的成功率,如今 Linux、FreeBSD、Windows 等主流操作系统都已采用了该技术。

把文件载入到调试器中,发现载入的首地址为 0x6B5C68,开头第一个不是 4,这个就是 ASLR 在起作用



如果没有 ASLR,载入的首地址可以计算出 ImageBase + AddressOfEntryPoint = 0x400000 + 0x5C68 = 0x405C68,而不是 0x6B5C68



在 ASLR 存在的情况下,每次重启操作系统后 (必须重启,载入首地址才会发生变化) 将文件载入调试器查看,载入首地址会变化,细心的你有没有发现其中规律,载入的基地址一般为几十万整 H,比如此示例为 0x6B0000,低 2 个字节全为 0,而 AddressOfEntryPoint 经常低 2 个字节有值,高 2 个字为 0,这样首地址的低 2 个字节始终不会变 (这里能玩出骚操作来,具体怎么玩,看你的能力了)。那如何去掉 ASLR 呢,有两种方法 (这里只从文件角度出发,通过修改注册表来关闭系统 ASLR 就不说了),一种是在有源码情况下只需要将随机基址选项设置为否即可:



另一种是没有源码情况下,即修改他人编写的程序,通过修改文件头中 Characteristics 字段中 IMAGE_FILE_RELOCS_STRIPPED 值为 1 即可 (stripped 意为剥离)



或者修改可选头中 DllCharacteristics 字段中 IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 的值为 0



在关掉 ASLR 后,重新载入到调试器中查看,发现载入的首地址变为 0x405C68 了



ImageBase 可谓是比较重要的字段,花了这么大的篇幅,接下来说下其它字段吧。SectionAlignment 字段表示内存对齐粒度,默认为 0x1000;FileAlignment 字段表示文件对齐粒度,默认为 0x200。

SizeOfImage 字段表示文件装入内存后的总大小 (从 ImageBase 到最后一个区段的总大小),为 SectionAligment 的倍数,因为将映像加载到内存时会使用该值。



0x1000 + 0x1C000 + 0xA000 + 0x2000 + 0x1000 = 0x2A000

这还可以通过区段结构中 VirtualSize 计算,不过这个字段并未做对齐处理,按 0x1000 对齐后处理后也是能计算的出。



SizeOfHeader 字段表示 MS-DOS 头、PE 头、区块表的大小之和,为 FileAlignment 的倍数。



CheckSum 字段表示文件的校验和 (在一些 exe 文件中此值可为 0,但在一些内核模式的驱动与系统 DLL 中此值必须为一个有效值)。

Subsystem 字段表示可执行文件所期望的子系统值,这个值只对 exe 文件是重要的。



DllCharacteristics 字段定义可执行映像文件的一些特征,例如是否兼容 NX 以及是否可以在运行时重定位。我不知道为什么它被命名为 DllCharacteristics ,它存在于正常的可执行映像文件中,并且定义了可应用于正常可执行文件的特征,文档说明 https://learn.microsoft.com/zh-c ... dll-characteristics

SizeOfStackReserve、SizeOfStackCommit、SizeOfHeapReserve 和 SizeOfHeapCommit 这些字段指定要保留的堆栈的大小、要提交的堆栈的大小,分别是要保留的本地堆空间的大小和要提交的本地堆空间的大小。

NumberOfRvaAndSizes 字段表示目录成员的数量,一般为 0x00000010 (最后一个目录成员值为 0)。

DataDirectory 字段表示 IMAGE_DATA_DIRECTORY 结构的数组,结构如下:

  1. typedef struct _IMAGE_DATA_DIRECTORY {
  2.     DWORD   VirtualAddress; // 数据块的起始RVA地址
  3.     DWORD   Size;   // 数据块的长度
  4. } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
复制代码

这是一个非常简单的结构,只有两个字段,第一个为数据目录起始 RVA,第二个为数据目录的大小。

什么是数据目录,数据目录是位于 PE 文件的一个部分中的一段数据。数据目录包含加载程序所需的有用信息,一个非常重要的目录例子是导入表目录,它包含从其它库导入的外部函数的列表,后面会专门来讨论。

请注意,并非所有数据目录都具有相同的结构,IMAGE_DATA_DIRECTORY.VirualAddress 指向数据目录,但是该目录的类型决定了如何解析该数据块。

以下为 winnt.h 中定义的数据目录表:

  1. #define IMAGE_DIRECTORY_ENTRY_EXPORT          0x0   //[0x78] 1)
  2. #define IMAGE_DIRECTORY_ENTRY_IMPORT          0x1   //[0x80] 2)
  3. #define IMAGE_DIRECTORY_ENTRY_RESOURCE        0x2   //[0x88] 3)
  4. #define IMAGE_DIRECTORY_ENTRY_EXCEPTION       0x3   //[0x90] 4)
  5. #define IMAGE_DIRECTORY_ENTRY_SECURITY        0x4   //[0x98] 5)
  6. #define IMAGE_DIRECTORY_ENTRY_BASERELOC       0x5   //[0xA0] 6)
  7. #define IMAGE_DIRECTORY_ENTRY_DEBUG           0x6   //[0xA8] 7)
  8. #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    0x7   //[0xB0] 8)
  9. #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       0x8   //[0xB8] 9)
  10. #define IMAGE_DIRECTORY_ENTRY_TLS             0x9   //[0xC0] 10)
  11. #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG     0xA   //[0xC8] 11)
  12. #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT    0xB   //[0xD0] 12)
  13. #define IMAGE_DIRECTORY_ENTRY_IAT             0xC   //[0xD8] 13)
  14. #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT    0xD   //[0xE0] 14)
  15. #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR  0xE   //[0xE8] 15)
复制代码

后面会专门来讨论数据目录表这个问题。

  1. // 9.可选头信息
  2. IMAGE_OPTIONAL_HEADER OptionalHeader = pNtHeader->OptionalHeader;
  3. std::cout << "\nOption Header" << std::endl;
  4. std::cout << "filetype: 0x" << std::hex << OptionalHeader.Magic << std::endl;
  5. // ...
  6. // 这里不就一一列出来了
  7. // 主要列下最后一个字段目录信息
  8. // 需加头文件 #include <iomanip>
  9. // 才能调用 setfill 和 setw
  10. std::cout << "DataDirectory\nRVA\t\tSize" << std::endl;
  11. for (int i = 0; i < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; ++i)
  12. {
  13.         std::cout << "0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.DataDirectory[i].VirtualAddress << "\t";
  14.         std::cout << "0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.DataDirectory[i].Size << std::endl;
  15. }
复制代码

运行后



对照一下,值没有显示的都为 0



最后来回顾下 NT 头的总体结构 (32 位),我画了一张图,方便大家脑海里有个大概样子:



最后一张高清图:


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

评分

参与人数 1金钱 +10 贡献 +4 沃币 +2 收起 理由
吉沃运营专员 + 10 + 4 + 2

查看全部评分

177

主题

34

回帖

30

荣誉

Rank: 9Rank: 9Rank: 9

UID
2
积分
343
精华
1
沃币
2 枚
注册时间
2021-6-24

论坛管理

发表于 2024-3-13 10:01:21 | 显示全部楼层
感谢楼主无私奉献
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼

Archiver|小黑屋|DecoyMini 技术交流社区 ( 京ICP备2021005070号 )

GMT+8, 2024-6-23 00:39 , Processed in 0.067200 second(s), 24 queries .

Powered by Discuz! X3.4

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表