PE檔案解析 基礎篇
阿新 • • 發佈:2018-11-09
PE檔案解析 基礎篇
來源 https://bbs.pediy.com/thread-247114.htm
前言
- 之前學習了PE格式,為了更好的理解,決定寫一個類似LoadPE的小工具。
- 編譯器是VS2015,採用MFC框架。
- 此係列文章採用邊介紹知識點,邊寫程式碼的形式,以免變的無聊喪失興趣。
- PE知識請參照《加密與解密》第10章
PE檔案格式
1.PE檔案基本概念
- PE檔案是windows系統中遵循PE結構的檔案,比如以.exe .dll為字尾名的檔案 以及系統驅動檔案。(PE結構框架看下圖)
- PE檔案從磁碟當中像記憶體中的對映,不是簡單的“1對1”的關係,而是“拉長”了。具體的位置表現在塊。 但是磁碟上的資料結構與在記憶體中的結構是一致的。
- 無論PE檔案在磁碟中還是在記憶體中,都少不了地址的概念,理解一下幾個概念至關重要。
的偏移量,所以你可以發現前三個概念的關係 : 虛擬地址(VA)= 基地址+ 相對虛擬地址(RVA) 檔案偏移地址(FOA):當PE檔案儲存在某個磁碟當中的時候,某個資料的位置相對於檔案頭的偏移量。 入口點(OEP):首先明確一個概念就是OEP是一個RVA,,然後使用 OEP + Imagebase == 入口點的VA,通常情況下,OEP指向的不是main函式。
- 存了張圖 比較好的解釋了各部分的關係
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
(最左邊是檔案頭的偏移量。)
IMAGE_DOS_HEADER STRUCT
{
+0h
WORD
e_magic
// MZ(4Dh 5Ah) DOS可執行檔案標記
+2h
WORD
e_cblp
+4h
WORD
e_cp
+6h
WORD
e_crlc
+8h
WORD
e_cparhdr
+0ah
WORD
e_minalloc
+0ch
WORD
e_maxalloc
+0eh
WORD
e_ss
+10h
WORD
e_sp
+12h
WORD
e_csum
+14h
WORD
e_ip
+16h
WORD
e_cs
+18h
WORD
e_lfarlc
+1ah
WORD
e_ovno
+1ch
WORD
e_res[4]
+24h
WORD
e_oemid
+26h
WORD
e_oeminfo
+29h
WORD
e_res2[10]
+3ch
DWORD
e_lfanew
// RVA 指向PE檔案頭
} IMAGE_DOS_HEADER ENDS
|
3.PE檔案頭
IMAGE_NT_HEADERS STRUCT 結構體1 2 3 4 5 6 |
IMAGE_NT_HEADERS STRUCT
{
+0h
DWORD
Signature
+4h IMAGE_FILE_HEADER FileHeader
+18h IMAGE_OPTIONAL_HEADER32 OptionalHeader
} IMAGE_NT_HEADERS ENDS
|
- Signature 欄位
- IMAGE_FILE_HEADER 結構體
1 2 3 4 5 6 7 8 9 10 |
struct
IMAGE_FILE_HEADER
{
WORD
Machine;
//執行平臺
WORD
NumberOfSections;
//區塊表的個數
DWORD
TimeDataStamp;
//檔案建立時間,是從1970年至今的秒數
DWORD
PointerToSymbolicTable;
//指向符號表的指標
DWORD
NumberOfSymbols;
//符號表的數目
WORD
SizeOfOptionalHeader;
//IMAGE_NT_HEADERS結構中OptionHeader成員的大小,對於win32平臺這個值通常是0x00e0
WORD
Characteristics;
//檔案的屬性值
}
|
- IMAGE_OPTIONAL_HEADER 結構體
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
typedef
struct
_IMAGE_OPTIONAL_HEADER
{
//
// Standard fields.
//
+18h
WORD
Magic;
// 標誌字, ROM 映像(0107h),普通可執行檔案(010Bh)
+1Ah
BYTE
MajorLinkerVersion;
// 連結程式的主版本號
+1Bh
BYTE
MinorLinkerVersion;
// 連結程式的次版本號
+1Ch
DWORD
SizeOfCode;
// 所有含程式碼的節的總大小
+20h
DWORD
SizeOfInitializedData;
// 所有含已初始化資料的節的總大小
+24h
DWORD
SizeOfUninitializedData;
// 所有含未初始化資料的節的大小
+28h
DWORD
AddressOfEntryPoint;
// 程式執行入口RVA
+2Ch
DWORD
BaseOfCode;
// 程式碼的區塊的起始RVA
+30h
DWORD
BaseOfData;
// 資料的區塊的起始RVA
//
// NT additional fields. 以下是屬於NT結構增加的領域。
//
+34h
DWORD
ImageBase;
// 程式的首選裝載地址
+38h
DWORD
SectionAlignment;
// 記憶體中的區塊的對齊大小
+3Ch
DWORD
FileAlignment;
// 檔案中的區塊的對齊大小
+40h
WORD
MajorOperatingSystemVersion;
// 要求作業系統最低版本號的主版本號
+42h
WORD
MinorOperatingSystemVersion;
// 要求作業系統最低版本號的副版本號
+44h
WORD
MajorImageVersion;
// 可運行於作業系統的主版本號
+46h
WORD
MinorImageVersion;
// 可運行於作業系統的次版本號
+48h
WORD
MajorSubsystemVersion;
// 要求最低子系統版本的主版本號
+4Ah
WORD
MinorSubsystemVersion;
// 要求最低子系統版本的次版本號
+4Ch
DWORD
Win32VersionValue;
// 莫須有欄位,不被病毒利用的話一般為0
+50h
DWORD
SizeOfImage;
// 映像裝入記憶體後的總尺寸
+54h
DWORD
SizeOfHeaders;
// 所有頭 + 區塊表的尺寸大小
+58h
DWORD
CheckSum;
// 映像的校檢和
+5Ch
WORD
Subsystem;
// 可執行檔案期望的子系統
+5Eh
WORD
DllCharacteristics;
// DllMain()函式何時被呼叫,預設為 0
+60h
DWORD
SizeOfStackReserve;
// 初始化時的棧大小
+64h
DWORD
SizeOfStackCommit;
// 初始化時實際提交的棧大小
+68h
DWORD
SizeOfHeapReserve;
// 初始化時保留的堆大小
+6Ch
DWORD
SizeOfHeapCommit;
// 初始化時實際提交的堆大小
+70h
DWORD
LoaderFlags;
// 與除錯有關,預設為 0
+74h
DWORD
NumberOfRvaAndSizes;
// 下邊資料目錄的項數,這個欄位自Windows NT 釋出以來一直是16
+78h IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
// 資料目錄表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
|
1 2 3 4 |
IMAGE_DATA_DIRECTORY STRUCT
VirtualAddress
DWORD
? ; 資料的起始RVA
Size
DWORD
? ; 資料塊的長度
IMAGE_DATA_DIRECTORY ENDS
|
4.寫程式碼操作一下
主要解析了DOS頭與PE檔案頭比較重要的欄位,直接放程式碼。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
//開啟檔案
m_hFile = CreateFile(
m_DeleFileName,GENERIC_READ,NULL,NULL,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,NULL);
DWORD
dwSize = GetFileSize(m_hFile, NULL);
PBYTE
pBuf =
new
BYTE
[dwSize]{};
//讀取
ReadFile(m_hFile,pBuf,dwSize,&dwSize,NULL);
//判斷是否為PE檔案
m_pDos = PIMAGE_DOS_HEADER(pBuf);
if
(m_pDos->e_magic!=IMAGE_DOS_SIGNATURE)
{
MessageBox(L
"不是有效的PE檔案 \n"
);
CloseHandle(m_hFile);
m_hFile = NULL;
return
;
}
m_pNTHeader = PIMAGE_NT_HEADERS(pBuf+m_pDos->e_lfanew);
if
(m_pNTHeader->Signature!= IMAGE_NT_SIGNATURE)
{
MessageBox(L
"不是有效的PE檔案 \n"
);
CloseHandle(m_hFile);
m_hFile = NULL;
return
;
}
//讀取檔案頭資訊
m_pFileHeader = &(m_pNTHeader->FileHeader);
m_NumberOfSections.Format(L
"%X"
,m_pFileHeader->NumberOfSections);
m_TimeDateStamp.Format(L
"%p"
, m_pFileHeader->TimeDateStamp);
m_SizeOfOptionalHeader.Format(L
"%X"
, m_pFileHeader->SizeOfOptionalHeader);
//拓展頭資訊
m_pOptionalHeader = &(m_pNTHeader->OptionalHeader);
m_AddressOfEntryPoint.Format(L
"%X"
,m_pOptionalHeader->AddressOfEntryPoint);
m_SizeOfHeaders.Format(L
"%X"
, m_pOptionalHeader->SizeOfHeaders);
m_ImageBase.Format(L
"%X"
, m_pOptionalHeader->ImageBase);
m_SizeOfImage.Format(L
"%X"
, m_pOptionalHeader->ImageBase);
m_BaseOfCode.Format(L
"%X"
, m_pOptionalHeader->BaseOfCode);
m_DllCharacteristics.Format(L
"%X"
, m_pOptionalHeader->DllCharacteristics);
m_BaseOfData.Format(L
"%X"
, m_pOptionalHeader->BaseOfData);
m_NumberOfRvaAndSizes.Format(L
"%X"
, m_pOptionalHeader->NumberOfRvaAndSizes);
m_SectionAlignment.Format(L
"%X"
, m_pOptionalHeader->SectionAlignment);
m_FileAlignment.Format(L
"%X"
, m_pOptionalHeader->FileAlignment);
m_CheckSum.Format(L
"%X"
, m_pOptionalHeader->CheckSum);
m_Magic.Format(L
"%X"
, m_pOptionalHeader->CheckSum);
m_Subsystem.Format(L
"%X"
, m_pOptionalHeader->Subsystem);
|
============== End