21. PE结构-PE各个结构的基本概念
exe與dll幾乎沒什么區別,唯一區別就是一個字段標識出這個文件是exe還是dll
32位叫PE32,64位叫PE32+
PE主要定義在winnt.h
vc搜索:image format,定義PE結構
PE文件分32和64位之分:IMAGE_NT_HEADERS32或IMAGE_NT_HEADERS64
第二張圖,百度搜索:PE文件格式示意圖
GetModuleHandle(LPCTSTR lpModuleName);獲取模塊句柄的地址
?
?
MS-DOS頭部
IMAGE_DOS_HEADER STRUCT?
{?
? ? +0h WORD e_magic? ?// Magic DOS signature MZ(4Dh 5Ah) ? ? DOS可執行文件標記?
? ? +2h ? WORD? e_cblp ?// Bytes on last page of file
? ? +4h WORD? e_cp? ?// Pages in file?
? ? +6h WORD? e_crlc? ?// Relocations?
? ? +8h WORD? e_cparhdr ? // Size of header in paragraphs?
? ? +0ah WORD? e_minalloc ?// Minimun extra paragraphs needs?
? ? +0ch WORD? e_maxalloc ?// Maximun extra paragraphs needs?
? ? +0eh WORD? e_ss? ? // intial(relative)SS value ? ???DOS代碼的初始化堆棧SS?
? ? +10h WORD? e_sp? ? // intial SP value ? ? ? ? ? ?? ? ?DOS代碼的初始化堆棧指針SP?
? ? +12h WORD? e_csum? ? // Checksum?
? ? +14h WORD? e_ip? ? // ? ?intial IP value? ? ? ? ? ? ? ? ? ? ?DOS代碼的初始化指令入口[指針IP]?
? ? +16h WORD? e_cs? ? // intial(relative)CS value ? ? ? ? ? ? ? ? ? ?DOS代碼的初始堆棧入口?
? ? +18h WORD? e_lfarlc? ? // File Address of relocation table?
? ? +1ah WORD? e_ovno ? ? ? ?// ? ?Overlay number?
? ? +1ch WORD? e_res[4]? ? // Reserved words?
? ? +24h WORD? e_oemid? ? // ? ?OEM identifier(for e_oeminfo)?
? ? +26h WORD ? ? ?e_oeminfo ? // ? ?OEM information;e_oemid specific ?
? ? +29h WORD? e_res2[10] ? // ? ?Reserved words?
? ? +3ch DWORD ? e_lfanew ? ? // Offset to start of PE header ? ? ? ? ? ? 指向PE文件頭?
} IMAGE_DOS_HEADER ENDS
結構體第一個最后一個變量,偏移為100?
?
PE頭文件
IMAGE_NT_HEADERS STRUCT?
{?
? ? +0h? ? DWORD? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Signature??//
? ? +4h? ? IMAGE_FILE_HEADER? ? ? ? ? ? ? ? ? ? FileHeader?//
? ? +18h? IMAGE_OPTIONAL_HEADER32? ? ?OptionalHeader ? //
} IMAGE_NT_HEADERS ENDS
Signature 字段:
在一個有效的 PE 文件里,Signature 字段被設置為00004550h, ASCII 碼字符是“PE00”。標志這 PE 文件頭的開始。
“PE00” 字符串是 PE 文件頭的開始,DOS 頭部的 e_lfanew 字段正是指向這里。
如下圖所示:
IMAGE_FILE_HEADER 結構?
typedef struct _IMAGE_FILE_HEADER? {+04h WORD ? Machine; // 運行平臺+06h WORD ? NumberOfSections; // 文件的區塊數目 +08h DWORD TimeDateStamp; // 文件創建日期和時間+0Ch DWORD PointerToSymbolTable; // 指向符號表(主要用于調試) +10h DWORD NumberOfSymbols; // 符號表中符號個數(同上) +14h WORD ? SizeOfOptionalHeader; // IMAGE_OPTIONAL_HEADER32 結構大小 +16h WORD ? Characteristics; // 文件屬性 } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; 該結構如下圖所示:(1)Machine:可執行文件的目標CPU類型。
| IMAGE_FILE_MACHINE_I386 0x014c ? | x86 |
| IMAGE_FILE_MACHINE_IA64 0x0200 | Intel Itanium |
| IMAGE_FILE_MACHINE_AMD64 0x8664 | x64 |
(2)NumberOfSection:?區塊的數目。(注:區塊表是緊跟在 IMAGE_NT_HEADERS 后邊的)
(3)TimeDataStamp:?表明文件是何時被創建的。
這個值是自1970年1月1日以來用格林威治時間(GMT)計算的秒數,這個值是比文件系統(FILESYSTEM)的日期時間更加精確的指示器。如何將這個值翻譯請看:http://home.fishc.com/space.php?uid=9&do=blog&id=555
提示:VC的話可以用_ctime 函數或者 gmtime 函數。
(4)PointerToSymbolTable:?COFF 符號表的文件偏移位置,現在基本沒用了。
(5)NumberOfSymbols:?如果有COFF 符號表,它代表其中的符號數目,COFF符號是一個大小固定的結構,如果想找到COFF 符號表的結束位置,則需要這個變量。
(6)SizeOfOptionalHeader:?緊跟著IMAGE_FILE_HEADER 后邊的數據結構(IMAGE_OPTIONAL_HEADER)的大小。(對于32位PE文件,這個值通常是00E0h;對于64位PE32+文件,這個值是00F0h )。
(7)Characteristics:?文件屬性,有選擇的通過幾個值可以運算得到。( 這些標志的有效值是定義于 winnt.h 內的 IMAGE_FILE_** 的值,具體含義見下表。普通的EXE文件這個字段的值一般是 0100h,DLL文件這個字段的值一般是 210Eh。)溫馨提示:多種屬性可以通過 “或運算” 使得同時擁有!
| IMAGE_FILE_RELOCS_STRIPPED ?? 0x0001 | Relocation information was stripped from the? file.?The file must be loaded at its preferred base address.?If the base address is not? available, the loader?reports an error. |
| IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 | The file is executable (there are no unresolved? external references). |
| IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 | COFF line numbers were stripped from the? file. |
| IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 | COFF symbol table entries were stripped from? file. |
| IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 | Aggressively trim the working set. This value is? obsolete?as of Windows 2000. |
| IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 | The application can handle addresses larger? than 2 GB. |
| IMAGE_FILE_BYTES_REVERSED_LO 0x0080 | The bytes of the word are reversed. This flag ?is obsolete. |
| IMAGE_FILE_32BIT_MACHINE 0x0100 | The computer supports 32-bit words. |
| IMAGE_FILE_DEBUG_STRIPPED 0x0200 | Debugging information was removed and stored? separately?in another file. |
| IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 | If the image is on removable media, copy it to and run it?from the swap file. |
| IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 | If the image is on the network, copy it to and? run it from?the swap file. |
| IMAGE_FILE_SYSTEM 0x1000 | The image is a system file. |
| IMAGE_FILE_DLL 0x2000 | The image is a DLL file. While it is an executable ?file,?it cannot be run directly. |
| IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 | The file should be run only on a uniprocessor ?computer. |
| IMAGE_FILE_BYTES_REVERSED_HI 0x8000 | The bytes of the word are reversed. This flag ?is obsolete. |
IMAGE_OPTIONAL_HEADER32
typedef struct _IMAGE_OPTIONAL_HEADER?
{
? ? ? ?//
? ? ? ?// Standard fields.??
? ? ? ?//
? ?+18h??? WORD??? Magic;? ? ? ? ? ? ? // 標志字, ROM 映像(0107h),普通可執行文件(010Bh)
? ?+1Ah??? BYTE????? MajorLinkerVersion;???? // 鏈接程序的主版本號
? ?+1Bh??? BYTE????? MinorLinkerVersion;???? // 鏈接程序的次版本號
? ?+1Ch??? DWORD?? SizeOfCode;???? // 所有含代碼的節的總大小
? ?+20h??? DWORD?? SizeOfInitializedData;??? // 所有含已初始化數據的節的總大小
? ?+24h??? DWORD?? SizeOfUninitializedData; // 所有含未初始化數據的節的大小
? ?+28h??? DWORD?? AddressOfEntryPoint;??? // 程序執行入口RVA
? ?+2Ch??? DWORD?? BaseOfCode;????? // 代碼的區塊的起始RVA
? ?+30h??? DWORD?? BaseOfData;????? // 數據的區塊的起始RVA
? ? ? ?//
? ? ? ?// NT additional fields.??? 以下是屬于NT結構增加的領域。
? ? ? ?//
? ?+34h??? DWORD?? ImageBase;????? // 程序的首選裝載地址
? ?+38h??? DWORD?? SectionAlignment;????? // 內存中的區塊的對齊大小
? ?+3Ch??? DWORD?? FileAlignment;????? // 文件中的區塊的對齊大小
? ?+40h??? WORD??? MajorOperatingSystemVersion;? // 要求操作系統最低版本號的主版本號
? ?+42h??? WORD??? MinorOperatingSystemVersion;? // 要求操作系統最低版本號的副版本號
? ?+44h??? WORD??? MajorImageVersion;?????? // 可運行于操作系統的主版本號
? ?+46h??? WORD??? MinorImageVersion;?????? // 可運行于操作系統的次版本號
? ?+48h??? WORD??? MajorSubsystemVersion;? // 要求最低子系統版本的主版本號
? ?+4Ah??? WORD??? MinorSubsystemVersion;? // 要求最低子系統版本的次版本號
? ?+4Ch??? DWORD?? Win32VersionValue;?????? // 莫須有字段,不被病毒利用的話一般為0
? ?+50h??? DWORD?? SizeOfImage;?????? // 映像裝入內存后的總尺寸
? ?+54h??? DWORD?? SizeOfHeaders;?????? // 所有頭 + 區塊表的尺寸大小
? ?+58h??? DWORD?? CheckSum;?????? // 映像的校檢和
? ?+5Ch??? WORD??? Subsystem;?????? // 可執行文件期望的子系統
? ?+5Eh??? WORD??? DllCharacteristics;?????? // DllMain()函數何時被調用,默認為 0
? ?+60h??? DWORD?? SizeOfStackReserve;?????? // 初始化時的棧大小
? ?+64h??? DWORD?? SizeOfStackCommit;?????? // 初始化時實際提交的棧大小
? ?+68h??? DWORD?? SizeOfHeapReserve;??????? // 初始化時保留的堆大小
? ?+6Ch??? DWORD?? SizeOfHeapCommit;??????? // 初始化時實際提交的堆大小
? ?+70h??? DWORD?? LoaderFlags;??????? // 與調試有關,默認為 0?
? ?+74h??? DWORD?? NumberOfRvaAndSizes;? // 下邊數據目錄的項數,這個字段自Windows NT 發布以來, 一直是16
? ?+78h??? IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
? ? ? ? ? // 數據目錄表 16個數組
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
●??AddressOfEntryPoint字段
指出文件被執行時的入口地址,這是一個RVA地址(RVA的含義在下一節中詳細介紹)。如果在一個可執行文件上附加了一段代碼并想讓這段代碼首先被執行,那么只需要將這個入口地址指向附加的代碼就可以了。
●??ImageBase字段
指出文件的優先裝入地址。也就是說當文件被執行時,如果可能的話,Windows優先將文件裝入到由ImageBase字段指定的地址中,只有指定的地址已經被**模塊使用時,文件才被裝入到**地址中。鏈接器產生可執行文件的時候對應這個地址來生成機器碼,所以當文件被裝入這個地址時不需要進行重定位操作,裝入的速度最快,如果文件被裝載到**地址的話,將不得不進行重定位操作,這樣就要慢一點。
對于EXE文件來說,由于每個文件總是使用獨立的虛擬地址空間,優先裝入地址不可能被**模塊占據,所以EXE總是能夠按照這個地址裝入,這也意味著EXE文件不再需要重定位信息。對于DLL文件來說,由于多個DLL文件全部使用宿主EXE文件的地址空間,不能保證優先裝入地址沒有被**的DLL使用,所以DLL文件中必須包含重定位信息以防萬一。因此,在前面介紹的 IMAGE_FILE_HEADER 結構的 Characteristics 字段中,DLL 文件對應的 IMAGE_FILE_RELOCS_STRIPPED 位總是為0,而EXE文件的這個標志位總是為1。
在鏈接的時候,可以通過對link.exe指定/base:address選項來自定義優先裝入地址,如果不指定這個選項的話,一般EXE文件的默認優先裝入地址被定為00400000h,而DLL文件的默認優先裝入地址被定為10000000h。
●??SectionAlignment 字段和 FileAlignment字段
SectionAlignment字段指定了節被裝入內存后的對齊單位。也就是說,每個節被裝入的地址必定是本字段指定數值的整數倍。(默認1000H)
FileAlignment字段指定了節存儲在磁盤文件中時的對齊單位。(默認200H)
●??Subsystem字段
指定使用界面的子系統,它的取值如表17.3所示。這個字段決定了系統如何為程序建立初始的界面,鏈接時的/subsystem:**選項指定的就是這個字段的值,在前面章節的編程中我們早已知道:如果將子系統指定為Windows CUI,那么系統會自動為程序建立一個控制臺窗口,而指定為Windows GUI的話,窗口必須由程序自己建立。
界面子系統的取值和含義
| 取???值 | Windows.inc中的預定義值 | 含???義 |
| 0 | IMAGE_SUBSYSTEM_UNKNOWN | 未知的子系統 |
| 1 | IMAGE_SUBSYSTEM_NATIVE | 不需要子系統(如驅動程序) |
| 2 | IMAGE_SUBSYSTEM_WINDOWS_GUI | Windows圖形界面 |
| 3 | IMAGE_SUBSYSTEM_WINDOWS_CUI | Windows控制臺界面 |
| 5 | IMAGE_SUBSYSTEM_OS2_CUI | OS2控制臺界面 |
| 7 | IMAGE_SUBSYSTEM_POSIX_CUI | POSIX控制臺界面 |
| 8 | IMAGE_SUBSYSTEM_NATIVE_WINDOWS | 不需要子系統 |
| 9 | IMAGE_SUBSYSTEM_WINDOWS_CE_GUI | Windows CE圖形界面 |
●??DataDirectory字段
這個字段可以說是最重要的字段之一,它由16個相同的IMAGE_DATA_DIRECTORY結構組成,雖然PE文件中的數據是按照裝入內存后的頁屬性歸類而被放在不同的節中的,但是這些處于各個節中的數據按照用途可以被分為導出表、導入表、資源、重定位表等數據塊,這16個IMAGE_DATA_DIRECTORY結構就是用來定義多種不同用途的數據塊的(如表17.4所示)。IMAGE_DATA_DIRECTORY結構的定義很簡單,它僅僅指出了某種數據塊的位置和長度。
IMAGE_DATA_DIRECTORY STRUCT
?VirtualAddress DWORD ? ;數據的起始RVA
?isize DWORD ? ;數據塊的長度
IMAGE_DATA_DIRECTORY ENDS
數據目錄列表的含義
| 索???引 | 索引值在Windows.inc中的預定義值 | 對應的數據塊 |
| 0 | IMAGE_DIRECTORY_ENTRY_EXPORT | 導出表 |
| 1 | IMAGE_DIRECTORY_ENTRY_IMPORT | 導入表 |
| 2 | IMAGE_DIRECTORY_ENTRY_RESOURCE | 資源 |
| 3 | IMAGE_DIRECTORY_ENTRY_EXCEPTION | 異常(具體資料不詳) |
| 4 | IMAGE_DIRECTORY_ENTRY_SECURITY | 安全(具體資料不詳) |
| 5 | IMAGE_DIRECTORY_ENTRY_BASERELOC | 重定位表 |
| 6 | IMAGE_DIRECTORY_ENTRY_DEBUG | 調試信息 |
| 7 | IMAGE_DIRECTORY_ENTRY_ARCHITECTURE | 版權信息 |
| 8 | IMAGE_DIRECTORY_ENTRY_GLOBALPTR | 具體資料不詳 |
| 9 | IMAGE_DIRECTORY_ENTRY_TLS | Thread Local Storage |
| 10 | IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG | 具體資料不詳 |
| 11 | IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT | 具體資料不詳 |
| 12 | IMAGE_DIRECTORY_ENTRY_IAT | 導入函數地址表 |
| 13 | IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT | 具體資料不詳 |
| 14 | IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR | 具體資料不詳 |
| 15 | 未使用 | ? |
在PE文件中尋找特定的數據時就是從這些IMAGE_DATA_DIRECTORY結構開始的,比如要存取資源,那么必須從第3個IMAGE_DATA_DIRECTORY結構(索引為2)中得到資源數據塊的大小和位置;同理,如果要查看PE文件導入了哪些DLL文件的哪些API函數,那就必須首先從第2個IMAGE_DATA_DIRECTORY結構得到導入表的位置和大小。
?
區塊表(節表)和區塊(節)
到此為止,和大家已經學了許多關于?DOS?header?和?PE?header?的知識。接下來就該輪到SectionTable?(區塊表,也成節表)。
PE文件到內存的映射
在執行一個PE文件的時候,windows 并不在一開始就將整個文件讀入內存的,而是采用與內存映射文件類似的機制。也就是說,windows 裝載器在裝載的時候僅僅建立好虛擬地址和PE文件之間的映射關系。
當且僅當真正執行到某個內存頁中的指令或者訪問某一頁中的數據時,這個頁面才會被從磁盤提交到物理內存,這種機制使文件裝入的速度和文件大小沒有太大的關系。
但是要注意的是,系統裝載可執行文件的方法又不完全等同于內存映射文件。
當使用內存映射文件的時候,系統對“原著”相當忠實,如果將磁盤文件和內存映像比較的話,可以發現不管是數據本身還是數據之間的相對位置它丫丫的都是完全相同的。
而我們知道,在裝載可執行文件的時候,有些數據在裝入前會被預處理,如重定位等,正因此,裝入以后,數據之間的相對位置可能發生微妙的變化。
Windows 裝載器在裝載DOS部分、PE文件頭部分和節表(區塊表)部分是不進行任何特殊處理的,而在裝載節(區塊)的時候則會自動按節(區塊)的屬性做不同的處理。
一般情況下,它會處理以下幾個方面的內容:
- 內存頁的屬性;
- 節的偏移地址;
- 節的尺寸;
- 不進行映射的節。
內存頁的屬性:
對于磁盤映射文件來說,所有的頁都是按照磁盤映射文件函數指定的屬性設置的。但是在裝載可執行文件時,與節對應的內存頁屬性要按照節的屬性來設置。所以,在同屬于一個模塊的內存頁中,從不同節映射過來的的內存頁的屬性是不同的。
?
節的偏移地址:
節的起始地址在磁盤文件中是按照 IMAGE_OPTIONAL_HEADER32 結構的 FileAlignment 字段的值進行對齊的,而當被加載到內存中時是按照同一結構中的 SectionAlignment 字段的值對其的,兩者的值可能不同,所以一個節被裝入內存后相對于文件頭的偏移和在磁盤文件中的偏移可能是不同的。
?
注意,節事實上就是相同屬性數據的組合!當節被裝入到內存中的時候,相同一個節所對應的內存頁都將被賦予相同的頁屬性, 事實上,Windows 系統對內存屬性的設置是以頁為單位進行的,所以節在內存中的對齊單位必須至少是一個頁的大小。(溫馨提示:對于32位操作系統來說,這個值一般是4KB==1000H; 對于64位操作系統這個值一般是8KB==2000H)
?
在磁盤中就沒有這個**,因為在磁盤中排放是以什么為主?肯定是以空間為主導,在磁盤只是存放,不是使用,所以不用設置那么詳細的屬性。試想想看,如果在磁盤中都是以4KB為大小對齊的話,不夠就用0來填充,那么一個只占20字節的數據就要消耗4KB的空間來存放,是不是浪費?有木有??
?
節的尺寸:
對節的尺寸的處理主要分為兩個方面:
第一個方面,正如剛剛我們所講的,由于磁盤映像和內存映像中節對齊存儲單位的不同而導致了長度擴展不同(填充的0數量不同嘛~);
第二個方面,是對于包含未初始化數據的節的處理問題。既然是未初始化,那么沒有必要為其在磁盤中浪費空間資源,但在內存中不同,因為程序一運行,之前未初始化的數據便有可能要被賦值初始化,那么就必須為他們留下空間。
?
不進行映射的節:
有些節并不需要被映射到內存中,例如.reloc節,重定位數據對于文件的執行代碼來說是透明的,無作用的,它只是提供Windows 裝載器使用,執行代碼根本不會去訪問到它們,所以沒有必要將他們映射到物理內存中。
節表(區塊表):
PE文件中所有節的屬性都被定義在節表中,節表由一系列的IMAGE_SECTION_HEADER結構排列而成,每個結構用來描述一個節,結構的排列順序和它們描述的節在文件中的排列順序是一致的。全部有效結構的最后以一個空的IMAGE_SECTION_HEADER結構作為結束,所以節表中總的IMAGE_SECTION_HEADER結構數量等于節的數量加一。節表總是被存放在緊接在PE文件頭的地方。
另外,節表中 IMAGE_SECTION_HEADER 結構的總數總是由PE文件頭 IMAGE_NT_HEADERS 結構中的 FileHeader.NumberOfSections 字段來指定的。
typedef?struct?_IMAGE_SECTION_HEADER?
{
??????? BYTE?Name[IMAGE_SIZEOF_SHORT_NAME];???? // 節表名稱,如“.text”?
????????//IMAGE_SIZEOF_SHORT_NAME=8
????????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;?
Name:區塊名。這是一個由8位的ASCII 碼名,用來定義區塊的名稱。多數區塊名都習慣性以一個“.”作為開頭(例如:.text),這個“.” 實際上是不是必須的。值得我們注意的是,如果區塊名超過 8 個字節,則沒有最后的終止標志“NULL” 字節。并且前邊帶有一個“$” 的區塊名字會從連接器那里得到特殊的待遇,前邊帶有“$” 的相同名字的區塊在載入時候將會被合并,在合并之后的區塊中,他們是按照“$” 后邊的字符的字母順序進行合并的。
另外童鞋要跟大家啰嗦一下的是:每個區塊的名稱都是唯一的,不能有同名的兩個區塊。但事實上節的名稱不代表任何含義,他的存在僅僅是為了正規統一編程的時候方便程序員查看方便而設置的一個標記而已。所以將包含代碼的區塊命名為“.Data” 或者說將包含數據的區塊命名為“.Code” 都是合法的。
因此,建議大家:當我們要從PE 文件中讀取需要的區塊時候,不能以區塊的名稱作為定位的標準和依據,正確的方法是按照 IMAGE_OPTIONAL_HEADER32 結構中的數據目錄字段結合進行定位。
Virtual Size:該表對應的區塊的大小,這是區塊的數據在沒有進行對齊處理前的實際大小。
Virtual Address:該區塊裝載到內存中的RVA 地址。這個地址是按照內存頁來對齊的,因此它的數值總是 SectionAlignment 的值的整數倍。在Microsoft 工具中,第一個快的默認 RVA 總為1000h。在OBJ 中,該字段沒有意義地,并被設為0。
SizeOfRawData:該區塊在磁盤中所占的大小。在可執行文件中,該字段是已經被FileAlignment 潛規則處理過的長度。
PointerToRawData:該區塊在磁盤中的偏移。這個數值是從文件頭開始算起的偏移量哦。
PointerToRelocations:這哥們在EXE文件中沒有意義,在OBJ 文件中,表示本區塊重定位信息的偏移值。(在OBJ 文件中如果不是零,它會指向一個IMAGE_RELOCATION 結構的數組)
PointerToLinenumbers:行號表在文件中的偏移值,文件的調試信息,于我們沒用,雞肋。
NumberOfRelocations:這哥們在EXE文件中也沒有意義,在OBJ 文件中,是本區塊在重定位表中的重定位數目來著。
NumberOfLinenumbers:該區塊在行號表中的行號數目,雞肋。
Characteristics:該區塊的屬性。該字段是按位來指出區塊的屬性(如代碼/數據/可讀/可寫等)的標志。
舉出最常用的一些屬性值:
具體內容可以參考MSDN在線文檔:http://msdn.microsoft.com/en-us/library/ms680341%28v=vs.85%29.aspx
| IMAGE_SCN_CNT_CODE 0x00000020 | ? The section contains executable code. 包含代碼,常與 0x10000000一起設置。 ? |
| IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 | ? The section contains initialized data. 該區塊包含以初始化的數據。 ? |
| IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 | ? The section contains uninitialized data. 該區塊包含未初始化的數據。 ? |
| IMAGE_SCN_MEM_DISCARDABLE 0x02000000 | The section can be discarded as needed. 該區塊可被丟棄,因為當它一旦被裝入后, 進程就不在需要它了,典型的如重定位區塊。 ? |
| IMAGE_SCN_MEM_SHARED 0x10000000 | The section can be shared in memory. 該區塊為共享區塊。 ? |
| IMAGE_SCN_MEM_EXECUTE 0x20000000 | The section can be executed as code. 該區塊可以執行。通常當0x00000020被設置 時候,該標志也被設置。 ? |
| IMAGE_SCN_MEM_READ 0x40000000 | The section can be read. 該區塊可讀,可執行文件中的區塊總是設置該 標志。 ? |
| IMAGE_SCN_MEM_WRITE 0x80000000 | The section can be written to. 該區塊可寫。 |
通常,區塊中的數據在邏輯上是關聯的。PE 文件一般至少都會有兩個區塊:一個是代碼塊,另一個是數據塊。每一個區塊都需要有一個截然不同的名字,這個名字主要是用來表達區塊的用途。例如有一個區塊叫.rdata,表明他是一個只讀區塊。注意:區塊在映像中是按起始地址(RVA)來排列的,而不是按字母表順序。
另外,使用區塊名字只是人們為了認識和編程的方便,而對操作系統來說這些是無關緊要的。微軟給這些區塊取了個有特色的名字,但這不是必須的。當編程從PE 文件中讀取需要的內容時,如輸入表、輸出表,不能以區塊名字作為參考,正確的方法是按照數據目錄表中的字段來進行定位。
下表中的區塊名稱以及意義:
當然我們在Visual C++ 中也可以自己命名我們的區塊,用#pragma 來聲明,告訴編譯器插入數據到一個區塊內,格式如下:
#pragma data_msg( "FC_data" )?
以上語句告訴編譯器將數據都放進一個叫“FC_data” 的區塊內,而不是默認的.data 區塊。區塊一般是從OBJ 文件開始,被編譯器放置的。鏈接器的工作就是合并左右OBJ 和庫中需要的塊,使其成為一個最終合適的區塊。鏈接器會遵循一套相當完整的規則,它會判斷哪些區塊將被合并以及如何被合并。
合并區塊:
鏈接器的一個有趣特征就是能夠合并區塊。如果兩個區塊有相似、一致性的屬性,那么它們在鏈接的時候能被合并成一個單一的區塊。這取決于是否開啟編譯器的 /merge 開關。事實上合并區塊有一個好處就是可以節省磁盤的內存空間……注意:我們不應該將.rsrc、.reloc、.pdata 合并到**的區塊里。
區塊的對齊值:
之前我們簡單了解過區塊是要對齊的,無論是在內存中存放還是在磁盤中存放~但他們一般的對齊值是不同的。
PE 文件頭里邊的FileAligment 定義了磁盤區塊的對齊值。每一個區塊從對齊值的倍數的偏移位置開始存放。而區塊的實際代碼或數據的大小不一定剛好是這么多,所以在多余的地方一般以00h 來填充,這就是區塊間的間隙。
例如,在PE文件中,一個典型的對齊值是200h ,這樣,每個區塊都將從200h 的倍數的文件偏移位置開始,假設第一個區塊在400h 處,長度為90h,那么從文件400h 到490h 為這一區塊的內容,而由于文件的對齊值是200h,所以為了使這一區塊的長度為FileAlignment 的整數倍,490h 到 600h 這一個區間都會被00h 填充,這段空間稱為區塊間隙,下一個區塊的開始地址為600h 。
PE 文件頭里邊的SectionAligment 定義了內存中區塊的對齊值。PE 文件被映射到內存中時,區塊總是至少從一個頁邊界開始。
一般在X86 系列的CPU 中,頁是按4KB(1000h)來排列的;在IA-64 上,是按8KB(2000h)來排列的。所以在X86 系統中,PE文件區塊的內存對齊值一般等于 1000h,每個區塊按1000h 的倍數在內存中存放。
RVA 和文件偏移的轉換
在前邊我們探討過RVA 這個詞,但對于初次接觸PE 文件的朋友來說,顯得尤其陌生和無奈。中國人不喜歡老外的縮寫,但總要**著接受……不過,在有了前邊知識的鋪墊之后,現在來談這個概念大家伙應該能夠得心應手了。起碼不用顯得那么的費解和無奈~
RVA 是相對虛擬地址(Relative Virtual Address)的縮寫,顧名思義,它是一個“相對地址”。PE 文件中的各種數據結構中涉及地址的字段大部分都是以 RVA 表示的,有木有??
更為準確的說,RVA 是當PE 文件被裝載到內存中后,某個數據位置相對于文件頭的偏移量。舉個例子,如果 Windows 裝載器將一個PE 文件裝入到 00400000h 處的內存中,而某個區塊中的某個數據被裝入 0040**xh 處,那么這個數據的 RVA 就是(0040**xh - 00400000h )= **xh,反過來說,將 RVA 的值加上文件被裝載的基地址,就可以找到數據在內存中的實際地址。
很明顯,我們發現,DOS 文件頭、PE 文件頭和區塊表的偏移位置與大小均沒有變化。而各個區塊映射到內存后,其偏移位置就發生了變化。
如何換算 RVA 和文件偏移呢?
當處理PE 文件時候,任何的 RVA 必須經過到文件偏移的換算,才能用來定位并訪問文件中的數據,但換算卻無法用一個簡單的公式來完成,事實上,唯一可用的方法就是最土最笨的方法:
步驟一:循環掃描區塊表得出每個區塊在內存中的起始 RVA(根據IMAGE_SECTION_HEADER 中的VirtualAddress 字段),并根據區塊的大小(根據IMAGE_SECTION_HEADER 中的SizeOfRawData 字段)算出區塊的結束 RVA(兩者相加即可),最后判斷目標 RVA 是否落在該區塊內。
步驟二:通過步驟一定位了目標 RVA 處于具體的某個區塊中后,那么用目標 RVA 減去該區塊的起始 RVA ,這樣就能得到目標 RVA 相對于起始地址的偏移量 RVA2.
步驟三:在區塊表中獲取該區塊在文件中所處的偏移地址(根據IMAGE_SECTION_HEADER 中的PointerToRawData 字段), 將這個偏移值加上步驟二得到的 RVA2 值,就得到了真正的文件偏移地址。
總結
以上是生活随笔為你收集整理的21. PE结构-PE各个结构的基本概念的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python json按输入顺序输出内容
- 下一篇: matlab实现一/多元线性回归