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