owned this note
owned this note
Published
Linked with GitHub
# PE Parser
# I. Introduction
- Đây là bài mở đầu cho một loạt các kĩ thuật liên quan đến malware mà mình sẽ viết sau này. Tương lai hướng tới sẽ là một bài về định dạng ELF nữa để cover đầy đủ các executable file format
# II. Content
## Quick view
- Về cơ bản thì PE format có cấu trúc như sau:

trong đó một vài header quan trọng mình sẽ phân tích là
:::info
- Stub
- Optional Header
- Data Directory
- Section Headers
- .idata và .edata
:::
- Các `PE` file không chỉ là executable file có extension `.exe` mà cũng có thể là `.dll` (đối với `ELF` file sẽ là `.so`, `.bin` hoặc `.o`), quick brief ở dưới đây:

- Mình sẽ nói một chút overview của các header thường được examine trong PE file:
### DOS Header
- Mỗi `PE` file đều bắt đầu với một header là một struct kích thước 64 bytes. Header này rất quan trọng và là tiền đề để một `PE` file có thể thực thi được
### Nt Header
- Header này cung cấp khá nhiều thông tin quan trọng cho người phân tích cũng như cho `PE` file. Nt Header bao gồm File Header và Optional Header.
#### File Header
- Một số thông tin của `PE` file nằm trong đây như
:::info
- Machine: Thông tin về architecture của `PE` file
- Section Count: Đếm số section của file
- Size of Optional Header: Thông tin về kích thước của Optional Header
:::
#### Optional Header
- Có thể coi Optional Header là Header chứa nhiều thông tin nhất của `PE` file, nó cho biết một số điều quan trọng cho việc thực thi như
:::info
- Magic: Thông tin `PE` file thuộc dạng x86 hay x64
- Entry Point: Địa chỉ RVA khi load `PE` file
- Base of Code: Địa chỉ RVA khi bắt đầu code section
- Image Base: Địa chỉ bắt đầu khi chương trình được load vào memory
- Data Directory (sẽ nói rõ ở dưới)
:::
### Section Header
- Một vài thông tin về các section được liệt kê trong đây, hỗ trợ việc thực thi
## 0. Preparation
- Chương trình mà mình sẽ parse chỉ đơn giản in ra:
```c=
#include <stdio.h>
int main(){
printf("Hello World!");
return 0;
}
```
- Bên cạnh đó chúng ta cũng cần một file `.dll` để phân tích phần `Export`:
```c=
#include <windows.h>
#define DLL_EXPORT extern "C" __declspec(dllexport)
DLL_EXPORT int add(int a, int b) {
return a + b;
}
```
## 1. DOS Header Explanation
- Đây là một ví dụ về DOS Header

- Trong đó một vài trường mà mình quan tâm nằm tại hai Offset:
:::success
- Offset 0x00: Magic Number
- Offset 0x3C: File Address
:::
- `Magic Number` với một file `.exe` (thông thường) là `PE` hay giá trị hex là `5A4D`. Bên cạnh đó, `File Address` (gọi tắt của `File Address of new exe header`) chính là file offset của `NtHeader`
- Để parse header này, mình sử dụng struct được định nghĩa sẵn của `winnt.h`:
```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;
```
- Do kiểu dữ liệu của các trường trong struct đều là `WORD` (2 `BYTE`) nên offset cũng là bội của 2, trừ `e_lfanew` là `LONG`
- Với `e_magic` thì chỉ là dấu hiệu nhận biết một file có thể thực thi hay không với giá trị `MZ`. Còn với `e_lfanew`, nó mang giá trị để dẫn tới offset của `Nt Header`, về chức năng thì có thể hiểu nó là con trỏ cũng được.
## 2. Nt Header Explanation
- Đối với header này, nó được trỏ tới bởi trường `e_lfanew` của header. Nhưng cũng có thể có câu hỏi tại sao lại tồn tại một trường như thế. Câu trả lời là giữa `Nt Header` và `DOS Header` còn có một đoạn code `Stub` và `Rich` ở giữa.
::: info
- Tuy nhiên, `Stub` chỉ để in ra cảnh báo
```This program cannot be run in DOS mode.```

- Trong khi đó, `Rich Header` được sử dụng để chứa một vài thông tin của file `.exe` khi được compile từ file `.c` bằng `Visual Studio`

(đây là ví dụ khác với ảnh trên)
:::
- Chính vì thế, sự tồn tại của trường `e_lfanew` là không thể xóa bỏ được vì mỗi `DOS Header` chỉ là 64-bytes, `e_lfanew` sẽ giúp liên kết các `Header` quan trọng trong file. Đối với file mình đang examine, `e_lfanew` mang giá trị hex `80`, và tại offset `0x80` sẽ là:


- Do file của mình là một file `PE` và valid nên `PE Bear` sẽ tự bỏ qua mà examine offset `0x84`. Cơ bản thì `Nt Header` bao gồm hai phần `File Header` và `Optional Header` mình đã nói ở trên
### 2.1. File Header Explanation
- Như cái tên của nó, header này cung cấp thông tin về file của chúng ta, và cũng có một struct tương ứng với header này được định nghĩa sẵn:
```c=
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;
```
- Thông tin mình quan tâm trong trường này là `Machine`, `NumberOfSections`, `TimeDateStamp`, và `SizeOfOptionalHeader`. Cụ thể hơn:
- `Machine` cung cấp thông tin về kiến trúc thiết bị cần để thực thi file, chi tiết có thể xem thêm ở [đây](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types)
- `NumberOfSections` cho biết số sections có ở trong file `PE`, có thể sử dụng để verify sau này
- `TimeDateStamp` cho biết ngày mà file được tạo ra
- `SizeOfOptionalHeader` cho biết kích thước của `Optional Header` mà mình sẽ phân tích ở dưới đây, và trường này đặc biệt cần thiết cho mọi file thực thi. Riêng với file object thì có thể được set là 0
- Hình ảnh chi tiết về thông tin của `File Header` như sau:

### 2.2. Optional Header Explanation
- `Optional Header` cũng được phân chia đủ cho hai kiến trúc máy là x86 và x64, sự khác biệt là:
- Ở trường `BaseOfData` không xuất hiện trong x64. `BaseOfData` có chức năng giống `BaseOfCode` là giá trị RVA của data secion (thường là `.data`)
- Một số kiểu dữ liệu của vài trường trong x64 được mở rộng hơn bản 32 bit, từ `DWORD` thành `ULONGLONG`:
:::info
- `ImageBase`
- `SizeOfStackReserve`
- `SizeOfStackCommit`
- `SizeOfHeapReserve`
- `SizeOfHeapCommit`
:::
- `Optional Header` cũng được định nghĩa struct của riêng nó, thông tin tham khảo thêm ở [OptHdr 32 bit](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header32), [OptHdr 64 bit](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64)
- Có khả năng đây sẽ là trường mình viết tốn giấy nhất, vì thông tin của nó quá nhiều, đây là hình ảnh minh họa:



#### 2.2.1. Before Data Directory
- Dưới đây sẽ là một số trường mình đặc biệt quan tâm khi parse một file `PE`:
- `Magic`: Thông tin về kiến trúc máy x86 hoặc x64
- `SizeOfCode`: Tổng kích thước của `.text` và các trường "có thể thực thi được"
- `SizeOfInitializedData`: Tổng kích thước của `.data` và các trường gần tương tự
- `SizeOfUninitializedData`: Tổng kích thước của `.bss` và các trường gần tương tự
- `AddressOfEntryPoint`: Địa chỉ RVA nơi mà file được load vào memory. Đối với `Dll` thì giá trị của nó có thể được đặt là 0
- `BaseOfCode`: Địa chỉ RVA nơi bắt đầu code section khi file được load vào memory
- `ImageBase`: Đây là trường cho biết địa chỉ đầu tiên mà file được load vào trong memory. Trên thực tế đối với các vùng nhớ được set `ASLR`, trường này gần như không bao giờ được sử dụng. Trường này còn liên quan mật thiết với section `.reloc` nhưng tạm thời mình chưa research về nó
- `SectionAlignment`: Trường này cho biết kích thước của một section nên có, các RVA của section sẽ được đặt trong các bội của 0x1000, cụ thể như sau:

Section đầu tiên ở 0x1000 và có size 0x73D0 nhưng khi cộng vào thì chỉ được 0x83D0 nên section tiếp theo được đặt hẳn lên 0x9000.
- `FileAlignment`: Tương tự như `SectionAlignment` nhưng áp dụng trên file, section sẽ được pad thêm zerobyte nếu kích thước của nó chưa đạt đủ.
- `NumberOfRvaAndSizes`: Kích thước của mảng `DataDirectory`, thường là 16 (0x10)
#### 2.2.2. Data Directory
- Thực chất `Data Directory` là một mảng gồm `NumberOfRvaAndSizes` phần tử, cấu trúc của mảng đơn giản gồm hai trường:
```c=
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
```
- Thông thường, 16 phần tử của `Data Directory` theo offset là:
```c=0
Export table address and size
Import table address and size
Resource table address and size
Exception table address and size
Certificate table address and size
Base relocation table address and size
Debugging information starting address and size
Architecture-specific data address and size
Global pointer register relative virtual address
Thread local storage (TLS) table address and size
Load configuration table address and size
Bound import table address and size
Import address table address and size
Delay import descriptor address and size
The CLR header address and size
Reserved
```
- Trong đó, đối với việc packing và unpacking, mình đặc biệt quan tâm đến RVA của hai thành phần là `Import table address and size` và `Import address table address and size`. Từ hai phần tử này, mình có thể chuyển đổi từ RVA sang file Offset để phân tích file tốt hơn, công thức như sau:
```c=
for (int i = 0; i < numberOfSections; i++) {
IMAGE_SECTION_HEADER section = sectionHeader[i];
if (rva >= section.VirtualAddress &&
rva < section.VirtualAddress + section.SizeOfRawData) {
return (rva - section.VirtualAddress) + section.PointerToRawData;
}
}
```
- Các phần tử trong `Data Directory` cũng được định nghĩa trong header `winnt.h`:
```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
```
## 3. Sections and Section Headers Explanation
- Hai phần này sẽ cover hầu như toàn bộ phần còn lại của file PE.
### 3.1. Section Explanation
- Phần này thực chất nằm sau `Section Headers` mà mình sẽ nói dưới đây. Sau đây là một ví dụ về các section có trong file mà mình đã chuẩn bị:

- Trong đây cũng đã có một số section's name khá phổ biến, mở rộng hơn thì đây là [danh sách những tên section được documented lại bởi Microsoft](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#special-sections):
:::info
- `.text`: Chứa phần code có thể thực thi được
- `.data`: Chứa phần data được khởi tạo sẵn.
- `.bss`: Phân vùng data chưa được khởi tạo.
- `.rdata`: Vùng chỉ đọc data đã khởi tạo.
- `.edata`: Chứa bảng export.
- `.idata`: Chứa bảng import.
- `.reloc`: Thông tin về image relocation.
- `.rsrc`: Chứa thông tin về các tài nguyên được sử dụng bởi chương trình, có thể là ảnh, icon hoặc thậm chí là binary được nhúng vào file
- `.tls`: (Thread Local Storage), storage cho các luồng chương trình được thực thi
:::
### 3.2. Section Headers Explanation
- `Section Headers` ở đây là số nhiều, và thực chất nó là một mảng, mỗi phần tử là 40 bytes được định nghĩa bằng cấu trúc `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;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
```
- Trong `PE Bear` thì hình ảnh của `Section Headers` như sau:

- Mình sẽ giải thích một vài trường trong cấu trúc của một `Header`:
- `RawAddress` hay `PointerToRawData` là file Offset xuất hiện section đó
- `RawSize` hay `SizeOfRawData` là kích thước của section **TRÊN FILE**, và là bội của giá trị tại trường `FileAlignment` đã nói ở trên `Optional Header`
- `VirtualAddress`, đúng ra ở đây phải là `Virtual Offset`, là địa chỉ byte đầu tiên của section so với `ImageBase`
- `VirtualSize` là kích thước của section **TRÊN MEM**, và là bội của giá trị tại trường `SectionAlignment`
- Một số kiến thức quan trọng cần lưu ý khi làm việc với các section là `RVA`, `VA` và `File Offset`, chúng ta đều có thể dễ dàng chuyển đổi giữa 3 giá trị này. Mình phân biệt `RVA` và `VA` bằng cách tự định nghĩa: `RVA` là khoảng cách giữa section và `ImageBase`; trong khi đó `VA` là khoảng cách giữa section so với 0, tức là:
```javascript
RVA = VA - ImageBase
```
- Và giả sử mình có ví dụ sau:
```python
.text:
Raw Address = 0x600
Raw Size = 0x7400
Virtual Address = 0x1000
Tính RVA của offset 0x800
```
- Để giải bài này cũng khá đơn giản, mình sẽ xác định offset đó nằm trong section nào. Trong trường hợp này thì offset đó nằm giữa `.text` và section tiếp theo. Vậy để tính được `RVA` thì chỉ cần làm như sau:
```python
RVA = (Offset - Raw Address) + Virtual Address
RVA = 0x800 - 0x600 + 0x1000
RVA = 0x1200
```
Check:

- Để convert ngược lại cũng khá đơn giản, cần xác định xem `RVA` của target đang nằm trong section nào, và thực hiện ngược lại tư duy ở trên để có thể tính lại `offset` của target:
```python
Offset = (RVA - Virtual Address) + Raw Address
Offset = (0x1200 - 0x1000) + 0x600
Offset = 0x800
```
- Dựa vào tư duy này thì chúng ta có thể recover được các file `PE` hỏng ở mức tương đối đơn giản.
## 4. Import/Export Explanation
- `Import` không phải là một header mà là một nội dung mình sẽ cover tương đối đầy đủ vì nó khá khó và quan trọng.
- Trước hết, để biết được phần `Import` nằm ở đâu, ta có hai cách:
- Nhìn vào phần tử `Import Directory` trong mảng `Data Directory`:

- Nhìn vào section `.idata`:


- Thông thường với file `PE` không định nghĩa section `.idata`, giá trị tại `Import Directory` sẽ là `RVA`, chúng ta cần chuyển nó sang `offset` theo đúng tư duy ở trên. Khi đã tìm được đến `offset` của `Import Directory` rồi, chúng ta sẽ cần phân tích dựa vào struct này:
```c=
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
} DUMMYUNIONNAME;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
```
- Thực chất `Import Directory` là một mảng gồm nhiều phần tử liên tiếp nhau có cấu trúc `IMAGE_IMPORT_DESCRIPTOR`, và máy sẽ load từng phần tử một cho tới khi gặp zerobytes, cụ thể là 20 bytes 0 thì dừng. Trong struct này, các trường mình quan tâm là:
- `OriginalFirstThunk`: RVA của Import Lookup Table (ILT)
- `Name`: RVA chứa tên của (thường là `.dll`) thư viện load vào
- `FirstThunk`: RVA của Import Address Table (IAT)
- Một phần tử `IMAGE_IMPORT DESCRIPTOR` trông như sau:

- Mình đang trỏ vào phần tử đầu tiên (load `KERNEL32.dll`). Thông tin của nó đều được parse đầy đủ, và tại các `RVA` đó là thông tin mình đã nói ở trên:
- `RVA ILT`:

- `Name`:

- `RVA IAT`:

nó còn kéo dài tới tận `RVA` 0xF228 nên mình bỏ qua
- Một phần kiến thức nữa cũng không thể bỏ qua là bảng `ILT` hay `Import Lookup Table`. Như đã thấy trong ảnh chụp IDA, các `RVA` đó đều chứa thông tin về các hàm (chủ yếu là tên và một số hint dùng để tra cứu hàm), đầy đủ như sau:

- Mỗi địa chỉ `RVA` tại `ILT` lại trỏ tới vùng thông tin về hàm để tra cứu, dựa trên struct này:
```c=
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
```
- Cuối cùng là `IAT` hay `Import Address Table` là mảng thông tin rất quan trọng về hàm để thực thi, đó chính là địa chỉ hàm. Về bản chất thì `IAT` có thể coi là một `Jump Table` giúp thực thi code một cách "gián tiếp".
### 4.1. How the program calls a function
- Function ở đây được hiểu là các function được declare trong phần `Import Directory`, chủ yếu đây là các hàm load từ các file `.dll`. Để gọi được hàm từ `.dll`, chương trình cần có tên hàm (đại diện cho nó là `Name` từ bảng `ILT`), và địa chỉ của hàm (nằm trong bảng `IAT`).
- Về flow, có thể biểu diễn như sau:
```
Executable (.exe)
|
+--> Import Directory Table
|
+--> ILT --> "func_name"
+--> IAT --> [func_address from dll]
```
- `Compiler` khi nhận được một lời gọi hàm `func` trong code sẽ chuyển đổi nó thành `call [IAT_ENTRY]`. Trong đó, `IAT_ENTRY` là địa chỉ thực của hàm `func` trong file `.dll`
- Bên cạnh đó, `Loader` đóng vai trò khá quan trọng là ghi lại địa chỉ hàm vào bảng `IAT`. Với mỗi `dll` được load, Loader sẽ trích xuất địa chỉ từng hàm trong `dll` đó và điền vào `IAT`.
- Kết luận lại, chương trình gọi hàm theo kiểu gián tiếp, gọi hàm từ `.dll` như thể hàm nằm trong chương trình nhưng thực chất là thông qua một bảng `IAT` chứa địa chỉ hàm trong file `.dll`
### 4.2. Export Explanation
- Nhân tiện nói về `Import Directory` thì `Export Directory` cũng là một chủ đề khá quan trọng. `Export Directory` thường được thấy trong các file thư viện như `.dll`, được sử dụng khi một chương trình ngoài cần thực thi hàm trong file.
- `Export` cũng có mối liên hệ mật thiết với `Export Directory` trong mảng `Data Directory`, và section `.edata`. Thông thường, `.edata` sẽ không bị ẩn đi như `.idata`. Đây là `Export Directory`:

- Trong header `winnt.h`, struct của `Export Directory` được định nghĩa như sau:
```c=
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
```
- Với file của mình, `Export Directory` nằm tại `RVA` 0x8000, đã được chương trình tự động chuyển sang `offset` 0x2A00:

- Một vài thông tin mình khá quan tâm là:
- `Name`: chứa tên của file `.dll`
- `AddressOfFunctions`: RVA trỏ tới địa chỉ của hàm `add`

- `AddressOfNames`: RVA trỏ tới địa chỉ tên của hàm `add`

- Đối với `AddressOfFucntions`, trong bài, `RVA` là 0x1370, tương ứng với `offset` 0x970:

- Và tại `offset` 0x970, `RVA` 0x1370 chứa code thực thi của hàm `add`:

# III. Wrapping up
- Với mình thực sự `PE File` là khởi đầu tốt để đến với Malware Analysis. Trong tương lai mình sẽ cover nhiều hơn chủ đề này. Hi vọng bài viết này hữu ích với các bạn. Dear!!!
# IV. Practice
- Phần này mình sẽ nói qua một chút kĩ thuật để recover PE file, một số kĩ thuật cơ bản thôi. Hai công cụ mình sẽ sử dụng là `PE Bear` để parse file và `HxD` để chỉnh sửa.
## 1. fix1.exe
- File này đang bị mất `Import Directory` (`PE Bear` không hiển thị):

- Cách giải quyết khá đơn giản là ghi địa chỉ đúng của `Import Directory` vào trong mảng `Data Directory`:

- Để tìm được địa chỉ đó, mình sẽ tìm string, cụ thể ở đây là `.dll`:

- Thông thường `PE` file sẽ luôn load `kernel32.dll` đầu tiên, và theo cấu trúc của `_IMAGE_IMPORT_DESCRIPTOR` sẽ tồn tại một trường là `NameRVA`:
```c=
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;
} DUMMYUNIONNAME;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
```
- Vì thế, `NameRVA` đầu tiên mà mình có là địa chỉ của `kernel32.dll`.
- Offset của string `kernel32.dll` là `0x3606` như trên ảnh, do `NameRVA` lưu theo địa chỉ trên Mem nên cần chuyển `0x3606` thành:

- Do `0x3606` nằm trên section `.rdata` nên mình có cách chuyển trên. Tìm kiếm giá trị `0x4C06` trên file bằng `HxD` thì tìm được:

- Vậy `NameRVA` sẽ nằm ở file offset `0x2E60`. Khi đó, `OriginalFirstThunk` sẽ nằm ở offset `0x2E58` và đây chính là địa chỉ của phần tử đầu tiên trong mảng `Import Directory`. Chuyển `0x2E58` từ offset sang RVA là

- Điền vào `Data Directory` là `Import` đã được recovered:


## 2. fix2.bin
- Đây là một bài tương đối khó, về cơ bản, file được cung cấp không phải file trên disk mà được dump ra từ memory. `PE Bear` cũng đưa ra cảnh báo:

- Trong trường hợp `PE Bear` không đưa ra cảnh báo, ta cũng có thể nhận biết đây là file `PE` trên memory do thấy có sự sai lệch khi `Raw Address` của các section toàn là byte 0 trong khi `Virtual Address` chứa các byte thực sự:


- Để làm được bài này, mình sẽ có hai cách: cách 1 dễ hơn và chỉ có thể đọc được `Export`, `Import`; cách 2 khó hơn chút và có thể thực thi được file
### 2.1. Cách 1
- Do file được dump ra từ memory, nên vốn dĩ các `Virtual Address` đều chứa byte chuẩn, khi đó chỉ cần thay đổi các giá trị `Raw Address` thành của `Virtual Address` là có thể xem được `Export` và `Import`:
- Trước:

- Sau:

- `Export`:

- `Import`:

### 2.2. Cách 2
- Do cách 1 chỉ máy móc thay đổi `Raw Address` thành `Virtual Address`, các section trên disk không thực sự hợp lí về logic, đặc biệt là trường `Raw Size`. Chính vì thế, cách 2 này sẽ nâng cấp hơn cách 1 và mapping chuẩn chỉ hơn.
- Do tại các section đều có byte 0 tại `Raw Address`, mình sẽ lợi dụng các byte này để ghi đè byte thực sự của nó, code như sau:
```python=
fileAlign = 0x200
def fitSection(size, align):
if size % align != 0:
size = align * (size // align + 1)
return size
oldFile = bytearray(open("fix2.bin", "rb").read())
raw_section = [0x400, 0x1600, 0x2200, 0x2400, 0x2600]
raw_size = [0x1200, 0xC00, 0x200, 0x200, 0x200]
virtual_section = [0x1000, 0x3000, 0x4000, 0x5000, 0x6000]
virtual_size = [0x10A5, 0xB02, 0x3DC, 0xF8, 0x1CC]
for i in range(5):
raw = raw_section[i]
raw_end = raw + raw_size[i]
virtual = virtual_section[i]
virtual_end = virtual + virtual_size[i]
srcWrite = oldFile[virtual : virtual_end]
if raw_size[i] >= len(srcWrite):
dstWrite = bytearray(srcWrite) + bytearray(raw_size[i] - len(srcWrite))
else:
dstWrite = bytearray(srcWrite[:raw_size[i]]) #Chấp nhận cắt byte
oldFile[raw : raw_end] = dstWrite
newFile = open("fix2FIXED.bin", "ab")
newFile.write(oldFile)
newFile.close()
```
- Code của mình gặp phải chút vấn đề tại section `.data` khi `Virtual Size` lớn hơn `Raw Size`. Điều này dẫn tới section tiếp theo bị lấn bởi section trước nếu làm theo ý tưởng bê nguyên từ `Virtual` sang `Raw`. Nhưng trong trường hợp file `PE` được dump từ trong mem, có thể chấp nhận được section `.data` bị cắt một vùng byte sao cho vừa đủ `Raw Size`. `Virtual Size` lớn hơn `Raw Size` (đặc biệt đối với section `.data`) khi dump `PE` file từ mem có thể do chương trình sử dụng heap hoặc đụng tới vùng `Unitialized Data` trên mem (không được khai báo trước trên disk) nên `PE Bear` parse nó thành `.data`.
## 3. fix3
- Đối với file này thì `PE Bear` không thể nhận dạng được file, khi ném vào `HxD` thì mình thấy vài vấn đề:


- File đã mất đi signature `MZ`, `PE` và `Stub`. Để file thực thi được thì chỉ cần recover hai signature kia là đủ. `MZ` ở hai offset đầu, ứng với giá trị `0x4D5A`. Thêm vào đó, ta biết được tại offset `0x3C` sẽ là trường `e_lfanew` và nó có giá trị:

- Vậy ta sẽ di chuyển đến offset `0xF0` để thêm signature `PE` vào đó, cuối cùng được:

- Khi này `PE Bear` đã có thể phân tích bình thường:

# V. References
- [0xrick](https://0xrick.github.io/win-internals/pe1/)
- [ChuongDong](https://chuongdong.com/reverse%20engineering/2020/08/15/PE-Parser/)
- [Skr1x](https://skr1x.github.io/portable-executable-format/)
- [Yuriygeorgiev](https://yuriygeorgiev.com/2023/12/18/windows-portable-executable-pe-file-format/)
- [Tech-zealots](https://tech-zealots.com/malware-analysis/pe-portable-executable-structure-malware-analysis-part-2/)
