PE檔案結構解析1
0x0導讀
今天覆習一下pe檔案結構,本文主要講了pe檔案結構基本概念和Dos頭,Nt頭,可選頭的一些比較重要的成員,話不多說來看文章。
0x1環境
編譯器:VirsualStudio2022
16進位制檢視工具:winhex
0x2基本概念
我們先來看下百度給出的定義:
PE檔案的全稱是Portable Executable,意為可移植的可執行的檔案,常見的EXE、DLL、OCX、SYS、COM都是PE檔案。
我的理解:
pe檔案結構就像一張說明書,用來說明這個pe檔案的情況,而pe檔案結構是表示符合pe檔案結構的檔案如exe,sys(驅動檔案),com等檔案的資訊。舉個例子,買電腦的時候都會有一張配置單,來說明這個電腦的cpu是啥,記憶體條多大,顯示卡是哪個廠的等資訊。pe檔案結構就像是這一張配置單,只不過配置單裡面的資訊是表示這臺電腦的情況,而pe檔案結構表示的是這個exe(這裡就只用exe來舉例),相關的資訊,比如哪一部分是程式碼,哪一部分是資料,執行的時候載入到記憶體的哪裡,pe檔案的大小是多少等資訊。
下面是pe檔案結構圖,因為pe檔案結構內容太多了所以今天只講Dos頭,PE檔案頭(nt頭),標準pe頭(file頭),可選檔案頭(option頭),剩下的以後在講(因為清楚的圖片比較大上傳不上去只能湊活著看了)。
0x3Dos頭解析
Dos頭定義,其實就是一個結構體,比較重要的有e_magic和e_lfanew,這兩個成員。
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD e_magic; // Magic number 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; // File address of new exe header } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
e_magic
是一個WORD型別,兩個位元組,用來判斷該pe檔案是不是可執行的,值是0x4D5A,用字串就是MZ所以它也叫mz標記,如果把它給改掉那麼程式就無法執行。
修改前
修改後
e_lfanew
是一個LONG型別大小是4位元組,可以把它理解為一個偏移,透過它加基址(程式碼開始的地方)來找到pe檔案頭(nt頭)。
程式碼解析
#include <stdio.h> #include <Windows.h> #define path "C:\\Users\\allen\\Desktop\\ipmsg.exe" void main() { FILE* fp = fopen(path, "rb"); fseek(fp, 0, SEEK_END); int size = ftell(fp); rewind(fp); PBYTE ptr = (PBYTE)malloc(size); memset(ptr, 0, size); fread(ptr, size, 1, fp); PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)ptr; printf("E_magic:%x\n", Dos->e_magic); printf("E_lfanew:%x\n", Dos->e_lfanew); getchar(); }
前三行程式碼主要是包含標頭檔案,因為不包含標頭檔案有一些函式就無法使用,第三行是定義一個宏來作為fopen
函式的引數。
FILE* fp = fopen(path, "rb");
定義一個檔案指標來接受fopen
函式返回值,fopen
第一個引數是要開啟檔案的路徑,第二個引數是以什麼方式開啟,這裡是rb也就是以二進位制方式開啟一個檔案,只能讀不可以寫。
fseek(fp, 0, SEEK_END);
第一個引數是要設定檔案的檔案指標,第二個引數是一個相對於第三個引數是一個偏移量,第三個引數SEEK_END
代表檔案的末尾,程式碼大致意思檔案流重定向到檔案末尾。
int size = ftell(fp);
定義一個變數用來接收ftell函式的返回值,ftell
函式作用是計算檔案的大小,第一個引數是要計算那個檔案的檔案指標。
rewind(fp);
將檔案流重定向到檔案開頭,為下面讀取資料做準備。
PBYTE ptr = (PBYTE)malloc(size);
定義一個指標指向malloc
函式申請的記憶體,malloc
函式第一個引數是申請記憶體的大小,PBYTE
就是char*
。
memset(ptr, 0, size);
填充剛才申請的記憶體塊為0,第一個引數記憶體塊的地址,第二個引數用什麼填充,第三個引數填充多大。
fread(ptr, size, 1, fp);
用於讀取資料到記憶體,第一個引數是要讀到哪裡,第二個引數是讀多少位元組,第三個引數讀多少次,第四個引數要讀取檔案的檔案指標。
PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)ptr;
定義一個結構體指標來指向那塊記憶體,也就是用這塊記憶體中的資料來填充這個結構體指標所指向的結構體。
printf("E_magic:%x\n", Dos->e_magic);//列印結構體成員e_magic的值,列印結構體成員要用-> printf("E_lfanew:%x\n", Dos->e_lfanew);//列印結構體成員e_lfanew的值 getchar();//暫停等待使用者輸入。
程式執行結果,因為我這裡開啟的是另一個程式所以e_lfanew
的值不一樣。
0x3Nt頭解析
Nt頭定義。
typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
Signature
是一個DWORD裡面儲存著PE標記也就是0x4550,這個值代表此檔案是一個有效的pe檔案,如果被修改程式也會無法執行。
FileHeader
一個結構體,是標準pe頭開始的地方,結構體定義如下,大小是20個位元組。因為主要講的是nt頭所以對此結構說的不是很詳細,下面會講到此結構。
typedef struct _IMAGE_FILE_HEADER { WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
OptionalHeader
也是一個結構體,是可選頭開始的地方,定義如下,因為主要講的是nt頭所以對此結構說的不是很詳細,下面會講到這個結構。
typedef struct _IMAGE_OPTIONAL_HEADER { // // Standard fields. // WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; // // NT additional fields. // DWORD ImageBase; DWORD SectionAlignment; DWORD FileAlignment; WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; DWORD SizeOfHeaders; DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics; 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;
程式碼解析
#include <stdio.h> #include <Windows.h> #define path "C:\\Users\\allen\\Desktop\\ipmsg.exe" void main() { FILE* fp = fopen(path, "rb"); fseek(fp, 0, SEEK_END); int size = ftell(fp); rewind(fp); PBYTE ptr = (PBYTE)malloc(size); memset(ptr, 0, size); fread(ptr, size, 1, fp); PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)ptr; printf("E_magic:%x\n", Dos->e_magic); printf("E_lfanew:%x\n", Dos->e_lfanew); PIMAGE_NT_HEADERS Nt = (PIMAGE_NT_HEADERS)(ptr + Dos->e_lfanew); printf("Signature:%x\n", Nt->Signature); printf("FileHeader:%x\n", Nt->FileHeader); printf("OptionalHeader:%x\n", Nt->OptionalHeader); getchar(); }
上面講過的程式碼就不在贅述了。
PIMAGE_NT_HEADERS Nt = (PIMAGE_NT_HEADERS)(ptr + Dos->e_lfanew);
也是定義一個結構體指標然後指向基址加上Dos頭的e_lfanew成員,定位到nt頭,並填充這個結構體指標指向的結構體。實際上還是基址加偏移的方式定位的。
printf("Signature:%x\n", Nt->Signature); printf("FileHeader:%x\n", Nt->FileHeader); printf("OptionalHeader:%x\n", Nt->OptionalHeader);
上面這幾行程式碼就是列印結構體的資料了,printf
函式就是列印資料。
程式執行結果
0x4File頭解析
File頭定義,大小20位元組。
typedef struct _IMAGE_FILE_HEADER { WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable;//COFF符號表格的偏移位置。此欄位只對COFF除錯資訊有用 DWORD NumberOfSymbols;//COFF符號表格中的符號個數。該值和上一個值在release版本的程式裡為0 WORD SizeOfOptionalHeader; WORD Characteristics; } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
Machine
大小兩位元組,表示當前pe檔案的目標CPU型別,0代表任何平臺,14c代表i386及後續處理器。
NumberOfSections
大小兩位元組,代表節表(區塊)的數量。
TimeDateStamp
大小四位元組,一個時間戳,表示當前pe檔案何時建立時間。
因為PointerToSymbolTable
和NumberOfSymbols
這兩個成員很少被使用所以就不多介紹,直接看SizeOfOptionalHeader
大小兩位元組,表示可選頭(Option頭)的大小。
Characteristics
大小兩位元組,表示檔案的型別,010f代表可執行檔案。
程式碼解析
#include <stdio.h> #include <Windows.h> #define path "C:\\Users\\allen\\Desktop\\ipmsg.exe" void main() { FILE* fp = fopen(path, "rb"); fseek(fp, 0, SEEK_END); int size = ftell(fp); rewind(fp); PBYTE ptr = (PBYTE)malloc(size); memset(ptr, 0, size); fread(ptr, size, 1, fp); PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)ptr; printf("E_magic:%x\n", Dos->e_magic); printf("E_lfanew:%x\n", Dos->e_lfanew); PIMAGE_NT_HEADERS Nt = (PIMAGE_NT_HEADERS)(ptr + Dos->e_lfanew); printf("Signature:%x\n", Nt->Signature); printf("FileHeader:%x\n", Nt->FileHeader); printf("OptionalHeader:%x\n", Nt->OptionalHeader); PIMAGE_FILE_HEADER File = (PIMAGE_FILE_HEADER)(ptr + Dos->e_lfanew + 4); printf("Machine:%x\n", File->Machine); printf("NumberOfSections:%x\n", File->NumberOfSections); printf("Characteristics:%x\n",File->Characteristics); printf("TimeDateStamp:%x\n", File->TimeDateStamp); printf("SizeOfOptionalHeader:%x\n", File->SizeOfOptionalHeader); printf("TimeDateStamp:%x\n",File->TimeDateStamp); getchar(); }
PIMAGE_FILE_HEADER File = (PIMAGE_FILE_HEADER)(ptr + Dos->e_lfanew + 4);
先定義一個結構體指標,在用基址+Dos頭的e_lfanew成員定位到nt頭,跟據nt頭的結構體定義可知到Signature成員後面就是File頭而Signature的大小是四位元組,所以加4就定位到File頭,用File頭的資料填充上面定義的結構體指標指向的結構體即可。
程式執行結果
0x5Option頭解析
Option頭結構體定義
typedef struct _IMAGE_OPTIONAL_HEADER { // // Standard fields. // WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; // // NT additional fields. // DWORD ImageBase; DWORD SectionAlignment; DWORD FileAlignment; WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; DWORD SizeOfHeaders; DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics; 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;
Magic
大小兩位元組,用於表示是32位pe檔案還是64位pe檔案,如果是10B就代表32位檔案,20B代表64位檔案。
SizeOfCode
大小四位元組,程式碼總大小,需要檔案對齊。
SizeOfInitializedData
大小四位元組,代表已經初始化資料的大小,需要按照檔案對齊
SizeOfUninitializedData
大小四位元組,代表未初始化資料大小,也是要按照檔案對齊。
AddressOfEntryPoint
大小四位元組,是程式入口地址,也就是OEP(需要加上ImageBase才是真正的程式入口)。
BaseOfCode
大小四位元組,程式碼節開始的地方。
BaseOfData
大小四位元組,資料開始的地方。
ImageBase
大小四位元組,記憶體映象基址也就是程式載入進記憶體中時的基址(一般是0x400000)。
FileAlignment
大小四位元組,檔案對齊,如果檔案對齊是200那麼不足200的會在後面補0,如過一個數是188那麼它按照檔案對齊後就是200,主要作用是提高cpu工作效率。
SectionAlignment
大小四位元組,記憶體對齊,和檔案對齊一樣,也是為了提高cpu工作效率。
SizeOfImage
大小四位元組,PE檔案在記憶體中的總大小,按照記憶體對齊
SizeOfHeaders
大小四位元組,所有頭的大小,Dos頭+Nt頭成員Signature+File頭+Option頭+節表的總大小,需要按照檔案對齊。
程式碼解析
#include <stdio.h> #include <Windows.h> #define path "C:\\Users\\blue\\Desktop\\ipmsg.exe" void main() { FILE* fp = fopen(path, "rb"); fseek(fp, 0, SEEK_END); int size = ftell(fp); rewind(fp); PBYTE ptr = (PBYTE)malloc(size); memset(ptr, 0, size); fread(ptr, size, 1, fp); PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)ptr; printf("E_magic:%x\n", Dos->e_magic); printf("E_lfanew:%x\n", Dos->e_lfanew); PIMAGE_NT_HEADERS Nt = (PIMAGE_NT_HEADERS)(ptr + Dos->e_lfanew); printf("Signature:%x\n", Nt->Signature); printf("FileHeader:%x\n", Nt->FileHeader); printf("OptionalHeader:%x\n", Nt->OptionalHeader); PIMAGE_FILE_HEADER File = (PIMAGE_FILE_HEADER)(ptr + Dos->e_lfanew + 4); printf("Machine:%x\n", File->Machine); printf("NumberOfSections:%x\n", File->NumberOfSections); printf("Characteristics:%x\n",File->Characteristics); printf("TimeDateStamp:%x\n", File->TimeDateStamp); printf("SizeOfOptionalHeader:%x\n", File->SizeOfOptionalHeader); printf("TimeDateStamp:%x\n",File->TimeDateStamp); PIMAGE_OPTIONAL_HEADER32 Option=(PIMAGE_OPTIONAL_HEADER32)(ptr + Dos->e_lfanew + 20+4); printf("AddressOfEntryPoint:%x\n",Option->AddressOfEntryPoint); printf("BaseOfCode:%x\n",Option->BaseOfCode); printf("BaseOfData:%x\n",Option->BaseOfData); printf("FileAlignment:%x\n",Option->FileAlignment); printf("SectionAlignment:%x\n",Option->SectionAlignment); printf("ImageBase:%x\n",Option->ImageBase); printf("Magic:%x\n",Option->Magic); printf("SizeOfCode:%x\n",Option->SizeOfCode); printf("SizeOfHeaders:%x\n",Option->SizeOfHeaders); printf("SizeOfImage:%x\n",Option->SizeOfImage); printf("SizeOfInitializedData:%x\n",Option->SizeOfInitializedData); printf("SizeOfUninitializedData:%x\n",Option->SizeOfUninitializedData); getchar(); }
PIMAGE_OPTIONAL_HEADER32 Option=(PIMAGE_OPTIONAL_HEADER32)(ptr + Dos->e_lfanew + 20+4);
還是定義一個結構體指標,然後填充結構體指標指向的結構體,我們們主要看怎麼找到Option頭的,(ptr + Dos->e_lfanew + 20+4);
還是先透過基址加Dos頭成員e_lfanew找到nt頭在加Nt頭成員Signature的大小找到File頭這裡加上File頭的大小就可以定位到Option頭。
程式執行結果
0x6結語
主要是介紹了Dos頭,Nt頭,File頭,Option頭的一些比較重要的成員,和如何定位到它們,涉及到指標與結構體相關操作。
由於作者水平有限,文章如有錯誤歡迎指出。
相關文章
- PE檔案結構解析32022-05-30
- PE檔案結構解析22022-05-23
- PE 檔案結構圖2023-09-05
- PE檔案結構複習2020-11-11
- PE檔案結構(五)基址重定位2014-10-07
- PE檔案結構(四) 輸出表2014-10-06
- 再探.NET的PE檔案結構(安全篇)2019-05-11
- PE檔案結構(二) 區塊,檔案偏移與RVA轉換2014-10-02
- PE檔案格式詳細解析(一)2020-03-08
- PE檔案格式詳細解析(二)--IAT2020-03-08
- PE檔案格式2015-11-15
- 深入解析Class類檔案的結構2019-03-24
- PE結構分析(二)2021-04-24
- 深入剖析PE檔案2014-09-25
- 手工構造一個超微型的 PE 檔案 (轉)2007-08-17
- SQLite3資料庫檔案結構解析2024-06-26SQLite資料庫
- PE結構體中匯出表/匯入表解析——初階2018-01-27結構體
- PE教程2: 檢驗PE檔案的有效性2015-11-15
- Java虛擬機器,類檔案結構深度解析2019-05-14Java虛擬機
- oracle體系結構梳理---redo和undo檔案解析2015-01-26Oracle
- 類檔案結構_class類檔案的的結構2018-04-11
- PE檔案格式的RVA概念2008-04-14
- PE檔案格式詳細解析(六)-- 基址重定位表(Base Relocation Table)2020-03-10
- 羽夏殼世界—— PE 結構(上)2022-04-10
- BMP檔案結構2013-12-20
- Xamarin XAML語言教程XAML檔案結構與解析XAML2017-04-13
- 初步瞭解PE檔案格式(上)2017-07-03
- win32 PE 檔案格式 (轉)2007-12-15Win32
- windows載入PE檔案的流程2024-09-08Windows
- PE結構各欄位偏移參考2013-04-24
- office檔案格式複合文件二進位制結構解析2019-12-16
- jmeter基礎之目錄結構解析及配置檔案修改2020-12-08JMeter
- Class類檔案結構2017-08-18
- wsdl檔案結構分析2011-06-16
- Linux檔案結構2011-06-19Linux
- 控制檔案的結構2010-07-10
- BMP檔案結構 (轉)2007-12-12
- oracle結構梳理---歸檔檔案2015-01-28Oracle