關於PE可執行文件的修改

在windows 9x、NT、2000下,所有的可執行文件都是基於Microsoft設計的一種新的文件格式Portable Executable File Format(可移植的執行體),即PE格式。有一些時候,我們需要對這些可執行文件進行修改,下面文字試圖詳細的描述PE文件的格式及對PE格式文件的修改。
1、PE文件框架構成
DOS MZ header
DOS stub
PE header
Section table
Section 1
Section 2
Section ...
Section n
上表是PE文件結構的總體層次分佈。所有 PE文件(甚至32位的 DLLs) 必須以一個簡單的 DOS MZ header 開始,在偏移0處有DOS下可執行文件的“MZ標誌”,有了它,一旦程序在DOS下執行,DOS就能識別出這是有效的執行體,然後運行緊隨 MZ header 之後的 DOS stub。DOS stub實際上是個有效的EXE,在不支持 PE文件格式的操作系統中,它將簡單顯示一個錯誤提示,類似於字符串 " This program cannot run in DOS mode " 或者程序員可根據自己的意圖實現完整的 DOS代碼。通常DOS stub由彙編器/編譯器自動生成,對我們的用處不是很大,它簡單調用中斷21h服務9來顯示字符串"This program cannot run in DOS mode"。
緊接着 DOS stub 的是 PE header。 PE header 是PE相關結構 IMAGE_NT_HEADERS 的簡稱,其中包含了許多PE裝載器用到的重要域。可執行文件在支持PE文件結構的操作系統中執行時,PE裝載器將從 DOS MZ header的偏移3CH處找到 PE header 的起始偏移量。因而跳過了 DOS stub 直接定位到真正的文件頭 PE header。
PE文件的真正內容劃分成塊,稱之爲sections(節)。每節是一塊擁有共同屬性的數據,比如“.text”節等,那麼,每一節的內容都是什麼呢?實際上PE格式的文件把具有相同屬性的內容放入同一個節中,而不必關心類似“.text”、“.data”的命名,其命名只是爲了便於識別,所有,我們如果對PE格式的文件進行修改,理論上講可以寫入任何一個節內,並調整此節的屬性就可以了。
PE header 接下來的數組結構 section table(節表)。每個結構包含對應節的屬性、文件偏移量、虛擬偏移量等。如果PE文件裏有5個節,那麼此結構數組內就有5個成員。
以上就是PE文件格式的物理分佈,下面將總結一下裝載一PE文件的主要步驟:
1、  PE文件被執行,PE裝載器檢查 DOS MZ header 裏的 PE header 偏移量。如果找到,則跳轉到 PE header。
2、PE裝載器檢查 PE header 的有效性。如果有效,就跳轉到PE header的尾部。
3、緊跟 PE header 的是節表。PE裝載器讀取其中的節信息,並採用文件映射方法將這些節映射到內存,同時付上節表裏指定的節屬性。
4、PE文件映射入內存後,PE裝載器將處理PE文件中類似 import table(引入表)邏輯部分。
上述步驟是一些前輩分析的結果簡述。
2、PE文件頭概述
我們可以在winnt.h這個文件中找到關於PE文件頭的定義:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;        
//PE文件頭標誌 :“PE/0/0”。在開始DOS header的偏移3CH處所指向的地址開始
IMAGE_FILE_HEADER FileHeader;    //PE文件物理分佈的信息
IMAGE_OPTIONAL_HEADER32 OptionalHeader;  //PE文件邏輯分佈的信息
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

typedef struct _IMAGE_FILE_HEADER {
WORD  Machine;      //該文件運行所需要的CPU,對於Intel平臺是14Ch
WORD  NumberOfSections;    //文件的節數目
DWORD  TimeDateStamp;    //文件創建日期和時間
DWORD  PointerToSymbolTable;  //用於調試
DWORD  NumberOfSymbols;    //符號表中符號個數
WORD  SizeOfOptionalHeader;  //OptionalHeader 結構大小
WORD  Characteristics;    //文件信息標記,區分文件是exe還是dll
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

typedef struct _IMAGE_OPTIONAL_HEADER {
WORD  Magic;      //標誌字(總是010bh)
BYTE  MajorLinkerVersion;    //連接器版本號
BYTE  MinorLinkerVersion;    //
DWORD  SizeOfCode;      //代碼段大小
DWORD  SizeOfInitializedData;  //已初始化數據塊大小
DWORD  SizeOfUninitializedData;  //未初始化數據塊大小
DWORD  AddressOfEntryPoint;  //PE裝載器準備運行的PE文件的第一個指令的RVA,若要改變整個執行的流程,可以將該值指定到新的RVA,這樣新RVA處的指令首先被執行。(許多文章都有介紹RVA,請去了解)
DWORD  BaseOfCode;      //代碼段起始RVA
DWORD  BaseOfData;      //數據段起始RVA
DWORD  ImageBase;      //PE文件的裝載地址
DWORD  SectionAlignment;    //塊對齊
DWORD  FileAlignment;    //文件塊對齊
WORD  MajorOperatingSystemVersion;//所需操作系統版本號
WORD  MinorOperatingSystemVersion;//
WORD  MajorImageVersion;    //用戶自定義版本號
WORD  MinorImageVersion;    //
WORD  MajorSubsystemVersion;  //win32子系統版本。若PE文件是專門爲Win32設計的
WORD  MinorSubsystemVersion;  //該子系統版本必定是4.0否則對話框不會有3維立體感
DWORD  Win32VersionValue;    //保留
DWORD  SizeOfImage;      //內存中整個PE映像體的尺寸
DWORD  SizeOfHeaders;    //所有頭+節表的大小
DWORD  CheckSum;      //校驗和
WORD  Subsystem;      //NT用來識別PE文件屬於哪個子系統
WORD  DllCharacteristics;    //
DWORD  SizeOfStackReserve;    //
DWORD  SizeOfStackCommit;    //
DWORD  SizeOfHeapReserve;    //
DWORD  SizeOfHeapCommit;    //
DWORD  LoaderFlags;      //
DWORD  NumberOfRvaAndSizes;  //
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
//IMAGE_DATA_DIRECTORY 結構數組。每個結構給出一個重要數據結構的RVA,比如引入地址表等
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

typedef struct _IMAGE_DATA_DIRECTORY {
DWORD  VirtualAddress;    //表的RVA地址
DWORD  Size;        //大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章