初識PE文件
0x00 前言
PE(Portable Executable),即可移植的執行體。
Linux平台:ELF(Executable and Linking Format)文件結構。
一般在Windows平台下,所有的可執行文件諸如:exe、dll、sys、ocx、com等均適用PE文件結構。這些使用PE文件結構也被稱為PE文件。
0x01 PE 結構
PE 結構是由若干個復雜的結構體組合而成的,不是單單的一個結構體那么簡單,它的結構就像文件系統的結構是由多個結構體組成的。
PE 結構包含的結構體有 DOS 頭、PE 標識、文件頭、可選頭、目錄結構、節表等。
Windows下如何判斷文件是否是PE文件?
1.通過導入文件到c32asm等工具,觀察MZ頭。
2.通過lordpe等工具。
從 數據管理的角度來看,可以把 PE 文件大致分為兩部分,DOS 頭、PE 頭和節表屬於 PE 文件的數據管理結構或數據組織結構部分,而節表數據才是 PE 文件真正的數據部分,其中包含着代碼、數據、資源等內容。
從PE結構圖中可以看出,PE 結構主要分為 4 大部分(DOS頭、PE頭、節表、節表數據),其中每個部分又進行了細分,存在若干個小的部分。
DOS頭:
無論是32位或64位可執行文件,其文件的頭部必定是IMAGE_DOS_HEADER.
IMAGE_DOS_HEADER 數據結構定義如下:
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;
而DOS頭又分兩部分:
MZ文件頭和Dos Stub。
MZ文件頭:IMAGE_DOS_HEADER 結構體,其大小占64個字節,並且該結構中的最后一個LONG類型e_lfanew
成員指向PE文件頭的位置為中的PE文件頭標志的地址。
這里有兩個比較有用的成員信息:
1、e_magic,用於判斷PE文件的標識。如果不是MZ即不是十六進制值:0x5A4D。計算機存儲順序是低位在前高位在后,所以存儲為:0x4D5A。
2、e_lfanew,這里是指pe的偏移量,用於找到pe頭的位置。
如下陰影區域:
DOS stub:dos存根,在IMAGE_DOS_HEADER和IMAGE_NT_HEADERS之間存在一DOS存根,這其實是一段匯編代碼:
PE文件是運行在32位或64位操作系統下的。
其功能是當該EXE運行在16位環境下,輸出一段文字:“This program cannot be run in DOS mode”,然后並退出該進程。
在pe文件利用的時候,我們可以把payload寫入到當前區域,諸如存放我們的shellcode,在讀取時,獲取dos頭字節數,減去MZ頭字節數,即為dos存根字節大小。然后拿去操作加載shellcode等。
PE頭:
在MS-DOS頭下main,就是PE頭,PE頭是PE相關結構NT映像頭(IMAGE_NT_HEADER)的簡稱,其中包含許多PE裝載器用到的重要字段。
IMAGE_NT_HEADER數據結構定義:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
參數具體含義:
Signature
將文件標識為 PE 映像的 4 字節簽名。字節為“PE\0\0”。這個字段是PE文件的標志字段,通常設置成00004550h,其ASCII碼為PE00,這個字段是PE文件頭的開始,前面的DOS_HEADER結構中的字段e_lfanew字段就是指向這里。
FileHeader
指定文件頭的 IMAGE_FILE_HEADER結構。
IMAGE_FILE_HEADER結構
這個字段也是包含幾個字段結構,它包含了PE文件的一些基本信息,最重要的是其中一個域指出了IMAGE_OPTIONAL_HEADER的大小。
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;//運行平台
WORD NumberOfSections;//文件的區塊數目
DWORD TimeDateStamp;//文件創建的用時間戳標識的日期
DWORD PointerToSymbolTable;//指向符號表(用於調試)
DWORD NumberOfSymbols;//符號表中符號的個數
WORD SizeOfOptionalHeader;//IMAGE_OPTIONAL_HEADER32結構大小
WORD Characteristics;//文件屬性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
在PE文件頭的后面,1234567個框分別對應_IMAGE_FILE_HEADER結構的七個參數位置以及各自的值。我們需要判斷運行平台,就可以通過第一個參數位置的值來判斷。
上述e_lfanew中,可以在下圖中看到,e_lfanew的值為0080,這里可以看到PE頭就在0080h。
常見標識如下,比如這里的014c,就是在Intel I386機器上運行。
機器 標識
Intel I386 14ch
MIPS R3000 162h
Alpha AXP 184h
Power PC 1F0h
MIPS R4000 184h
2)NumberOfSection,標識區塊的數目,關於區塊后面會詳細講。
3)TimeDateStamp
這個字段沒啥好說的,指的就是PE文件創建的事件,這個時間是指從1970年1月1日到創建該文件的所有的秒數。
4)PointerToSymbolTable。這個字段用的比較少,略
5)NumberOfSymbol。這個字段也用得很少,略
6)SizeOfOptionalHeader:緊跟着IMAGE_FILE_HEADER后面的數據大小,這也是一個數據結構,它叫做IMAGE_OPTIONAL_HEADER,其大小依賴於是64位還是32位文件。32位文件值通常是00EOh,對於64位值通常為00F0h。
7)Characteristics:文件屬性,普通EXE文件這個字段值為010fh,DLL文件這個字段一般是0210h。
IMAGE_OPTIONAL_HEADER結構
OptionalHeader
指定可選文件頭的 IMAGE_OPTIONAL_HEADER結構。
這個結構是IMAGE_FILE_HEADER結構的補充。這兩個結構合起來才能對整個PE文件頭進行描述。左邊的16位字符表示相對於文件頭的偏移量。
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 DWORD DataDirctory[16]; // ********* 數據目錄表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
這里總共31個字段。通常用的就是加*字段。
字段6:AddressOfEntryPoint 表 程序入口RVA,即OEP:
``EOP:程序入口點,殼相關概念
``OEP:原本的程序入口點(實際為偏移,+模塊基址=實際入口點)
``EP: 被加工后的入口點
字段9:ImageBase 表 模塊加載基地址,exe默認0x400000,dll默認0x10000000
``建議裝載地址:exe映射加載到內存中的首地址= PE 0處,即實例句柄hInstance
``一般而言,exe文件可遵從裝載地址建議,但dll文件無法滿足
尾字段:DataDirectory 表 數據目錄表,用來定義多種不通用處的數據塊。
``存儲了PE中各個表的位置,詳情參考IMAGE_DIRECTORY_ENTRY...系列宏
這里可以知道我們確定的PE文件頭在0080h處,通過偏移計算,我們可以得到IMAGE_OPTIONAL_HEADER結構的的首個字段在80h+18h=98h的地方。這里直接通過c32asm跳轉到對應位置。如圖所示:
然后比較重要的就是最后一個成員,即數據目錄表,大小為16,每個元素都是一個IMAGE_DATA_DIRECTORY結構體,這里看到是一個數組類型。
它的定義如下:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //所指向的數據結構的虛擬地址
DWORD Size; //數據結構的大小
} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;
在winnt.h中的定義:
#define IMAGE_DIRECTORY_ENTRY_EXPORT //0 導出表
#define IMAGE_DIRECTORY_ENTRY_IMPORT //1 導入表
#define IMAGE_DIRECTORY_ENTRY_RESOURCE //2 資源目錄
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION //3 異常目錄
#define IMAGE_DIRECTORY_ENTRY_SECURITY //4 安全目錄
#define IMAGE_DIRECTORY_ENTRY_BASERELOC //5 重定位基本表
#define IMAGE_DIRECTORY_ENTRY_DEBUG //6 調試目錄
#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT //7 描術字串
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR //8 機器值
#define IMAGE_DIRECTORY_ENTRY_TLS //9 TLS目錄
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG //10 載入配值目錄
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT //11 綁定輸入表
#define IMAGE_DIRECTORY_ENTRY_IAT //12 導入地址表
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT //13 延遲載入描述
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR //14 COM信息
在這個數據目錄結構體中只有兩個成員VirtualAddress
和Size
,這兩個成員的含義比較簡單,VirtualAddress指定了數據塊的相對虛擬地址(RVA)。Size則指定了該數據塊的大小,有時並不是該類型數據的總大小,可能只是該類型數據一個數據項的大小。這兩個成員(主要是VirtualAddress)成為了定位各種表的關鍵,所以一定要知道每個數組元素所指向的數據塊類型,以下表格就是它的對應關系:
下面是DataDirctory[16]即數據目錄表的各個成員
索 引 | 索引值在Windows.inc中的預定義值 | 對應的數據塊 | 偏移量 |
---|---|---|---|
0 | IMAGE_DIRECTORY_ENTRY_EXPORT | 導出表 | 78h |
1 | IMAGE_DIRECTORY_ENTRY_IMPORT | 導入表 | 80h |
2 | IMAGE_DIRECTORY_ENTRY_RESOURCE | 資源 | 88h |
3 | IMAGE_DIRECTORY_ENTRY_EXCEPTION | 異常(具體資料不詳) | 90h |
4 | IMAGE_DIRECTORY_ENTRY_SECURITY | 安全(具體資料不詳) | 98h |
5 | IMAGE_DIRECTORY_ENTRY_BASERELOC | 重定位表 | A0h |
6 | IMAGE_DIRECTORY_ENTRY_DEBUG | 調試信息 | A8h |
7 | IMAGE_DIRECTORY_ENTRY_ARCHITECTURE | 版權信息 | B0h |
8 | IMAGE_DIRECTORY_ENTRY_GLOBALPTR | 具體資料不詳 | B8h |
9 | IMAGE_DIRECTORY_ENTRY_TLS | Thread Local Storage | C0h |
10 | IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG | 具體資料不詳 | C8h |
11 | IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT | 具體資料不詳 | D0h |
12 | IMAGE_DIRECTORY_ENTRY_IAT | 導入函數地址表 | D8h |
13 | IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT | 具體資料不詳 | E0h |
14 | IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR | 具體資料不詳 | E8h |
15 | 未使用 | 保留 |
對於安全人員來說,通常需要了解比較重要的導出表和導入表。這里放在下篇學習。
0x03 參考
https://www.cnblogs.com/a-s-m/p/12251728.html