PE文件結構及其加載機制


一、PE文件結構

PE即Portable Executable,是win32環境自身所帶的執行體文件格式,其部分特性繼承自Unix的COFF(Common Object File Format)文件格式。PE表示該文件格式是跨win32平台的,即使Windows運行在非Intel的CPU上,任何Win32平台的PE裝載器也能識別和使用該文件格式的文件。

所有Win32執行體(除了VxD和16位的DLL)都使用PE文件格式,如EXE文件、DLL文件等,包括NT的內核模式驅動程序(Kernel Mode Driver)。

PE文件至少包含兩個段,即數據段和代碼段。Windows NT 的應用程序有9個預定義的段,分別為 .text 、.bss 、.rdata 、.data 、.pdata 和.debug 段,這些段並不是都是必須的,當然,也可以根據需要定義更多的段(比如一些加殼程序)。

在應用程序中最常出現的段有以下6種:

.執行代碼段,通常  .text (Microsoft)或 CODE(Borland)命名;

.數據段,通常以 .data 、.rdata 或 .bss(Microsoft)、DATA(Borland)命名;

.資源段,通常以 .rsrc命名;

.導出表,通常以 .edata命名;

.導入表,通常以 .idata命名;

.調試信息段,通常以 .debug命名;

 

PE文件的結構在磁盤和內存中是基本一樣的,但在裝入內存中時又不是完全復制。Windows裝載器在裝載的時候僅僅建立好虛擬地址和PE文件之間的映射關系,只有真正執行到某個內存頁中的指令或訪問某一頁中的數據時,這個頁才會被從磁盤提交到物理內存。但因為裝載可執行文件時,有些數據在裝入前會被預先處理(如需要重定位的代碼),裝入以后,數據之間的相對位置也可能發生改變。因此,一個節的偏移和大小在裝入內存前后可能是完全不同的。

..

PE的基本結構就是這樣了。

下面開始各個部分學習。

==================================================

(1)IMAGE_DOS_HEADER和Dos Stub

其實IMAGE_DOS_HEADER和Dos Stub沒有什么重要的,只是IMAGE_DOS_HEADER中的第十九個成員指向IMAGE_NT_HEADERS的位置。

復制代碼
   typedef struct IMAGE_DOS_HEADER
  {
        WORD e_magic;
        WORD e_cblp;
        WORD e_cp;
        WORD e_crlc;
        WORD e_cparhdr;
        WORD e_minalloc;
        WORD e_maxalloc;
        WORD e_ss;
        WORD e_sp;
        WORD e_csum;
        WORD e_ip;
        WORD e_cs;
        WORD e_lfarlc;
        WORD e_ovno;
        WORD e_res[4];
        WORD e_oemid;
        WORD e_oeminfo;
        WORD e_res2[10];
        DWORD e_lfanew;             //指向IMAGE_NT_HEADERS的所在
  }IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; 
復制代碼

下面我們用一個具體的PE文件來看看。

這是64個字節(64Byte),下面是一些分析。

首先,只要是PE文件,那么開始兩個字節就一定是4D 5A。

然后看最后四個字節,是E8000000,代表了地址是000000E8h,我們往下找找。

通過搜索,可以直接到達這個位置,一會我們再來看這個位置。

也就是說,00000040h到000000E8h之間的數據都是Dos Stub(稱為dos殘余程序)。

 

-----------------------------------------------------------------------------

(2)PE文件頭

現在我們來看看IMAGE_NT_HEADERS的情況,看看e_lfanew指向的這里是什么含義,看看000000E8h(這個數值是不固定的,不同的PE程序的值可能不同,我們只要找到這個位置,讀取它的值即可找到PE文件的頭所在)指向的這里又是什么。

typedef struct IMAGE_NT_HEADERS
  {
        DWORD Signature;
        IMAGE_FILE_HEADER FileHeader;
        IMAGE_OPTIONAL_HEADER32 OptionalHeader;
  }IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS; 

首先我們看到的,這是一個結構體,而且結構體中還有結構體。

我們先看看它的總大小和在c32中的情況

【1】其中紅色的部分就是DWORD Signature,表示字符“PE\0\0”,十六進制為00004550h,這個值也是不會變化的。

【2】綠色線部分表示IMAGE_FILE_HEADER FileHeader,我們具體來看看這個結構體代表了什么含義。

 

復制代碼
typedef struct _IMAGE_FILE_HEADER {   
        WORD      Machine;                 //運行平台 
        WORD      NumberOfSections;        //塊(section)數目      
        DWORD     TimeDateStamp;           //時間日期標記     
        DWORD     PointerToSymbolTable;    //COFF符號指針,這是程序調試信息    
        DWORD     NumberOfSymbols;         //符號數  
        WORD      SizeOfOptionalHeader;    //可選部首長度,是IMAGE_OPTIONAL_HEADER的長度    
        WORD      Characteristics;         //文件屬性 
} 
復制代碼

第一個表示這個程序運行需要的平台,來看看我剛才這個程序的值

這里表示運行的平台是Intel的CPU,下面是一個列表代表各種對應的平台的值:

第二個表示塊的數目

0004當然就是4個節了,一會我們可以看看是哪四個節

第三個表示時間,我們先看看

也就是4F91318Fh,用計算器算一下就是1334915471,這個值應該是秒,我們換算一下,結果大約是42年

這個是程序的創建日期,減去42年,大約就是1970年,這個日期就是從1970到文件最后修改時間之間的秒數?

這個我可不知道,有待研究。

第六個SizeOfOptionalHeader表示之后OptionalHeader的大小,我們先來看看

00E0就是十進制的224,也就是說OptionalHeader的大小是224字節。

第七個值Characteristics表示文件屬性,它的每一個bit都代表了某種含義。

復制代碼
         Bit 0 :置1表示文件中沒有重定向信息。每個段都有它們自己的重定向信息。
                 這個標志在可執行文件中沒有使用,在可執行文件中是用一個叫做基址重定向目錄表來表示重定向信息的,這將在下面介紹。
         Bit 1 :置1表示該文件是可執行文件(也就是說不是一個目標文件或庫文件)。
         Bit 2 :置1表示沒有行數信息;在可執行文件中沒有使用。
         Bit 3 :置1表示沒有局部符號信息;在可執行文件中沒有使用。
         Bit 4 :
         Bit 7 
         Bit 8 :表示希望機器為32位機。這個值永遠為1。
         Bit 9 :表示沒有調試信息,在可執行文件中沒有使用。
         Bit 10:置1表示該程序不能運行於可移動介質中(如軟驅或CD-ROM)。在這    種情況下,OS必須把文件拷貝到交換文件中執行。
         Bit 11:置1表示程序不能在網上運行。在這種情況下,OS必須把文件拷貝到交換文件中執行。
         Bit 12:置1表示文件是一個系統文件例如驅動程序。在可執行文件中沒有使用。
         Bit 13:置1表示文件是一個動態鏈接庫(DLL)。
         Bit 14:表示文件被設計成不能運行於多處理器系統中。
         Bit 15:表示文件的字節順序如果不是機器所期望的,那么在讀出之前要進行
                 交換。在可執行文件中它們是不可信的(操作系統期望按正確的字節順序執行程序)。
復制代碼

010Fh就是0000000100001111

具體的我們來看一張圖:

【3】下面是OptionalHeader,占224個字節

我們看看 它的結構是怎樣的

復制代碼
typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic;
  BYTE                 MajorLinkerVersion;
  BYTE                 MinorLinkerVersion;
  DWORD                SizeOfCode;
  DWORD                SizeOfInitializedData;
  DWORD                SizeOfUninitializedData;
  DWORD                AddressOfEntryPoint;
  DWORD                BaseOfCode;
  DWORD                BaseOfData;
  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_HEADER, *PIMAGE_OPTIONAL_HEADER;
復制代碼

 

同樣的,我們先在msdn中查看下這個結構體

第一個值,表示文件的格式,它可能的值有

IMAGE_NT_OPTIONAL_HDR_MAGIC,這個值包括兩個值,分別為IMAGE_NT_OPTIONAL_HDR32_MAGICIMAGE_NT_OPTIONAL_HDR64_MAGIC,分別表示是32位的應用程序和64位的應用程序,具體的十六進制值為0x10b和0x20b。

還有一個值是IMAGE_ROM_OPTIONAL_HDR_MAGIC,表示這是一個ROM文件,其值為0x107。

總結為一張圖

對於這個文件,它的值是

表示32位的應用程序。

第二個值是MajorLinkerVersion,表示主版本號的鏈接器,這個值一般來說是不重要的。

對於這個文件,它的值為

第三個值是MinorLinkerVersion,表示次版本號的連接器,這個值一般來說是不重要的。

它的值

第四個值是SizeOfCode,表示The size of the code section, in bytes, or the sum of all such sections if there are multiple code sections.

代碼段的總大小,單位為字節,如果是多個部分,則表示它們的總和。

94208字節,即92kb。

第五個值是SizeOfInitializedData,表示所有含已初始化數據的節的大小。

The size of the initialized data section, in bytes, or the sum of all such sections if there are multiple initialized data sections.

77824字節,76kb。

第六個值是SizeOfUninitializedData,表示未初始化數據的節的大小。

The size of the uninitialized data section, in bytes, or the sum of all such sections if there are multiple uninitialized data sections.

第七個值是AddressOfEntryPoint,表示程序的入口點。

A pointer to the entry point function, relative to the image base address. For executable files, this is the starting address. For device drivers, this is the address of the initialization function. The entry point function is optional for DLLs. When no entry point is present, this member is zero.

我們用PEiD來看看這個值

如果看過《C++反匯編與逆向分析技術揭秘》的童鞋就會知道,PEiD是如何工作的。

第八個值是BaseOfCode,表示代碼段的起始RVA。

即RVA為00001000,如果加上ImageBase的話,就可以知道這個位置在內存中的位置為00401000,我們用OD打開這個程序看看

第九個值是BaseOfData,表示數據段的起始RVA。

同樣,我們用OD來查看下,00018000+00400000=00418000

第十個值是ImageBase,表示程序的建議裝載地址。

用PEiD查看

 

第十一個值SectionAlignment,表示節對齊粒度。這個值一定要大於或等於文件對齊粒度。
The alignment of sections loaded in memory, in bytes. This value must be greater than or equal to the FileAlignment member.
The default value is the page size for the system.

原來是4096B,也就是4kb了。

第十二個值是FileAlignment,表示文件對齊粒度。

The alignment of the raw data of sections in the image file, in bytes. The value should be a power of 2 between 512 and 64K (inclusive). The default is 512. If the SectionAlignment member is less than the system page size, this member must be the same as SectionAlignment.

這個值應該是512B的倍數。

第十三個值是MajorOperatingSystemVersion,所需操作系統的主版本號。

The major version number of the required operating system.

第十四個值是MinorOperatingSystemVersion,所需操作系統的副版本號。

The minor version number of the required operating system.

第十五個值是MajorImageVersion

The major version number of the image.

第十六個值是MinorImageVersion

The minor version number of the image.

第十七個值是MajorSubsystemVersion

The major version number of the subsystem.

第十八個值為MinorSubsystemVersion

The minor version number of the subsystem.

第十九個值為Win32VersionValue,保留值,且必須為零。

This member is reserved and must be 0.

 

 

 

第二十個值為SizeOfImage,4個字節,表示程序調入后占用內存大小(字節),等於所有段的長度之和。

The size of the image, in bytes, including all headers. Must be a multiple of SectionAlignment.

 

0x2B000=?

好吧,兩三天了,終於弄明白這個值了,由於在實驗過程中,為了防止意外,所以復制了一個副本在當前文件夾下,通過二進制的對比,發現這兩個文件的SizeOfImage值是不一樣的,所以走了彎路。

既然錯了文件,那么我還是以這個文件為例吧,因為其他的部分都一樣,所以就不修改其他的部分了。

0x25000+0x5188=0x2A188,再考慮內存對齊,我們試着用這個值除以對齊粒度0x1000,看是否能除盡。

結果是不能除盡,所以要求大一點,結果這個SizeOfImage就變成了0x2B000。

第二十一個值為SizeOfHeaders,占用4個字節,表示所有頭加節表的大小。

The combined size of the following items, rounded to a multiple of the value specified in the FileAlignment member.

 

      • 4 byte signature
      • size of IMAGE_FILE_HEADER
      • size of optional header
      • size of all section headers

 

也就是0x1000了。

 

第二十二個值為CheckSum,占用四個字節。

The image file checksum. The following files are validated(驗證) at load time: all drivers, any DLL loaded at boot time, and any DLL loaded into a critical (關鍵)system process.

 

 

第二十三個值為Subsystem,占用兩個字節。表示文件運行所需的子系統。

 The subsystem required to run this image. The following values are defined.

 

Value Meaning
IMAGE_SUBSYSTEM_UNKNOWN
0

Unknown subsystem.

IMAGE_SUBSYSTEM_NATIVE
1

No subsystem required (device drivers and native system processes).

IMAGE_SUBSYSTEM_WINDOWS_GUI
2

Windows graphical user interface (GUI) subsystem.

IMAGE_SUBSYSTEM_WINDOWS_CUI
3

Windows character-mode user interface (CUI) subsystem.

IMAGE_SUBSYSTEM_OS2_CUI
5

OS/2 CUI subsystem.

IMAGE_SUBSYSTEM_POSIX_CUI
7

POSIX CUI subsystem.

IMAGE_SUBSYSTEM_WINDOWS_CE_GUI
9

Windows CE system.

IMAGE_SUBSYSTEM_EFI_APPLICATION
10

Extensible Firmware Interface (EFI) application.

IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER
11

EFI driver with boot services.

IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER
12

EFI driver with run-time services.

IMAGE_SUBSYSTEM_EFI_ROM
13

EFI ROM image.

IMAGE_SUBSYSTEM_XBOX
14

Xbox system.

IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION
16

Boot application.

 

 

 

 

第二十四個值為DllCharacteristics,占用兩個字節。表示dll文件的屬性值。

The DLL characteristics of the image. The following values are defined.

 

Value Meaning
0x0001

Reserved.(保留)

0x0002

Reserved.

0x0004

Reserved.

0x0008

Reserved.

IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
0x0040

The DLL can be relocated at load time.(允許在載入的時候進行重定位)

IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY
0x0080

Code integrity checks are forced. If you set this flag and a section contains only uninitialized data, set the PointerToRawData member ofIMAGE_SECTION_HEADER for that section to zero; otherwise, the image will fail to load because the digital signature cannot be verified.

IMAGE_DLLCHARACTERISTICS_NX_COMPAT
0x0100

The image is compatible(兼容) with data execution prevention (DEP).

IMAGE_DLLCHARACTERISTICS_NO_ISOLATION
0x0200

The image is isolation(隔離) aware, but should not be isolated.

IMAGE_DLLCHARACTERISTICS_NO_SEH
0x0400

The image does not use structured exception handling (SEH). No handlers can be called in this image.

IMAGE_DLLCHARACTERISTICS_NO_BIND
0x0800

Do not bind the image.

0x1000

Reserved.

IMAGE_DLLCHARACTERISTICS_WDM_DRIVER
0x2000

A WDM driver.

0x4000

Reserved.

IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE
0x8000

The image is terminal server aware.

 

 

 

第二十五個值為SizeOfStackReserve,占用4個字節。表示初始化是的堆棧大小。

The number of bytes to reserve for the stack. Only the memory specified by the SizeOfStackCommit member is committed at load time; the rest is made available one page at a time until this reserve size is reached.

 

0x00100000=1MB

第二十六個值為SizeOfStackCommit,占用四個字節。表示初始化時實際提交的堆棧大小。

The number of bytes to commit for the stack.

 

0x1000字節=4kb

第二十七個值為SizeOfHeapReserve,占用四個字節。初始化時保留堆的大小。

The number of bytes to reserve for the local heap. Only the memory specified by the SizeOfHeapCommit member is committed at load time; the rest is made available one page at a time until this reserve size is reached.

 

第二十八個值為SizeOfHeapCommit,占用四個字節。初始化時實際提交的堆得大小。

The number of bytes to commit for the local heap.

 

第二十九個值為LoaderFlags,占用4個字節。未使用。

This member is obsolete. 

第三十個值為NumberOfRvaAndSizes,占用四個字節。表示下面個成員數據目錄結構的數量。

 

這個值一般就直接是16.

 


下面是最后一個成員DataDirectory,占用128個字節,為一個IMAGE_DATA_DIRECTORY structure結構體數組(16個)。

A pointer to the first IMAGE_DATA_DIRECTORY structure in the data directory.

 

 

typedef struct _IMAGE_DATA_DIRECTORY {
     DWORD VirtualAddress;
     DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

 

這個結構體有兩個成員,一個成員占用4個字節,也就是8個字節。這個數組有16個數據,也就是16*8=128字節。

我們來看第一個。

IMAGE_DIRECTORY_ENTRY_EXPORT    導出表

這個程序沒有導出函數,所以沒有導出表。

第二個IMAGE_DIRECTORY_ENTRY_IMPORT  導入表

這個程序需要用到dll中的函數

 

我們用PEiD來查看下

 

結果是一樣的。這個是RVA,表示偏移地址哦。

 

第三個IMAGE_DIRECTORY_ENTRY_RESOURCE   資源目錄

從上面這張圖也可以看出。RVA為00025000,大小為5188byte

 

第四個IMAGE_DIRECTORY_ENTRY_EXCEPTION  異常目錄

 

未使用。

 


第五個 IMAGE_DIRECTORY_ENTRY_SECURITY  安全目錄

 

 

第六個 IMAGE_DIRECTORY_ENTRY_BASERELOC   重定位表

 

 

第七個 IMAGE_DIRECTORY_ENTRY_DEBUG  調試信息

 

 

第八個 IMAGE_DIRECTORY_ENTRY_COPYRIGHT 版權信息

 

 

第九個  IMAGE_DIRECTORY_ENTRY_GLOBALPTR 

 

 

第十個 IMAGE_DIRECTORY_ENTRY_TLS  線程的本地存儲器

 

 

第十一個 IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 載入配置目錄

Load configuration table address and size

 

 

第十二個 IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT  綁定導入表地址和大小

Bound import table address and size

 

 

第十三個 IMAGE_DIRECTORY_ENTRY_IAT  導入函數地址表Import Address Table

Import address table address and size

 

用Exeinfo PE 查看

 

 

第十四個 IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT

Delay import descriptor address and size

 

 

 

第十五個 IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR

The CLR header address and size

 

 

第十六個 IMAGE_NUMBEROF_DIRECTORY_ENTRIES   保留值

 

 

到此,整個PE文件頭結束了。

 

下面我們開始學習節表。

不知道還記不記得在前面哪個結構體中出現過節的數量?

 

嘿嘿,忘記了吧,我們翻開以前的記錄,看看。

原來是

typedef struct IMAGE_NT_HEADERS
  {
        DWORD Signature;
        IMAGE_FILE_HEADER FileHeader;
        IMAGE_OPTIONAL_HEADER32 OptionalHeader;
  }IMAGE_NT_HEADERS,*PIMAGE_NT_HEADERS; 

中的

復制代碼
typedef struct _IMAGE_FILE_HEADER {   
        WORD      Machine;                 //運行平台 
        WORD      NumberOfSections;        //塊(section)數目      
        DWORD     TimeDateStamp;           //時間日期標記     
        DWORD     PointerToSymbolTable;    //COFF符號指針,這是程序調試信息    
        DWORD     NumberOfSymbols;         //符號數  
        WORD      SizeOfOptionalHeader;    //可選部首長度,是IMAGE_OPTIONAL_HEADER的長度    
        WORD      Characteristics;         //文件屬性 
}
復制代碼

第二個成員就是了。

我們回去找找這個程序的這個值是多少。

原來是四個節啊,當然了,也可以說四個段。

果然是四個段。

好了,復習完需要的知識,我們就繼續學習。

 

================================================

typedef  struct  _IMAGE_SECTION_HEADER {
   BYTE   Name[IMAGE_SIZEOF_SHORT_NAME];
   union  {
     DWORD  PhysicalAddress;
     DWORD  VirtualSize;
   } Misc;
   DWORD  VirtualAddress;
   DWORD  SizeOfRawData;
   DWORD  PointerToRawData;
   DWORD  PointerToRelocations;
   DWORD  PointerToLinenumbers;
   WORD   NumberOfRelocations;
   WORD   NumberOfLinenumbers;
   DWORD  Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

 同樣的,這也是一個結構體,而且有幾個節,就有幾個這種類似的結構體。

"#define IMAGE_SIZEOF_SHORT_NAME 8"    原來是個8

 
  二、導入表與IAT

(一)、導入表簡介

在編程中常常用到“導入函數”(Import functions),導入函數就是被程序調用但其執行代碼又不在程序中的函數,這些函數的代碼位於一個或者多個DLL中,在調用者程序中只保留一些函數信息,包括函數名及其駐留的DLL名等。

於磁盤上的PE 文件來說,它無法得知這些輸入函數在內存中的地址,只有當PE 文件被裝入內存后,Windows 加載器才將相關DLL 裝入,並將調用輸入函數的指令和函數實際所處的地址聯系起來。這就是“動態鏈接”的概念。動態鏈接是通過PE 文件中定義的“導入表”來完成的,導入表中保存的正是函數名和其駐留的DLL 名等。

1.調用導入函數的指令

程序被執行的時候是怎樣使用導入函數的呢?我們來對一個簡單的彈出一個MessageBox的程序反匯編一把,看看調用導入函數的指令都是什么樣子的.

image

灰常簡單的一個小程序,如圖雙擊程序只顯示一個對話窗口,然后就結束~試驗用小程序,我們盡量的將內部的結構刪減,調試起來才方便些。我們這次體驗的目的就是想靠所學的知識,試圖來找到MessageBox 在內存中的地址。
注:MessageBox 是來自於USER32.DLL 動態鏈接庫里的一個函數,我們通過對PE 文件的靜態反編譯分析來觀察hello.exe 這個試驗品是如何定位和調用MessageBox 這個在USER32.dll的函數的。
(MessageBox 有兩個版本,一個是MessageBoxA 還有一個是MessageBoxW 分別代表ASCII碼形式和UNICODE~)

需要反匯編的兩句源碼如下:

invoke  MessageBox,NULL,offset szText,offset szCaption,MB_OK

invoke  ExitProcess,NULL

我們直接對其反匯編:

image

反匯編后,對MessageBox和ExitProcess函數的調用變成了對0040101A和00401020地址的調用,但是這兩個地址顯然是位於程序自身模塊而不是DLL模塊中,實際上,這是由編譯器在程序所有代碼的后面自動加上的Jmp dword ptr[xxxxxxxx]類型的指令,這個指令時一個間接尋址的跳轉指令,xxxxxxxx地址中存放的才是真正的導入函數的地址。在這個例子中,00402000地址處存放的就是ExitProcess函數的地址。

那在沒有裝載到內存前,PE文件中的00402000地址處的內容又是什么呢?我們來分析一下它的節表。

image

由於建議裝入地址是00400000h,所以00402000地址實際上處於RVA為2000h的地方,再看看各個節的虛擬地址,可以發現2000h開始的地方位於.rdata節內,而這個節的Raw_偏移為600h,也就是00402000h的內容實際上對應於PE文件中偏移600h處的數據。

我們再來看看文件0600h處的內容是什么:

image

查看的結果是0002076h,這顯然不是內存中的ExitProcess函數的地址。不過,我們將它作為RVA看會怎么樣?RVA地址00002076h也處於.rdata節內,減去節的其實地址00002000h后得到這個RVA相對於節首的偏移是76h,也就是對應文件0676h開始的地方,接下來會驚奇地發現,0676h再過去兩個字節的內容正是函數名字符串“ExitProcess”!

是不是感覺有點不對?

如果我告訴你,當PE文件被裝載的時候,Windows裝載器會根據xxxxxxxx處的RVA得到函數名,再根據函數名在內存中找到函數地址,並且用函數地址將xxxxxxx處的內容替換成真正的函數地址,那么所有的疑惑就迎刃而解了。接下來看看如何獲取導入表的位置。

2.獲取導入表的位置

導入表的位置和大小可以從PE文件中IMAGE_OPTIONAL_HEADER32結構的數據目錄字段中獲取。從IMAGE_OPTIONAL_DIRECTORY結構的VirtualAddress字段得到的是導入表的RVA值,如果在內存中查找導入表,那么將RVA值加上PE文件裝入的基址就是實際的地址,如果在PE文件中查找導入表,那么需要使用上一篇中講的將RVA轉換成文件偏移的方法進行轉換。

 

(二)、導入表的結構

1.PE文件中的導入表

導入表由一系列的IMAGE_IMPORT_DESCRIPTOR結構組成,結構的數量取決於程序要使用的DLL文件的數量,每一個結構對應一個DLL文件,在所有這些結構的最后,由一個內容全為0的IMAGE_IMPORT_DESCRIPTOR結構作為結束。

IMAGE_IMPORT_DESCRIPTOR結構的定義:

IMAGE_IMPORT_DESCRIPTOR STRUCT 
union 
    Characteristics              DWORD   ? 
    OriginalFirstThunk        DWORD   ? 
ends 
TimeDateStamp                     DWORD   ? 
ForwarderChain                     DWORD   ? 
Name1                                    DWORD   ? 
FirstThunk                            DWORD   ?
IMAGE_IMPORT_DESCRIPTOR ENDS

①Name1

它表示DLL 名稱的相對虛地址(譯注:相對一個用null作為結束符的ASCII字符串的一個RVA,該字符串是該導入DLL文件的名稱,如:KERNEL32.DLL)。

②OriginalFirstThunk和FirstThunk

現在可以看成是相同的(現在),它們都指向一個包含一系列IMAGE_THUNK_DATA結構的數組,數組中的每個IMAGE_THUNK_DATA結構定義了一個導入函數的信息,數組最后以一個內容為0的IMAGE_THUNK_DATA結構作為結束。

一個IMAGE_THUNK_DATA結構實際上就是一個雙字,之所以把它定義成結構,是因為它在不同時刻有不同的含義:

IMAGE_THUNK_DATA STRUC
union u1
ForwarderString       DWORD  ?        ; 指向一個轉向者字符串的RVA
Function                      DWORD  ?        ; 被輸入的函數的內存地址
Ordinal                       DWORD  ?        ; 被輸入的API 的序數值
AddressOfData         DWORD  ?        ; 指向 IMAGE_IMPORT_BY_NAME
ends
IMAGE_THUNK_DATA ENDS

當 IMAGE_THUNK_DATA 值的最高位為 1時,表示函數以序號方式輸入,這時候低 31位被看作一個函數序號。(讀者可以用預定義值IMAGE_ORDINAL_FLAG32或80000000h來對最高位進行測試)
當 IMAGE_THUNK_DATA 值的最高位為 0時,表示函數以字符串類型的函數名方式輸入,這時雙字的值是一個 RVA,指向一IMAGE_IMPORT_BY_NAME 結構。

IMAGE_IMPORT_BY_NAME STRUCT
Hint        WORD    ? 
Name1      BYTE      ?
IMAGE_IMPORT_BY_NAME ENDS

結構中的 Hint 字段也表示函數的序號,不過這個字段是可選的,有些編譯器總是將它設置為 0,Name1字段定義了導入函數的名稱字符串,這是一個以 0 為結尾的字符串。

我們來看一個例子:

image2.內存中的導入表

為什么需要兩個一樣的IMAGE_THUNK_DATA 數組呢?

當PE文件被裝入內存的時候,其中一個數組的值將被改作他用,正如上面分析的,Windows裝載器會將指令Jmp dword ptr[xxxxxxxx]指定的xxxxxxxx處的RVA替換成真正的函數地址,其實xxxxxxx地址正是FirstThunk字段指向的那個數組的一員。

實際上,當PE文件被裝入內存后,內存中的映象就被Windows裝載器修正成了下圖的樣子,其中由FirstThunk字段指向的那個數組中的每個雙字都被替換成了真正的函數入口地址,之所以在PE文件中使用兩份IMAGE_THUNK_DATA 數組的拷貝並修改其中的一份,是為了最后還可以留下一份拷貝用來反過來查詢地址所對應的導入函數名。

3.導入地址表(IAT)

暫把上面FirstThunk指向的真正導入函數地址數組稱為導入地址數組。在PE文件中,所有DLL對應的導入地址數組是被排列在一起的,全部這些數組的組合也被稱為導入地址表(Import Address Table),導入表中第一個IMAGE_IMPORT_DESCRIPTOR結構的FirstThunk字段指向的就是IAT的起始地址。也可以通過數據目錄表的第13項找到IAT數據塊的位置和大小。

image

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM