PE文件(二)

    xiaoxiao2026-09-30  10

    上篇讲到的PE文件DOS头中的两个重要成员,一个是e_magic 用来判段是否为PE文件,另一个 e_lfanew用来找出NT头的位置。

    在DOS头与NT头 之间还有一部分区域,这里储存着一些被DOS头使用的数据,包括一些字符串等等,这部分的大小不太确定,所以,NT头的具体位置要由DOS头的最后一个成员来确定 。 我们可以利用这一部分空间实现一些特殊用途。

    NT头的定义

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

    其中第一个成员我们已经使用过了 可以用来判断是否为PE文件,在系统中定义为

    #define IMAGE_NT_SIGNATURE 0x00004550 // PE00

    另外两个成员都是结构体,里面存储的信息非常有用,解析这两个结构体才可以说是解析PE文件的开始。 NT头的第二个成员是文件头 这个文件头储存着关于这个PE文件的信息

    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;

    Machine: 标识这个文件可以运行在哪一个运行平台,并没有什么卵用, NumberOfSections: 是区段的个数,也就是PE文件的主体被分为多少个部分 TimeDateStamp :表示文件是何时被创建的 PointerToSymbolTable: 符号表偏移,据说已经没什么用0.0 NumberOfSymbols: 符号个数,也没什么用 SizeOfOptionalHeader:扩展头的大小,32位和64位是不一样的 Characteristics:PE文件属性值,这个属性很重要,具体的值可以查询

    如果在上篇文章中的代码里加上

    PIMAGE_FILE_HEADER pFile = &pNt->FileHeader;

    就可以遍历文件头的信息了。想要显示什么直接用pFile指出来就行。

    NT头的第三个成员扩展头

    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;

    Magic:表示可选头的类型。

    MajorLinkerVersion和MinorLinkerVersion:链接器的版本号。

    SizeOfCode:代码段的长度,如果有多个代码段,则是代码段长度的总和。

    SizeOfInitializedData:初始化的数据长度。

    SizeOfUninitializedData:未初始化的数据长度。

    AddressOfEntryPoint:程序入口的RVA,对于exe这个地址可以理解为WinMain的RVA。对于DLL,这个地址可以理解为DllMain的RVA,如果是驱动程序,可以理解为DriverEntry的RVA。当然,实际上入口点并非是WinMain,DllMain和DriverEntry,在这些函数之前还有一系列初始化要完成,当然,这些不是本文的重点。

    BaseOfCode:代码段起始地址的RVA。

    BaseOfData:数据段起始地址的RVA。

    ImageBase:映象(加载到内存中的PE文件)的基地址,这个基地址是建议,对于DLL来说,如果无法加载到这个地址,系统会自动为其选择地址。

    SectionAlignment:节对齐,PE中的节被加载到内存时会按照这个域指定的值来对齐,比如这个值是0x1000,那么每个节的起始地址的低12位都为0。

    FileAlignment:节在文件中按此值对齐,SectionAlignment必须大于或等于FileAlignment。

    MajorOperatingSystemVersion、MinorOperatingSystemVersion:所需操作系统的版本号,随着操作系统版本越来越多,这个好像不是那么重要了。

    MajorImageVersion、MinorImageVersion:映象的版本号,这个是开发者自己指定的,由连接器填写。

    MajorSubsystemVersion、MinorSubsystemVersion:所需子系统版本号。

    Win32VersionValue:保留,必须为0。

    SizeOfImage:映象的大小,PE文件加载到内存中空间是连续的,这个值指定占用虚拟空间的大小。

    SizeOfHeaders:所有文件头(包括节表)的大小,这个值是以FileAlignment对齐的。

    CheckSum:映象文件的校验和。

    Subsystem:运行该PE文件所需的子系统

    DllCharacteristics:DLL的文件属性

    SizeOfStackReserve:运行时为每个线程栈保留内存的大小。

    SizeOfStackCommit:运行时每个线程栈初始占用内存大小。

    SizeOfHeapReserve:运行时为进程堆保留内存大小。

    SizeOfHeapCommit:运行时进程堆初始占用内存大小。

    LoaderFlags:保留,必须为0。

    NumberOfRvaAndSizes:数据目录的项数,即下面这个数组的项数。

    DataDirectory:数据目录(非常重要)

    下面是数据目录的定义

    typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

    VirtualAddress:是一个RVA。

    Size:是一个大小。

    这两个数一个是地址,一个是大小,可以看出这个数据目录项定义的是一个区域。这个区域包括导入表,导出表等等,根据不同的索引取出来的是不同的结构,这个等后面再讲。 我们可以在上篇的代码中加入

    PIMAGE_OPTIONAL_HEADER pOptional = &pNt->OptionalHeader;

    以此来解析NT头的扩展头,想要显示什么直接用pOptional指出来就行。

    转载请注明原文地址: https://ju.6miu.com/read-1312490.html
    最新回复(0)