2.2 PE结构:文件头详细解析

news2024/10/7 14:31:50

PE结构是Windows系统下最常用的可执行文件格式,理解PE文件格式不仅可以理解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,DOS头是PE文件开头的一个固定长度的结构体,这个结构体的大小为64字节(0x40)。DOS头包含了很多有用的信息,该信息可以让Windows操作系统使用正确的方式加载可执行文件。从DOS文件头IMAGE_DOS_HEADERe_lfanew字段向下偏移003CH的位置,就是真正的PE文件头的位置,该文件头是由IMAGE_NT_HEADERS结构定义的,IMAGE_NT_HEADERS是PE文件格式的一部分,它包含了PE头和可选头的信息,用于描述PE文件的结构和属性。

2.2 DOS文件头详细解析

DOS头是PE文件开头的一个固定长度的结构体,这个结构体的大小为64字节(0x40)。DOS头包含了很多有用的信息,该信息可以让Windows操作系统使用正确的方式加载可执行文件。一个DOS头通常会包含以下一些主要信息:

  • Magic Number: 接下来64字节的文件内容的开始是以MZ(Mark Zbikowski)2个字符(即0x4D, 0x5A)开头,被称为DOS签名。
  • PE头偏移:DOS头中的e_lfanew(这是一个类型为LONG的成员)指示了PE头的偏移量,即PE头的起始位置距离DOS头的偏移量,Windows操作系统根据DOS头的这个属性来定位PE头的位置。
  • DOS头结束标识:保留用于以后增加的内容, 用于确认DOS头的结束,通常被赋值给字节0x0B。

如上图所示,图中的4D5A则表示这是一个PE文件,其下08010000则代表DOS头的最后一个数据集e_lfanew字段,该字段指向了PE头的开始50450000用于表示NT头的其实位置,而途中的英文单词则是一个历史遗留问题,在某些时候可通过删除此标识已让PE文件缩小空间占用,总的来说DOS头是PE文件中的一个重要的标志,它使得Windows操作系统能够在正确的位置开始加载可执行文件。由于DOS头中包含了PE头的偏移位置,Windows操作系统可以很容易地找到PE头,并通过PE头来加载程序并执行。

DOS头结构时PE文件中的重要组成部分,PE文件中的DOS部分由MZ格式的文件头和可执行代码部分组成,可执行代码被称为DOS块(DOS stub),MZ格式的文件头由IMAGE_DOS_HEADER结构定义,在C语言头文件winnt.h中有对这个DOS结构详细定义,如下所示:

typedef struct _IMAGE_DOS_HEADER { 
    WORD   e_magic;                     // DOS的头部
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // 指向了PE文件的开头(重要)
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

在DOS文件头中,第一个字段e_magic被定义为MZ,标志着DOS文件的开头部分,最后一个字段e_lfanew则指明了PE文件的开头位置,现在来说除了第一个字段和最后一个字段有些用处,其他字段几乎已经废弃,当读者通过调用OpenPeFile打开一个PE文件时,则下一步我们需要实现对PE文件有效性及位数的判断,并以此作为参考在后续的解析中使用不同的变量长度。

首先将镜像转换为PIMAGE_DOS_HEADER格式,并通过pDosHead->e_magic属性找到PIMAGE_NT_HEADERS结构,然后判断其是否符合PE文件规范,这里需要注意32位于64位PE结构所使用的的结构定义略有不同,代码中已经对其进行了区分。

BOOL IsPeFile(HANDLE ImageBase, BOOL Is64 = FALSE)
{
    PIMAGE_DOS_HEADER pDosHead = NULL;
    if (ImageBase == NULL)
        return FALSE;

    // 将映射文件转为DOS结构,并判断开头是否为MZ
    pDosHead = (PIMAGE_DOS_HEADER)ImageBase;
    if (IMAGE_DOS_SIGNATURE != pDosHead->e_magic)
        return FALSE;

    if (Is64 == TRUE)
    {
        // 根据 IMAGE_DOS_HEADER 的 e_lfanew 的值得到 64位 NT 头的位置
        PIMAGE_NT_HEADERS64 pNtHead64 = NULL;
        pNtHead64 = (PIMAGE_NT_HEADERS64)((DWORD64)pDosHead + pDosHead->e_lfanew);
        if (pNtHead64->Signature != IMAGE_NT_SIGNATURE)
            return FALSE;
    }
    else if (Is64 == FALSE)
    {
        // 根据 IMAGE_DOS_HEADER 的 e_lfanew 的值得到 32位 NT 头的位置
        PIMAGE_NT_HEADERS pNtHead32 = NULL;
        pNtHead32 = (PIMAGE_NT_HEADERS)((DWORD)pDosHead + pDosHead->e_lfanew);
        if (pNtHead32->Signature != IMAGE_NT_SIGNATURE)
            return FALSE;
    }
    return TRUE;
}

int main(int argc, char * argv[])
{
    BOOL PE = IsPeFile(OpenPeFile("c://pe/x86.exe"), 0);

    if (PE == TRUE)
    {
        printf("程序是标准的PE文件 \n");
    }
    else
    {
        printf("非标准程序 \n");
    }

    system("pause");
    return 0;
}

运行此段代码,则读者可以看到如下图所示的输出结果,程序会首先判断读入文件的pDosHead->e_magic是否为IMAGE_DOS_SIGNATURE用以验证是否为DOS头,接着通过IMAGE_DOS_HEADERe_lfanew值得到NT头部位置,并以此进一步判断是否为PE文件;

接下来则是读入PE文件中DOS头的重点部分,读者通过DosHeader指针,即可依次遍历出IMAGE_DOS_HEADER结构中的所有参数信息,这段代码可以总结为如下案例;

int main(int argc, char * argv[])
{
    BOOL PE = IsPeFile(OpenPeFile("c://pe/x86.exe"), 0);

    if (PE == TRUE)
    {
        printf("\t\t\t 十六进制 \t 十进制 \n");
        printf("DOS标志:                  %08X \t %08d \n", DosHeader->e_magic, DosHeader->e_magic);
        printf("文件最后一页的字节数:     %08X \t %08d \n", DosHeader->e_cblp, DosHeader->e_cblp);
        printf("文件中的页面:             %08X \t %08d \n", DosHeader->e_cp, DosHeader->e_cp);
        printf("重定位:                   %08X \t %08d \n", DosHeader->e_crlc, DosHeader->e_crlc);
        printf("段落中标题的大小:         %08X \t %08d \n", DosHeader->e_cparhdr, DosHeader->e_cparhdr);
        printf("至少需要额外段落:         %08X \t %08d \n", DosHeader->e_minalloc, DosHeader->e_minalloc);
        printf("所需的最大额外段落数:     %08X \t %08d \n", DosHeader->e_maxalloc, DosHeader->e_maxalloc);
        printf("初始(相对)SS值:         %08X \t %08d \n", DosHeader->e_ss, DosHeader->e_ss);
        printf("初始SP值:                 %08X \t %08d \n", DosHeader->e_sp, DosHeader->e_sp);
        printf("校验和:                   %08X \t %08d \n", DosHeader->e_csum, DosHeader->e_csum);
        printf("初始IP值:                 %08X \t %08d \n", DosHeader->e_ip, DosHeader->e_ip);
        printf("初始(相对)CS值:         %08X \t %08d \n", DosHeader->e_cs, DosHeader->e_cs);
        printf("重新定位表的文件地址:     %08X \t %08d \n", DosHeader->e_lfarlc, DosHeader->e_lfarlc);
        printf("叠加编号:                 %08X \t %08d \n", DosHeader->e_ovno, DosHeader->e_ovno);
        printf("保留字:                   %08X \t %08d \n", DosHeader->e_res, DosHeader->e_res);
        printf("OEM标识符                 %08X \t %08d \n", DosHeader->e_oemid, DosHeader->e_oemid);
        printf("OEM信息                   %08X \t %08d \n", DosHeader->e_res2, DosHeader->e_res2);
        printf("PE指针:                   %08X \t %08d \n", DosHeader->e_lfanew, DosHeader->e_lfanew);
    }
    else
    {
        printf("非标准程序 \n");
    }

    system("pause");
    return 0;
}

编译并运行上述代码片段,则读者可看到如下图所示的输出效果,此时DOS头部数据将被全部完整的输出;

2.3 PE文件头详细解析

从DOS文件头IMAGE_DOS_HEADERe_lfanew字段向下偏移003CH的位置,就是真正的PE文件头的位置,该文件头是由IMAGE_NT_HEADERS结构定义的,IMAGE_NT_HEADERS是PE文件格式的一部分,它包含了PE头和可选头的信息,用于描述PE文件的结构和属性。

typedef struct _IMAGE_NT_HEADERS
{
    DWORD Signature;                            // PE文件标识字符
    IMAGE_FILE_HEADER FileHeader;               // 文件头
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;     // 可选头
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

IMAGE_NT_HEADERS由IMAGE_NT_SIGNATURE(标识符)和IMAGE_FILE_HEADER(文件头)组成。其中,IMAGE_NT_SIGNATURE用于标识该文件是否为有效的PE文件,IMAGE_FILE_HEADER则用于描述可执行文件的基本结构信息,包括机器类型、段的数量、时间戳、符号表指针、符号表数量、可选头大小以及文件的各种标志和属性等。

如上_IMAGE_NT_HEADERS文件头的第一个DWORD是一个标志,默认情况下它被定义为00004550h也就是P,E两个字符另外加上两个零,而大部分的文件属性由标志后面的IMAGE_FILE_HEADERIMAGE_OPTIONAL_HEADER32结构来定义。

2.3.1 IMAGE_FILE_HEADER

我们跟进IMAGE_FILE_HEADER这个结构,文件头结构体IMAGE_FILE_HEADERIMAGE_NT_HEADERS结构体中的一个结构体,紧接在PE标识符的后面,IMAGE_FILE_HEADER结构体的大小为20字节,起始位置为0x000000CC结束位置在0x000000DF,这个IMAEG_FILE_HEADER结构体中包含了PE文件的大部分基础信息其结构的定义如下:

#define _IMAGE_FILE_HEADER 20

typedef struct _IMAGE_FILE_HEADER
{
    WORD    Machine;                  // 运行平台
    WORD    NumberOfSections;         // 文件的节数目
    DWORD   TimeDateStamp;            // 文件创建日期和时间
    DWORD   PointerToSymbolTable;     // 指向符号表(用于调试)
    DWORD   NumberOfSymbols;          // 符号表中的符号数量
    WORD    SizeOfOptionalHeader;     // IMAGE_OPTIONAL_HANDLER32结构的长度
    WORD    Characteristics;          // 文件的属性 exe=010fh dll=210eh
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

2.3.2 IMAGE_OPTINAL_HEADER

此外IMAGE_NT_HEADERS还包含了IMAGE_OPTIONAL_HEADER可选头的信息,用于描述PE文件的高级结构信息,包括各种代码段、数据段、栈大小、堆大小、程序入口点、镜像基址等等。

我们继续跟进_IMAGE_NT_HEADERS结构体里面的第二个结构IMAGE_OPTINAL_HEADER,该头结构非常重要要,里面存储着程序的数据目录表,可选头紧挨着文件头,文件头的结束位置在0x000000DF,那么可选头的起始位置为0x000000E0,可选头的大小在文件头中已经给出,其大小为0x00E0字节,其结束位置为0x000000E0 + 0x00E0 – 1 = 0x000001BF,可选头非常容易辨别,只需要找到PE字眼就是了。

可选头是对文件头的一个扩展,文件头主要描述文件的相关信息,而可选头主要用来管理PE文件被操作系统装载时所需要的信息,该头是有32位版本与64位版本之分的,其实IMAGE_OPTIONAL_HEADER是一个宏,定义如下所示;

#define IMAGE_NT_OPTIONAL_HDR32_MAGIC      0x10b
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC      0x20b
#define IMAGE_ROM_OPTIONAL_HDR_MAGIC       0x107

#ifdef _WIN64
typedef IMAGE_OPTIONAL_HEADER64             IMAGE_OPTIONAL_HEADER;
typedef PIMAGE_OPTIONAL_HEADER64            PIMAGE_OPTIONAL_HEADER;
#define IMAGE_NT_OPTIONAL_HDR_MAGIC         IMAGE_NT_OPTIONAL_HDR64_MAGIC
#else
typedef IMAGE_OPTIONAL_HEADER32             IMAGE_OPTIONAL_HEADER;
typedef PIMAGE_OPTIONAL_HEADER32            PIMAGE_OPTIONAL_HEADER;
#define IMAGE_NT_OPTIONAL_HDR_MAGIC         IMAGE_NT_OPTIONAL_HDR32_MAGIC
#endif

32位版本和64位版本的选择是根据是否定义了_WIN64而决定的,这里只讨论其32位的版本,IMAGE_OPTIONAL_HEADER32的定义如下所示;

typedef struct _IMAGE_OPTIONAL_HEADER
{
    WORD    Magic;                        // 0x10b(可执行文件) 0x107(ROM文件)
    BYTE    MajorLinkerVersion;           // 主连接器版本号
    BYTE    MinorLinkerVersion;           // 次连接器版本号
    DWORD   SizeOfCode;                   // 所有包含代码节的总大小
    DWORD   SizeOfInitializedData;        // 所有已初始化数据的节总大小
    DWORD   SizeOfUninitializedData;      // 所有未初始化数据的节总大小
    DWORD   AddressOfEntryPoint;          // 程序执行入口RVA
    DWORD   BaseOfCode;                   // 代码节的起始RVA
    DWORD   BaseOfData;                   // 数据节的起始RVA
    DWORD   ImageBase;                    // 程序镜像基地址
    DWORD   SectionAlignment;             // 内存中节的对其粒度
    DWORD   FileAlignment;                // 文件中节的对其粒度
    WORD    MajorOperatingSystemVersion;  // 要求最低操作系统的主版本号
    WORD    MinorOperatingSystemVersion;  // 要求最低操作系统的次版本号
    WORD    MajorImageVersion;            // 可执行文件的主版本号
    WORD    MinorImageVersion;            // 可执行文件的次版本号
    WORD    MajorSubsystemVersion;        // 可运行于操作系统的最小子版本号
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;            // 该成员变量是被保留的
    DWORD   SizeOfImage;                  // 内存中整个PE映像尺寸
    DWORD   SizeOfHeaders;                // 所有头加节表的大小
    DWORD   CheckSum;                     // 校验和值
    WORD    Subsystem;                    // 可执行文件的子系统类型
    WORD    DllCharacteristics;           // 指定DLL文件的属性,该值大部分时候为0
    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;

从上方结构体定义中可知,最后一个结构属性IMAGE_DATA_DIRECTORY其又指向了数据目录列表,该表由16个相同的IMAGE_DATA_DIRECTORY结构组成,这16个数据目录结构定义很简单,仅仅指出了某种数据的位置和长度,该结构的定义如下;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

typedef struct _IMAGE_DATA_DIRECTORY
{
    DWORD   VirtualAddress;      // 数据起始RVA
    DWORD   Size;                // 数据块的长度
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

有了上方的解析流程,读者应该能理解如何实现分析PE头了,首先读者找到DOS头,并从该头部找到NT头,当读者得到了NT头就可以根据NT头向下分别解析FileHeaderOptionalHeader中的参数,根据参数定义依次输出即可得到所有的NT头部数据,其完整代码如下所示;

int main(int argc, char * argv[])
{
    BOOL PE = IsPeFile(OpenPeFile("c://pe/x86.exe"), 0);

    if (PE == TRUE)
    {
        printf("\t\t\t 十六进制 \t 十进制 \n");
        printf("NT标志:               0x%08X \t %08d \n", NtHeader->Signature, NtHeader->Signature);

        printf("运行平台:             0x%08X \t %08d \n", NtHeader->FileHeader.Machine, NtHeader->FileHeader.Machine);
        printf("区段数目:            0x%08X \t %08d \n", NtHeader->FileHeader.NumberOfSections, NtHeader->FileHeader.NumberOfSections);
        printf("时间日期标志:        0x%08X \t %08d \n", NtHeader->FileHeader.TimeDateStamp, NtHeader->FileHeader.TimeDateStamp);
        printf("特征值:              0x%08X \t %08d \n", NtHeader->FileHeader.Characteristics, NtHeader->FileHeader.Characteristics);
        printf("可选头部大小:        0x%08X \t %08d \n", NtHeader->FileHeader.SizeOfOptionalHeader, NtHeader->FileHeader.SizeOfOptionalHeader);
        printf("文件符号标志:        0x%08X \t %08d \n", NtHeader->FileHeader.NumberOfSymbols, NtHeader->FileHeader.NumberOfSymbols);
        printf("文件符号指针:        0x%08X \t %08d \n", NtHeader->FileHeader.PointerToSymbolTable, NtHeader->FileHeader.PointerToSymbolTable);

        printf("入口点:              0x%08X \t %08d \n", NtHeader->OptionalHeader.AddressOfEntryPoint, NtHeader->OptionalHeader.AddressOfEntryPoint);
        printf("镜像基址:            0x%08X \t %08d \n", NtHeader->OptionalHeader.ImageBase, NtHeader->OptionalHeader.ImageBase);
        printf("镜像大小:            0x%08X \t %08d \n", NtHeader->OptionalHeader.SizeOfImage, NtHeader->OptionalHeader.SizeOfImage);
        printf("代码基址:            0x%08X \t %08d \n", NtHeader->OptionalHeader.BaseOfCode, NtHeader->OptionalHeader.BaseOfCode);
        printf("内存对齐:            0x%08X \t %08d \n", NtHeader->OptionalHeader.SectionAlignment, NtHeader->OptionalHeader.SectionAlignment);
        printf("文件对齐:            0x%08X \t %08d \n", NtHeader->OptionalHeader.FileAlignment, NtHeader->OptionalHeader.FileAlignment);
        printf("子系统:              0x%08X \t %08d \n", NtHeader->OptionalHeader.Subsystem, NtHeader->OptionalHeader.Subsystem);
        printf("首部大小:            0x%08X \t %08d \n", NtHeader->OptionalHeader.SizeOfHeaders, NtHeader->OptionalHeader.SizeOfHeaders);
        printf("校验和:              0x%08X \t %08d \n", NtHeader->OptionalHeader.CheckSum, NtHeader->OptionalHeader.CheckSum);
        printf("RVA 数及大小:        0x%08X \t %08d \n", NtHeader->OptionalHeader.NumberOfRvaAndSizes, NtHeader->OptionalHeader.NumberOfRvaAndSizes);

        printf("主操作系统版本:      0x%08X \t %08d \n", NtHeader->OptionalHeader.MajorOperatingSystemVersion, NtHeader->OptionalHeader.MajorOperatingSystemVersion);
        printf("从操作系统版本:      0x%08X \t %08d \n", NtHeader->OptionalHeader.MinorOperatingSystemVersion, NtHeader->OptionalHeader.MinorOperatingSystemVersion);
        printf("主映像版本:          0x%08X \t %08d \n", NtHeader->OptionalHeader.MajorImageVersion, NtHeader->OptionalHeader.MajorImageVersion);
        printf("从映像版本:          0x%08X \t %08d \n", NtHeader->OptionalHeader.MinorImageVersion, NtHeader->OptionalHeader.MinorImageVersion);
        printf("主子系统版本:        0x%08X \t %08d \n", NtHeader->OptionalHeader.MajorSubsystemVersion, NtHeader->OptionalHeader.MajorSubsystemVersion);
        printf("从子系统版本:        0x%08X \t %08d \n", NtHeader->OptionalHeader.MinorSubsystemVersion, NtHeader->OptionalHeader.MinorSubsystemVersion);
        printf("Win32版本:           0x%08X \t %08d \n", NtHeader->OptionalHeader.Win32VersionValue, NtHeader->OptionalHeader.Win32VersionValue);
        printf("DLL标识:             0x%08X \t %08d \n", NtHeader->OptionalHeader.DllCharacteristics, NtHeader->OptionalHeader.DllCharacteristics);
        printf("SizeOfStackReserve:  0x%08X \t %08d \n", NtHeader->OptionalHeader.SizeOfStackReserve, NtHeader->OptionalHeader.SizeOfStackReserve);
        printf("SizeOfStackCommit:   0x%08X \t %08d \n", NtHeader->OptionalHeader.SizeOfStackCommit, NtHeader->OptionalHeader.SizeOfStackCommit);
        printf("SizeOfHeapReserve:   0x%08X \t %08d \n", NtHeader->OptionalHeader.SizeOfHeapReserve, NtHeader->OptionalHeader.SizeOfHeapReserve);
        printf("SizeOfHeapCommit:    0x%08X \t %08d \n", NtHeader->OptionalHeader.SizeOfHeapCommit, NtHeader->OptionalHeader.SizeOfHeapCommit);
        printf("LoaderFlags:         0x%08X \t %08d \n", NtHeader->OptionalHeader.LoaderFlags, NtHeader->OptionalHeader.LoaderFlags);
    }
    else
    {
        printf("非标准程序 \n");
    }

    system("pause");
    return 0;
}

当程序被运行后,则可输出NT头中针对FileHeaderOptionalHeader表中的所有内容,输出效果图如下图所示;

此外针对数据目录表的枚举,也将变得很容易实现,一般而言通过NtHeader->OptionalHeader.NumberOfRvaAndSizes读者可得到数据目录表的数量,当得到了数据目录表的数量后则可通过循环的方式依次输出DataDirectory[x]数组中每一个变量的参数信息,根据每次循环的不同则输出不同的参数;

// --------------------------------------------------
// 临时将RVA转换为FOA的函数
// --------------------------------------------------
DWORD RVAtoFOA(DWORD rva)
{
    auto SectionTables = IMAGE_FIRST_SECTION(NtHeader);    // 获取区段表
    WORD Count = NtHeader->FileHeader.NumberOfSections;    // 获取区段数量

    for (int i = 0; i < Count; ++i)
    {
        // 判断是否存在于区段中
        DWORD Section_Start = SectionTables[i].VirtualAddress;
        DWORD Section_Ends = SectionTables[i].VirtualAddress + SectionTables[i].SizeOfRawData;
        if (rva >= Section_Start && rva < Section_Ends)
        {
            // 找到之后计算位置并返回值
            return rva - SectionTables[i].VirtualAddress + SectionTables[i].PointerToRawData;
        }
    }
    return -1;
}

int main(int argc, char * argv[])
{
    BOOL PE = IsPeFile(OpenPeFile("c://pe/x86.exe"), 0);

    if (PE == TRUE)
    {
        int Data_Size = NtHeader->OptionalHeader.NumberOfRvaAndSizes;
        printf("编号 \t 目录RVA \t 目录FOA \t Size长度(十进制) \t Size长度(十六进制) \t 功能描述 \n");

        for (int x = 0; x < Data_Size; x++)
        {
            printf("%03d \t 0x%08X \t 0x%08X \t %08d \t\t 0x%08X \t\t", x + 1, NtHeader->OptionalHeader.DataDirectory[x].VirtualAddress,
                RVAtoFOA(NtHeader->OptionalHeader.DataDirectory[x].VirtualAddress),
                NtHeader->OptionalHeader.DataDirectory[x].Size, NtHeader->OptionalHeader.DataDirectory[x].Size);

            switch (x)
            {
            case 0: printf("Export symbols \n"); break;
            case 1: printf("Import symbols \n"); break;
            case 2: printf("Resources \n"); break;
            case 3: printf("Exception \n"); break;
            case 4: printf("Security \n"); break;
            case 5: printf("Base relocation \n"); break;
            case 6: printf("Debug \n"); break;
            case 7: printf("Copyright string \n"); break;
            case 8: printf("Globalptr \n"); break;
            case 9: printf("Thread local storage (TLS) \n"); break;
            case 10: printf("Load configuration \n"); break;
            case 11: printf("Bound Import \n"); break;
            case 12: printf("Import Address Table \n"); break;
            case 13: printf("Delay Import \n"); break;
            case 14: printf("COM descriptor \n"); break;
            case 15: printf("NoUse \n"); break;
            default: printf("None \n"); break;
            }
        }
    }
    else
    {
        printf("非标准程序 \n");
    }

    system("pause");
    return 0;
}

运行上述程序,则读者可看到如下图所示的输出信息,至此针对数据目录表的枚举也就实现了;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/971126.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MyBatisPlus之逻辑删除、MyBatisPlus解决并发问题的乐观锁机制

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 MyBatisPlus 一、 逻辑删除1.1 数据库表中添加逻辑…

广州华锐互动:3D数字孪生楼宇资产管理系统展示楼宇实时信息

3D数字孪生楼宇资产管理系统由广州华锐互动开发&#xff0c;是一种基于数字孪生技术的智能化展示平台&#xff0c;它可以将楼宇的各项数据进行实时展示&#xff0c;为楼宇的管理者和使用者提供便捷的信息查询和服务。以下是一些实用功能&#xff1a; 1.实时监控&#xff1a;实时…

问道管理:刚刚,“金九”来了?

今天早盘&#xff0c;A股商场可谓“全面开花”。 银行、白酒等权重板块携手发力&#xff0c;带动上证指数、深证成指半日涨超1%&#xff1b;北交所股票更是全线飘红&#xff0c;北证50指数盘中最大涨幅超越8%&#xff0c;半日上涨5.85%。 到午间休市&#xff0c;A股商场超越3…

【C++】智能指针(RAII)详解

我们在上篇文章中&#xff08;异常处理详解&#xff09;提到了 RAII 。那么本篇文章会对此进行详解。重点是智能指针的详解。其中会讲解到 RAII 思想、auto_ptr、unique_ptr、shared_ptr、weak_ptr、循环引用问题。希望本篇文章会对你有所帮助。 文章目录 一、为什么需要智能指…

【java】【项目实战】[外卖九]项目优化(缓存)

目录 一、问题说明 二、环境搭建 2.1 Git管理代码 2.1.1 创建本地仓库 2.1.2 创建远程仓库 2.1.3 创建分支--》推送到远程仓库 2.2 maven坐标 2.3 配置文件application.yml 2.4 配置类RedisConfig 三、缓存短信验证码 3.1 实现思路 3.2 代码改造 3.2.1 UserContro…

CS420 课程笔记 P5 - 内存编辑 数据类型

文章目录 IntroductionData typesBooleansNegative numbers (Signed integers)Floating-point numbers (fractional numbers) Unknown value scansHealth findingFloat finding (Player position hack / Teleport hack) Additional things Introduction 这节课将结束数据类型并…

POI实现word文档导出

1 需求 在列表页面中点击合同按钮&#xff0c;跳转到合同页面 页面中有下载按钮&#xff0c;点击下载按钮&#xff0c;把页面展示的内容导出到word中。 2 分析 2.1 POI操作Word的API介绍 poi对低版本的doc本身支持的就不好所以我们直接说高版本的docx版本的api。 1、poi…

朴素,word,任何参考文献导入endnote

朴素&#xff0c;word&#xff0c;任何参考文献导入endnote 注意&#xff1a;对于以下这几种不做阐述&#xff0c;看其他帖子都有讲述&#xff1a; 这里的参考文献指的是类似于&#xff1a; [1]. Li Y, Lu Y, Huo X, et al. Bandgap tuning strategy by cations and halide io…

【python零基础入门学习】python基础篇之文件对象open、模块以及函数的使用(三)

本站以分享各种运维经验和运维所需要的技能为主 《python》&#xff1a;python零基础入门学习 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8》暂未更新 《docker学习》暂未更新 《ceph学习》ceph日常问题解…

【人月神话】重新探索人月神话:软件工程的现实与挑战

人月神话是一篇由美国软件工程师弗雷德里克布鲁克斯所写的软件工程经典之作&#xff0c;最早发表于1975年。这篇文章的全名是《人月神话&#xff1a;软件工程的神话与现实》&#xff08;The Mythical Man-Month: Essays on Software Engineering&#xff09;&#xff0c;它涵盖…

智慧导览|智能导游系统|AR景区导览系统|景区电子导览

随着文旅市场的加快复苏&#xff0c;以及元宇宙、VR、AR、虚拟数字人等新兴技术的快速发展&#xff0c;文旅行业也正在加快数字化转型的步伐&#xff0c;向智慧景区建设迈进。为满足不同年龄段游客的游览需要&#xff0c;提升旅游服务体验&#xff0c;越来越多的旅游景区、博物…

BlueStore BlueFS rocksdb 关联性梳理

Tag: ceph 12.2.4 BlueStore空间初始化 BlueStore磁盘空间管理 总述 OSD挂载目录基于文件系统管理&#xff0c;Slow、WAL、DB空间区域基于裸盘管理&#xff1b;Slow区域&#xff1a;此类空间主要用于存储对象数据&#xff0c;由BlueStore管理&#xff0c;其中分配于BlueFS空…

如何将枯燥的大数据进行可视化处理?

在数字时代&#xff0c;大数据已经成为商业、科学、政府和日常生活中不可或缺的一部分。然而&#xff0c;大数据本身往往是枯燥的、难以理解的数字和文字&#xff0c;如果没有有效的方式将其可视化&#xff0c;就会错失其中的宝贵信息。以下是一些方法&#xff0c;可以将枯燥的…

Python入门 类class 基础篇

记住一句话&#xff1a;类是模板&#xff0c;而实例则是根据类创建的对象。 我初学时对类的理解是从类的字面上&#xff0c;可以片面的认为它是一个种类&#xff0c;它是相似特征的抽像&#xff0c;也就是相似的东西&#xff0c;可以把相似特征的事务抽象成一个类。&#xff0…

基于串口校时的数字钟设计

文章目录 设计目标硬件设计数码管串口 软件设计顶层模块串口接收模块数据处理模块时钟模块串口发送模块 总结 设计目标 环境&#xff1a;ACX720开发板 实现功能&#xff1a; 数码管能够显示时分秒能够接收串口数据修改时间能够将当前时间以1s一次速率发送到电脑 硬件设计 数…

java之SpringBoot基础篇、前后端项目、MyBatisPlus、MySQL、vue、elementUi

文章目录 前言JC-1.快速上手SpringBootJC-1-1.SpringBoot入门程序制作&#xff08;一&#xff09;JC-1-2.SpringBoot入门程序制作&#xff08;二&#xff09;JC-1-3.SpringBoot入门程序制作&#xff08;三&#xff09;JC-1-4.SpringBoot入门程序制作&#xff08;四&#xff09;…

virtualbox 扩展磁盘大小

此处设置完成后&#xff0c;还需要进入虚拟机&#xff0c;实际扩展磁盘大小 参考 https://zhuanlan.zhihu.com/p/319431032

搭一个shinyAPP就是一篇《Bioinformatics》?

写在前面 原本想引用一番shiny&#xff0c;结果并没有检索出shiny研发团队所发表的论文&#xff0c;倒是有诸多shiny爱好者搭建shinyApp所发表的文章。例如这篇题为“ShinyGO: a graphical gene-set enrichment tool for animals and plants”、于2020年发表于《Bioinformatic…

【AWS】如何用SSH连接aws上的EC2实例(虚拟机)?

目录 0.环境 1.连接结果示例 2.SSH连接思路 3.具体步骤 1&#xff09;安装并运行ssh服务 2&#xff09;启动ssh服务 3&#xff09;在AWS上找到正在运行的EC2实例&#xff0c;并且根据提供的ssh连接语句进行连接 0.环境 windows 11 64位 前提&#xff1a; 有aws账户&…

B. Swap and Reverse

Problem - B - Codeforces 思路&#xff1a;这个题想复杂了&#xff0c;对于第一个条件&#xff0c;我们发现其实就是可以对所有的奇数位置之间任意交换&#xff0c;所有的偶数位置之间任意交换&#xff0c;对于第二个条件来说&#xff0c;如果k为奇数是没有意义的&#xff0c;…