天天看點

《黑客免殺攻防學習筆記》——PE檔案結構2

文章中的圖檔大部分為自己做截屏,少量摘自網絡,若有侵權請及時告知,我會盡快删除。轉載請注明出處。

接上一篇,上一篇了解了PE頭及可選頭的基本結構,這一篇從區段表開始學習。

3.區段表

         PE檔案頭後面是區段表,用于描述各個區段的屬性,檔案至少擁有一個區段才能執行。區段表是多個相連的IMAGE_SECTION_HEADER結構組成。

3.1IMAGE_SECTION_HEADER結構

typedef struct _IMAGE_SECTION_HEADER {

   BYTE   Name[IMAGE_SIZEOF_SHORT_NAME]; //區段名,長度8位元組的ASCII字元串

   union {

           DWORD   PhysicalAddress;

           DWORD   VirtualSize;

    }Misc;      //區段大小,實際被使用的區段大小,即未被對齊之前的大小

   DWORD   VirtualAddress;    //此區段加載到記憶體後的RVA,按照記憶體頁對齊

   DWORD   SizeOfRawData;   //此區段在磁盤中的體積,按照檔案頁對齊

   DWORD   PointerToRawData;//此區段在檔案中的偏移

   DWORD   PointerToRelocations;//此區段重定位表的偏移(用于OBJ檔案)

   DWORD   PointerToLinenumbers;//行号表在檔案中的偏移(用于調試)

   WORD    NumberOfRelocations;//重定位表項數量(用于OBJ檔案)

   WORD    NumberOfLinenumbers;//行号表項數量

   DWORD   Characteristics;//區段屬性值,具體的值和前面那張檔案屬性表相同

} IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;

3.2區段名功能約定

區段名 描述
.text 代碼段,裡面的資料全都是代碼
.data 可讀寫的資料段,存放全局變量或靜态變量
.rdata 隻讀資料區
 .idata 導入資料區,存放導入表資訊
.edata 導出資料區,導出表資訊
.rsrc 資源區段,存放程式用到的所有資源,如圖表,菜單等
.bss 未初始化資料區
.crt 用于支援C++運作時庫所添加的資料
.tls 存儲線程局部變量
.reloc 包含重定位資訊
.sdata 包含相對于可被全局指針定位的可讀寫資料
.srdata 包含相對于可被全局指針定位的隻讀資料
.pdata 包含異常表
.debug$S 包含OBJ檔案中的Codeview格式符号
.debug$T 包含OBJ檔案中的Codeview格式類型的符号
.debug$P 包含使用預編譯頭時的一些資訊
.drectve 包含編譯時的一些連結指令
.didat 包含延遲裝入的資料

         上面表格中空着的都是不常用的。

4.導出表

導出表是PE檔案為其他應用程式提供API的一種函數導出方式

4.1IMAGE_EXPORT_DIRECTORY

typedef struct _IMAGE_EXPORT_DIRECTORY {

   DWORD   Characteristics;   //保留,恒為0x00000000

   DWORD   TimeDateStamp;//時間戳

   WORD    MajorVersion;       //主版本号

   WORD    MinorVersion;       //子版本号

   DWORD   Name;           //指向子產品名稱的RVA

   DWORD   Base;             //索引基數

   DWORD   NumberOfFunctions;    //導出位址表中的成員個數

   DWORD   NumberOfNames;        //導出名稱表中的成員個數

   DWORD   AddressOfFunctions;     // RVA from base of image導出位址表RVA

   DWORD   AddressOfNames;         // RVA from base of image導出名稱表RVA

   DWORD  AddressOfNameOrdinals;  // RVAfrom base of image指向導出序列号的數組

} IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY;

4.2識别導出表

         到處便可以由名稱表、函數表和序号表,後兩者是必須要的,名稱表則是可選的。名稱表和序号表起到索引找到函數表中的函數的作用,而函數表則是記錄函數位址的。然而導出表的序号順序和函數順序并不是對應的,在記憶體中的資料是被完全打亂的,函數的順序是按照名稱表來确定的。

         首先需要在資料目錄項中獲得導出表的RVA,然後再觀察所有段的RVA,這時就會知道導出表在所在的段的RVA,相減獲得偏移位址,最後檢視段表獲得該段在檔案中的偏移,最終獲得導出表的偏移位址。公式如下:

導出表Offset = 導出表RVA – 有導出表的段RVA + 該段在檔案中的偏移Offset

         這裡把書上的例子dll檔案拿來,導出表截圖如下:

《黑客免殺攻防學習筆記》——PE檔案結構2

可以根據上面的資料結構獲得每一項的值。比如此導出表的序号從1開始,導出位址表EAT偏移為0x1398,導出名稱表ENT偏移為0x13A8。下面分别是ENT和EAT的截圖:

《黑客免殺攻防學習筆記》——PE檔案結構2

根據上面的偏移可以知道從0x1398~0x13A7為EAT,後面的0x10位元組為ENT,根據ENT中的位址計算出函數名在檔案中的偏移量是從0x12C8開始;而0x13B8~0x13BF八個位元組則是序号表。

         通過上面這張圖很明顯的看到函數名和函數位址的對應關系,但是有一個有點問題,就是最後一個導出函數的位址指向0x00003354,這是在.data段中的,是不可執行的,那為什麼會這樣呢?從ENT指向的對應函數名可以看到他的名字是nPEDemo而不是帶有fn開頭的字元串,其實這是一個導出的全局變量,是以會在.data 段中。

5.導入表

5.1導入表結構

         導入表機制是指PE檔案從第三方程式中導入API以提供本函數調用的機制。事實上Windows平台下所有系統提供的API函數都是通過導入導出表完成的。是以想要看程式調用了哪些函數就要看導入表。但是其實也可以手工來調用而不是直接調用,比如用LoadLibrary()等函數加載dll檔案然後獲得裡面函數的指針,最終直接通過位址調用函數。

typedef struct _IMAGE_IMPORT_DESCRIPTOR {

   union {

       DWORD   Characteristics;  // 0 for terminating null import descriptor

       DWORD   OriginalFirstThunk; // RVAto original unbound INT (PIMAGE_THUNK_DATA)

    };//指向輸入名稱表INT的RVA,INT是一個IMAGE_THUNK_DATA數組,而//IMAGE_THUNK_DATA數組又指向IMAGE_IMPORT_BY_NAME,數組的最後是内容為0

//的IMAGE_THUNK_DATA結構

   DWORD   TimeDateStamp; // 0 if notbound, -1 if bound, and real date\time stamp

    DWORD   ForwarderChain;//inIMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)

                            // O.W. date/time stampof DLL bound to (Old BIND)

                                                            //-1 if no forwarders。轉發鍊,為0則不轉發

   DWORD   Name;  //指向導入映像檔案的名字

   DWORD   FirstThunk;  // RVA to IAT (if bound this IAT has actualaddresses)指向輸入位址表IAT的RVA

} IMAGE_IMPORT_DESCRIPTOR;

         在32位系統中IMAGE_THUNK_DATA的結構為:

typedef struct _IMAGE_THUNK_DATA32 {

   union {

       PBYTE  ForwarderString;//轉發字元串RVA,當上面的轉發字段為0此值有效

       PDWORD Function;          //被導入函數的實際記憶體位址

       DWORD Ordinal;      //被導入函數的序号,當IMAGE_THUNK_DATA高位1則有效

       PIMAGE_IMPORT_BY_NAME AddressOfData;//指向輸入名稱表,上面3個均無效時有效

    }u1;

} IMAGE_THUNK_DATA32;

typedef IMAGE_THUNK_DATA32 *PIMAGE_THUNK_DATA32;

         下面再來看看最後兩個值的意思。Ordinal當最高位為1表示使用序号導入函數,這時低31位就是序号。Function字段是系統所用,在系統加載程式之前先逐個周遊IAT然後從這個字段取出導入函數的記憶體位址,将這些位址逐一跳入對應的IAT。

         下面再來看看IMAGE_IMPORT_BY_NAME結構:

typedef struct _IMAGE_IMPORT_BY_NAME {

   WORD    Hint;      //需導入的函數序号

   BYTE    Name[1];//需導入的函數名稱

} IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME;

5.2識别導入表

         還是使用上面的那個dll檔案,現在資料目錄表中第二項找到導入表RVA和大小,然後計算在檔案中的偏移:

《黑客免殺攻防學習筆記》——PE檔案結構2

據此找到導入表及其範圍:

《黑客免殺攻防學習筆記》——PE檔案結構2

OriginalFirstThunk的RVA偏移0x227C,偏移為0x107C,就跟在導入表後面,這就是IAT。由于IMAGE_IMPORT_DESCRIPTOR長度為0x14,可以看到這裡有三個這樣的結構組成數組表示從3個dll檔案導入了函數,最後由填充了0x00的IMAGE_IMPORT_DESCRIPTOR結構結束。這裡就拿第一個結構為例進行INT和IAT介紹,這裡結構的頭4位元組OriginalFirstThunk指向INT是0x227C,轉化為偏移0x107C:

《黑客免殺攻防學習筆記》——PE檔案結構2

裡面的值為0x2304,指向的是對應的IMAGE_IMPORT_BY_NAME。再看塊的最後4位元組0x2000,偏移量0xE00:

《黑客免殺攻防學習筆記》——PE檔案結構2

裡面的值0x2304,也是指向同一個IMAGE_IMPORT_BY_NAME,再來看這個結構:

《黑客免殺攻防學習筆記》——PE檔案結構2

可以看到前兩位元組0x0421為函數序号,後面的字元串就是函數名。下面這幅圖很好的解釋了導入表的結構:

《黑客免殺攻防學習筆記》——PE檔案結構2

6.1資源結構

         資源在PE檔案中是以三級目錄形式存在的,分别是資源類型、目錄資源ID與資源代碼頁。這三個目錄都是由一個IMAGE_RESOURCE_DIRECTORY為頭部的,并且在後面跟着IMAGE_RESOURCE_DIRECTORY_ENTRY結構數組。頭部為後面的數組指定成員個數,而後面的結構則指向下一層目錄結構(或資源資料)。

typedef struct _IMAGE_RESOURCE_DIRECTORY {

   DWORD   Characteristics;   //資源屬性,一般均為0

   DWORD   TimeDateStamp; //資源建立時間

   WORD    MajorVersion;       //資源的主版本,一般情況下為0x0004

   WORD    MinorVersion;       //資源的子版本,一般情況下為0x0000

   WORD    NumberOfNamedEntries;//用字元串作為資源辨別的條目個數

   WORD    NumberOfIdEntries;//用數字ID作為資源表示的條目個數

// IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];

} IMAGE_RESOURCE_DIRECTORY,*PIMAGE_RESOURCE_DIRECTORY;

         下面再來看看緊随其後的IMAGE_RESOURCE_DIRECTORY_ENTRY結構:

typedef struct_IMAGE_RESOURCE_DIRECTORY_ENTRY {

   union {

       struct {

           DWORD NameOffset:31;         // NameIsString為1時資源名字元串偏移,其指向一個儲存資源名稱的結構體

           DWORD NameIsString:1;//資源名為字元串

       };

       DWORD   Name;//當位于第一層目錄時表示資源類型的值,當位于第三層時,儲存資源語言區域類型值

       WORD    Id;          //資源數字ID

   };

   union {

       DWORD   OffsetToData;       //資料偏移位址

       struct {

           DWORD   OffsetToDirectory:31;// DataIsDirectory:1時指向下層目錄偏移位址

           DWORD   DataIsDirectory:1;       //資料為目錄

       };

   };

} IMAGE_RESOURCE_DIRECTORY_ENTRY,*PIMAGE_RESOURCE_DIRECTORY_ENTRY;

下面是資源類型的值:

《黑客免殺攻防學習筆記》——PE檔案結構2

上面這個IMAGE_RESOURCE_DIRECTORY_ENTRY結構由兩個大小為4位元組的聯合體組成,不同情況下聯合體的有效字段是不同的:

         第一個聯合體由結構所處的目錄層決定,位于第一層目錄則Name字段有效,儲存資源類型;位于第二層去取決于資源引用方式,若是采用編号則ID有效,否則結構體有效;位于第三層Name字段有效,儲存的資訊是資源語言類型。

         第二個聯合體内的字段根據具體情況決定,如果下一級是子目錄則結構體有效,否則OffsetToData字段有效。

         經過三層目錄之後最終達到一個結構體IMAGE_RESOURCE_DATA_ENTRY,這個結構将指引我們找到資源資料:

typedef struct _IMAGE_RESOURCE_DATA_ENTRY {

   DWORD   OffsetToData;//資源資料的RVA

   DWORD   Size;               //資源資料長度

   DWORD   CodePage;    //代碼頁資訊

   DWORD   Reserved;     //保留字段,恒為0x00000000

} IMAGE_RESOURCE_DATA_ENTRY,*PIMAGE_RESOURCE_DATA_ENTRY;

         下面這個圖簡要描述了資源的結構:

《黑客免殺攻防學習筆記》——PE檔案結構2

6.2識别資源

         還是和上面一樣現在資料目錄表中找到資源在檔案中的偏移,這裡為0x1600。下面還是通過例子來看:

《黑客免殺攻防學習筆記》——PE檔案結構2

頭0x10位元組是IMAGE_RESOURCE_DIRECTORY,最後兩個位元組是用數字作為ID的項目個數,這裡兩個表示後面有兩個0x08位元組的IMAGE_RESOURCE_DIRECTORY_ENTRY結構體。拿第一個作為例子。由于是第一層目錄,是以頭4位元組是Name字段,表示資源類型為字元串,0x80000020中第一位為1表示DataIsDirectory,即為目錄,後面31位表示偏移,是以這裡二級目錄偏移為0x20,可以得到相對檔案偏移為0x1620,轉到那裡去看:

《黑客免殺攻防學習筆記》——PE檔案結構2

同樣看到利用編号索引的子項目數為1,是以接下來隻有0x8位元組是第三級目錄。由于使用編号索引,是以前4位元組ID有效為0x07,後4位元組計算的資源偏移為0x1650,到那裡看看:

《黑客免殺攻防學習筆記》——PE檔案結構2

這裡也是隻有一個用編号索引的目錄,由于是第三層,是以前4位元組Name有效,儲存資源語言區域類型,後4位元組表示資源偏移,是以可以到0x1680處尋找資源結構IMAGE_RESOURCE_DATA_ENTRY:

《黑客免殺攻防學習筆記》——PE檔案結構2

可以看到資料RVA為0x40A0,長度為0x38,代碼頁資訊為0x04E4。

         至此,關于資源的三層目錄簡要介紹就差不多了。

PE檔案的前兩篇主要是講一些重要的結構,後面的一些内容不常用,是以第3篇會講的簡單一點。

繼續閱讀