# Ch5 Windows PE/COFF [toc] ## 5.1 簡介 - PE 和 ELF 都源自 COFF 格式 - PE: 32bit, PE32+: 64bit - 和 ELF 一樣 PE 也是基於 section 的格式,程式碼放 `.code` 資料放 `.data` - 也允許在程式中指定 section 位置,像 gcc 中的 `__attribute__((section("name")))` ```c= #pragma data_seg("FOO") int bar = 1; #pragma data_seg(".data") ``` ## 5.2 PE 的前身 - COFF 嘗試在 Windows 編譯 `SimpleSection.c` ```= cl /c /Za SimpleSection.c dumpbin /ALL SimpleSection.obj ``` - `/c`: 編譯時不 linking - `/Za`: 禁用 MSVC C/C++ 的 extension ``` $ dumpbin /SUMMARY SimpleSection.obj Microsoft (R) COFF/PE Dumper Version 14.38.33133.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file SimpleSection.obj File Type: COFF OBJECT Summary 4 .bss 40 .chks64 C .data 90 .debug$S 18 .drectve 18 .pdata 64 .text$mn 10 .xdata ``` ### COFF 結構 COFF 格式與 ELF 很像,由檔頭與 Section 組成: - 檔頭(Image Header) - Section Header(Section Table) ![image](https://hackmd.io/_uploads/HyGgIKzq6.png) 在 C 中的 struct 由 `IMAGE_FILE_HEADER` 表示,放在 `WinNT.h` 裏頭: ```c= typedef struct _IMAGE_FILE_HEADER { WORD Machine; // 目標機器 WORD NumberOfSections; // Section 數量 DWORD TimeDateStamp; // 創建時間 DWORD PointerToSymbolTable; // 指向 Symbol Table 的指標 DWORD NumberOfSymbols; // Symbol 的數量 WORD SizeOfOptionalHeader; // Optional Header 的大小,只存在 PE 中,COFF 沒有 WORD Characteristics; // 額外資訊 } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; ``` 可以觀察 `dumpbin` 最開始輸出的訊息,直接對應到了這個 struct 的值 ``` Microsoft (R) COFF/PE Dumper Version 14.38.33133.0 Copyright (C) Microsoft Corporation. All rights reserved. Dump of file SimpleSection.obj File Type: COFF OBJECT FILE HEADER VALUES 8664 machine (x64) 8 number of sections 65B503A5 time date stamp Sat Jan 27 21:22:45 2024 342 file pointer to symbol table 21 number of symbols 0 size of optional header 0 characteristics ``` 後面跟著就是 Section Header 的陣列,在 C 中的 struct 是: `IMAGE_SECTION_HEADER` ```c= typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 名稱 union { DWORD PhysicalAddress; // 物理地址 DWORD VirtualSize; // 載入記憶體後的大小 } Misc; DWORD VirtualAddress; // 虛擬地址 DWORD SizeOfRawData; // Section 在文件中的大小 (可能和 VirtualSize 不一樣) DWORD PointerToRawData; // 指向 Section 在文件中的位置 DWORD PointerToRelocations; // 指向重定位表在文件中的位置 DWORD PointerToLinenumbers; // 指向 COFF line number entries 的指標 WORD NumberOfRelocations; // Relocation entries 的數量 WORD NumberOfLinenumbers; // COFF line number entries的數量 DWORD Characteristics; // section 的資訊,包含 section 的類型、 // 對齊方式、wrx 權限等,這些資訊用 bit flag // 方式儲存。 } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; ``` 每一個表的 header 後面接著 section 的實際內容,這裡與 ELF 幾乎一樣,唯有兩個 section 只有在 PE 裡面有: `.drectve` 和 `.debug$S`。 > https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_section_header ## 5.3 `.drectve` Linking directive message ```= SECTION HEADER #1 .drectve name 0 physical address 0 virtual address 18 size of raw data 154 file pointer to raw data (00000154 to 0000016B) 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 100A00 flags Info Remove 1 byte align RAW DATA #1 00000000: 20 20 20 2F 44 45 46 41 55 4C 54 4C 49 42 3A 22 /DEFAULTLIB:" 00000010: 4C 49 42 43 4D 54 22 20 LIBCMT" Linker Directives ----------------- /DEFAULTLIB:LIBCMT ``` `.drectve` 為 "Directive" 縮寫,儲存 compiler 告訴 linker 如何 linking object file。 內容包含了 section 的地址、長度、位置、和 flag ,即 `IMAGE_SECTION_HEADER` 中的 `Characteristics` 成員,上面範例的值是 `0x100A00` 其意義如 5-2 所示: ![image](https://hackmd.io/_uploads/rJF5e9fqa.png) 接著就是 `RAW DATA #1`,這是原始數據, dumpbin 解析出來的訊息為 "/DEFAULTLIB:'LIBCMT'", linker 就要去 lib 下面找出 libcmt.lib,並 link 進來。linker 拿到 SimpleSection.obj 時看到這個訊息,他就會自動加上 /DEFAULT:'LIBCMT' 到 linker 參數中。 > LIBCMT 是 Library C Multithreaded 的縮寫,代表 MSVC 的靜態連接的多線程 CRT ## 5.4 Debug Info COFF 中所有以 `.debug` 開頭的 section 都包含 debug 訊息。 - `.debug$S`: 符號(Symbol) 相關的 debug 訊息 - `.debug$P`: Precompiled Header 相關的 debug 訊息 - `.debug$T`: 類型(Type) 相關的 debug 訊息 從 SimpleSection 的 dumpbin 中可以看到,只有 `.debug$S`,可以從中看到目標文件的原始路徑、編譯器資訊等 ```= SECTION HEADER #2 .debug$S name 0 physical address 0 virtual address 90 size of raw data 16C file pointer to raw data (0000016C to 000001FB) 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 42100040 flags Initialized Data Discardable 1 byte align Read Only RAW DATA #2 00000000: 04 00 00 00 F1 00 00 00 83 00 00 00 45 00 01 11 ....?......E... 00000010: 00 00 00 00 43 3A 5C 55 73 65 72 73 5C 72 6F 79 ....C:\Users\roy 00000020: 34 38 30 31 5C 44 65 73 6B 74 6F 70 5C 50 72 6F 4801\Desktop\Pro 00000030: 67 72 61 6D 5C 74 6D 70 5C 4C 69 6E 6B 69 6E 67 gram\tmp\Linking 00000040: 5C 53 69 6D 70 6C 65 53 65 63 74 69 6F 6E 2E 6F \SimpleSection.o 00000050: 62 6A 00 3A 00 3C 11 00 62 00 00 D0 00 13 00 26 bj.:.<..b..?..& 00000060: 00 6D 81 00 00 13 00 26 00 6D 81 00 00 4D 69 63 .m.....&.m...Mic 00000070: 72 6F 73 6F 66 74 20 28 52 29 20 4F 70 74 69 6D rosoft (R) Optim 00000080: 69 7A 69 6E 67 20 43 6F 6D 70 69 6C 65 72 00 00 izing Compiler.. ``` > 延伸: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#the-debug-section ## 5.5 大家都有符號表 COFF 符號表,基本上和 ELF 的一樣,包含了符號名稱、類型、位置等 ```= COFF SYMBOL TABLE 000 0104816D ABS notype Static | @comp.id 001 80010190 ABS notype Static | @feat.00 002 00000002 ABS notype Static | @vol.md 003 00000000 SECT1 notype Static | .drectve Section length 18, #relocs 0, #linenums 0, checksum 0 005 00000000 SECT2 notype Static | .debug$S Section length 90, #relocs 0, #linenums 0, checksum 0 007 00000000 SECT3 notype Static | .data Section length C, #relocs 0, #linenums 0, checksum AC5AB941 009 00000000 SECT3 notype External | global_init_var 00A 00000004 UNDEF notype External | global_uninit_var 00B 00000000 SECT4 notype Static | .text$mn Section length 64, #relocs 5, #linenums 0, checksum D696A53 00D 00000000 UNDEF notype () External | printf 00E 00000000 SECT4 notype () External | func1 00F 00000030 SECT4 notype () External | main 010 00000000 SECT4 notype Label | $LN3 011 00000030 SECT4 notype Label | $LN3 012 00000000 SECT5 notype Static | .xdata Section length 10, #relocs 0, #linenums 0, checksum 434E1581 014 00000000 SECT5 notype Static | $unwind$func1 015 00000000 SECT6 notype Static | .pdata Section length 18, #relocs 6, #linenums 0, checksum 5710F00F 017 00000000 SECT6 notype Static | $pdata$func1 018 00000008 SECT5 notype Static | $unwind$main 019 0000000C SECT6 notype Static | $pdata$main 01A 00000004 SECT3 notype Static | $SG7474 01B 00000008 SECT3 notype Static | ?static_var@?1??main@@9@9 (`main'::`2'::static_var) 01C 00000000 SECT7 notype Static | .bss Section length 4, #relocs 0, #linenums 0, checksum 0 01E 00000000 SECT7 notype Static | ?static_var2@?1??main@@9@9 (`main'::`2'::static_var2) 01F 00000000 SECT8 notype Static | .chks64 Section length 40, #relocs 0, #linenums 0, checksum 0 ``` - 每一行從左邊依序是: 1. symbol 的 index 2. symbol 大小 3. 符號所在的位置: - `ABS`:表示符號不存在任何一個 section - `SECT1`:等同 SECTION #1,表示這個符號存在於第一個 section - `UNDEF`:未定義 4. 符號類型 - `notype`: 變數和其他類型 - `notype()`: funciton 5. 符號的可見範圍: - `static`:local variable,只有 object file 的內部可見 - `external`:global variable,可以被其他 object file 引用 6. 符號名稱 - 怎麼看這張表? - 例如 `global_uninit_var` ``` 00A 00000004 UNDEF notype External | global_uninit_var ``` - `00A`: 第10個變數 - `00000004`: 大小為 4 bytes - `UNDEF`: 不存在於任何一個 section - `notype`: global_uninit_var 是一個變數 - `External`: 可以被其他的 object file 引用 - `global_uninit_var`: 這個變數的名字 - 比較特殊的是 `$SG7474` - 其實是指向 `"%d\n"` 的符號,但因為 string constant 沒有名字所以編譯器自動產生了一個 - ELF 並沒有這種符號 ``` SECTION HEADER #3 .data name 0 physical address 0 virtual address C size of raw data 1FC file pointer to raw data (000001FC to 00000207) 0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers C0300040 flags Initialized Data 4 byte align Read Write RAW DATA #3 00000000: 54 00 00 00 25 64 0A 00 55 00 00 00 T...%d..U... ``` - 指向段的符號 - `dumpbin` 還會解析該符號指向的 section 資訊 ``` 007 00000000 SECT3 notype Static | .data Section length C, #relocs 0, #linenums 0, checksum AC5AB941 ``` ## 5.6 PE format PE format 是基於 COFF 的擴展,跟 COFF 最大的差別在多了: DOS MZ Header 和 stub、原本的 `IMAGE_FILE_HEADER` 變成 `IMAGE_NT_HEADER`。 ![image](https://hackmd.io/_uploads/r1PHEFXca.png) > DOS 和 Windows 的程式 extension 都是 `.exe`,但 DOS 的執行檔格式是 `MZ`,PE 為了向後相容,所以檔頭就有 Image DOS Header 和 Image DOS Stub,這部分和 MZ 格式是一樣的(為了讓 DOS 也可以打開) > 但 PE 所產生的 `IMAGE_DOS_HEADER` 其中的 `e_cs` 和 `e_ip` (EntryPoint address) 其實指向 Image DOS Stub,裡面包含了一小段程式,只用來輸出 `This program cannot be run in DOS`。 ```c= 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; ``` `IMAGE_DOS_HEADER` 裡面大多數成員都不重要,只有 `e_lfanew` 代表 PE 檔頭(`IMAGE_NT_HEADERS`) 在檔案中的偏移。 如果該 field 為 `0` 代表他是 DOS MZ;不為 `0` 代表是 Windows PE。 `IMAGE_NT_HEADERS` 是真正的 PE 檔頭,包含了一個 magic 和兩個 struct,`Signature` 永遠是 `"PE\x00\x00"`(`0x00004550`),再來是 `IMAGE_FILE_HEADER` 和 PE擴展檔頭`IMAGE_OPTIONAL_HEADER`。 ```c= typedef struct _IMAGE_NT_HEADERS64 { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER64 OptionalHeader; } IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64; typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; ``` `IMAGE_FILE_HEADER` 與 COFF 一樣,`IMAGE_OPTIONAL_HEADER` 就是 PE 的檔頭部分。 :::spoiler `IMAGE_OPTIONAL_HEADER32` ```c= typedef struct _IMAGE_OPTIONAL_HEADER { // // Standard fields. // WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; // // NT additional fields. // 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_HEADER32, *PIMAGE_OPTIONAL_HEADER32; ``` ::: :::spoiler `IMAGE_OPTIONAL_HEADER64` ```c= typedef struct _IMAGE_OPTIONAL_HEADER64 { WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; ULONGLONG 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; ULONGLONG SizeOfStackReserve; ULONGLONG SizeOfStackCommit; ULONGLONG SizeOfHeapReserve; ULONGLONG SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64; ``` ::: - MSVC 用 `_WIN64` 這個 macro 來切換 image header 的版本。 ```c= #ifdef _WIN64 typedef IMAGE_OPTIONAL_HEADER64 IMAGE_OPTIONAL_HEADER; typedef PIMAGE_OPTIONAL_HEADER64 PIMAGE_OPTIONAL_HEADER; #define IMAGE_NT_OPTIONAL_HDR_MAGIC IMAGE_NT_OPTIONAL_HDR64_MAGIC #else typedef IMAGE_OPTIONAL_HEADER32 IMAGE_OPTIONAL_HEADER; typedef PIMAGE_OPTIONAL_HEADER32 PIMAGE_OPTIONAL_HEADER; #define IMAGE_NT_OPTIONAL_HDR_MAGIC IMAGE_NT_OPTIONAL_HDR32_MAGIC #endif ``` :::info 這本書沒有很詳細講每個成員的意義,這章節只講和 static linking 相關的 ::: ### 5.6.1 PE Data Directory 對於 Windows 的 Loader 來說,在載入 PE 時,會去看 `IMAGE_OPTIONAL_HEADER` 裏頭的叫做 `DataDirectory` 的成員,裡面記載了關於 Load 所需要的資料結構: 導入表、導出表、資源、重定位表等。 ```c= typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 ``` 這個陣列每個 Entry 代表的意義都定義好了,例如第 `0` 個就是 `IMAGE_DIRECTORY_ENTRY_EXPORT`,也就是導出表(Export Table)所在的地址和大小。 ```c= // Directory Entries #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory // IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage) #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP #define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers #define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor ``` :::info 這本書沒有很詳細講每個成員的意義 ::: ## Links https://courses.cs.washington.edu/courses/cse378/03wi/lectures/LinkerFiles/coff.pdf