PE結構是`Windows`系統下最常用的可執行文件格式,理解PE文件格式不僅可以理解操作系統的載入流程,還可以更好的理解操作系統對進程和記憶體相關的管理知識,DOS頭是PE文件開頭的一個固定長度的結構體,這個結構體的大小為64位元組(0x40)。DOS頭包含了很多有用的信息,該信息可以讓Windows... ...
PE結構是Windows
系統下最常用的可執行文件格式,理解PE文件格式不僅可以理解操作系統的載入流程,還可以更好的理解操作系統對進程和記憶體相關的管理知識,DOS頭是PE文件開頭的一個固定長度的結構體,這個結構體的大小為64位元組(0x40)。DOS頭包含了很多有用的信息,該信息可以讓Windows操作系統使用正確的方式載入可執行文件。從DOS文件頭IMAGE_DOS_HEADER
的e_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_HEADER
的e_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_HEADER
的e_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_HEADER
和IMAGE_OPTIONAL_HEADER32
結構來定義。
2.3.1 IMAGE_FILE_HEADER
我們跟進IMAGE_FILE_HEADER
這個結構,文件頭結構體IMAGE_FILE_HEADER
是IMAGE_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頭向下分別解析FileHeader
及OptionalHeader
中的參數,根據參數定義依次輸出即可得到所有的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頭中針對FileHeader
及OptionalHeader
表中的所有內容,輸出效果圖如下圖所示;
此外針對數據目錄表的枚舉,也將變得很容易實現,一般而言通過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 許可協議。轉載請註明出處!