PE頭詳細分析
0x00 前言
最近我在學習Linux PWN相關知識的時候,也是在看《程序員的自我修養(裝載->鏈接->庫)》這本書的時候,接觸到了可執行文件格式,COFF、ELF、PE,所以也找了Bilibili上海東老師的《滴水逆向三期》視頻中的PE課程在學習,剛看完PE頭這節課。在此做個筆記也算是整理學習的結果 並且分享給大家,共同學習,如有錯誤歡迎指正。
0x01 PE文件介紹
PE文件是Windows上的可執行文件,就是我們鼠標雙擊就能運行的程序,當然也有雙擊不能運行的程序。其中.exe
、.dll
、.sys
這些都是PE文件,那么讀者可能有個疑問,我雙擊.txt的文件也是能直接打開運行的啊?Emmmmm....這個其實他是用Notepad記事本加載並打開的,而PE可執行文件他是經過系統加載並運行的。
PE文件是分塊存儲的。在圖中我們可以看出數據被加載到內存后會不一樣,其中PE文件被加載到內存中的時候(相當於一個拉伸的過程),把數據給拉長了。
不過塊中的數據還是一樣的,我們可以看到最開頭的快就是PE頭的數據,接下來是節表等其他塊的數據,這些我們會在后續文章中介紹。
0x02 PE頭詳細分析
PE主要由3部分構造,(1)、DOS頭 (2)、NT頭(標准PE頭、可選PE頭)。
參考圖:(OpenRCE.org網站上的PE格式圖.pdf)
DOS頭解析
DOS頭中的數據如下,其中帶*號的是比較重要的數據,DOS頭大小為:64字節。
//--> DOS頭(_IMAGE_DOS_HEADER ) <--
struct _IMAGE_DOS_HEADER
{
WORD e_magic; //*DOS頭魔數 Magic*
WORD e_cblp; //[Bytes on last page]
WORD e_cp; //[Pages in file]
WORD e_crlc; //[Relocations]
WORD e_cparhdr; //[Size of header]
WORD e_minalloc;//[Minium memory]
WORD e_maxalloc;//[Maxium Memory]
WORD e_ss; //[Inital SS value]
WORD e_sp; //[Inital SP value]
WORD e_csum; //[Checksum]
WORD e_ip; //[Inital IP value]
WORD e_cs; //[Inital CS value]
WORD e_lfarlc; //[Table offset]
WORD e_ovno; //[Overlay number]
WORD e_res[4]; //[Reserved words]
WORD e_oemid; //[OEM id]
WORD e_oeminfo; //[OEM infomation]
WORD e_res2[10];//[Reserved words]
DWORD e_lfanew; //*NT頭地址*
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
NT頭解析
NT頭主要由3部分構成,標記、標准PE頭、可選PE頭。其中NT頭魔數就是PE
字符串,可見上圖。
//--> NT頭(_IMAGE_NT_HEADERS) <--
struct _IMAGE_NT_HREADERS
{
DWORD Signature;//*NT頭魔數
_IMAGE_FILE_HEADER FileHeader;//標准PE頭
_IMAGE_OPTIONAL_HEADER OptionalHeader;//可選PE頭
}IMAGE_NT_HREADERS,*PIMAGE_NT_HREADERS;
標准PE頭解析
標志PE頭的固定大小是20字節,其中我們可以看見里面有區段數目的數據。
還有比較關注的點是時間戳,這個時間戳我們可以用文章https://www.cnblogs.com/17bdw/p/6412158.html中的方法轉換成文件創建時間
。
最后特征的數據也要關注下,因為可以用他來判斷PE文件的許多特征信息,比如是否為DLL文件、重定位信息是否被移去、是否為系統文件等等。
//--> 標准PE頭(_IMAGE_FILE_HEADER) <--
struct _IMAGE_FILE_HEADER
{
WORD Machine;//*運行平台
WORD NumberOfSections;//*區段數目
DWORD TimeDateStamp;//*時間戳
DWORD PointerToSymbolTable;//[Pointer to COFF]
DWORD NumberOfSymbols;//[COFF table size]
WORD SizeOfOptionalHeader;//*可選PE頭大小
WORD Characteristics;//*特征
}IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;
HEX數據
解析后
可選PE頭解析
可選PE頭結構
//--> 可選PE頭(_IMAGE_OPTIONAL_HEADER) <--
struct _IMAGE_OPTIONAL_HEADER
{
WORD Magic;//[可選PE頭魔數]
BYTE MajorLinkerVersion;//[主鏈接器版本]
BYTE MinorLinkerVersion;//[副鏈接器版本]
DWORD SizeOfCode;//[代碼段大小]
DWORD SizeOfInitializedData;//[初始化數據大小]
DWORD SizeOfUninitializedData;//[未初始化數據大小]
DWORD AddressOfEntryPoint;//*[程序入口點]
DWORD BaseOfCode;//*[代碼段地址]
DWORD BaseOfData;//*[數據段地址]
DWORD ImageBase;// *[加載到內存的開始地址] -> 一般好像都是0x400000
DWORD SectionAlignment;//*[內存頁對其大小]
DWORD FileAlignment;//[文件對其大小]
WORD MajorOperatingSystemVersion;//*[操作系統的主版本號]
WORD MinjorOperatingSystemVersion;//*[操作系統的次版本號]
WORD MajorImageVersion;//[程序主版本號]
WORD MinorImageVersion;//[程序次版本號]
WORD MajorSubsystemVersion;//[子系統主版本號]
WORD MinorSubsystemVersion;//[子系統次版本號]
DWORD Win32VersionValue;//[默認保留]
DWORD SizeOfImage;//*[加載到內存映像的大小]
DWORD SizeOfHeaders;//[DOS頭 PE頭 節頭組合大小]
DWORD CheckSum;//*[獲取加載到內存映像的hash]
WORD Subsystem;//[運行此映像所需要的子系統名稱]
WORD DllCharacteristics;//[DLL映像的特征]
DWORD SizeOfStackReserve;//*[獲取保留堆棧的大小]
DWORD SizeOfStackCommit;//*[獲取要提交堆棧的大小]
DWORD SizeOfHeapReserve;//*[獲取保留堆空間的大小]
DWORD SizeOfHeapCommit;//*[獲取要提交的本地堆空間大小]
DWORD LoaderFlags;//[之前保留的成員]
DWORD NumberOfRvaAndSizes;//*[獲取 PEHeader 剩余部分中數據目錄項的數目 |位置和大小]
_IMAGE_DATA_DIRECTORY DataDirectory[16];//[指向數據目錄中的第一個 IMAGE_DATA_DIRECTORY 結構的指針。]
}IMAGE_OPTIONAL_HEADER,*PIMAGE_OPTIONAL_HEADER;
可以看出可選PE頭數據最多,不過這是好事對我們有用的數據也很多,比如下面標紅的都是平時在逆向或者脫殼、破解中比較有用的信息。
基址
首先第一個魔數0x010B我們就不說了,主要應該是為了區別32位和64位程序吧。
接着我們看程序基地址0x01000000
,這個基地址的意思就是程序加載到內存時候PE文件所在的位置,Windows他會為每個程序分配一個虛擬的4GB空間。
這里有的讀者會問那為什么基址非要那么大,為什么不是0呢?因為Windows他有一段內存是用來保護用的,平時我們在寫C++代碼時候比如:我們引用了一個NULL(空指針)其指向的內存地址就是0的時候,程序就會崩潰就會報錯!,沒錯這就是Windows為了保護程序設計的。
我們也可以用Winhex打開加載在內存中Notepad.exe的數據,可以發現其首地址就是程序基址。
代碼段地址
接着我們來看代碼段的地址0x001000
,也就是PE文件在這地方開始存的都是程序代碼,當然在底層被轉為了匯編代碼。
我們可以用radare2套件中的rasm2來將十六進制轉換成匯編代碼看看。
rasm2 -a x86 -b 32 -d "十六進制"
#-a 代表平台 x86架構平台
#-b 位數 32位
#-d 解碼 解析成匯編
數據段地址
接下來看數據段的地址0x009000
,數據段主要存放數據,比如字符串等數據,在下圖中能看到存放了Notepad字符串。
OEP程序入口點
OEP是可選PE頭結構體中第7個成員AddressOfEntryPoint
的數據,顧名思義指的是程序開始執行的第一行代碼位置。
由於程序被加載到內存,所以我們還需要加上基址
才是真實的程序入口點。
即:基址
+OEP
= 0x01000000 + 0x739D = 0x0100739D
。
我們可以用工具將其轉換成匯編,看看第一行匯編代碼是什么?
我們也可以利用OD來加載程序,OD加載程序默認會自動加載到OEP處。所以可以來驗證下我們找的位置對不對。
好了可選頭PE內容介紹到這里就結束了,其中可選PE頭還有最有一個結構體數據_IMAGE_DATA_DIRECTORY
,這個暫時就先不介紹了,留在后面介紹其他內容的時候在詳解。
0x03 PE頭解析工具編寫
知道了PE頭結構后,代碼寫起來也是很方便,而且微軟有自帶的PE頭結構體,我們可以直接open()文件后直接讀取內容到結構體解析即可。
/*******************************************************
*
* 學習滴水逆向 PE結構分析代碼練習
*
* 海東老師 Bilibili:滴水逆向三期
*********************************************************/
//頭文件定義
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <Windows.h>
using namespace std;
//--------------------------PE結構----------------------------------
//--> DOS頭(_IMAGE_DOS_HEADER ) <--
struct _IMAGE_DOS_HEADER_2
{
WORD e_magic; //*DOS頭魔數 Magic*
WORD e_cblp; //[Bytes on last page]
WORD e_cp; //[Pages in file]
WORD e_crlc; //[Relocations]
WORD e_cparhdr; //[Size of header]
WORD e_minalloc;//[Minium memory]
WORD e_maxalloc;//[Maxium Memory]
WORD e_ss; //[Inital SS value]
WORD e_sp; //[Inital SP value]
WORD e_csum; //[Checksum]
WORD e_ip; //[Inital IP value]
WORD e_cs; //[Inital CS value]
WORD e_lfarlc; //[Table offset]
WORD e_ovno; //[Overlay number]
WORD e_res[4]; //[Reserved words]
WORD e_oemid; //[OEM id]
WORD e_oeminfo; //[OEM infomation]
WORD e_res2[10];//[Reserved words]
DWORD e_lfanew; //*PE文件頭地址*
} IMAGE_DOS_HEADER_2, *PIMAGE_DOS_HEADER_2;
//--> NT頭(_IMAGE_NT_HEADERS) <--
struct _IMAGE_NT_HREADERS_2
{
DWORD Signature;
_IMAGE_FILE_HEADER FileHeader;
_IMAGE_OPTIONAL_HEADER OptionalHeader;
}IMAGE_NT_HREADERS_2,*PIMAGE_NT_HREADERS_2;
//--> 標准PE頭(_IMAGE_FILE_HEADER) <--
struct _IMAGE_FILE_HEADER_2
{
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
}IMAGE_FILE_HEADER_2,*PIMAGE_FILE_HEADER_2;
//--> 可選PE頭(_IMAGE_OPTIONAL_HEADER) <--
struct _IMAGE_OPTIONAL_HEADER_2
{
WORD Magic;//[可選PE頭魔數]
BYTE MajorLinkerVersion;//[主鏈接器版本]
BYTE MinorLinkerVersion;//[副鏈接器版本]
DWORD SizeOfCode;//[代碼段大小]
DWORD SizeOfInitializedData;//[初始化數據大小]
DWORD SizeOfUninitializedData;//[未初始化數據大小]
DWORD AddressOfEntryPoint;//*[程序入口點]
DWORD BaseOfCode;//*[代碼段地址]
DWORD BaseOfData;//*[數據段地址]
DWORD ImageBase;// *[加載到內存的開始地址] -> 一般好像都是0x400000
DWORD SectionAlignment;//*[內存頁對其大小]
DWORD FileAlignment;//[文件對其大小]
WORD MajorOperatingSystemVersion;//*[操作系統的主版本號]
WORD MinjorOperatingSystemVersion;//*[操作系統的次版本號]
WORD MajorImageVersion;//[程序主版本號]
WORD MinorImageVersion;//[程序次版本號]
WORD MajorSubsystemVersion;//[子系統主版本號]
WORD MinorSubsystemVersion;//[子系統次版本號]
DWORD Win32VersionValue;//[默認保留]
DWORD SizeOfImage;//*[加載到內存映像的大小]
DWORD SizeOfHeaders;//[DOS頭 PE頭 節頭組合大小]
DWORD CheckSum;//*[獲取加載到內存映像的hash]
WORD Subsystem;//[運行此映像所需要的子系統名稱]
WORD DllCharacteristics;//[DLL映像的特征]
DWORD SizeOfStackReserve;//*[獲取保留堆棧的大小]
DWORD SizeOfStackCommit;//*[獲取要提交堆棧的大小]
DWORD SizeOfHeapReserve;//*[獲取保留堆空間的大小]
DWORD SizeOfHeapCommit;//*[獲取要提交的本地堆空間大小]
DWORD LoaderFlags;//[之前保留的成員]
DWORD NumberOfRvaAndSizes;//*[獲取 PEHeader 剩余部分中數據目錄項的數目 |位置和大小]
_IMAGE_DATA_DIRECTORY DataDirectory[16];//[指向數據目錄中的第一個 IMAGE_DATA_DIRECTORY 結構的指針。]
}IMAGE_OPTIONAL_HEADER_2,*PIMAGE_OPTIONAL_HEADER_2;
//-----------------------------------------------------------------
int main(int args,char *argv[])
{
if (args < 2)
{
printf("參數有誤,請按照如下格式調用本程序!\n");
printf("PEAnysis.exe 程序名.exe\n");
return 0;
}
//初始化可有顏色終端Handle
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
//
printf("===================PE Anysis-PE文件分析程序工具!==========================\n\n");
FILE* fp = fopen(argv[1], "rb");
if (fp != NULL)
{
//讀取DOS頭
fread(&IMAGE_DOS_HEADER_2, sizeof(IMAGE_DOS_HEADER_2), 1, fp);
//跳轉到NT頭
fseek(fp, IMAGE_DOS_HEADER_2.e_lfanew, 0);
//讀取NT頭
fread(&IMAGE_NT_HREADERS_2, sizeof(IMAGE_NT_HREADERS_2), 1, fp);
printf("---------------PE頭數據----------------\n");
//輸出DOS頭信息
cout << "--> DOS頭(_IMAGE_DOS_HEADER ) <--" << endl;
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
char szMagic[3] = { 0 };
memcpy(szMagic, &IMAGE_DOS_HEADER_2.e_magic, 2);
printf("*DOS頭魔數:0x%x|%s\n", IMAGE_DOS_HEADER_2.e_magic, szMagic);
SetConsoleTextAttribute(handle, 0x07);
printf("[Bytes on last page]:0x%x\n", IMAGE_DOS_HEADER_2.e_cblp);
printf("[Pages in file]:0x%x\n", IMAGE_DOS_HEADER_2.e_cp);
printf("[Relocations]:0x%x\n", IMAGE_DOS_HEADER_2.e_crlc);
printf("[Size of header]:0x%x\n", IMAGE_DOS_HEADER_2.e_cparhdr);
printf("[Minium memory]:0x%x\n", IMAGE_DOS_HEADER_2.e_minalloc);
printf("[Maxium Memory]:0x%x\n", IMAGE_DOS_HEADER_2.e_maxalloc);
printf("[Inital SS value]:0x%x\n", IMAGE_DOS_HEADER_2.e_ss);
printf("[Inital SP value]:0x%x\n", IMAGE_DOS_HEADER_2.e_sp);
printf("[Checksum]:0x%x\n", IMAGE_DOS_HEADER_2.e_csum);
printf("[Inital IP value]:0x%x\n", IMAGE_DOS_HEADER_2.e_ip);
printf("[Inital CS value]:0x%x\n", IMAGE_DOS_HEADER_2.e_cs);
printf("[Table offset]:0x%x\n", IMAGE_DOS_HEADER_2.e_lfarlc);
printf("[Overlay number]:0x%x\n", IMAGE_DOS_HEADER_2.e_ovno);
printf("[Reserved words]:", IMAGE_DOS_HEADER_2.e_res);
for (size_t i = 0; i < 4; i++)
{
printf("0x%x, ", IMAGE_DOS_HEADER_2.e_res[0]);
}
cout << endl;
printf("[OEM id]:0x%x\n", IMAGE_DOS_HEADER_2.e_oemid);
printf("[OEM infomation]:0x%x\n", IMAGE_DOS_HEADER_2.e_oeminfo);
printf("[Reserved words]:", IMAGE_DOS_HEADER_2.e_res2);
for (size_t i = 0; i < 10; i++)
{
printf("0x%x, ", IMAGE_DOS_HEADER_2.e_res2[0]);
}
cout << endl;
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
printf("*PE文件頭地址:0x%x\n", IMAGE_DOS_HEADER_2.e_lfanew);
SetConsoleTextAttribute(handle, 0x07);
cout << "DOS頭大小:" << sizeof(IMAGE_DOS_HEADER_2) << endl;
cout << endl;
//輸出標准PE頭信息
cout << "--> 標准PE頭(_IMAGE_FILE_HEADER) <--" << endl;
char szNTSignature[3] = { 0 };
memcpy(szNTSignature, &IMAGE_NT_HREADERS_2.Signature, 2);
printf("[NT頭標識]:%s\n", szNTSignature);
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
printf("*[運行平台]:0x%x\n", IMAGE_NT_HREADERS_2.FileHeader.Machine);
printf("*[節數量]:0x%x\n", IMAGE_NT_HREADERS_2.FileHeader.NumberOfSections);
printf("*[時間戳]:0x%x\n", IMAGE_NT_HREADERS_2.FileHeader.TimeDateStamp);
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_GREEN);
struct tm test_gmtime_s;
errno_t err = gmtime_s(&test_gmtime_s, (time_t*)&IMAGE_NT_HREADERS_2.FileHeader.TimeDateStamp);
printf(" 文件創建時間:%d年%d月%d日 %02d時:%02d分:%02d秒(周%d)\n", test_gmtime_s.tm_year + 1900, test_gmtime_s.tm_mon, test_gmtime_s.tm_mday,
test_gmtime_s.tm_hour + 8, test_gmtime_s.tm_min, test_gmtime_s.tm_sec, test_gmtime_s.tm_wday);
SetConsoleTextAttribute(handle, 0x07);
printf("[Pointer to COFF]:0x%x\n", IMAGE_NT_HREADERS_2.FileHeader.PointerToSymbolTable);
printf("[COFF table size]:0x%x\n", IMAGE_NT_HREADERS_2.FileHeader.NumberOfSections);
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
printf("*[可選頭大小]:0x%x\n", IMAGE_NT_HREADERS_2.FileHeader.SizeOfOptionalHeader);
printf("*[特征/特性]:0x%x\n", IMAGE_NT_HREADERS_2.FileHeader.Characteristics);
SetConsoleTextAttribute(handle, 0x07);
cout << "標准PE頭大小:" << sizeof(IMAGE_NT_HREADERS_2.FileHeader) << endl;
cout << endl;
//輸出可選PE頭信息
cout << "--> 可選PE頭(_IMAGE_OPTIONAL_HEADER) <--" << endl;
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
printf("*[程序內存入口點]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.AddressOfEntryPoint + IMAGE_NT_HREADERS_2.OptionalHeader.ImageBase);
printf("*[可選PE頭魔數]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.Magic);
printf("*[主鏈接器版本]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.MajorLinkerVersion);
printf("*[副鏈接器版本]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.MinorLinkerVersion);
printf("*[代碼段大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfCode);
printf("*[初始化數據大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfInitializedData);
printf("*[未初始化數據大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfUninitializedData);
printf("*[代碼段地址]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.BaseOfCode);
printf("*[數據段地址]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.BaseOfData);
printf("*[PE文件基地址]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.ImageBase);
printf("*[程序入口點]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.AddressOfEntryPoint);
SetConsoleTextAttribute(handle, 0x07);
printf("[內存對其大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SectionAlignment);
printf("[文件對其大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.FileAlignment);
printf("[操作系統的主版本號]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.MajorOperatingSystemVersion);
printf("[操作系統的次版本號]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.MinorOperatingSystemVersion);
printf("[程序主版本號]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.MajorImageVersion);
printf("[程序次版本號]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.MinorImageVersion);
printf("[子系統主版本號]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.MajorSubsystemVersion);
printf("[子系統次版本號]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.MinorSubsystemVersion);
printf("[Win32版本值]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.Win32VersionValue);
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
printf("*[內存映像大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfImage);
printf("*[DOS|PE|節頭大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfHeaders);
printf("*[內存映像hash]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.CheckSum);
SetConsoleTextAttribute(handle, 0x07);
printf("[程序可以運行的系統]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.Subsystem);
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
printf("*[DLL映像的特征]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.DllCharacteristics);
printf("*[獲取保留堆棧的大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfStackReserve);
printf("*[獲取要提交堆棧的大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfStackCommit);
printf("*[獲取保留堆空間的大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfHeapReserve);
printf("*[獲取要提交的本地堆空間大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfHeapCommit);
SetConsoleTextAttribute(handle, 0x07);
printf("[加載標志(已廢棄)]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.LoaderFlags);
printf("[獲取PEHeader剩余部分數據,位置和大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.NumberOfRvaAndSizes);
printf("[指向IMAGE_DATA_DIRECTORY結構指針]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.DataDirectory);
cout << "可選PE頭大小:" << sizeof(IMAGE_NT_HREADERS_2.OptionalHeader) << endl;
cout << endl;
printf("---------------節表數據----------------\n");
printf("===========================================================================\n\n");
}
else
{
printf("文件打開失敗,請檢查是否被占用!\n");
return 0;
}
int x;
cin >> x;
return 0;
}
最后歡迎大家加群:1145528880