溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

PE文件結(jié)構(gòu)是怎樣的

發(fā)布時(shí)間:2021-10-15 17:21:07 來(lái)源:億速云 閱讀:175 作者:柒染 欄目:編程語(yǔ)言

這期內(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è)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(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)容。

pe
AI