您好,登錄后才能下訂單哦!
這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)PE文件結(jié)構(gòu)是怎樣的,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
1、PE文件的結(jié)構(gòu)
1、什么是可執(zhí)行文件?
可執(zhí)行文件 (executable file) 指的是可以由操作系統(tǒng)進(jìn)行加載執(zhí)行的文件。
可執(zhí)行文件的格式:
-Windows平臺(tái):PE(Portable Executable)文件結(jié)構(gòu)
-Linux平臺(tái):ELF(Executable and Linking Format)文件結(jié)構(gòu)
PE和ELF非常相似,它們都是源于同一種可執(zhí)行文件格式 COFF
-COFF 是由Unix System V Release 3首先提出并且使用的格式規(guī)范,
-微軟基于COFF格式,制定了PE格式標(biāo)準(zhǔn),并將其用于當(dāng)時(shí)的Windows NT系統(tǒng)
-System V Release 4在COFF的基礎(chǔ)上引入了ELF格式。
事實(shí)上,在Windows平臺(tái),VISUAL C++編譯器產(chǎn)生的目標(biāo)文件仍然使用COFF格式,而可執(zhí)行文件為PE格式
微軟對(duì)64位Windows平臺(tái)上的PE文件結(jié)構(gòu)叫做PE32+,就是把那些原來(lái)32位的字段變成了64位。
2、PE文件的特征
識(shí)別一個(gè)文件是不是PE文件不應(yīng)該只看文件后綴名,還應(yīng)該通過(guò)PE指紋
使用UE打開(kāi)一個(gè)exe文件,發(fā)現(xiàn)文件的頭兩個(gè)字節(jié)都是MZ,0x3C位置保存著一個(gè)地址,查該地址處發(fā)現(xiàn)保存著“PE”,這樣基本可以認(rèn)定改文件是一個(gè)PE文件
通過(guò)這些重要的信息(“MZ”和“PE”)驗(yàn)證文件是否為PE文件,這些信息即PE指紋。
3、PE文件的整體結(jié)構(gòu)
這里將一個(gè)PE文件的主要部分列為4部分,這里可以先有模糊概念,后面會(huì)詳細(xì)解釋
“節(jié)”或“塊”或”區(qū)塊“都是一個(gè)意思,后文會(huì)穿插使用
下面從二進(jìn)制層面整體把握其結(jié)構(gòu),看看一個(gè)PE文件的組成
4、PE文件到內(nèi)存的映射
PE文件存儲(chǔ)在磁盤(pán)時(shí)的結(jié)構(gòu)和加載到內(nèi)存后的結(jié)構(gòu)有所不同。
當(dāng)PE文件通過(guò)Windows加載器載入內(nèi)存后,內(nèi)存中的版本稱為模塊(Module)。
映射文件的起始地址稱為模塊句柄(hModule),也稱為基地址(ImageBase)。
(模塊句柄是不是和其他句柄不太一樣呢?)
文件數(shù)據(jù)一般512字節(jié)(1扇區(qū))對(duì)齊(現(xiàn)也多4k),32位內(nèi)存一般4k(1頁(yè))對(duì)齊,512D = 200H,4096D = 1000H
文件中塊的大小為200H的整數(shù)倍,內(nèi)存中塊的大小為1000H的整數(shù)倍,映射后實(shí)際數(shù)據(jù)的大小不變,多余部分可用0填充
PE文件頭部(DOS頭+PE頭)到塊表之間沒(méi)有間隙,然而他們卻和塊之間有間隙,大小取決于對(duì)齊參數(shù)
VC編譯器默認(rèn)編譯時(shí),exe文件基地址是0x400000,DLL文件基地址是0x10000000
VA:虛擬內(nèi)存地址
RVA:相對(duì)虛擬地址即相對(duì)于基地址的偏移地址
FOA: 文件偏移地址
5、DOS部分
DOS MZ文件頭實(shí)際是一個(gè)結(jié)構(gòu)體(IMAGE_DOS_HEADER),占64字節(jié)
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頭用于16位系統(tǒng)中,在32位系統(tǒng)中DOS頭成為冗余數(shù)據(jù),但還存在兩個(gè)重要成員e_magic字段(偏移 0x0)和 e_lfanew字段(偏移 0x3C)
e_magic保存“MZ”字符,e_lfanew保存PE文件頭地址,通過(guò)這個(gè)地址找到PE文件頭,得到PE文件標(biāo)識(shí)“PE”。
e_magic和e_lfanew是驗(yàn)證PE指紋的重要字段,其他字段現(xiàn)基本不使用(可填充任意數(shù)據(jù))
“DOS Stub”區(qū)域的數(shù)據(jù)由鏈接器填充(可自己填充如意數(shù)據(jù)),是一段可以在DOS下運(yùn)行的一小段代碼,
這段代碼的唯一作用是向終端輸出一行字:“This program cannot be run in DOS”(“e_cs”和“e_ip”指向)
然后退出程序,表示該程序不能在DOS下運(yùn)行。
6、PE文件頭(PE Header)
PE文件頭是一個(gè)結(jié)構(gòu)體(IMAGE_NT_HEADERS32),里面還包含兩個(gè)其它結(jié)構(gòu)體,占用4B + 20B + 224B
typedef struct _IMAGE_NT_HEADERS { DWORD Signature; // PE文件標(biāo)識(shí) 4Bytes IMAGE_FILE_HEADER FileHeader; // 40 Bytes IMAGE_OPTIONAL_HEADER32 OptionalHeader; // 224 Bytes PE32可執(zhí)行文件,不討論P(yáng)E32+的情況} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
Signature字段設(shè)置為0x00004550,ANCII碼字符是“PE00”,標(biāo)識(shí)PE文件頭的開(kāi)始,PE標(biāo)識(shí)不能破壞。
1、IMAGE_FILE_HEADER結(jié)構(gòu)體
IMAGE_FILE_HEADER(映像文件頭或標(biāo)準(zhǔn)PE頭)結(jié)構(gòu)包含PE文件的一些基本信息,該結(jié)構(gòu)在微軟的官方文檔中被稱為標(biāo)準(zhǔn)通用對(duì)象文件格式(Common Object File Format,COFF)頭
typedef struct _IMAGE_FILE_HEADER { WORD Machine; // 可運(yùn)行在什么樣的CPU上。0代表任意,Intel 386及后續(xù):0x014C, x64: 0x8664 WORD NumberOfSections; // 文件的區(qū)塊(節(jié))數(shù) DWORD TimeDateStamp; // 文件的創(chuàng)建時(shí)間。1970年1月1日以GMT計(jì)算的秒數(shù),編譯器填充的,不重要的值 DWORD PointerToSymbolTable; // 指向符號(hào)表(用于調(diào)試) DWORD NumberOfSymbols; // 符號(hào)表中符號(hào)的個(gè)數(shù)(用于調(diào)試) WORD SizeOfOptionalHeader; // IMAGE_OPTIONAL_HEADER32結(jié)構(gòu)的大小,可改變,32位為E0,64位為F0 WORD Characteristics; // 文件屬性} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
重要字段:NumberOfSections,SizeOfOptionalHeader
對(duì)應(yīng)結(jié)構(gòu)為下圖紫線部分
0x014C說(shuō)明運(yùn)行于x86 CPU;0x0007說(shuō)明當(dāng)前exe有7個(gè)節(jié);
0x00E0說(shuō)明IMAGE_OPTIONAL_HEADER32為224字節(jié);
0x030F(0000 0011 0000 1111)代表文件屬性 ,由下列對(duì)應(yīng)位為1的組合
2、IMAGE_OPTIONAL_HEADER結(jié)構(gòu)體
IMAGE_OPTIONAL_HEADER(可選映像頭或擴(kuò)展PE頭)是一個(gè)可選的結(jié)構(gòu),是IMAGE_FILE_HEADER結(jié)構(gòu)的擴(kuò)展
大小由IMAGE_FILE_HEADER結(jié)構(gòu)的SizeOfOptionalHeader字段記錄(可能不準(zhǔn)確)
typedef struct _IMAGE_OPTIONAL_HEADER { // // Standard fields. // WORD Magic; //說(shuō)明文件的類型 PE32:10BH PE32+:20BH Rom映像文件:107H BYTE MajorLinkerVersion; //鏈接器主版本號(hào) BYTE MinorLinkerVersion; //鏈接器次版本號(hào) DWORD SizeOfCode; //所有代碼節(jié)的總和(基于文件對(duì)齊) 編譯器填的 沒(méi)用 DWORD SizeOfInitializedData; //包含所有已經(jīng)初始化數(shù)據(jù)的節(jié)的總大小 編譯器填的 沒(méi)用 DWORD SizeOfUninitializedData;//包含未初始化數(shù)據(jù)的節(jié)的總大小 編譯器填的 沒(méi)用 DWORD AddressOfEntryPoint; //程序入口RVA 在大多數(shù)可執(zhí)行文件中,這個(gè)地址不直接指向Main、WinMain或DIMain函數(shù),而指向運(yùn)行時(shí)的庫(kù)代碼并由它來(lái)調(diào)用上述函數(shù) DWORD BaseOfCode; //代碼起始RVA,編譯器填的 沒(méi)用 DWORD BaseOfData; //數(shù)據(jù)段起始RVA,編譯器填的 沒(méi)用 // // NT additional fields. // DWORD ImageBase; //內(nèi)存鏡像基址 ,可鏈接時(shí)自己設(shè)置 DWORD SectionAlignment; //內(nèi)存對(duì)齊 一般一頁(yè)大小4k DWORD FileAlignment; //文件對(duì)齊 一般一扇區(qū)大小512字節(jié),現(xiàn)在也多4k WORD MajorOperatingSystemVersion; //標(biāo)識(shí)操作系統(tǒng)版本號(hào) 主版本號(hào) WORD MinorOperatingSystemVersion; //標(biāo)識(shí)操作系統(tǒng)版本號(hào) 次版本號(hào) WORD MajorImageVersion; //PE文件自身的主版本號(hào) WORD MinorImageVersion; //PE文件自身的次版本號(hào) WORD MajorSubsystemVersion; //運(yùn)行所需子系統(tǒng)主版本號(hào) WORD MinorSubsystemVersion; //運(yùn)行所需子系統(tǒng)次版本號(hào) DWORD Win32VersionValue; //子系統(tǒng)版本的值,必須為0 DWORD SizeOfImage; //內(nèi)存中整個(gè)PE文件的映射的尺寸,可比實(shí)際的值大,必須是SectionAlignment的整數(shù)倍 DWORD SizeOfHeaders; //所有頭+節(jié)表按照文件對(duì)齊后的大小,否則加載會(huì)出錯(cuò) DWORD CheckSum; //校驗(yàn)和,一些系統(tǒng)文件有要求.用來(lái)判斷文件是否被修改 WORD Subsystem; //子系統(tǒng)驅(qū)動(dòng)程序(1) 圖形界面(2) 控制臺(tái)、DLL(3) WORD DllCharacteristics; //文件特性 不是針對(duì)DLL文件的 DWORD SizeOfStackReserve; //初始化時(shí)保留的棧大小 DWORD SizeOfStackCommit; //初始化時(shí)實(shí)際提交的大小 DWORD SizeOfHeapReserve; //初始化時(shí)保留的堆大小 DWORD SizeOfHeapCommit; //初始化時(shí)保留的堆大小 DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; //數(shù)據(jù)目錄項(xiàng)數(shù)目 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //數(shù)據(jù)目錄表} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
重要字段:
AddressOfEntryPoint:程序入口地址(RVA),下圖為32C40H
ImageBase:內(nèi)存鏡像基地址,下圖為400000H
FileAlignment:文件對(duì)齊,下圖為200H
SectionAlignment:內(nèi)存對(duì)齊,下圖為1000H
DataDirectory[16]:數(shù)據(jù)目錄表,由數(shù)個(gè)相同的IMAGE_DATA_DIRECTORY結(jié)構(gòu)組成,
指向輸出表、輸入表、資源塊,重定位表等(后面詳解這里先跳過(guò))
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; //對(duì)應(yīng)表的起始RVA DWORD Size; //對(duì)應(yīng)表長(zhǎng)度} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
ImageBase+AddressOfEntryPoint = 程序?qū)嶋H運(yùn)行入口地址(實(shí)際加載地址等于ImageBase)
0x400000 +0x32C40 = 0x432C40 (使用OD運(yùn)行程序發(fā)現(xiàn)就是從這個(gè)地址開(kāi)始運(yùn)行)
應(yīng)用:在PE文件空白區(qū)添加代碼,讓程序執(zhí)行先執(zhí)行添加的代碼再跳轉(zhuǎn)程序入口
思路:
① 在PE的空白區(qū)構(gòu)造一段代碼(call -> E8)
② 修改入口地址為新增代碼(IMAGE_OPTIONAL_HEADER.AddressOfEntryPoint)
③ 新增代碼執(zhí)行后,跳回入口地址(jmp -> E9)
7、塊表
塊表是一個(gè)IMAGE_SECTION_HEADER的結(jié)構(gòu)數(shù)組,每個(gè)IMAGE_SECTION_HEADER結(jié)構(gòu)40字節(jié)。
每個(gè)IMAGE_SECTION_HEADER結(jié)構(gòu)包含了它所關(guān)聯(lián)的區(qū)塊的信息,例如位置、長(zhǎng)度、屬性。
#define IMAGE_SIZEOF_SHORT_NAME 8 typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //塊名。多數(shù)塊名以一個(gè)“.”開(kāi)始(例如.text),這個(gè)“.”不是必需的 union { DWORD PhysicalAddress; //常用第二個(gè)字段 DWORD VirtualSize; //加載到內(nèi)存實(shí)際區(qū)塊的大?。▽?duì)齊前),為什么會(huì)變呢?可能是有時(shí)未初始化的全局變量不放bss段而是通過(guò)擴(kuò)展這里 } Misc; DWORD VirtualAddress; //該塊裝載到內(nèi)存中的RVA(內(nèi)存對(duì)齊后,數(shù)值總是SectionAlignment的整數(shù)倍) DWORD SizeOfRawData; //該塊在文件中所占的空間(文件對(duì)齊后),VirtualSize的值可能會(huì)比SizeOfRawData大 例如bss節(jié)(SizeOfRawData為0),data節(jié)(關(guān)鍵看未初始化的變量放哪) DWORD PointerToRawData; //該塊在文件中的偏移(FOA) /*調(diào)試相關(guān),忽略*/ DWORD PointerToRelocations; //在“.obj”文件中使用,指向重定位表的指針 DWORD PointerToLinenumbers; WORD NumberOfRelocations; //重定位表的個(gè)數(shù)(在OBJ文件中使用)。 WORD NumberOfLinenumbers; DWORD Characteristics; //塊的屬性 該字段是一組指出塊屬性(例如代碼/數(shù)據(jù)、可讀/可寫(xiě)等)的標(biāo)志} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
重要字段:Name[8],VirtualSize,VirtualAddress,SizeOfRawData,PointerToRawData,Characteristics
IMAGE_FILE_HEADER的NumberOfSections字段是不是記錄著當(dāng)前文件的節(jié)數(shù)呢?
31C80H代表載入內(nèi)存代碼塊對(duì)齊前大??;1000H代表代碼塊裝載到內(nèi)存RVA1000H;
31E00H代表文件對(duì)齊后代碼塊大??;400H代表代碼塊在文件中的偏移
60000020H代表代碼塊屬性(?0110 0000 0000 0000 0000 0000 0010 0000?)查下表得到屬性為可讀可執(zhí)行的代碼
更多屬性參考:https://docs.microsoft.com/zh-cn/windows/win32/api/winnt/ns-winnt-image_section_header
8、RVA與FOA的轉(zhuǎn)換
RVA:相對(duì)虛擬地址,F(xiàn)OA:文件偏移地址。
計(jì)算步驟:
①計(jì)算RVA= 虛擬內(nèi)存地址 - ImageBase
② 若RVA是否位于PE頭:FOA == RVA
③ 判斷RVA位于哪個(gè)節(jié):
RVA >= 節(jié).VirtualAddress (節(jié)在內(nèi)存對(duì)齊后RVA)
RVA <= 節(jié).VirtualAddress + 當(dāng)前節(jié)內(nèi)存對(duì)齊后的大小
偏移量= RVA - 節(jié).VirtualAddress;
④ FOA = 節(jié).PointerToRawData + 偏移量;
應(yīng)用舉例:
有初始值的全局變量初始值會(huì)存儲(chǔ)在PE文件中,想要修改文件中全局變量的數(shù)據(jù)值即
需要找到文件中存儲(chǔ)全局變量值的地方,然后修改即可
2、輸出表和輸入表
可選PE頭(擴(kuò)展PE頭)的最后一個(gè)字段DataDirectory[16]代表數(shù)據(jù)目錄表,由16個(gè)相同的IMAGE_DATA_DIRECTORY結(jié)構(gòu)組成,成員分別指向輸出表、輸入表、資源塊等
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; //對(duì)應(yīng)表的起始RVA DWORD Size; //對(duì)應(yīng)表大小(包含子表)} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
1、輸出表(導(dǎo)出表)創(chuàng)建一個(gè)DLL時(shí),實(shí)際上創(chuàng)建了一組能讓EXE或其他DLL調(diào)用的函數(shù)
DLL文件通過(guò)輸出表(Export Table)向系統(tǒng)提供輸出函數(shù)名、序號(hào)和入口地址等信息。
數(shù)據(jù)目錄表的第1個(gè)成員指向輸出表。
找到文件中的輸出表(以DllDemo.dll為例,看圖就行)
成功找到輸出表在文件偏移0C00H處,如下:
特別說(shuō)明:① 如果文件對(duì)齊與內(nèi)存對(duì)齊都是4k則不需要地址轉(zhuǎn)換 ② 輸出表大小是指輸出表大小與其子表大小和
輸出表實(shí)際是一個(gè)40字節(jié)的結(jié)構(gòu)體(IMAGE_EXPORT_DIRECTORY),輸出表的結(jié)構(gòu)如下
typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; //未定義,總是為0。 DWORD TimeDateStamp; //輸出表創(chuàng)建的時(shí)間(GMT時(shí)間) WORD MajorVersion; //輸出表的主版本號(hào)。未使用,設(shè)置為0。 WORD MinorVersion; //輸出表的次版本號(hào)。未使用,設(shè)置為0。 DWORD Name; //指向一個(gè)ASCII字符串的RVA。這個(gè)字符串是與這些輸出函數(shù)相關(guān)聯(lián)的DLL的名字(例如"KERNEL32.DLL") DWORD Base; //導(dǎo)出函數(shù)起始序號(hào)(基數(shù))。當(dāng)通過(guò)序數(shù)來(lái)查詢一個(gè)輸出函數(shù)時(shí),這個(gè)值從序數(shù)里被減去,其結(jié)果將作為進(jìn)入輸出地址表(EAT)的索引 DWORD NumberOfFunctions; //輸出函數(shù)地址表(Export Address Table,EAT)中的條目數(shù)量(最大序號(hào) - 最小序號(hào)) DWORD NumberOfNames; //輸出函數(shù)名稱表(Export Names Table,ENT)里的條目數(shù)量 DWORD AddressOfFunctions; // EAT的RVA(輸出函數(shù)地址表RVA) DWORD AddressOfNames; // ENT的RVA(輸出函數(shù)名稱表RVA),每一個(gè)表成員指向ANCII字符串 表成員的排列順序取決于字符串的排序 DWORD AddressOfNameOrdinals; // 輸出函數(shù)序號(hào)表RVA,每個(gè)表成員2字節(jié)} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
重要字段: Name,Base,NumberOfNames,AddressOfFunctions,AddressOfNames,AddressOfNameOrdinals
過(guò)程分析:
//功能:加載動(dòng)態(tài)鏈接庫(kù)到內(nèi)存HMODULE WINAPI LoadLibrary(LPCTSTR lpFileName //模塊的文件名);
/*功能:檢索指定的動(dòng)態(tài)鏈接庫(kù)(DLL)中的輸出庫(kù)函數(shù)地址*/FARPROC GetProcAddress(HMODULE hModule, // DLL模塊句柄 (模塊基地址)LPCSTR lpProcName // 函數(shù)名 或者 指定函數(shù)的序數(shù)值);
PE裝載器調(diào)用GetProcAddress來(lái)查找DlIDemo.DLL里的API函數(shù)MsgBox,
系統(tǒng)通過(guò)定位DlIDemo.DLL的輸出表(IMAGE_EXPORT_DIRECTORY)結(jié)構(gòu)獲得輸出函數(shù)名稱表(ENT)的起始地址,
對(duì)名字進(jìn)行二進(jìn)制查找,直到發(fā)現(xiàn)字符串“MsgBox”為止,PE裝載器發(fā)現(xiàn)MsgBox是數(shù)組的第1個(gè)條目后,加載器從輸出序數(shù)表
中讀取相應(yīng)的第1個(gè)值,這個(gè)值是MsgBox的在函數(shù)地址表(EAT)的索引。使用索引在EAT取值得到MsgBox的RVA1008h。
用1008h加DllDemo.DLL的載入地址,得到MsgBox的實(shí)際地址。
特別說(shuō)明:如果lpProcName 是序號(hào),則需要通過(guò)字段Base確定起始序號(hào),序號(hào) - Base的差值作為索引得到函數(shù)RVA地址(注意這里的序號(hào)和索引)
注意:輸出序號(hào)表存放的是索引值而不是序號(hào),真正的序號(hào)是Base+索引值
例如:寫(xiě)一個(gè)簡(jiǎn)單加法函數(shù)(int add(int a, int b)),創(chuàng)建一個(gè)A.dll
//def文件EXPORTS add @12
分析A.dll的導(dǎo)出表
當(dāng)時(shí)用序號(hào)(12)獲得函數(shù)地址時(shí)會(huì)拿12-Base = 0作為輸出函數(shù)地址表的索引值
使用A.dll
#include <iostream>#include <windows.h> using namespace std; typedef int(*lpAdd)(int, int); lpAdd myAdd; int main(){//動(dòng)態(tài)加載dll到內(nèi)存中HINSTANCE hModule = LoadLibrary("A.dll"); cout << "ImageBase: " << hModule << endl; //通過(guò)函數(shù)名獲取函數(shù)地址myAdd = (lpAdd)GetProcAddress(hModule, "add"); cout << "myAdd(10, 20) = " << myAdd(10, 20) << endl; //通過(guò)序號(hào)獲取函數(shù)地址myAdd = (lpAdd)GetProcAddress(hModule, (char*)0x0C); cout << "myAdd(10, 20) = " << myAdd(10, 20) << endl; FreeLibrary(hModule); return 0;}
2、輸入表(導(dǎo)入表)
PE 文件映射到內(nèi)存后,Windows 將相應(yīng)的 DLL文件裝入,EXE 文件通過(guò)“輸入表”找到相應(yīng)的 DLL 中的導(dǎo)入函數(shù),從而完成程序的正常運(yùn)行
數(shù)據(jù)目錄表的第2個(gè)成員指向輸入表。當(dāng)前文件依賴幾個(gè)模塊就會(huì)有幾張輸入表且是連續(xù)排放的。
如何找到輸入表?
上圖看出當(dāng)前文件只依賴一個(gè)模塊,只有一張導(dǎo)入表,如果有多個(gè)會(huì)連續(xù)存放直到連續(xù)出現(xiàn)20個(gè)0說(shuō)明結(jié)束。
輸入表實(shí)際是個(gè)20字節(jié)的結(jié)構(gòu)體 IMAGE_IMPORT_DESCRIPTOR
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; // 0 for terminating null import descriptor DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA) } DUMMYUNIONNAME; DWORD TimeDateStamp; // 0 if not bound, // -1 if bound, and real date\time stamp // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND) // O.W. date/time stamp of DLL bound to (Old BIND) DWORD ForwarderChain; // -1 if no forwarders DWORD Name; DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)} IMAGE_IMPORT_DESCRIPTOR;
重要字段:
Name:DLL(依賴模塊)名字的指針。是一個(gè)以“00”結(jié)尾的ASCII字符的RVA地址。
OriginalFirstThunk:包含指向輸入名稱表(INT)的RVA。
INT是一個(gè)IMAGE_THUNK_DATA結(jié)構(gòu)的數(shù)組,數(shù)組中的每個(gè)IMAGE_THUNK_DATA結(jié)構(gòu)都指向
IMAGE_IMPORT_BY_NAME結(jié)構(gòu),數(shù)組以一個(gè)內(nèi)容為0的IMAGE_THUNK_DATA結(jié)構(gòu)結(jié)束。
FirstThunk:包含指向輸入地址表(IAT)的RVA。IAT是一個(gè)IMAGE_THUNK_DATA結(jié)構(gòu)的數(shù)組。
IMAGE_THUNK_DATA結(jié)構(gòu)實(shí)際只占4字節(jié)
typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; // 指向一個(gè)轉(zhuǎn)向者字符串的RVA DWORD Function; // 被輸入的函數(shù)的內(nèi)存地址 DWORD Ordinal; // 被輸入的API的序數(shù) DWORD AddressOfData; // 指向IMAGE_IMPORT BY NAME } u1;} IMAGE_THUNK_DATA32;
如果IMAGE_THUNK_DATA32的最高位為1,則低31位代表函數(shù)的導(dǎo)出序號(hào),
否則4個(gè)字節(jié)是一個(gè)RVA,指向IMAGE_IMPORT_BY_NAME結(jié)構(gòu)
IMAGE_IMPORT_BY_NAME結(jié)構(gòu)字面僅有4個(gè)字節(jié),存儲(chǔ)了一個(gè)輸入函數(shù)的相關(guān)信息
typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; // 輸出函數(shù)地址表的索引(不是導(dǎo)出序號(hào)),(究竟是啥沒(méi)試驗(yàn),因?yàn)榭吹暮芏噘Y料說(shuō)是序號(hào)),不必須,鏈接器可能將其置0 CHAR Name[1]; // 函數(shù)名字字符串,以“”作為字符串結(jié)束標(biāo)志,大小不確定} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
由上圖,我們是不是通過(guò)導(dǎo)入表能夠很輕松獲得當(dāng)前文件依賴模塊的名字和函數(shù)名?
這里INT和IAT完全內(nèi)容一致,為什么呢?稍后解釋
INT和IAT內(nèi)容一致其實(shí)是PE文件未加載時(shí)的狀態(tài),
PE加載器將文件載入內(nèi)存后會(huì)向IAT填入真正的函數(shù)地址(GetProcAddress)
例如:
3、重定位表
如果PE文件不在首選的地址(ImageBase)載入,那么文件中的每一個(gè)絕對(duì)地址都需要被修正。
需要修正的地址有很多,可以在文件中使用重定位表記錄這些絕對(duì)地址的位置,在載入內(nèi)存后若載入基地址與ImageBase不同再進(jìn)行修正,若相同就不需要修正這些地址。
數(shù)據(jù)目錄項(xiàng)的第6個(gè)結(jié)構(gòu),指向重定位表(Relocation Table)
重定位表由一個(gè)個(gè)的重定位塊組成,每個(gè)塊記錄了4KB(一頁(yè))的內(nèi)存中需要重定位的地址。
每個(gè)重定位數(shù)據(jù)塊的大小必須以DWORD(4字節(jié))對(duì)齊。它們以一個(gè)IMAGE_BASE_RELOCATION結(jié)構(gòu)開(kāi)始,格式如下
typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; //記錄內(nèi)存頁(yè)的基址RVA DWORD SizeOfBlock; //當(dāng)前重定位塊結(jié)構(gòu)的大小。這個(gè)值減8就是TypeOffset數(shù)組的大小 /*下面字段可加與不加*/ /*數(shù)組每項(xiàng)大小為2字節(jié)。代表頁(yè)內(nèi)偏移,16位分為高4位和低12位。高4位代表重定位類型; 低12位是重定位地址(12位就可以尋址4k),與VitualAddress相加就是一個(gè)完整RVA */ //WORD TypeOffset[1]; } IMAGE_BASE_RELOCATION;typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
這些字段可能直接不好理解在后面會(huì)看一個(gè)實(shí)例一切就徹底明白了
雖然有多種重定位類型,但對(duì)x86可執(zhí)行文件來(lái)說(shuō),所有的基址重定位類型都是IMAGE_REL_BASED_HIGHLOW。
在一組重定位結(jié)束的地方會(huì)出現(xiàn)一個(gè)類型IMAGE_REL_BASED_ABSOLUTE的重定位,這些重定位什么都不做,只用于填充,以便下一個(gè)MAGE_BASE_RELOCATION按4字節(jié)分界線對(duì)齊。
對(duì)于IA-64可執(zhí)行文件,重定位類型似乎總是IMAGE_REL_BASED_DIR64。
有趣的是,盡管IA-64的EXE頁(yè)大小是8KB,但基址重定位仍是4KB的塊
所有重定位塊以一個(gè)VitualAddress字段為0的MAGE_BASE_RELOCATION結(jié)構(gòu)結(jié)束。
//// Based relocation types.// #define IMAGE_REL_BASED_ABSOLUTE 0 // 沒(méi)有具體含義,只是為了讓每個(gè)段4字節(jié)對(duì)齊#define IMAGE_REL_BASED_HIGH 1#define IMAGE_REL_BASED_LOW 2#define IMAGE_REL_BASED_HIGHLOW 3 // 重定位指向的整個(gè)地址都需要修正,實(shí)際上大部分情況下都是這樣的#define IMAGE_REL_BASED_HIGHADJ 4#define IMAGE_REL_BASED_MACHINE_SPECIFIC_5 5#define IMAGE_REL_BASED_RESERVED 6#define IMAGE_REL_BASED_MACHINE_SPECIFIC_7 7#define IMAGE_REL_BASED_MACHINE_SPECIFIC_8 8#define IMAGE_REL_BASED_MACHINE_SPECIFIC_9 9#define IMAGE_REL_BASED_DIR64 10 // 出現(xiàn)在64位PE文件中,對(duì)指向的整個(gè)地址進(jìn)行修正
示例分析:
繼續(xù)以DllDemo.dll為例
先用工具定位重定位表在文件的位置如下
查看重定位表信息如下
->Relocation Directory 1. Relocation Block: VirtualAddress: 0x00001000 ("CODE") SizeOfBlock: 0x00000010 (0x0004 block entries) RVA Type ---------- ----------------- 0x0000100F HIGHLOW 0x00001023 HIGHLOW n/a ABSOLUTE n/a ABSOLUTE————————————————
下面實(shí)際分析
根據(jù)下面判斷出當(dāng)前RVA在CODE節(jié)
所以
100Fh(RVA) → 60Fh(FOA)
1023h(RVA) → 623h(FOA)
60Fh和623h分別指向00402000h和00403030h處,即為所需要重定位的數(shù)據(jù)
執(zhí)行PE文件前,加載程序在進(jìn)行重定位的時(shí)候,會(huì)用PE文件在內(nèi)存中的實(shí)際映像地址減PE文件所要求的映像地址,根據(jù)重定位類型的不同將差值添加到相應(yīng)的地址數(shù)據(jù)中。
可以看到重定位表扮演的角色:文件加載到內(nèi)存后,通過(guò)重定位表記錄的RVA找到需要重定位的數(shù)據(jù)
重定位表通過(guò)頁(yè)基址RVA+頁(yè)內(nèi)偏移地址方式得到一個(gè)完整RVA大大縮小了表大小。
4、資源
Windows程序的各種界面稱為資源,包括加速鍵(Accelerator)、位圖(Bitmap)、光標(biāo)(Cursor)、對(duì)話框(Dialog Box)、圖標(biāo)(Icon)、菜單(Menu)、串表(String Table)、工具欄(Toolbar)和版本信息(Version Information)等。
定義資源時(shí),既可以使用字符串作為名稱來(lái)標(biāo)識(shí)一個(gè)資源,也可以通過(guò)ID號(hào)來(lái)標(biāo)識(shí)資源
資源分類
- 標(biāo)準(zhǔn)資源類型
- 非標(biāo)準(zhǔn)資源類型
若資源類型的高位如果為1,說(shuō)明對(duì)應(yīng)的資源類別是一個(gè)非標(biāo)準(zhǔn)的新類型
數(shù)據(jù)目錄項(xiàng)的第3個(gè)結(jié)構(gòu),指向資源表,不直接指向資源數(shù)據(jù),而是以磁盤(pán)目錄形式定位資源數(shù)據(jù)
資源表是一個(gè)四層的二叉排序樹(shù)結(jié)構(gòu)。
每一個(gè)節(jié)點(diǎn)都是由資源目錄結(jié)構(gòu)和緊隨其后的數(shù)個(gè)資源目錄項(xiàng)結(jié)構(gòu)組成的,
兩種結(jié)構(gòu)組成了一個(gè)資源目錄結(jié)構(gòu)單元(目錄塊)
資源目錄結(jié)構(gòu)(IMAGE_RESOURCE_DIRECTORY)占16字節(jié),其定義如下
typedef struct _IMAGE_RESOURCE_DIRECTORY { DWORD Characteristics; //理論上是資源的屬性標(biāo)志,但是通常為0 DWORD TimeDateStamp; //資源建立的時(shí)間 WORD MajorVersion; //理論上是放置資源的版本,但是通常為0 WORD MinorVersion; //定義資源時(shí),既可以使用字符串作為名稱來(lái)標(biāo)識(shí)一個(gè)資源,也可以通過(guò)ID號(hào)來(lái)標(biāo)識(shí)資源。資源目錄項(xiàng)的數(shù)量等于兩者之和。 WORD NumberOfNamedEntries; //以字符串命名的資源數(shù)量 WORD NumberOfIdEntries; //以整型數(shù)字(ID)命名的資源數(shù)量// IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
資源目錄項(xiàng)結(jié)構(gòu)(IMAGE_RESOURCE_DIRECTORY_ENTRY),占8字節(jié),包含2個(gè)字段,結(jié)構(gòu)定義如下。
//如果看不懂下面的結(jié)構(gòu)建議復(fù)習(xí)一下C中的union,struct,位域typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY { union { struct { DWORD NameOffset:31; DWORD NameIsString:1; } DUMMYSTRUCTNAME; DWORD Name; WORD Id; } DUMMYUNIONNAME; union { DWORD OffsetToData; struct { DWORD OffsetToDirectory:31; DWORD DataIsDirectory:1; } DUMMYSTRUCTNAME2; } DUMMYUNIONNAME2;} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
重要字段:
Name字段:定義目錄項(xiàng)的名稱或ID。
- 當(dāng)結(jié)構(gòu)用于第1層目錄時(shí),定義的是資源類型;
- 當(dāng)結(jié)構(gòu)用于第2層目錄時(shí),定義的是資源的名稱;
- 當(dāng)結(jié)構(gòu)用于第3層目錄時(shí),定義的是代碼頁(yè)編號(hào)。
- 當(dāng)最高位為0時(shí),表示字段的值作為ID使用;由該字段的低16位組成整數(shù)標(biāo)識(shí)符ID
- 當(dāng)最高位為1時(shí),表示字段的低位作為指針使用,資源名稱字符串使用Unicode編碼,
這個(gè)指針不直接指向字符串,而指向一個(gè)IMAGE_RESOURCE_DIR_STRING_U結(jié)構(gòu)。
typedef struct _IMAGE_RESOURCE_DIR_STRING_U { WORD Length; //字符串的長(zhǎng)度 WCHAR NameString[ 1 ]; //Unicode字符串,按字對(duì)齊,長(zhǎng)度可變;由Length 指明 Unicode字符串的長(zhǎng)度} IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
OffsetToData字段:是一個(gè)指針。
- 當(dāng)最高位(位31)為1時(shí),低位數(shù)據(jù)指向下一層目錄塊的起始地址;
- 當(dāng)最高位為0時(shí),指針指向IMAGE_RESOURCE_DATA_ENTRY結(jié)構(gòu)。
第3層目錄結(jié)構(gòu)中的OffsetToData將指向IMAGE_RESOURCE_DATA_ENTRY結(jié)構(gòu)。
該結(jié)構(gòu)描述了資源數(shù)據(jù)的位置和大小,其定義如下。
typedef struct _IMAGE_RESOURCE_DATA_ENTRY { DWORD OffsetToData; //資源數(shù)據(jù)的RVA DWORD Size; //資源數(shù)據(jù)的長(zhǎng)度 DWORD CodePage; //代碼頁(yè),一般為0 DWORD Reserved; //保留字段} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
重要字段:
OffsetToData:指向資源數(shù)據(jù)的指針(RVA)
Size:資源數(shù)據(jù)的長(zhǎng)度
實(shí)例分析:
定位資源在文件中的位置
由于當(dāng)前exe文件對(duì)齊與內(nèi)存對(duì)齊都是4k,RVA不需要轉(zhuǎn)FOA
所以:
圖標(biāo)的真正資源數(shù)據(jù)RVA為4100h,大小為2E8h。
菜單的真正資源數(shù)據(jù)RVA為4400h,大小為5Ah。
圖標(biāo)組的真正資源數(shù)據(jù)RVA為43E8h,大小為14h。
使用工具驗(yàn)證
'
可以清晰看到根目錄有3個(gè)資源目錄項(xiàng)(Icon,Menu,Icon Group)
第二層為資源ID或資源名稱
第三層為代碼頁(yè)ID為2052表簡(jiǎn)體中文,1033表美國(guó)英語(yǔ)
右下角圖標(biāo)為真正資源數(shù)據(jù)
上述就是小編為大家分享的PE文件結(jié)構(gòu)是怎樣的了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。