2.2 PE結構:文件頭詳細解析

来源:https://www.cnblogs.com/LyShark/archive/2023/09/04/17677292.html
-Advertisement-
Play Games

PE結構是`Windows`系統下最常用的可執行文件格式,理解PE文件格式不僅可以理解操作系統的載入流程,還可以更好的理解操作系統對進程和記憶體相關的管理知識,DOS頭是PE文件開頭的一個固定長度的結構體,這個結構體的大小為64位元組(0x40)。DOS頭包含了很多有用的信息,該信息可以讓Windows... ...


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;
}

運行上述程式,則讀者可看到如下圖所示的輸出信息,至此針對數據目錄表的枚舉也就實現了;

文章作者:lyshark (王瑞)
文章出處:https://www.cnblogs.com/LyShark/p/17677292.html
本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ## switch語句 使用switch語句來選擇要執行的多個代碼塊中的一個。 在Go中的switch語句類似於C、C++、Java、JavaScript和PHP中的switch語句。不同之處在於它只執行匹配的case,因此不需要使用break語句。 單一case的switch語法 ```Go sw ...
  • ## 1、條件變數 當線程需要等待特定事件發生、或是某個條件成立時,可以使用條件變數`std::condition_variable`,它在標準庫頭文件``內聲明。 ```c++ std::mutex mut; std::queue data_queue; std::condition_variab ...
  • # 學習Markdown Typora初體驗 ## 標題 語法:`#+空格+標題+回車`。幾級標題就有幾個#號。 ### 三級標題 #### 四級標題 ##### 五級標題 ###### 六級標題 ####### 七級標題。。。我沒有 ## 字體 *斜體* 語法: $$ *內容* $$ **加粗** ...
  • # 第一章HTML #### 1.1 html的定義 html是超文本標記語言,是一個基於HTTP(超文本傳輸協議)協議的網頁語言 #### 1.2 html的版本 HTML 4.01 以及具備完善的網頁編輯 HTML 5.0 移動端網頁編輯 XHTML 語法嚴格 #### 1.3 瀏覽器 保障相容 ...
  • ### 歡迎訪問我的GitHub > 這裡分類和彙總了欣宸的全部原創(含配套源碼):[https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) ### 本篇概覽 - 本文是《LeetCode952三部曲之三 ...
  • # Python文件的基本操作 - 文件的基本操作 - 文件的讀寫模式 - 文件的讀寫操作相關的方法 - 文件的操作模式 - 文件的練習題 ## 文件的基本操作 ```python 1. 我們能夠操作哪些類型的文件: .txt 沒有尾碼名的文件 # 我們現在不能操作word、Excel、PPT等文件 ...
  • ## 單點登錄服務端搭建 1、下載cas包 `https://github.com/apereo/cas-overlay-template/tree/5.3` 這好像是最後一個maven版本的,之後都是grade版本的 2、使用idea打開代碼,導入依賴 3、新建src目錄、resource目錄 4 ...
  • 部署 操作系統:CentOS:7.4,perl版本:v5.16.3,opensearch版本:3.0.8 1.下載地址:https://www.openssl.org/source/ 2.安裝cmd.pm模塊,不然編譯的時候會引發【Can‘t locate IPC/Cmd.pm in @INC】錯誤 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...