owned this note
owned this note
Published
Linked with GitHub
---
tags: [Technique]
---
# PE Parser
## Mở đầu
* Trong phần trước, mình đã bóc tách **PE file** dưới góc nhìn cấu trúc và có trình bày tại [đây](https://hackmd.io/XHiqM5aXTzqsHBpm4rj4hg). Nhưng lý thuyết thôi thì chưa đủ – lần này, hãy cùng nhau code tay một **PE parser**, không cần tới **CFF Explorer** hay **PEview**. Điều này không chỉ giúp ta hiểu **PE** ở tầng thấp hơn, mà còn có thể xây dựng công cụ của chính mình để phục vụ **reverse**, **malware analysis**, hoặc **fuzzing**.
* Thực chất thì một số **PE Parser** phổ biến chính là **CFF Explorer**, **PE-bear**, ...
* Nhưng chúng ta có bao giờ đặt ra câu hỏi, trong ngữ cảnh **CTF** nói riêng, hay phát triển rộng hơn trong lĩnh vực **malware**, thì có phải lúc nào chúng ta nhặt được 1 con `.exe` nào đấy, mà ném nó vào **CFF Explorer** hay **PE-bear** thì đều giúp chúng ta có thông tin đầy đủ của các trường không? Ý mình là có thể file bị pack, hay mất 1 vài trường quan trọng khiến cho khó phân tích tiếp, ....
* Đó chính là lý do, mà ít nhất những người học ``"rì vợt"`` như chúng ta, luôn cần có trong tay 1 code **PE Parser**, để phòng cho những trường hợp bất tiện như này, đúng không nào?
## 1 vài khái niệm
### VA
* **VA(`Virtual Address`)**, là địa chỉ ảo khi chương trình được load vào bộ nhớ.
* Ví dụ, load file vào **IDA** thấy địa chỉ hàm `main()` là `0x401000`, thì đó là **VA**.
### **Base Address**
* Là địa chỉ cơ sở của chương trình khi nạp vào bộ nhớ. Thường có giá trị là `0x400000` trên **Windows x86**.
### RVA
* **RVA(`Relative Virtual Address`)** là địa chỉ ảo tương đối của file. Được tính toán như sau: **RVA = VA - Base Address**.
### IAT
* **IAT(`Import Address Table`)** là 1 bảng sử dụng để lưu trữ địa chỉ của các **API** mà chương trình gọi trong lúc thực thi. Điều này giúp thuận tiện hơn trong quá trình trong việc quản lý, truy xuất, resolve, ... các **API** so với việc địa chỉ được lưu 1 cách rời rạc.
### File Offset
* Là thuật ngữ chỉ vị trí của một trường, hoặc một giá trị nào đó trong `raw file`( ví dụ như `e_magic` có `file offset` là $0x00$), nghĩa là vị trí này khi file chưa chạy vẫn ở trong bộ nhớ vật lý.
* Cách tính `file offset`:
**```File_offset = RVA - pSect->VirtualAddress + pSect->PointerToRawData```**
### Cấu trúc file PE

* Cấu trúc cơ bản của 1 file **PE** chia thành 2 phần chính:
* **Header**:
* DOS MZ header
* DOS Stub
* PE Header
* Section table
* **Section**:
* code
* data

* Có lẽ chỉ `"overview"` 1 chút như thế, còn chi tiết hơn thì trước mình đã có 1 writeup về phần này rồi. Mình để ở [đây](https://hackmd.io/XHiqM5aXTzqsHBpm4rj4hg?view) nhaaa.
* Hoặc đơn giản hơn thì bảng dưới đây cũng cung cấp đủ:
| **Thuật ngữ** | **Ý nghĩa** | **Ví dụ / Ghi chú** |
| ------------------------------------ | --------------------------------------------------------------- | ---------------------------------------------- |
| **VA** *(Virtual Address)* | Địa chỉ ảo của thành phần khi chương trình được load vào bộ nhớ | VD: `main()` tại `0x401000` trong IDA |
| **Base Address** | Địa chỉ bắt đầu khi chương trình được nạp vào RAM | Thường là `0x400000` (Windows x86) |
| **RVA** *(Relative Virtual Address)* | Địa chỉ ảo tương đối so với `Base Address` | `RVA = VA - Base Address` |
| **IAT** *(Import Address Table)* | Bảng lưu địa chỉ các API Windows mà chương trình sử dụng | Hỗ trợ resolve tên hàm sang địa chỉ thực |
| **File Offset** | Vị trí byte trong file PE khi còn nằm trên đĩa | VD: `e_magic` ở offset `0x00` |
| *(Công thức)* | `FileOffset = RVA - VirtualAddress + PointerToRawData` | Dùng để tìm vị trí thực của dữ liệu trong file |
## Code ...
### Part 1: Định nghĩa các structures, constants
#### Các types
```py
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef unsigned long long QWORD;
typedef unsigned long LONG;
typedef __int64 LONGLONG;
typedef unsigned __int64 ULONGLONG;
```
#### Các Constants
```py
#define ___IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b
#define ___IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b
#define ___IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
#define ___IMAGE_DOS_SIGNATURE 0x5A4D
#define ___IMAGE_DIRECTORY_ENTRY_EXPORT 0
#define ___IMAGE_DIRECTORY_ENTRY_IMPORT 1
#define ___IMAGE_DIRECTORY_ENTRY_RESOURCE 2
#define ___IMAGE_DIRECTORY_ENTRY_EXCEPTION 3
#define ___IMAGE_DIRECTORY_ENTRY_SECURITY 4
#define ___IMAGE_DIRECTORY_ENTRY_BASERELOC 5
#define ___IMAGE_DIRECTORY_ENTRY_DEBUG 6
#define ___IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7
#define ___IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8
#define ___IMAGE_DIRECTORY_ENTRY_TLS 9
#define ___IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10
#define ___IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11
#define ___IMAGE_DIRECTORY_ENTRY_IAT 12
#define ___IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13
#define ___IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14
#define ___IMAGE_SIZEOF_SHORT_NAME 8
#define ___IMAGE_SIZEOF_SECTION_HEADER 40
```
* Giải thích:
| Macro | Giá trị | Giải thích ngắn gọn |
| ----------------------------------------- | -------: | ---------------------------------------------------------------------------- |
| `___IMAGE_DOS_SIGNATURE` | `0x5A4D` | Magic của file PE ở DOS header: `"MZ"` (chữ cái đầu tên **Mark Zbikowski**) |
| `___IMAGE_NT_OPTIONAL_HDR32_MAGIC` | `0x10B` | Magic xác định là **PE32** (32-bit) |
| `___IMAGE_NT_OPTIONAL_HDR64_MAGIC` | `0x20B` | Magic xác định là **PE32+** (64-bit) |
| `___IMAGE_NUMBEROF_DIRECTORY_ENTRIES` | `16` | Số lượng phần tử trong `DataDirectory[]` của Optional Header |
| `___IMAGE_DIRECTORY_ENTRY_EXPORT` | `0` | Export Table – thông tin các hàm/biến được export từ PE |
| `___IMAGE_DIRECTORY_ENTRY_IMPORT` | `1` | Import Table – chứa thông tin các DLL và hàm được import |
| `___IMAGE_DIRECTORY_ENTRY_RESOURCE` | `2` | Resource Table – icon, dialog, string table, bitmap,... |
| `___IMAGE_DIRECTORY_ENTRY_EXCEPTION` | `3` | Exception Table – dùng cho cơ chế xử lý ngoại lệ (SEH/VEH) |
| `___IMAGE_DIRECTORY_ENTRY_SECURITY` | `4` | Digital Signature – xác thực tính hợp lệ của PE (Authenticode) |
| `___IMAGE_DIRECTORY_ENTRY_BASERELOC` | `5` | Base Relocation Table – hỗ trợ relocate khi PE không được load tại ImageBase |
| `___IMAGE_DIRECTORY_ENTRY_DEBUG` | `6` | Debug Data – thông tin phục vụ debugger (PDB) |
| `___IMAGE_DIRECTORY_ENTRY_ARCHITECTURE` | `7` | Dự phòng cho kiến trúc đặc biệt (Itanium) |
| `___IMAGE_DIRECTORY_ENTRY_GLOBALPTR` | `8` | Địa chỉ GlobalPtr (ít được dùng, chỉ với CPU cũ) |
| `___IMAGE_DIRECTORY_ENTRY_TLS` | `9` | Thread Local Storage – khởi tạo dữ liệu theo từng thread riêng |
| `___IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG` | `10` | Load Config Table – chứa thông tin cấu hình bảo mật (CFG, SEH table, ...) |
| `___IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT` | `11` | Bound Import Table – tối ưu tốc độ load DLL bằng cách lưu cache |
| `___IMAGE_DIRECTORY_ENTRY_IAT` | `12` | Import Address Table – lưu địa chỉ **đã resolve** của các hàm import |
| `___IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT` | `13` | Delay Load Table – cho phép chỉ resolve import khi cần dùng |
| `___IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR` | `14` | CLR Header – bắt buộc với PE thuộc .NET assembly |
| `___IMAGE_SIZEOF_SHORT_NAME` | `8` | Kích thước tối đa của tên section trong `SectionHeader` |
| `___IMAGE_SIZEOF_SECTION_HEADER` | `40` | Kích thước mỗi `Section Header`, cố định là 40 byte |
#### Các structures
##### IMAGE_DOS_HEADER
```py
typedef struct __IMAGE_DOS_HEADER {
WORD e_magic; // Magic number
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
LONG e_lfanew; // Offset tới PE Header
} ___IMAGE_DOS_HEADER, * ___PIMAGE_DOS_HEADER;
```
* Đây là header đầu tiên trong file **PE**.
* Được dùng bởi **DOS stub** để hiển thị thông báo (ví dụ: ``"This program cannot be run in DOS mode"``).
* Trường quan trọng nhất là `e_lfanew`: offset tới `IMAGE_NT_HEADERS`, đánh dấu bắt đầu phần **PE** thật sự.
##### IMAGE_DATA_DIRECTORY
```py
typedef struct __IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} ___IMAGE_DATA_DIRECTORY, * ___PIMAGE_DATA_DIRECTORY;
```
* Mỗi phần tử trong `DataDirectory[]` của **Optional Header** là một `IMAGE_DATA_DIRECTORY`.
* Dùng để lưu `RVA` và kích thước của:
* `Import Table`
* `Export Table`
* `Base Relocations`
* ...
##### IMAGE_OPTIONAL_HEADER32 và IMAGE_OPTIONAL_HEADER64
```py
typedef struct __IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
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;
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;
```
* Chứa thông tin quan trọng để loader xử lý **PE**: **entry point, image base, section alignment, heap/stack sizes**, ...
* `DataDirectory[]`: bảng $16$ phần tử chứa **RVA** và `size` cho các bảng phụ như **Import Table, Export Table, Relocations, Resources**...
##### IMAGE_FILE_HEADER
```py
typedef struct __IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} ___IMAGE_FILE_HEADER, * ___PIMAGE_FILE_HEADER;
```
* Đây là phần **File Header** trong **PE**.
* Chứa thông tin như số lượng `section`, loại máy, thời gian build, ...
##### IMAGE_NT_HEADERS32 và IMAGE_NT_HEADERS64
```py
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;
```
* Phần header chính của **PE** file.
* Nếu `OptionalHeader.Magic == 0x20b` → dùng **IMAGE_NT_HEADERS64**.
##### IMAGE_IMPORT_DESCRIPTOR
```py
typedef struct __IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
} DUMMYUNIONNAME;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} ___IMAGE_IMPORT_DESCRIPTOR, * ___PIMAGE_IMPORT_DESCRIPTOR;
```
* Cấu trúc mô tả từng **DLL** được import.
* `Name → RVA` tới tên **DLL**.
* `FirstThunk / OriginalFirstThunk` → mô tả hàm import.
##### IMAGE_IMPORT_BY_NAME
```py
typedef struct __IMAGE_IMPORT_BY_NAME {
WORD Hint;
char Name[100];
} ___IMAGE_IMPORT_BY_NAME, * ___PIMAGE_IMPORT_BY_NAME;
```
* Cấu trúc mô tả một hàm được import theo tên.
##### IMAGE_BASE_RELOCATION
```py
typedef struct __IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
} ___IMAGE_BASE_RELOCATION, * ___PIMAGE_BASE_RELOCATION;
```
* Dùng cho phần **Base Relocation Table**.
* Cho phép sửa đổi địa chỉ nếu không load tại đúng **ImageBase** gốc.
##### IMAGE_SECTION_HEADER
```py
typedef struct __IMAGE_SECTION_HEADER {
BYTE Name[___IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} ___IMAGE_SECTION_HEADER, * ___PIMAGE_SECTION_HEADER;
```
* Mỗi section trong **PE** được mô tả bằng một struct này.
* Chứa thông tin mapping giữa file offset và virtual memory.
##### RICH_HEADER_INFO
```py
typedef struct __RICH_HEADER_INFO {
int size;
char* ptrToBuffer;
int entries;
} RICH_HEADER_INFO, * PRICH_HEADER_INFO;
```
* Đây là struct lưu meta thông tin về phần `Rich Header`.
* `size`: Kích thước của **Rich Header** (tính bằng byte).
* `ptrToBuffer`: Một con trỏ tới bộ đệm chứa dữ liệu của **Rich Header**.
* `entries`: Số lượng mục trong **Rich Header**.
##### RICH_HEADER_ENTRY
```py
typedef struct __RICH_HEADER_ENTRY {
WORD prodID;
WORD buildID;
DWORD useCount;
} RICH_HEADER_ENTRY, * PRICH_HEADER_ENTRY;
```
* Một entry trong vùng RICH, cho thấy công cụ nào đã tham gia build file (ví dụ: `Microsoft Linker`, `VS Compiler`...)
* Phân tích **Rich Header** là cách để truy ngược lại quá trình build file **PE** — hữu ích khi phân tích mã độc hoặc xác định môi trường build trong forensic.
* `prodID`: Type ID / Product ID.
* `buildID`: Build ID.
* `useCount`: Số lần thành phần đó được sử dụng trong quá trình build file.
##### RICH_HEADER
```py
typedef struct __RICH_HEADER {
PRICH_HEADER_ENTRY entries;
} RICH_HEADER, * PRICH_HEADER;
```
* Đây là container chứa các **RICH_HEADER_ENTRY**, thường sau khi đã giải mã thành công vùng **RICH**.
* `entries`: Con trỏ trỏ tới mảng các entry **RICH_HEADER_ENTRY**.
##### ILT_ENTRY_64 – Import Lookup Table (64-bit)
```py
typedef struct __ILT_ENTRY_64 {
union {
DWORD ORDINAL : 16;
DWORD HINT_NAME_TABE : 32;
} FIELD_2;
DWORD ORDINAL_NAME_FLAG : 1;
} ILT_ENTRY_64, * PILT_ENTRY_64;
```
* Dùng để biểu diễn một phần tử trong bảng `Import Lookup` (trong **PE**), ở chế độ 64-bit.
* `ORDINAL_NAME_FLAG = 1`: dùng số thứ tự (ordinal) thay vì tên **API**.
* `HINT_NAME_TABE`: offset đến vùng chứa tên hàm cần import (từ `.idata`).
##### ILT_ENTRY_32 – Import Lookup Table (32-bit)
```py
typedef struct __ILT_ENTRY_32 {
union {
DWORD ORDINAL : 16;
DWORD HINT_NAME_TABE : 32;
DWORD ORDINAL_NAME_FLAG : 1;
} FIELD_1;
} ILT_ENTRY_32, * PILT_ENTRY_32;
```
* Tương tự như trên, nhưng cho định dạng 32-bit **PE**.
##### BASE_RELOC_ENTRY
```py
typedef struct __BASE_RELOC_ENTRY {
WORD OFFSET : 12;
WORD TYPE : 4;
} BASE_RELOC_ENTRY, * PBASE_RELOC_ENTRY;
```
* Biểu diễn một entry trong **Base Relocation Table** — thứ giúp **Windows** di chuyển (relocate) file **PE** khi không load đúng địa chỉ gốc (`ImageBase`).
### Part 2: Định nghĩa các Class và Constructors
* Định dạng **PE64**:
```py
class PE64FILE
```
* Lớp này đại diện cho một file **PE** 64-bit.
* Trong project parser, sẽ có hai class **PE32FILE** và **PE64FILE**, chỉ khác nhau ở kiểu dữ liệu nhưng logic xử lý thì giống nhau.
#### **Public Member**
```py
public:
PE64FILE(char* _NAME, FILE* Ppefile);
void PrintInfo();
```
* **Constructor**: nhận vào tên file (`char* _NAME`) và con trỏ **FILE*** đến file nhị phân đang mở.
* **PrintInfo()**: phương thức công khai duy nhất, dùng để in ra thông tin toàn bộ file **PE** sau khi **parse**.
#### **Private Member**
* **Metadata**:
```py
char* NAME;
FILE* Ppefile;
int _import_directory_count, _import_directory_size;
int _basreloc_directory_count;
```
* Lưu thông tin tổng quát như:
* Tên file
* Con trỏ **FILE**
* Số lượng entry của bảng import và relocation.
* **DOS Header**:
```py
// HEADERS
___IMAGE_DOS_HEADER PEFILE_DOS_HEADER;
___IMAGE_NT_HEADERS64 PEFILE_NT_HEADERS;
// DOS HEADER
DWORD PEFILE_DOS_HEADER_EMAGIC;
LONG PEFILE_DOS_HEADER_LFANEW;
```
* `e_magic`: `0x5A4D` (chữ 'MZ').
* `e_lfanew`: offset đến **PE** header.
* **Rich Header**:
```py
// RICH HEADER
RICH_HEADER_INFO PEFILE_RICH_HEADER_INFO;
RICH_HEADER PEFILE_RICH_HEADER;
```
* Chứa thông tin về vùng **Rich Header**.
* **NT Headers**:
```py
// NT_HEADERS.Signature
DWORD PEFILE_NT_HEADERS_SIGNATURE;
// NT_HEADERS.FileHeader
WORD PEFILE_NT_HEADERS_FILE_HEADER_MACHINE;
WORD PEFILE_NT_HEADERS_FILE_HEADER_NUMBER0F_SECTIONS;
WORD PEFILE_NT_HEADERS_FILE_HEADER_SIZEOF_OPTIONAL_HEADER;
```
* Bao gồm:
* `Signature` ``"PE\0\0"``
* `FileHeader` (architecture, số lượng section, ...)
* `OptionalHeader` (các thông số quan trọng của vùng nhớ)
* **Optional Header**:
```py
// NT_HEADERS.OptionalHeader
DWORD PEFILE_NT_HEADERS_OPTIONAL_HEADER_MAGIC;
DWORD PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_CODE;
DWORD PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_INITIALIZED_DATA;
DWORD PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_UNINITIALIZED_DATA;
DWORD PEFILE_NT_HEADERS_OPTIONAL_HEADER_ADDRESSOF_ENTRYPOINT;
DWORD PEFILE_NT_HEADERS_OPTIONAL_HEADER_BASEOF_CODE;
ULONGLONG PEFILE_NT_HEADERS_OPTIONAL_HEADER_IMAGEBASE;
DWORD PEFILE_NT_HEADERS_OPTIONAL_HEADER_SECTION_ALIGNMENT;
DWORD PEFILE_NT_HEADERS_OPTIONAL_HEADER_FILE_ALIGNMENT;
DWORD PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_IMAGE;
DWORD PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_HEADERS;
```
* Các thông số quan trọng ảnh hưởng đến cách file được load vào bộ nhớ: `EntryPoint`, `ImageBase`, `Alignment`, `SizeOfImage`, ...
* **Data Directory Entries**:
```py
___IMAGE_DATA_DIRECTORY PEFILE_EXPORT_DIRECTORY;
___IMAGE_DATA_DIRECTORY PEFILE_IMPORT_DIRECTORY;
___IMAGE_DATA_DIRECTORY PEFILE_RESOURCE_DIRECTORY;
___IMAGE_DATA_DIRECTORY PEFILE_EXCEPTION_DIRECTORY;
___IMAGE_DATA_DIRECTORY PEFILE_SECURITY_DIRECTORY;
___IMAGE_DATA_DIRECTORY PEFILE_BASERELOC_DIRECTORY;
___IMAGE_DATA_DIRECTORY PEFILE_DEBUG_DIRECTORY;
___IMAGE_DATA_DIRECTORY PEFILE_ARCHITECTURE_DIRECTORY;
___IMAGE_DATA_DIRECTORY PEFILE_GLOBALPTR_DIRECTORY;
___IMAGE_DATA_DIRECTORY PEFILE_TLS_DIRECTORY;
___IMAGE_DATA_DIRECTORY PEFILE_LOAD_CONFIG_DIRECTORY;
___IMAGE_DATA_DIRECTORY PEFILE_BOUND_IMPORT_DIRECTORY;
___IMAGE_DATA_DIRECTORY PEFILE_IAT_DIRECTORY;
___IMAGE_DATA_DIRECTORY PEFILE_DELAY_IMPORT_DIRECTORY;
___IMAGE_DATA_DIRECTORY PEFILE_COM_DESCRIPTOR_DIRECTORY;
```
* Đây là các thành phần nằm trong **Optional Header** (`DataDirectory[16]`) → mỗi cái chỉ đến một bảng/section như `Import Table`, `Export Table`, `TLS`, `Reloc`, `Debug`...
* **Section Headers**:
```py
// SECTION HEADERS
___PIMAGE_SECTION_HEADER PEFILE_SECTION_HEADERS;
```
* Mỗi section (`.text`, `.data`, `.rsrc`, ...) được mô tả bằng 1 entry trong **Section Table**.
* Con trỏ trỏ tới mảng các `IMAGE_SECTION_HEADER`.
* **Import và Reloc Table**:
```py
// IMPORT TABLE
___PIMAGE_IMPORT_DESCRIPTOR PEFILE_IMPORT_TABLE;
// BASE RELOCATION TABLE
___PIMAGE_BASE_RELOCATION PEFILE_BASERELOC_TABLE;
```
* Dùng để lưu bảng `import descriptor` và bảng `relocation` sau khi phân tích.
#### Các phương thức xử lý
* **Address Resolution**:
```py
// ADDRESS RESOLVERS
int locate(DWORD VA);
DWORD resolve(DWORD VA, int index);
```
* `locate`: xác định file offset từ một `virtual address`.
* `resolve`: dùng để tra một địa chỉ ảo và ánh xạ nó về offset thực trong file.
* Các phương thức **parser**:
```py
// PARSERS
void ParseFile();
void ParseDOSHeader();
void ParseNTHeaders();
void ParseSectionHeaders();
void ParseImportDirectory();
void ParseBaseReloc();
void ParseRichHeader();
```
* Phân tích từng phần của **PE** file:
* **DOS Header**
* **NT Headers**
* **Section Table**
* **Import Table**
* **Relocation Table**
* **Rich Header**
#### Các hàm in thông tin
```py
// PRINT INFO
void PrintFileInfo();
void PrintDOSHeaderInfo();
void PrintRichHeaderInfo();
void PrintNTHeadersInfo();
void PrintSectionHeadersInfo();
void PrintImportTableInfo();
void PrintBaseRelocationsInfo();
```
* Tương ứng với từng phần **parser**, sau khi phân tích xong sẽ gọi hàm tương ứng để in thông tin ra màn hình.
* Như đã nói ở trên, thì định dạng 32 bit cũng như 64 bit hoi, chỉ khác nhau ở kiểu dữ liệu nhưng logic xử lý thì giống nhau.
### Part 3: Triển khai class PE64FILE / PE32FILE
#### 1. Constructor PE64FILE::PE64FILE
```py
PE64FILE::PE64FILE(char* _NAME, FILE* _Ppefile) {
NAME = _NAME;
Ppefile = _Ppefile;
ParseFile();
}
```
* Lưu tên file và con trỏ **FILE**.
* Gọi `ParseFile()` để phân tích toàn bộ cấu trúc file **PE** sau khi mở.
#### 2. Hàm hỗ trợ địa chỉ: `locate()` và `resolve()`
```py
// ADDRESS RESOLVERS
int PE64FILE::locate(DWORD VA) {
int index;
for (int i = 0; i < PEFILE_NT_HEADERS_FILE_HEADER_NUMBER0F_SECTIONS; i++) {
if (VA >= PEFILE_SECTION_HEADERS[i].VirtualAddress
&& VA < (PEFILE_SECTION_HEADERS[i].VirtualAddress + PEFILE_SECTION_HEADERS[i].Misc.VirtualSize)) {
index = i;
break;
}
}
return index;
}
DWORD PE64FILE::resolve(DWORD VA, int index) {
return (VA - PEFILE_SECTION_HEADERS[index].VirtualAddress) + PEFILE_SECTION_HEADERS[index].PointerToRawData;
}
```
* `locate(VA)`: Tìm section chứa địa chỉ ảo **VA**
* `resolve(VA, index)`: Chuyển **VA** thành offset trong file
* ----> Đây là cơ chế ánh xạ `VirtualAddress` ↔ `FileOffset`.
#### 3. Parse*() – Phân tích từng vùng trong PE
* Theo đúng thứ tự phân tích các phương thức **parser**:
```py
// PARSERS
void ParseFile();
void ParseDOSHeader();
void ParseNTHeaders();
void ParseSectionHeaders();
void ParseImportDirectory();
void ParseBaseReloc();
void ParseRichHeader();
```
##### ParseDOSHeader()
```py
void PE64FILE::ParseDOSHeader() {
fseek(Ppefile, 0, SEEK_SET);
fread(&PEFILE_DOS_HEADER, sizeof(___IMAGE_DOS_HEADER), 1, Ppefile);
PEFILE_DOS_HEADER_EMAGIC = PEFILE_DOS_HEADER.e_magic;
PEFILE_DOS_HEADER_LFANEW = PEFILE_DOS_HEADER.e_lfanew;
}
```
* Đọc đầu file, trích `e_magic`, `e_lfanew`.
* `e_lfanew` là offset đến **NT Header**.
##### ParseNTHeaders()
```py
void PE64FILE::ParseNTHeaders() {
fseek(Ppefile, PEFILE_DOS_HEADER.e_lfanew, SEEK_SET);
fread(&PEFILE_NT_HEADERS, sizeof(PEFILE_NT_HEADERS), 1, Ppefile);
PEFILE_NT_HEADERS_SIGNATURE = PEFILE_NT_HEADERS.Signature;
PEFILE_NT_HEADERS_FILE_HEADER_MACHINE = PEFILE_NT_HEADERS.FileHeader.Machine;
PEFILE_NT_HEADERS_FILE_HEADER_NUMBER0F_SECTIONS = PEFILE_NT_HEADERS.FileHeader.NumberOfSections;
PEFILE_NT_HEADERS_FILE_HEADER_SIZEOF_OPTIONAL_HEADER = PEFILE_NT_HEADERS.FileHeader.SizeOfOptionalHeader;
PEFILE_NT_HEADERS_OPTIONAL_HEADER_MAGIC = PEFILE_NT_HEADERS.OptionalHeader.Magic;
PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_CODE = PEFILE_NT_HEADERS.OptionalHeader.SizeOfCode;
PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_INITIALIZED_DATA = PEFILE_NT_HEADERS.OptionalHeader.SizeOfInitializedData;
PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_UNINITIALIZED_DATA = PEFILE_NT_HEADERS.OptionalHeader.SizeOfUninitializedData;
PEFILE_NT_HEADERS_OPTIONAL_HEADER_ADDRESSOF_ENTRYPOINT = PEFILE_NT_HEADERS.OptionalHeader.AddressOfEntryPoint;
PEFILE_NT_HEADERS_OPTIONAL_HEADER_BASEOF_CODE = PEFILE_NT_HEADERS.OptionalHeader.BaseOfCode;
PEFILE_NT_HEADERS_OPTIONAL_HEADER_IMAGEBASE = PEFILE_NT_HEADERS.OptionalHeader.ImageBase;
PEFILE_NT_HEADERS_OPTIONAL_HEADER_SECTION_ALIGNMENT = PEFILE_NT_HEADERS.OptionalHeader.SectionAlignment;
PEFILE_NT_HEADERS_OPTIONAL_HEADER_FILE_ALIGNMENT = PEFILE_NT_HEADERS.OptionalHeader.FileAlignment;
PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_IMAGE = PEFILE_NT_HEADERS.OptionalHeader.SizeOfImage;
PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_HEADERS = PEFILE_NT_HEADERS.OptionalHeader.SizeOfHeaders;
PEFILE_EXPORT_DIRECTORY = PEFILE_NT_HEADERS.OptionalHeader.DataDirectory[___IMAGE_DIRECTORY_ENTRY_EXPORT];
PEFILE_IMPORT_DIRECTORY = PEFILE_NT_HEADERS.OptionalHeader.DataDirectory[___IMAGE_DIRECTORY_ENTRY_IMPORT];
PEFILE_RESOURCE_DIRECTORY = PEFILE_NT_HEADERS.OptionalHeader.DataDirectory[___IMAGE_DIRECTORY_ENTRY_RESOURCE];
PEFILE_EXCEPTION_DIRECTORY = PEFILE_NT_HEADERS.OptionalHeader.DataDirectory[___IMAGE_DIRECTORY_ENTRY_EXCEPTION];
PEFILE_SECURITY_DIRECTORY = PEFILE_NT_HEADERS.OptionalHeader.DataDirectory[___IMAGE_DIRECTORY_ENTRY_SECURITY];
PEFILE_BASERELOC_DIRECTORY = PEFILE_NT_HEADERS.OptionalHeader.DataDirectory[___IMAGE_DIRECTORY_ENTRY_BASERELOC];
PEFILE_DEBUG_DIRECTORY = PEFILE_NT_HEADERS.OptionalHeader.DataDirectory[___IMAGE_DIRECTORY_ENTRY_DEBUG];
PEFILE_ARCHITECTURE_DIRECTORY = PEFILE_NT_HEADERS.OptionalHeader.DataDirectory[___IMAGE_DIRECTORY_ENTRY_ARCHITECTURE];
PEFILE_GLOBALPTR_DIRECTORY = PEFILE_NT_HEADERS.OptionalHeader.DataDirectory[___IMAGE_DIRECTORY_ENTRY_GLOBALPTR];
PEFILE_TLS_DIRECTORY = PEFILE_NT_HEADERS.OptionalHeader.DataDirectory[___IMAGE_DIRECTORY_ENTRY_TLS];
PEFILE_LOAD_CONFIG_DIRECTORY = PEFILE_NT_HEADERS.OptionalHeader.DataDirectory[___IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG];
PEFILE_BOUND_IMPORT_DIRECTORY = PEFILE_NT_HEADERS.OptionalHeader.DataDirectory[___IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT];
PEFILE_IAT_DIRECTORY = PEFILE_NT_HEADERS.OptionalHeader.DataDirectory[___IMAGE_DIRECTORY_ENTRY_IAT];
PEFILE_DELAY_IMPORT_DIRECTORY = PEFILE_NT_HEADERS.OptionalHeader.DataDirectory[___IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT];
PEFILE_COM_DESCRIPTOR_DIRECTORY = PEFILE_NT_HEADERS.OptionalHeader.DataDirectory[___IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR];
}
```
* Đọc **NT_HEADERS** tại vị trí `e_lfanew`.
* Trích **Signature**, **FileHeader**, **OptionalHeader**.
* Lưu lại toàn bộ **DataDirectory** như **Import**, **Export**, **Reloc**, **TLS**, v.v.
##### ParseSectionHeaders()
```py
void PE64FILE::ParseSectionHeaders() {
PEFILE_SECTION_HEADERS = new ___IMAGE_SECTION_HEADER[PEFILE_NT_HEADERS_FILE_HEADER_NUMBER0F_SECTIONS];
for (int i = 0; i < PEFILE_NT_HEADERS_FILE_HEADER_NUMBER0F_SECTIONS; i++) {
int offset = (PEFILE_DOS_HEADER.e_lfanew + sizeof(PEFILE_NT_HEADERS)) + (i * ___IMAGE_SIZEOF_SECTION_HEADER);
fseek(Ppefile, offset, SEEK_SET);
fread(&PEFILE_SECTION_HEADERS[i], ___IMAGE_SIZEOF_SECTION_HEADER, 1, Ppefile);
}
}
```
* Lặp qua số **section** (`NumberOfSections`) để đọc từng `IMAGE_SECTION_HEADER`.
* Đặt con trỏ `PEFILE_SECTION_HEADERS[i]` cho từng entry.
##### ParseImportDirectory()
```py
void PE64FILE::ParseImportDirectory() {
DWORD _import_directory_address = resolve(PEFILE_IMPORT_DIRECTORY.VirtualAddress, locate(PEFILE_IMPORT_DIRECTORY.VirtualAddress));
_import_directory_count = 0;
while (true) {
___IMAGE_IMPORT_DESCRIPTOR tmp;
int offset = (_import_directory_count * sizeof(___IMAGE_IMPORT_DESCRIPTOR)) + _import_directory_address;
fseek(Ppefile, offset, SEEK_SET);
fread(&tmp, sizeof(___IMAGE_IMPORT_DESCRIPTOR), 1, Ppefile);
if (tmp.Name == 0x00000000 && tmp.FirstThunk == 0x00000000) {
_import_directory_count -= 1;
_import_directory_size = _import_directory_count * sizeof(___IMAGE_IMPORT_DESCRIPTOR);
break;
}
_import_directory_count++;
}
PEFILE_IMPORT_TABLE = new ___IMAGE_IMPORT_DESCRIPTOR[_import_directory_count];
for (int i = 0; i < _import_directory_count; i++) {
int offset = (i * sizeof(___IMAGE_IMPORT_DESCRIPTOR)) + _import_directory_address;
fseek(Ppefile, offset, SEEK_SET);
fread(&PEFILE_IMPORT_TABLE[i], sizeof(___IMAGE_IMPORT_DESCRIPTOR), 1, Ppefile);
}
}
```
* Lấy **RVA** của `Import Directory` → `resolve()` → `offset`.
* Duyệt từng `IMAGE_IMPORT_DESCRIPTOR` đến khi gặp ``{0,0}`` kết thúc.
* Sau đó đọc toàn bộ các `entries` vào mảng.
##### ParseBaseReloc()
```py
void PE64FILE::ParseBaseReloc() {
DWORD _basereloc_directory_address = resolve(PEFILE_BASERELOC_DIRECTORY.VirtualAddress, locate(PEFILE_BASERELOC_DIRECTORY.VirtualAddress));
_basreloc_directory_count = 0;
int _basereloc_size_counter = 0;
while (true) {
___IMAGE_BASE_RELOCATION tmp;
int offset = (_basereloc_size_counter + _basereloc_directory_address);
fseek(Ppefile, offset, SEEK_SET);
fread(&tmp, sizeof(___IMAGE_BASE_RELOCATION), 1, Ppefile);
if (tmp.VirtualAddress == 0x00000000 &&
tmp.SizeOfBlock == 0x00000000) {
break;
}
_basreloc_directory_count++;
_basereloc_size_counter += tmp.SizeOfBlock;
}
PEFILE_BASERELOC_TABLE = new ___IMAGE_BASE_RELOCATION[_basreloc_directory_count];
_basereloc_size_counter = 0;
for (int i = 0; i < _basreloc_directory_count; i++) {
int offset = _basereloc_directory_address + _basereloc_size_counter;
fseek(Ppefile, offset, SEEK_SET);
fread(&PEFILE_BASERELOC_TABLE[i], sizeof(___IMAGE_BASE_RELOCATION), 1, Ppefile);
_basereloc_size_counter += PEFILE_BASERELOC_TABLE[i].SizeOfBlock;
}
}
```
* Lấy **RVA** bảng **Reloc** → **offset**.
* Duyệt qua từng `IMAGE_BASE_RELOCATION` block (có `SizeOfBlock`).
* Tính toán số lượng entries trong mỗi `block = (SizeOfBlock - 8)/2`. Cụ thể hơn thì chính là `ENTRIES = (BLOCKSIZE - sizeof(___IMAGE_BASE_RELOCATION)) / sizeof(WORD);` tại hàm `PrintBaseRelocationsInfo()`.
##### ParseRichHeader()
```py
void PE64FILE::ParseRichHeader() {
char* dataPtr = new char[PEFILE_DOS_HEADER_LFANEW];
fseek(Ppefile, 0, SEEK_SET);
fread(dataPtr, PEFILE_DOS_HEADER_LFANEW, 1, Ppefile);
int index_ = 0;
for (int i = 0; i <= PEFILE_DOS_HEADER_LFANEW; i++) {
if (dataPtr[i] == 0x52 && dataPtr[i + 1] == 0x69) {
index_ = i;
break;
}
}
if (index_ == 0) {
printf("Error while parsing Rich Header.");
PEFILE_RICH_HEADER_INFO.entries = 0;
return;
}
char key[4];
memcpy(key, dataPtr + (index_ + 4), 4);
int indexpointer = index_ - 4;
int RichHeaderSize = 0;
while (true) {
char tmpchar[4];
memcpy(tmpchar, dataPtr + indexpointer, 4);
for (int i = 0; i < 4; i++) {
tmpchar[i] = tmpchar[i] ^ key[i];
}
indexpointer -= 4;
RichHeaderSize += 4;
if (tmpchar[1] = 0x61 && tmpchar[0] == 0x44) {
break;
}
}
char* RichHeaderPtr = new char[RichHeaderSize];
memcpy(RichHeaderPtr, dataPtr + (index_ - RichHeaderSize), RichHeaderSize);
for (int i = 0; i < RichHeaderSize; i += 4) {
for (int x = 0; x < 4; x++) {
RichHeaderPtr[i + x] = RichHeaderPtr[i + x] ^ key[x];
}
}
PEFILE_RICH_HEADER_INFO.size = RichHeaderSize;
PEFILE_RICH_HEADER_INFO.ptrToBuffer = RichHeaderPtr;
PEFILE_RICH_HEADER_INFO.entries = (RichHeaderSize - 16) / 8;
delete[] dataPtr;
PEFILE_RICH_HEADER.entries = new RICH_HEADER_ENTRY[PEFILE_RICH_HEADER_INFO.entries];
for (int i = 16; i < RichHeaderSize; i += 8) {
WORD PRODID = (uint16_t)((unsigned char)RichHeaderPtr[i + 3] << 8) | (unsigned char)RichHeaderPtr[i + 2];
WORD BUILDID = (uint16_t)((unsigned char)RichHeaderPtr[i + 1] << 8) | (unsigned char)RichHeaderPtr[i];
DWORD USECOUNT = (uint32_t)((unsigned char)RichHeaderPtr[i + 7] << 24) | (unsigned char)RichHeaderPtr[i + 6] << 16 | (unsigned char)RichHeaderPtr[i + 5] << 8 | (unsigned char)RichHeaderPtr[i + 4];
PEFILE_RICH_HEADER.entries[(i / 8) - 2] = {
PRODID,
BUILDID,
USECOUNT
};
if (i + 8 >= RichHeaderSize) {
PEFILE_RICH_HEADER.entries[(i / 8) - 1] = { 0x0000, 0x0000, 0x00000000 };
}
}
delete[] PEFILE_RICH_HEADER_INFO.ptrToBuffer;
}
```
* Đọc vùng giữa **DOS header** và **NT header** để tìm chuỗi ``"Rich"``.
* Trích ra mảng **RICH_HEADER_ENTRY**, chứa: `prodID`, `buildID`, `useCount`.
#### 4. Các hàm Print*() – In thông tin ra màn hình
##### PrintFileInfo()
```py
void PE64FILE::PrintFileInfo() {
printf(" FILE: %s\n", NAME);
printf(" TYPE: 0x%X (PE32+)\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_MAGIC);
}
```
* In tên file, loại **PE32+** (64-bit)
##### PrintDOSHeaderInfo()
```py
void PE64FILE::PrintDOSHeaderInfo() {
printf(" DOS HEADER:\n");
printf(" -----------\n\n");
printf(" Magic: 0x%X\n", PEFILE_DOS_HEADER_EMAGIC);
printf(" File address of new exe header: 0x%X\n", PEFILE_DOS_HEADER_LFANEW);
}
```
* In `e_magic`, `e_lfanew`.
##### PrintRichHeaderInfo()
```py
void PE64FILE::PrintRichHeaderInfo() {
printf(" RICH HEADER:\n");
printf(" ------------\n\n");
for (int i = 0; i < PEFILE_RICH_HEADER_INFO.entries; i++) {
printf(" 0x%X 0x%X 0x%X: %d.%d.%d\n",
PEFILE_RICH_HEADER.entries[i].buildID,
PEFILE_RICH_HEADER.entries[i].prodID,
PEFILE_RICH_HEADER.entries[i].useCount,
PEFILE_RICH_HEADER.entries[i].buildID,
PEFILE_RICH_HEADER.entries[i].prodID,
PEFILE_RICH_HEADER.entries[i].useCount);
}
}
```
* In từng **RICH_HEADER_ENTRY**: `prodID`, `buildID`, `useCount`.
##### PrintNTHeadersInfo()
```py
void PE64FILE::PrintNTHeadersInfo() {
printf(" NT HEADERS:\n");
printf(" -----------\n\n");
printf(" PE Signature: 0x%X\n", PEFILE_NT_HEADERS_SIGNATURE);
printf("\n File Header:\n\n");
printf(" Machine: 0x%X\n", PEFILE_NT_HEADERS_FILE_HEADER_MACHINE);
printf(" Number of sections: 0x%X\n", PEFILE_NT_HEADERS_FILE_HEADER_NUMBER0F_SECTIONS);
printf(" Size of optional header: 0x%X\n", PEFILE_NT_HEADERS_FILE_HEADER_SIZEOF_OPTIONAL_HEADER);
printf("\n Optional Header:\n\n");
printf(" Magic: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_MAGIC);
printf(" Size of code section: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_CODE);
printf(" Size of initialized data: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_INITIALIZED_DATA);
printf(" Size of uninitialized data: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_UNINITIALIZED_DATA);
printf(" Address of entry point: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_ADDRESSOF_ENTRYPOINT);
printf(" RVA of start of code section: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_BASEOF_CODE);
printf(" Desired image base: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_IMAGEBASE);
printf(" Section alignment: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_SECTION_ALIGNMENT);
printf(" File alignment: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_FILE_ALIGNMENT);
printf(" Size of image: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_IMAGE);
printf(" Size of headers: 0x%X\n", PEFILE_NT_HEADERS_OPTIONAL_HEADER_SIZEOF_HEADERS);
printf("\n Data Directories:\n");
printf("\n * Export Directory:\n");
printf(" RVA: 0x%X\n", PEFILE_EXPORT_DIRECTORY.VirtualAddress);
printf(" Size: 0x%X\n", PEFILE_EXPORT_DIRECTORY.Size);
printf("\n * Import Directory:\n");
printf(" RVA: 0x%X\n", PEFILE_IMPORT_DIRECTORY.VirtualAddress);
printf(" Size: 0x%X\n", PEFILE_IMPORT_DIRECTORY.Size);
printf("\n * Resource Directory:\n");
printf(" RVA: 0x%X\n", PEFILE_RESOURCE_DIRECTORY.VirtualAddress);
printf(" Size: 0x%X\n", PEFILE_RESOURCE_DIRECTORY.Size);
printf("\n * Exception Directory:\n");
printf(" RVA: 0x%X\n", PEFILE_EXCEPTION_DIRECTORY.VirtualAddress);
printf(" Size: 0x%X\n", PEFILE_EXCEPTION_DIRECTORY.Size);
printf("\n * Security Directory:\n");
printf(" RVA: 0x%X\n", PEFILE_SECURITY_DIRECTORY.VirtualAddress);
printf(" Size: 0x%X\n", PEFILE_SECURITY_DIRECTORY.Size);
printf("\n * Base Relocation Table:\n");
printf(" RVA: 0x%X\n", PEFILE_BASERELOC_DIRECTORY.VirtualAddress);
printf(" Size: 0x%X\n", PEFILE_BASERELOC_DIRECTORY.Size);
printf("\n * Debug Directory:\n");
printf(" RVA: 0x%X\n", PEFILE_DEBUG_DIRECTORY.VirtualAddress);
printf(" Size: 0x%X\n", PEFILE_DEBUG_DIRECTORY.Size);
printf("\n * Architecture Specific Data:\n");
printf(" RVA: 0x%X\n", PEFILE_ARCHITECTURE_DIRECTORY.VirtualAddress);
printf(" Size: 0x%X\n", PEFILE_ARCHITECTURE_DIRECTORY.Size);
printf("\n * RVA of GlobalPtr:\n");
printf(" RVA: 0x%X\n", PEFILE_GLOBALPTR_DIRECTORY.VirtualAddress);
printf(" Size: 0x%X\n", PEFILE_GLOBALPTR_DIRECTORY.Size);
printf("\n * TLS Directory:\n");
printf(" RVA: 0x%X\n", PEFILE_TLS_DIRECTORY.VirtualAddress);
printf(" Size: 0x%X\n", PEFILE_TLS_DIRECTORY.Size);
printf("\n * Load Configuration Directory:\n");
printf(" RVA: 0x%X\n", PEFILE_LOAD_CONFIG_DIRECTORY.VirtualAddress);
printf(" Size: 0x%X\n", PEFILE_LOAD_CONFIG_DIRECTORY.Size);
printf("\n * Bound Import Directory:\n");
printf(" RVA: 0x%X\n", PEFILE_BOUND_IMPORT_DIRECTORY.VirtualAddress);
printf(" Size: 0x%X\n", PEFILE_BOUND_IMPORT_DIRECTORY.Size);
printf("\n * Import Address Table:\n");
printf(" RVA: 0x%X\n", PEFILE_IAT_DIRECTORY.VirtualAddress);
printf(" Size: 0x%X\n", PEFILE_IAT_DIRECTORY.Size);
printf("\n * Delay Load Import Descriptors:\n");
printf(" RVA: 0x%X\n", PEFILE_DELAY_IMPORT_DIRECTORY.VirtualAddress);
printf(" Size: 0x%X\n", PEFILE_DELAY_IMPORT_DIRECTORY.Size);
printf("\n * COM Runtime Descriptor:\n");
printf(" RVA: 0x%X\n", PEFILE_COM_DESCRIPTOR_DIRECTORY.VirtualAddress);
printf(" Size: 0x%X\n", PEFILE_COM_DESCRIPTOR_DIRECTORY.Size);
}
```
* In `Signature`, `FileHeader`, `OptionalHeader`
* In toàn bộ các **DataDirectory** (`Export`, `Import`, `Reloc`, `TLS`...)
##### PrintSectionHeadersInfo()
```py
void PE64FILE::PrintSectionHeadersInfo() {
printf(" SECTION HEADERS:\n");
printf(" ----------------\n\n");
for (int i = 0; i < PEFILE_NT_HEADERS_FILE_HEADER_NUMBER0F_SECTIONS; i++) {
printf(" * %.8s:\n", PEFILE_SECTION_HEADERS[i].Name);
printf(" VirtualAddress: 0x%X\n", PEFILE_SECTION_HEADERS[i].VirtualAddress);
printf(" VirtualSize: 0x%X\n", PEFILE_SECTION_HEADERS[i].Misc.VirtualSize);
printf(" PointerToRawData: 0x%X\n", PEFILE_SECTION_HEADERS[i].PointerToRawData);
printf(" SizeOfRawData: 0x%X\n", PEFILE_SECTION_HEADERS[i].SizeOfRawData);
printf(" Characteristics: 0x%X\n\n", PEFILE_SECTION_HEADERS[i].Characteristics);
}
}
```
* In thông tin từng section:
* `.text`, `.data`, `.rdata`, ...
* `VirtualAddress`, `PointerToRawData`, `Size`, `Characteristics`.
##### PrintImportTableInfo()
```py
void PE64FILE::PrintImportTableInfo() {
printf(" IMPORT TABLE:\n");
printf(" ----------------\n\n");
for (int i = 0; i < _import_directory_count; i++) {
DWORD NameAddr = resolve(PEFILE_IMPORT_TABLE[i].Name, locate(PEFILE_IMPORT_TABLE[i].Name));
int NameSize = 0;
while (true) {
char tmp;
fseek(Ppefile, (NameAddr + NameSize), SEEK_SET);
fread(&tmp, sizeof(char), 1, Ppefile);
if (tmp == 0x00) {
break;
}
NameSize++;
}
char* Name = new char[NameSize + 2];
fseek(Ppefile, NameAddr, SEEK_SET);
fread(Name, (NameSize * sizeof(char)) + 1, 1, Ppefile);
printf(" * %s:\n", Name);
delete[] Name;
printf(" ILT RVA: 0x%X\n", PEFILE_IMPORT_TABLE[i].DUMMYUNIONNAME.OriginalFirstThunk);
printf(" IAT RVA: 0x%X\n", PEFILE_IMPORT_TABLE[i].FirstThunk);
if (PEFILE_IMPORT_TABLE[i].TimeDateStamp == 0) {
printf(" Bound: FALSE\n");
}
else if (PEFILE_IMPORT_TABLE[i].TimeDateStamp == -1) {
printf(" Bound: TRUE\n");
}
printf("\n");
DWORD ILTAddr = resolve(PEFILE_IMPORT_TABLE[i].DUMMYUNIONNAME.OriginalFirstThunk, locate(PEFILE_IMPORT_TABLE[i].DUMMYUNIONNAME.OriginalFirstThunk));
int entrycounter = 0;
while (true) {
ILT_ENTRY_64 entry;
fseek(Ppefile, (ILTAddr + (entrycounter * sizeof(QWORD))), SEEK_SET);
fread(&entry, sizeof(ILT_ENTRY_64), 1, Ppefile);
BYTE flag = entry.ORDINAL_NAME_FLAG;
DWORD HintRVA = 0x0;
WORD ordinal = 0x0;
if (flag == 0x0) {
HintRVA = entry.FIELD_2.HINT_NAME_TABE;
}
else if (flag == 0x01) {
ordinal = entry.FIELD_2.ORDINAL;
}
if (flag == 0x0 && HintRVA == 0x0 && ordinal == 0x0) {
break;
}
printf("\n Entry:\n");
if (flag == 0x0) {
___IMAGE_IMPORT_BY_NAME hint;
DWORD HintAddr = resolve(HintRVA, locate(HintRVA));
fseek(Ppefile, HintAddr, SEEK_SET);
fread(&hint, sizeof(___IMAGE_IMPORT_BY_NAME), 1, Ppefile);
printf(" Name: %s\n", hint.Name);
printf(" Hint RVA: 0x%X\n", HintRVA);
printf(" Hint: 0x%X\n", hint.Hint);
}
else if (flag == 1) {
printf(" Ordinal: 0x%X\n", ordinal);
}
entrycounter++;
}
printf("\n ----------------------\n\n");
}
}
```
* In từng DLL được import: `"KERNEL32.dll"`, ``"USER32.dll"`` ...
* In **ILT/IAT**, phân biệt dùng **Ordinal** hay **Name**.
* Duyệt qua **ILT_ENTRY_64**, phân biệt qua **ORDINAL_NAME_FLAG**.
##### PrintBaseRelocationsInfo()
```py
void PE64FILE::PrintBaseRelocationsInfo() {
printf(" BASE RELOCATIONS TABLE:\n");
printf(" -----------------------\n");
int szCounter = sizeof(___IMAGE_BASE_RELOCATION);
for (int i = 0; i < _basreloc_directory_count; i++) {
DWORD PAGERVA, BLOCKSIZE, BASE_RELOC_ADDR;
int ENTRIES;
BASE_RELOC_ADDR = resolve(PEFILE_BASERELOC_DIRECTORY.VirtualAddress, locate(PEFILE_BASERELOC_DIRECTORY.VirtualAddress));
PAGERVA = PEFILE_BASERELOC_TABLE[i].VirtualAddress;
BLOCKSIZE = PEFILE_BASERELOC_TABLE[i].SizeOfBlock;
ENTRIES = (BLOCKSIZE - sizeof(___IMAGE_BASE_RELOCATION)) / sizeof(WORD);
printf("\n Block 0x%X: \n", i);
printf(" Page RVA: 0x%X\n", PAGERVA);
printf(" Block size: 0x%X\n", BLOCKSIZE);
printf(" Number of entries: 0x%X\n", ENTRIES);
printf("\n Entries:\n");
for (int i = 0; i < ENTRIES; i++) {
BASE_RELOC_ENTRY entry;
int offset = (BASE_RELOC_ADDR + szCounter + (i * sizeof(WORD)));
fseek(Ppefile, offset, SEEK_SET);
fread(&entry, sizeof(WORD), 1, Ppefile);
printf("\n * Value: 0x%X\n", entry);
printf(" Relocation Type: 0x%X\n", entry.TYPE);
printf(" Offset: 0x%X\n", entry.OFFSET);
}
printf("\n ----------------------\n\n");
szCounter += BLOCKSIZE;
}
}
```
* In các Block trong `Reloc Table`:
* Mỗi block có **Page RVA** và các entry con.
* Mỗi entry hiển thị: `Type`, `Offset`
#### 5. ParseFile() và PrintInfo()
```py
// MAIN
void PE64FILE::ParseFile() {
// PARSE DOS HEADER
ParseDOSHeader();
// PARSE RICH HEADER
ParseRichHeader();
//PARSE NT HEADERS
ParseNTHeaders();
// PARSE SECTION HEADERS
ParseSectionHeaders();
// PARSE IMPORT DIRECTORY
ParseImportDirectory();
// PARSE BASE RELOCATIONS
ParseBaseReloc();
}
void PE64FILE::PrintInfo() {
printf("\n\n");
PrintFileInfo();
printf("\n ----------------------------------\n\n");
PrintDOSHeaderInfo();
printf("\n ----------------------------------\n\n");
PrintRichHeaderInfo();
printf("\n ----------------------------------\n\n");
PrintNTHeadersInfo();
printf("\n ----------------------------------\n\n");
PrintSectionHeadersInfo();
printf("\n ----------------------------------\n\n");
PrintImportTableInfo();
printf("\n ----------------------------------\n\n");
PrintBaseRelocationsInfo();
printf("\n ----------------------------------\n\n");
return;
}
```
* Chỉ là chạy tuần tự từng **parser**, sau đó in tất cả thông tin đã được phân tích thuiii.
* Như ở trên mình cũng có nói rồi á, kiến trúc 32 bit về logic xây dựng tương tự chỉ khác nhau ở kiểu dữ liệu thoi.
### Linh tinh ...
* Logic chính của code **PE Parser** này chỉ có vậy, cuối cùng chỉ là 1 vài hàm nữa đơn giản hơn. Dưới đây là entry point cho chương trình parser **PE file** hoàn chỉnh:
```py
#include <iostream>
#include <fstream>
#include "PEFILE.h"
int main(int argc, char* argv[])
{
if (argc != 2) {
printf("Usage: %s [path to executable]\n", argv[0]);
return 1;
}
FILE * PpeFile;
fopen_s(&PpeFile, argv[1], "rb");
if (PpeFile == NULL) {
printf("Can't open file.\n");
return 1;
}
if (INITPARSE(PpeFile) == 1) {
exit(1);
}
else if (INITPARSE(PpeFile) == 32) {
PE32FILE PeFile_1(argv[1], PpeFile);
PeFile_1.PrintInfo();
fclose(PpeFile);
exit(0);
}
else if (INITPARSE(PpeFile) == 64) {
PE64FILE PeFile_1(argv[1], PpeFile);
PeFile_1.PrintInfo();
fclose(PpeFile);
exit(0);
}
return 0;
}
```
* Phân tích thì có lẽ không cần thiết, nhưng mình vẫn sẽ giải thích qua qua mấy mẩu code:
```py
if (argc != 2) {
printf("Usage: %s [path to executable]\n", argv[0]);
return 1;
}
```
* Kiểm tra truyền đúng đối số (đường dẫn file).
```py
FILE * PpeFile;
fopen_s(&PpeFile, argv[1], "rb");
if (PpeFile == NULL) {
printf("Can't open file.\n");
return 1;
}
```
* Mở file chế độ `rb` → gán vào **PpeFile**. Không mở được thì báo lỗi.
```py
if (INITPARSE(PpeFile) == 1) {
exit(1);
}
else if (INITPARSE(PpeFile) == 32) {
PE32FILE PeFile_1(argv[1], PpeFile);
PeFile_1.PrintInfo();
fclose(PpeFile);
exit(0);
}
else if (INITPARSE(PpeFile) == 64) {
PE64FILE PeFile_1(argv[1], PpeFile);
PeFile_1.PrintInfo();
fclose(PpeFile);
exit(0);
}
```
* Nếu `INITPARSE()` trả về $1$ → lỗi khi xác định định dạng **PE**.
* Nếu file là **PE32 / PE64**:
* Tạo đối tượng `PE32FILE / PE64FILE` để phân tích.
* Gọi `PrintInfo()` để in thông tin ra console.
* Đóng file.
* Chi tiết hàm `INITPARSE()`:
```py
#include "PEFILE.h"
// INITIAL PARSE //
int INITPARSE(FILE* PpeFile) {
___IMAGE_DOS_HEADER TMP_DOS_HEADER;
WORD PEFILE_TYPE;
fseek(PpeFile, 0, SEEK_SET);
fread(&TMP_DOS_HEADER, sizeof(___IMAGE_DOS_HEADER), 1, PpeFile);
if (TMP_DOS_HEADER.e_magic != ___IMAGE_DOS_SIGNATURE) {
printf("Error. Not a PE file.\n");
return 1;
}
fseek(PpeFile, (TMP_DOS_HEADER.e_lfanew + sizeof(DWORD) + sizeof(___IMAGE_FILE_HEADER)), SEEK_SET);
fread(&PEFILE_TYPE, sizeof(WORD), 1, PpeFile);
if (PEFILE_TYPE == ___IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
return 32;
}
else if (PEFILE_TYPE == ___IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
return 64;
}
else {
printf("Error while parsing IMAGE_OPTIONAL_HEADER.Magic. Unknown Type.\n");
return 1;
}
}
```
* Chức năng chính xác định **PE file**:
* Không hợp lệ (`return 1`)
* **PE32** (32-bit) (`return 32`)
* **PE32+** (64-bit) (`return 64`)
* Được rồi. Vậy có thể nói tổng thể xây dựng ~~1 mẩu~~ code **PE Parser** đã xong ~~(khá là chubby)~~. Thực chất là mình ~~bú~~ của [0xRick](https://0xrick.github.io/win-internals/pe8/), tham khảo và sau đó giải thích lại logic code theo những kiến thức mình biết thôi. Hẹ hẹ hẹ....
## Build và kiểm chứng code
### Build file
* Đây là cây thư mục mà khi mình ~~git clone~~ của [0xRick](https://0xrick.github.io/win-internals/pe8/). Cá nhân mình thấy thì gộp lại thành 1 file rồi compile thì không vấn đề gì, nhưng chia nhỏ ra để tiện theo dõi cũng oke, nên mình vẫn giữ nguyên trạng thái thư mục, không thay đổi gì cả :))

### Build 32 bit
* Bước 1: Từ **Start Menu**, tìm `x86 Native Tools Command Prompt for VS 2022`
* Bước 2: Di chuyển về đúng thư mục.
* Bước 3: Compile với `cl.exe`. Như sau:
`cl /EHsc /FePE_Parser_32bit.exe PE-Parser.cpp PEFILE.cpp PE32FILE.cpp PE64FILE.cpp`
* `/EHsc`: bật xử lý exception **C++**
* `/FePE_Parser_32bit.exe`: đặt tên output là `PE_Parser_32bit.exe`
### Build 64 bit
* Bước 1: Từ **Start Menu**, tìm `x64 Native Tools Command Prompt for VS 2022`
* Bước 2: Di chuyển về đúng thư mục.
* Bước 3: Compile với `cl.exe`. Như sau:
`cl /EHsc /FePE_Parser_64bit.exe PE-Parser.cpp PEFILE.cpp PE32FILE.cpp PE64FILE.cpp`
* `/EHsc`: bật xử lý exception **C++**
* `/FePE_Parser_64bit.exe`: đặt tên output là `PE_Parser_64bit.exe`
### Test ...
* Sau khi compile và chạy thử với file `notepad.exe`, thì output khá dài nên không patse vô đây được, mình chỉ đưa ra các thông tin mà quan trọng thôi nhe. Các hàm hay logic thì ở trên mình cũng có nói, phần này chủ yếu là kết hợp với **CFF Explorer** để xem code chạy đúng không nha. 😶🌫️
### Type và file

* Yea không có gì bàn cãi :))
* Nếu `0x10B` thì là `PE32` nha :))
### DOS HEADER

* Magic: `0x5A4D`
* Đây là `e_magic` trong **IMAGE_DOS_HEADER**
* `0x5A4D = "MZ"`: dấu hiệu nhận diện một file **EXE** hợp lệ
* Luôn nằm ở offset `0x0` của file.

* `0xF0` đây là `e_lfanew` trong **IMAGE_DOS_HEADER**, offset chỉ tới `NT Headers`
* Tức là tại offset `0xF0`, sẽ thấy ``"PE\0\0"``.

### RICH HEADER

* **Rich Header** là một phần ẩn nằm giữa `DOS Stub` và `NT Header` ----> **Rich Header** luôn nằm trước offset `e_lfanew`

* Nó không được **Microsoft** công khai tài liệu nhưng chứa thông tin về:
* Phiên bản Linker
* Công cụ build (e.g. VS version)
* Số lượng module được build (compilers, linkers, assemblers...)
* Kiểu máy (target machine)
* Một vài `ProductID` tiêu biểu:
| ProductID (Dec) | Hex | Công cụ MSVC |
| --------------- | ----- | ------------------------- |
| `0xC9 = 201` | `201` | MASM Assembler |
| `0x93 = 147` | `147` | `cl.exe` Compiler (C/C++) |
| `0x101 = 257` | `257` | Linker |
| `0x103 = 259` | `259` | Export |
| `0x104 = 260` | `260` | Import |
| `0x108 = 264` | `264` | .NET Assembly tools |
| `0xFF = 255` | `255` | Unknown / filler |
* Dựa trên **Rich Header**, file `notepad.exe` được build bằng:
* `CL.exe` (147) → biên dịch C++
* `Link.exe` (257)
* `ALINK.exe` (258)
* Các builder khác: `Resource`, `Import`, `Export`
* Build bằng: `MSVC v142` (VS 2022) – build `30795`
* So sánh với 1 vài thông tin ở **Detect It Easy** thì khá khớp nha :))

### NT HEADERS
```py
NT HEADERS:
-----------
PE Signature: 0x4550
File Header:
Machine: 0x8664
Number of sections: 0x7
Size of optional header: 0xF0
Optional Header:
Magic: 0x20B
Size of code section: 0x28000
Size of initialized data: 0x31000
Size of uninitialized data: 0x0
Address of entry point: 0x19A0
RVA of start of code section: 0x1000
Desired image base: 0x40000000
Section alignment: 0x1000
File alignment: 0x1000
Size of image: 0x5A000
Size of headers: 0x1000
Data Directories:
* Export Directory:
RVA: 0x0
Size: 0x0
* Import Directory:
RVA: 0x30900
Size: 0x3FC
* Resource Directory:
RVA: 0x3A000
Size: 0x1E1D0
* Exception Directory:
RVA: 0x37000
Size: 0x1464
* Security Directory:
RVA: 0x0
Size: 0x0
* Base Relocation Table:
RVA: 0x59000
Size: 0x2F8
* Debug Directory:
RVA: 0x2E450
Size: 0x54
* Architecture Specific Data:
RVA: 0x0
Size: 0x0
* RVA of GlobalPtr:
RVA: 0x0
Size: 0x0
* TLS Directory:
RVA: 0x0
Size: 0x0
* Load Configuration Directory:
RVA: 0x29790
Size: 0x140
* Bound Import Directory:
RVA: 0x0
Size: 0x0
* Import Address Table:
RVA: 0x298D0
Size: 0xB68
* Delay Load Import Descriptors:
RVA: 0x30438
Size: 0xE0
* COM Runtime Descriptor:
RVA: 0x0
Size: 0x0
----------------------------------
```
* Phân tích qua các giá trị:
* `PE Signature: 0x4550` cái này thì thôi nha :)))
#### File Header (COFF Header)

* Giá trị: `0x8664 = IMAGE_FILE_MACHINE_AMD64` ----> File được compile cho kiến trúc `x64` (64-bit)
* `Number of sections (0x7)`
* `Size of optional header (0xF0)`
* Ta có thể xác nhận thông qua thông tin mà **CFF Explorer** dưới đây nha:

#### Optional Header
* `Magic (0x20B) = IMAGE_NT_OPTIONAL_HDR64_MAGIC`, đây là `PE32+`.
* `Size of code section (0x28000)`: Tổng kích thước của tất cả các section chứa code.
* `Size of initialized data (0x31000)`: Tổng kích thước data đã được khởi tạo.
* `Address of entry point (0x19A0)`: `RVA = 0x19A0`, địa chỉ tương đối của hàm `main/entry point`.
* `RVA of start of code section (0x1000)`: `RVA` của section chứa code đầu tiên.
* `Section alignment (0x1000) & File alignment (0x1000)`: alignment trong memory và alignment trong file.
* `Size of image (0x5A000)`: Tổng kích thước của image khi được load vào memory.
* `Size of headers (0x1000)`: Kích thước của tất cả headers (`DOS + NT + Section Headers`)
* Đối chiếu các giá trị đó trong **CFF Explorer** juan nha:

* Còn các **Data Directories** thì verify tương tự nha, thực chất nó cũng chuẩn đó.

#### Section Headers
```py
SECTION HEADERS:
----------------
* .text:
VirtualAddress: 0x1000
VirtualSize: 0x27F42
PointerToRawData: 0x1000
SizeOfRawData: 0x28000
Characteristics: 0x60000020
* .rdata:
VirtualAddress: 0x29000
VirtualSize: 0xA608
PointerToRawData: 0x29000
SizeOfRawData: 0xB000
Characteristics: 0x40000040
* .data:
VirtualAddress: 0x34000
VirtualSize: 0x26C0
PointerToRawData: 0x34000
SizeOfRawData: 0x1000
Characteristics: 0xC0000040
* .pdata:
VirtualAddress: 0x37000
VirtualSize: 0x1464
PointerToRawData: 0x35000
SizeOfRawData: 0x2000
Characteristics: 0x40000040
* .didat:
VirtualAddress: 0x39000
VirtualSize: 0xF8
PointerToRawData: 0x37000
SizeOfRawData: 0x1000
Characteristics: 0xC0000040
* .rsrc:
VirtualAddress: 0x3A000
VirtualSize: 0x1E1D0
PointerToRawData: 0x38000
SizeOfRawData: 0x1F000
Characteristics: 0x40000040
* .reloc:
VirtualAddress: 0x59000
VirtualSize: 0x2F8
PointerToRawData: 0x57000
SizeOfRawData: 0x1000
Characteristics: 0x42000040
```
* Các giá trị y hệt luôn.

### Linh tinh ...
* Tiếp sau đó là các giá trị cũng chính xác như ở trên mình đã phân tích. Cụ thể hơn thì là `Import Table` - danh sách các `API functions` mà **PE file** sẽ gọi từ các `DLL`, như là `GDI32.dll`, `USER32.dll`, ..., sau đó là các [Universal CRT (Universal C Runtime)](https://support.microsoft.com/en-us/topic/update-for-universal-c-runtime-in-windows-c0514201-7fe6-95a3-b0a5-287930f3560c) và [Windows API Set DLLs](https://learn.microsoft.com/en-us/windows/win32/apiindex/windows-apisets), chủ yếu là phục vụ runtime. ~~(theo mình tìm hiểu là thế)~~ :))
* **CFF Explorer** cũng cho ra kết quả tương tự:

## Tổng kết
* Cá nhân mình thấy vọc vạch tự code hay tham khảo từ các nguồn về **PE Parser** này cũng hay, nhưng theo mình thấy thì dùng **tools** vẫn tiện lợi và dễ dàng theo dõi hơn. Còn kha khá nguồn code hay mình tìm được để ở dưới, hoặc các bạn cũng có thể tìm được rất nhiều, nhưng chung quy lại về output thì mình nghĩ cũng không quá khác gì nhau đâu.
* Vậy ở wu này mình đã trình bày code **PE Parser** (~~thực ra là đi bú của [0xRick](https://0xrick.github.io/win-internals/pe8/))~~, mình có điểm qua cấu trúc của 1 file **PE**, kết hợp với **CFF Explorer** để kiểm chứng tính đúng đắn của code. Trong lúc tổng hợp wu có thể có những sai sót, các bạn đọc tham khảo có thể góp ý để wu mình hoàn thiện hơn. Tới đây là kết thúc wu rồi, chúc các bạn 1 ngày tốt lành và mong wu này giúp ích cho các bạn!!!
## Refs
https://0xrick.github.io/win-internals/pe8/
https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/pe-file-header-parser-in-c++
https://chuongdong.com/reverse%20engineering/2020/08/15/PE-Parser/
https://github.com/trailofbits/pe-parse
https://bladchan.github.io/2023/08/03/%E4%B8%80%E6%AD%A5%E4%B8%80%E6%AD%A5%E6%89%8B%E6%90%93%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84PE%E8%A7%A3%E6%9E%90%E5%99%A8/