# 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)

在 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 所示:

接著就是 `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`。

> 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