---
# System prepended metadata

title: Phân tích cấu trúc của một file PE
tags: [RE]

---

# Phân tích cấu trúc của một file PE

PE File Format (Portable Executable File Format): là định dạng file riêng của Win32. Tất cả các file có thể thực thi được trên Win32 như: .exe, .dll (32 bit),… đều là định dạng PE. Ngay cả NT’s kernel mode driver cũng sử dụng định dạng tệp PE. Do đó nghiên cứu định dạng tập tin PE cung cấp cho bạn cái nhìn sâu sắc về cấu trúc của Windows.

## Cấu trúc cơ bản

![pe_file_struct](https://hackmd.io/_uploads/Bk1IT4hop.png)
Cấu trúc cơ bản của một file PE gồm hai phần chính là Section và Header, trong đó Header sẽ gồm:

- DOS MZ Header
- DOS Stub
- PE Header
- Section Table

Cụ thể hơn về cấu trúc của file có thể xem ở dưới đây

![](https://hackmd.io/_uploads/B1xRhV2o6.png)


## Header

### 1. DOS MZ Header

Tất cả các PE file đếu bắt đầu bằng một DOS MZ Header đơn giản. Nó chiếm 64 bytes đầu tiên. Vùng này được dùng  trong trường hợp chương trình chạy trên nền DOS, hệ điều hành DOS nhận biết đây là một file thực thi hợp lệ và sẽ thực thi nội dung trong phần DOS STUB. Dưới đây là cấu trúc _IMAGE_DOS_HEADER , đây là cấu trúc được định nghĩa trong winnt.h

![pe_file_struct_dos_mz_header](https://hackmd.io/_uploads/Hkon0Nnsp.png)

Trong các trường trên có hai trường đáng chú ý:

- e_magic: Chữ ký của PE file, giá trị: 4Dh, 5Ah (Ký tự “MZ”, tên của người sáng lập MS-DOS: Mark Zbikowsky). Giá trị này đánh dấu một DOS Header hợp lệ và được phép thực thi tiếp.
- e_lfanew: là một DWORD nằm ở cuối cùng của DOS Header, là trường chứa offset của PE Header so với vị trí đầu file.

Dưới đây là phần DOS Header trong file notepad++.exe
![Screenshot 2024-02-16 085558](https://hackmd.io/_uploads/ByZ1fShj6.png)

### 2. DOS Stub

DOS Stub chỉ là một chương trình DOS EXE nhỏ hiển thị một thông báo lỗi, là phần để tương thích với Windows 16bit. Ví dụ như trong hình minh họa dưới đây, thông báo sẽ hiện ra như sau: “This is program cannot be run in DOS mode”

![Screenshot 2024-02-16 090342](https://hackmd.io/_uploads/BJN9zr2sa.png)

### 3. PE Header

PE Header thực chất là cấu trúc IMAGE_NT_HEADERS bao gồm các thông tin cần thiết cho quá trình loader load file lên bộ nhớ. Cấu trúc này gồm 3 phần được định nghĩa trong windows.inc như dưới đây:

![upload_d00eb06107b27c765aeeaea00092b913](https://hackmd.io/_uploads/r1hQXBnia.png)

#### Signature

Đây là 1 DWORD bắt đầu PE Header chứa chữ ký PE: 50h, 45h, 00h, 00

#### File Header

Bao gồm 20 bytes tiếp theo của PE Header, phần này chứa thông tin về sơ đồ bố trí vật lý và các đặc tính của file. File Header là cấu trúc _IMAGE_FILE_HEADER trong thư viện winnt.h

```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;
```

Trong các trường trên ta cần chú ý tới:
- **Machine** : chứa thông tin về kiến trúc máy tính mà mà tệp thực thi được xây dựng để chạy trên( Intel 386, Intel 64, MIPS, ARM,…)
- **NumberOfSections:** đây là trường chứa số section của file. Nếu muốn thêm/xoá section trong PE file, ta cần thay đổi tương ứng trường này.
- **SizeOfOptionalHeader:** kích thước của Option Header
- **Characteristics:** là bit cờ, xác định định dạng PE File.

#### Optional Header

Là struct _IMAGE_OPTIONAL_HEADER được định nghĩa trong windows.inc, đây là phần chứa thông tin về sơ đồ logic trong PE file. Dưới đây là danh sách các trường trong cấu trúc này, đồng thời sẽ đưa ra một số chỉ dẫn về thông tin của một số trường cần quan tâm khi muốn chỉnh sửa file.


![pe_file_struct_optional_header](https://hackmd.io/_uploads/Bk79UB3i6.png)

Ta cần chú ý một số trường quan trọng sau:
- **Magic:** xác định là file 32 bit (0B 01) hay 64 bit (0B 20)
- **AddressOfEntryPoint:** chứa địa chỉ ảo tương đối (RVA) của câu lệnh đầu tiên sẽ được thực thi khi chương trình PE loader sẵn sàng để chạy PE File (.text). Nếu muốn chương trình bắt đầu từ một địa chỉ khác (để thực thi câu lệnh với mục đích khác) thì cần thay đổi địa chỉ này về địa chỉ tương đối của câu lệnh muốn thực thi.
- **SizeOfCode:** Kích thước của Code thực thi.
- **SizeOfInitializedData:** Kích thước của dữ liệu được khởi tạo.
- **BaseOfCode:** chứa địa chỉ RVA của phần code.
- **ImageBase:** Địa chỉ nạp được ưu tiên cho PE File. Lấy ví dụ: Nếu như giá trị trong trường này là 400000h, PE Loader sẽ cố gắng để nạp file vào trong không gian địa chỉ ảo mà bắt đầu tại 400000h. Từ "được ưu tiên" ở đây không thể nạp file tại địa chỉ đó nếu như có một module nào khác đã chiếm giữ vùng địa chỉ này. Hầu hết các trường hợp của ImageBase luôn là 400000h
- **Section Alignment:** Phần liên kết của các Section trong bộ nhớ. Tức là một section luôn luôn được bắt đầu bằng bội số của sectionAlignment. Ví dụ: sectionAlignment là 1000h, section đầu tiên bắt đầu ở vị trí 401000h và kích thước là 10h, section tiếp theo sẽ bắt đầu tại địa chỉ 402000h.
- **File Alignment:** Phần liên kết của các Section trong File. Tương tự như SectionAlignment nhưng áp dụng với file.
- **SizeOfImage:** Toàn bộ kích thước của Pe image trong bộ nhớ, là tổng của tất cả các headers và sections được liên kết tới Section Alignment
- **SizeOfHeaders:** Kích thước của tất cả các headers + section table = kích thước file trừ đi tổng kích thước của các section trong file.

Trường cuối cùng của _IMAGE_OPTIONAL_HEADER là Data Directory. Đây là một mảng cấu trúc IMAGE_DATA_DIRECTORY structure, cứ mỗi 8 bytes thì mỗi phần lại có liên quan tới một cấu trúc dữ liệu quan trọng trong PE File. Mỗi mảng tham chiếu tới một mục đã định trước, ví dụ như import table. Cấu trúc của Data Directory bao gồm thông tin về địa chỉ và kích thước của cấu trúc dữ liệu

```C
typedef struct _IMAGE_DATA_DIRECTORY {
  DWORD VirtualAddress;
  DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
```

_IMAGE_DATA_DIRECTORY gồm hai trường:
- **VirtualAddress:** là một địa chỉ ảo tương đối(RVA) của cấu trúc dữ liệu
- **Size:** Kích thước theo bytes của cấu trúc dữ liệu

Dưới đây là nội dung của 16 cấu trúc trong Data Directory

![upload_709f9333cf63d701629bfcd29eca7e13](https://hackmd.io/_uploads/SJzWnB3sp.png)

Dưới đây là toàn bộ phần PE Header trong file notepad++.exe
![Screenshot 2024-02-16 094542](https://hackmd.io/_uploads/rJwn0Hho6.png)


### 4. Section Table

Section Table là thành phần ngày sau PE Header, bao gồm một mảng những cấu trúc IMAGE_SECTION_HEADER, mỗi phần tử chứa thông tin về một section trong PE file. Như vậy nếu có bao nhiêu NumberOfSection trong FileHeader thì sẽ có bấy nhiêu bản sao của cấu trúc này trong table. Cấu trúc này được định nghĩa trong file windows.inc

![upload_941d9e24c912db652fab6d0993176e74](https://hackmd.io/_uploads/ByV7yqni6.png)

Thông tin về một số trường quan trọng:
- **Name:** Tên của Section
- **VirtualSize:** Kích thước thật sự của dữ liệu trên section tính theo byte, giá trị này có thể nhỏ hơn kích thước trên ổ đĩa (SizeOfRawData)
- **VirtualAddress:** RVA của section, là giá trị để ánh xạ khi section được load lên bộ nhớ
- **SizeOfRawData:** Kích thước section data trên ổ đĩa, được làm tròn lên bội số tiếp theo của File Alignment bởi trình biên dịch
- **PointerToRawData:** là offset từ vị trí đầu file tới section data.
- **Characteristics:** đặc tính của section: thực thi, dữ liệu khởi tạo …

Dưới đây là toàn bộ phần Section Table trong file notepad++.exe
![Screenshot 2024-02-16 144354](https://hackmd.io/_uploads/H1X8f5nsa.png)

## Section

Là những section chứa nội dung chính của file, bao gồm code, data, resource và những thông tin khác của file thực thi. Mỗi section có một Header và một body. Những Section Headers thì được chứa trong Section Table ta vừa phân tích nhưng những Section Bodies lại không có cấu trúc, chúng có thể được sắp xếp theo bất kì cách nào với điều kiện là Header được điều thông tin đầy đủ để có thể giải mã dữ liệu.

Một số Section thường thấy như sau:

- **Executable Code Section:** Thường được đặt tên là **.text** hoặc là CODE, thường chứa mã thực thi.
- **Data Sections:** Section **.bss** biểu diễn dữ liệu không được khởi tạo cho ứng dụng, bao gồm toàn bộ các biến đã được khai báo tĩnh trong một hàm hoặc một module nguồn; Section **.rdata** biểu diễn dữ liệu chỉ đọc( read-only), ví dụ như chuỗi, các hằng, và thông tin thư mục debug; Tất cả các biến khác(ngoại trừ biến cấp phát động, mà chỉ xuất hiện trên Stack) được lưu trong Section **.data**. Đó là những biến ứng dụng hoặc là những biến toàn cục.
- **Resources Section:** Section **.rsrc** chứa các thông tin resource cho một module. 16 bytes đầu tiên bao gồm 1 header giống như những section khác, nhưng dữ liệu của Section này được cấu trúc vào 1 resource tree và được quan sát tốt nhất qua 1 chương trình resource editor(Resource Hacker).
- **Export Data Section:** Section **.edata** chứa Export Directory cho một chương trình hoặc file DLL. Khi biểu diễn, section này bao gồm các thông tin về tên và địa chỉ của những hàm exported functions.
- **Import Data Section:** Section **.idata** chứa những thông tin khác nhau về những hàm imported functions bao gồm cả Import Directory và bảng Import Address Table.
- **Debug Information Section:** Thông tin Debug được đặt ban đầu trong Section **.debug**. Thường file debug có đuôi là .dbg. Section debug chứa thông tin debug, nhưng những thư mục debug lại nằm trong **.rdata** như được đề cập ở trên. Mỗi thư mục sẽ liên quan đến thông tin Debug trong Section **.debug**







