PE文件结构解析 Part3 NT Headers

文章来源:https://0xrick.github.io/win-internals/pe4/

简介

在前面的文章中,我们看过了DOS Header的结构以及逆向了DOS stub。
这篇文章我们准备讨论一下PE文件结构中NT Header的部分。
在我们进入正题之前,我们需要讲一讲等下我们会用到很多次的一个重要的概念,相对虚拟地址(Relative Virtual Address)或者RVA。 RVA(相对虚拟地址)就是相对于EXE在内存中起始地址的一个偏移(相对于Image Base)。也就是说,将相对虚拟地址Relative Virtual Address转化为绝对虚拟地址,我们需要将RVA的值加上ImageBase的值。接下来我们会看到,PE很多地方都会用到RVA。

NT Headers(IMAGE_NT_HEADERS)

NT Headers是一个定义在winnt.h的结构体IMAGE_NT_HEADERS,观察它的定义,我们可以看到它有三个成员(DWORD类型的签名,IMAGE_FILE_HEADER类型的FileHeader,以及IMAGE_OPTIONAL_HEADER类型的OptionalHeader)。

值得一提的是,这个结构体有两个版本的定义。一个用于32bit,IMAGE_NT_HEADERS 。 一个用于64bit,IMAGE_NT_HEADERS64

typedef struct _IMAGE_NT_HEADERS64 {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

签名 Signature

NT Headers结构体的第一个成员是PE签名,它是DWORD类型,意味着它需要4字节的空间。
这个字段的值总是固定为0x50450000,用ASCII码表示为PE\0\0
下面是来自PE-Bear的截图。

File Header(IMAGE_FILE_HEADER)

也被称作"COFF File Header", File Header这个结构体持有PE文件的一些信息。
它在winnt.h中定义为IAMGE_FILE_HEADER, 下面是它的定义:

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

这是一个有着7个成员的结构体:

  • Machine:用于表示可执行文件的目标机器,类型是WORD。这个字段可以有很多值,但是我们只对其中两个感兴趣,0x8864 for AMD64 and 0x14c for i386. 想要了解全部可能的值,可以访问微软官方文档
  • NumberOfSections:这个字段存储了sections的个数。(也可以说是section header的个数)
  • TimeDateStamp: 表示文件被创建时的unix时间戳。
  • PointerToSymbolTableNumberOfSymbols: 这两个字段保存了 到COFF符号表的偏移以及符号表中有几个对象。它们也可能被设置为0,意味着没有符号表,这是因为COFF调试信息被丢弃了。
  • SizeOfOptionalHeader:Optional Header的大小。
  • Characteristics:用于表示文件属性的flag。这些属性可以是文件能否被执行,是否是系统文件,以及很多其他信息。详情可以访问微软官方文档

下面是一个真实的PE文件的PE Header的截图。

Optional Header (IMAGE_OPTIONAL_HEADER)

Optional Header是NT headers中最重要的,PE加载器会查找这个header中提供的特定的信息来加载以及执行EXE文件。
它被称为可选头部信息是因为有些文件类型 像是obj文件不需要,但是这个header对于镜像文件image file很重要。
Optional Header没有固定的大小,所以会存在 IMAGE_FILE_HEADER.SizeOfOptionalHeader

Optional Header前8个成员对于COFF文件格式来说是必须实现的标准,header剩余的部分是微软对标准定义的一个扩展,结构体中扩展部分的成员会被用于Windows的PE加载器以及链接器。

正如之前提到的,Optional Header 有两个版本,一个用于32bit的Exe,一个用于64bit。这两个版本有以下两方面的区别:

  • 结构体本身的大小(或者说结构体中定义的成员的数量): IMAGE_OPTIONAL_HEADER32有31个成员,而IMAGE_OPTIONAL_HEADER64只有30个成员, 32bit版本多出的成员为DWORD类型的BaseOfData,存储了data section起始位置的Relative Virtual Address.
  • 一些成员的数据类型:下面5个成员在32bit版本中是DWORD,在64bit版本中是ULONGLONG:
    • ImageBase
    • SizeOfStackReserve
    • SizeOfStackCommit
    • SizeOfHeapReserve
    • SizeOfHeapCommit

我们来看一下两个结构体的定义:

typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

    //
    // NT additional fields.
    //

    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

每个字段的详细解释可以看对应的文档

让我们看一下实际PE文件中Optional Header的内容。

我们可以讨论一下其中的一些字段,首先是Magic字段,它的值是0x20B意味着这是一个64位的可执行程序。

我们可以看到程序入口点entry point的相对虚拟地址RVA是0x12C4并且代码段Code Section开始于相对虚拟地址0x1000,内存中的段对齐SectionAlignment的大小也是0x1000.

文件中的段对齐File Alignment被设置为0x200,并且我们可以观察任何一个section来验证。

你可以看到,data section的实际内容是从0x22000x2229,然而section剩余部分被0填充直到0x23ff来满足FileAlignment对齐的要求。
SizeOfImage镜像被加载到内存中的大小被设置为 7000 并且 SizeOfHeaders被设置为400,两个各自都是SectionAlignmentFileAlignment的倍数。

Subsystem字段被设置为3,表示这是一个Windows控制台程序。

DataDirectory下面会讲。

总结

本篇文章到此结束,至此我们看了NT Headers结构,详细讨论了File Header和Optional Header。
下一篇文章我们会看一下Data Directories, Section Headers, 以及sections。

PE文件结构解析 Part2 DOS Header, DOS Stub and Rich Header
PE文件结构解析 Part4 Data Directories, Sections

posted @   dewxin  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示