owned this note
owned this note
Published
Linked with GitHub
---
tags: [Technique]
---
# PE file và những gì liên quan
## PE file là gì?
* [PE (Portable Executable)](https://en.wikipedia.org/wiki/Portable_Executable) là định dạng tệp nhị phân được sử dụng chủ yếu trong hệ điều hành Windows cho các tệp thực thi **(EXE)**, thư viện liên kết động **(DLL)**, và các loại file chạy khác. Định dạng này mô tả cấu trúc cần thiết để hệ điều hành có thể tải và thực thi chương trình, bao gồm thông tin về mã máy, dữ liệu, thư viện cần thiết, và thông tin định vị các thành phần bên trong tệp.
* Tên gọi ``“portable”`` phản ánh tính linh hoạt của định dạng này trong việc hỗ trợ nhiều nền tảng phần cứng, mặc dù nó chủ yếu được sử dụng trên **Windows**. Định dạng **PE** được xây dựng dựa trên định dạng **COFF (Common Object File Format)** và cũng là nền tảng cho định dạng thực thi trong `.NET`
(tức là file **.NET PE**).
## 1 số 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 1 file PE
### Cấu trúc cơ bản

* 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

### DOS Header và DOS Stub
* Đối với các loại file ảnh như **PNG**, **JPG**, hay âm thanh như **MP3**, **MP4**, hoặc tệp nén như **ZIP**, **RAR**, .... đều có định dạng riêng. Để có thể phân biệt, nhận diện các loại file, người ta sẽ dùng phần đầu để xác định, gọi là **header**. Trong phần **header** này có chứa 1 dãy byte, gọi là **magic byte**, đóng vai trò như **signature** (chữ ký) của file đó.
* Ví dụ với **PNG**, thì mọi người chỉ cần search [PNG header signature](https://asecuritysite.com/forensics/png?file=%2Flog%2Fbasn0g01.png) là có ngay. Ở đây mình thử mở bằng **CFF Explorer**.

* Tương tự với các loại file khác.
* Còn khi thử mở 1 file **PE** trên **CFF Explorer**:

* Mở đầu 1 file **PE** đều là **DOS Header** và **DOS Stub**. Trong hệ điều hành **Windows** hiện đại bây giờ, 2 giá trị này không còn quan trọng với **PE** file. Nhưng nếu như nó không quan trọng, thì tại sao vẫn giữ lại trong cấu trúc **PE** file?
:::success
* Tham khảo ở [đây](https://0xrick.github.io/win-internals/pe3/#dos-header), thì thấy rằng lý do mà nó vẫn còn được xuất hiện là vì mục đích tương thích ngược với các hệ điều hành cũ như **MS-DOS**. Trong giai đoạn chuyển đổi từ **MS-DOS** sang **Windows NT**, nhờ có **DOS Header**, tệp **PE** có thể được nhận dạng như là một file thực thi của **MS-DOS**.
* Khi tệp này được chạy trong môi trường **MS-DOS** (thay vì **Windows**), phần tiếp theo là **DOS Stub** sẽ được thực thi thay thế cho chương trình chính
(vì **MS-DOS** không hiểu định dạng **PE**).
* Ở hình ảnh trên thì có thể thấy, thông báo `"This program cannot be run in DOS mode"` xuất hiện trong **DOS Stub**.
:::
* Vậy:
:::info
* **DOS Header**: $64$ bytes đầu tiên của tất cả các file **PE**. Có tác dụng tệp **PE** có thể được nhận dạng như là một file thực thi của **MS-DOS**.
* **DOS Stub**: Là nơi ngay sau **DOS Header**, có chức năng hiển thị thông báo lỗi `"This program cannot be run in DOS mode"` khi chương trình được khởi chạy trong **DOS** mode.
:::
* **structure DOS Header** là một cấu trúc được định nghĩa trong các file `windows.inc` hoặc `winnt.h`. Như hình dưới đây:


* Ta thấy 2 bytes magic là `4D, 5A` tương ứng `MZ`, là tên viết tắt của người sáng tạo chính của **MS-DOS** - Mark Zbikowsky. Và giá trị này tương ứng với offset `e_magic`.
* Ngoài ra, 1 trường khác cũng quan trọng cần chú ý đó là `e_lfanew`, có vị trí tại offset $0x3C$.

* Có thể thấy giá trị tại đó là $0x108$

* Giá trị này xác định vị trí bắt đầu của **PE header**. **Windows Loader** sẽ tìm kiếm offset này vì vậy nó có thể bỏ qua **DOS Stub** và đi trực tiếp tới **PE** file.
* Có thể thấy tại $0x108$ bắt đầu bằng **PE** (giá trị `50,45` hexa), đây chính là signature của **PE header**.
* Trước khi kết thúc phần **DOS Header,DOS Stub** và đi tìm hiểu tiếp về **PE header**, chúng ta có thắc mắc là phần ở giữa **DOS Stub** và **PE header** là gì?
* Sau khi ~~nhặt nhạnh~~ ở [đây](https://0xrick.github.io/win-internals/pe3/#rich-header), thì phần đó chính là **Rich-Header**, có ít tài liệu nhắc tới do nó không gây ảnh hưởng gì cho chương trình, chỉ để lưu lại thông tin chương trình biên dịch Visual Studio.

### PE Header
* Hay còn gọi là **NT Header**, gồm 3 phần chính là **Signature**, **File Header**, và **Optional Header** được định nghĩa trong file **windows.inc**:


#### **Signature**
* Như trên phần **DOS Header,DOS Stub** cũng có giới thiệu rồi, thì nó là giá trị hexa `50,45,00,00` - kí tự **P,E** và theo sau là 2 byte null. Còn tại trong `CFF Explorer` thì hiển thị theo `little-endian` nên ngược nha.

#### **File Header**
* Là 1 structure $20$ bytes, gồm $7$ phần được định nghĩa trong `winnt.h`:


##### **Machine**
* Dùng để giữ giá trị chỉ định loại bộ xử lý mà file **PE** này được thiết kế để chạy trên. Có thể thấy ở hình dưới đây, bộ xử lý `Intel i386` tương ứng $0x14C$. Ngoài ra còn có các bộ xử lý khác như `AMD64` - $0x8664$ cho kiến trúc $64$ bit, `ARM` - $0x1C0$ cho thiết bị di động và IoT.

##### **NumberOfSections**
Dùng để lưu trữ số lượng `section` có trong file **PE**. Như hình dưới thì ta thấy có $8$ `section`:

Các `section` thông thường xuất hiện trong file **PE** bao gồm:
`.text`: chứa mã thực thi của file **PE**.
`.rdata`: chứa các dữ liệu hằng (constant) trong file **PE**.
`.data`: chứa dữ liệu đã được khai báo và định nghĩa.
`.bss`: chứa dữ liệu đã được khai báo nhưng chưa được định nghĩa.
`.rsrc`: chứa các tài nguyên của file **PE**, có thể là icon, file âm thanh, hình ảnh, …
`.reloc`: chứa thông tin cần thiết cho quá trình tái định vị (relocation).

##### **TimeDateStamp**
* Với độ dài là $4$ bytes, được sử dụng để ghi lại thời điểm mà file **PE** được tạo hoặc chỉnh sửa. Điều này là quan trọng vì nó giúp xác định thời điểm cuối cùng khi file được biên dịch hoặc sửa đổi. Tuy nhiên ta cũng phải lưu ý rằng các hacker có thể thay đổi giá trị của trường này để làm cho quá trình phân tích tĩnh của file **PE** trở nên khó khăn hơn.

##### PointerToSymbolTable và NumberOfSymbols
* Trường thông tin này giữ offset của file chỉ đến **COFF symbol table** và số lượng `entry` trong `symbol table` đó. Tuy nhiên chúng được đặt giá trị là $0$ có nghĩa là không có **COFF symbol table**. Điều này được thực hiện là do thông tin **COFF debugging** không được dùng nữa.
##### **SizeOfOptionalHeader**
* Được sử dụng để lưu trữ kích thước của `section` **Optional Header**. Do **Optional Header** không có kích thước cố định, thay vào đó, kích thước của nó có thể thay đổi tùy thuộc vào kiến trúc của các tệp tin **PE** khác nhau và theo các tính năng cụ thể mà tệp tin **PE** có hoặc không có.

##### **Characteristics**
* Với kích thước là 2 byte, trường này đại diện cho một số đặc tính của file **PE**. Để xem và hiểu rõ các thuộc tính này, ta có thể sử dụng công cụ như `CFF Explorer`. Công cụ này không chỉ giúp xem tất cả các thuộc tính có thể xuất hiện mà còn cho phép kiểm tra các thuộc tính mà file **PE** hiện tại đang sở hữu.

Một vài thuộc tính quan trọng như:
* **File is executable**: thuộc tính này chỉ ra rằng file **PE** hiện tại là một file thực thi.
* **File is a DLL**: thuộc tính này chỉ ra rằng file **PE** hiện tại là một
`dynamic-link library (DLL)`.
* **32 bit word machine**: thuộc tính này ta xác định xem file **PE** hiện tại là `32-bit` hay `64-bit`.
Ta cũng có thể chỉnh sửa các thuộc tính này bằng cách tick vào các box trong `CFF Explorer`.


#### **Optional Header**
* Đây là vùng chứa nhiều trường thông tin quan trọng cho **Windows loader** khi thực hiện quá trình sao chép và ánh xạ file **PE** lên bộ nhớ của process.

##### **magic**
* $2$ bytes, giá trị `0x10B` tương ứng file **PE** 32-bit, `0x20B` là **PE** 64-bit.

##### 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(2 giá trị **BaseOfCode** và **AddressOfEntryPoint** - có thể giống nhau nếu phần địa chỉ bắt đầu của mã thực thi và địa chỉ bắt đầu của vùng nhớ mã thực thi trùng nhau).
##### **AddressOfEntryPoint**
* Kích thước 4 byte. Nó chứa địa chỉ `Relative Virtual Address (RVA)` của câu lệnh đầu tiên mà sẽ được thực thi khi chương trình **PE** Loader sẵn sàng để run **PE** file. Nếu như bạn muốn làm thay đổi luồng thứ tự thực hiện, bạn phải thay đổi lại giá trị trong trường này thành một `RVA` mới và do đó câu lệnh tại giá trị `RVA` mới này sẽ được thực thi đầu tiên.
##### **ImageBase**
* Là địa chỉ nạp được ưu tiên cho **PE** file. 99% giá trị này là 0x400000. Nếu như giá trị trong trường này là 0x400000, **PE** Loader sẽ cố gắng cấp phát không gian trong bộ nhớ ảo để sao chép file PE và các section của nó vào trong, bắt đầu tại 0x400000. **Ư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. Có thể giải thích rằng do trong quá trình tạo một process hoàn chỉnh, không chỉ **Windows** loader nạp module tương ứng với file **PE** mà còn phải nạp các module khác, chẳng hạn như **DLL** và mapped file,... Điều này làm cho địa chỉ 0x400000 có thể đã được sử dụng bởi một module khác, khiến cho **Windows** loader phải tìm một vị trí khác để nạp file **PE** lên.
##### Section Alignment
* Là giá trị thể hiện đơn vị căn chỉnh ``(alignment)`` khi ánh xạ các `section` vào bộ nhớ ảo ``(Virtual Memory)``.
* Mỗi `section` khi được nạp vào bộ nhớ phải bắt đầu tại một địa chỉ ảo là bội số của giá trị **Section Alignment**. Trên **Windows**, giá trị này thường là 0x1000 (tức 4096 bytes).
* Điều này đảm bảo rằng mỗi **section** không bị chồng lấn lên nhau trong không gian địa chỉ ảo, và giúp hệ điều hành quản lý bộ nhớ dễ dàng hơn.
* Ví dụ: nếu **SectionAlignment** = 0x1000 (4096 bytes), và một `section` đang ở địa chỉ ảo 0x401000, dù kích thước thực tế của nó chỉ là 10 bytes, thì `section` kế tiếp vẫn phải bắt đầu tại địa chỉ 0x402000 (tức là bội số kế tiếp của 0x1000).
##### File Alignment
* **File Alignment** là giá trị thể hiện đơn vị căn chỉnh ``(alignment)`` của các `section` khi chúng nằm trong file thực thi.
* Trên **Windows**, giá trị thường gặp là 0x200 (512 bytes)
* Ví dụ: nếu **FileAlignment** = 0x200 (512 bytes), và `section` đầu tiên có dữ liệu dài 10 bytes, thì `section` tiếp theo vẫn phải bắt đầu tại offset 0x200, chứ không được viết liền ngay sau 10 byte kia. Tức là các `section` trong file cũng được căn chỉnh theo **FileAlignment**, để khi nạp vào bộ nhớ thì căn chỉnh lại theo **SectionAlignment**.
##### SizeOfImage
* Toàn bộ kích thước của **PE image** trong bộ nhớ. Nó là tổng tất cả các `headers` và `sections` được liên kết tới **Section Alignment**. Lưu ý rằng độ lớn của module tương ứng với file **PE** khi nó được ánh xạ vào bộ nhớ của process sẽ khác với kích thước của file **PE** trên đĩa.
##### SizeOfHeader
* Kích thước 4 byte chứa độ lớn tất cả các `header` và `section table`. Nói tóm lại, giá trị này là bằng kích thước file trừ đi kích thước được tổng hợp của toàn bộ `sections` trong file. Bạn cũng có thể sử dụng giá trị này như một file offset của `Section` đầu tiên trong **PE** file.
##### Data Directory
* **Data Directory** là mảng gồm $16$ mục trong **PE** header ($128$ byte cuối của **OptionalHeader**), mỗi mục chứa `RVA` và kích thước của một bảng dữ liệu quan trọng như `Import/Export/Resource Directory` và cả `IAT(Import Address Table)`. Nếu một mục không tồn tại, cả hai giá trị sẽ là $0$. **Data Directory** giúp hệ điều hành định vị và nạp các thành phần của **PE** file.

* Công cụ như **CFF Explorer** hỗ trợ tra cứu và xác định mỗi **Data Directory** nằm trong `section` nào của file, rất hữu ích cho việc phân tích hoặc unpacking.

### Section Table
* Tiếp sau **PE Header** là **Section Table**. Nghe tên thì chắc chúng ta cũng đoán được ý nghĩa và tầm quan trọng của thành phần này. Nó là 1 structure, mà mỗi phần tử sẽ chứa thông tin về một `section` trong **PE** file. Từ đó **Windows loader** mới xác định được vị trí của các `section` trong file **PE** rồi mới có thể biết được nên nạp các `section` vào những vị trí cụ thể nào trên bộ nhớ.
* Đầu tiên là **Section Header**:


#### Name
* Trường này có kích thước $8$ bytes, là label lưu trữ tên của `section`.Vậy nên tên của `section` không cần kết thúc bằng null và có thể để trống, tối đa thì $8$ ký tự.
#### Virtual Size và Virtual Address
* Lần lượt là **kích thước theo bytes** và **RVA** của `section` trên không gian địa chỉ ảo của process.

#### SizeOfRawData/Raw Size và PointerToRawData/Raw Address
* Lần lượt là **kích thước các `section` trên file** và **offset của `section`** tính từ đầu file **PE** trên đĩa.

#### Characteristics
* Trường cuối cùng của **Section Table** là **Characteristics**. Trường này giúp xác định các đặc tính của một `section` như `executable code, initialized data, uninitialized data, read, write,....`

* Với `notepad.exe` khi mở bằng **CFF Explorer**:

* Màu hồng là **PointerToRawData** của từng `section` nha.
### PE Sections
* Là `section` chứa nội dung chính của file, gồm `.code`, `.data`, ... nằm sau **Section Header** và cũng chính là phần còn lại của file **PE**. Mỗi `section` có một **Header** và một **Body**. **Section Header** thì đã nói ở trên rồi, nhưng những **Section Body** 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** chứa đầy đủ thông tin để có thể giải mã dữ liệu.
#### Executable Code Section
* **`.text`**: Chứa code thực thi của chương trình.
#### Data Sections
* **`.data`**: Chứa data được khởi tạo. Có thể là những biến toàn cục, chỉ xuất hiện trên **Stack**, ...
* **`.bss`**: Chứa data không được khởi tạo. Có thể là các biến khai báo tĩnh trong 1 hàm hoặc module nguồn nào đó.
* **`.radata`**: Chứa data **read-only** được khởi tạo. Ví dụ như `string, constant,...`
#### Import Data Section
* **`.edata`**: Chứa `export tables` của 1 chương trình hoặc 1 file **DLL**. **Section** này chứa tên và địa chỉ của các hàm trong đó.
* **`.idata`**: Chứa `import tables`. **Section** này chứa những thông tin khác nhau của các hàm trong đó, bao gồm cả **Import Directory** và **IAT(Import Address Table)**.
#### Base Relocation Section
* **`.reloc`**: Chứa thông tin về `relocation`, là những thông tin cần thiết để thực hiện thay đổi **base address** của 1 module khi nó được nạp vào bộ nhớ (hay còn gọi là tái định vị). Cái này quan trọng vì nó đảm bảo các địa chỉ trong chương trình là chính xác và đúng với địa chỉ bộ nhớ của chương trình đang chạy. Tức là đảm bảo việc không xảy ra xung đột địa chỉ khi nạp hoặc lỗi tham chiếu, ...
#### Resources Section
* **`.rsrc`**: Chứa tài nguyên được dùng bởi chương trình, bao gồm ảnh, icon hay thậm chí các mã nhị phân được nhúng.
#### Thread Local Storage
* **`.tls`**: cung cấp bộ nhớ riêng cho mỗi luồng thực thi của chương trình.
## Unpack file PE
### Khái niệm
* Đây là 1 kỹ thuật mà ta có thể hiểu là file **PE** sẽ bị `pack`, khiến chúng ta không thể đọc và phân tích code được bình thường nữa.
* Vậy thì **Unpack** chính là làm ngược lại, khôi phục lại mã gốc hoặc trạng thái thực thi ban đầu của một file đã bị đóng gói (`packed`).
### Mục đích
* Trong ngữ cảnh liên quan tới `malware`, việc pack file **PE** giúp che giấu hành vi bên trong bằng cách nén hoặc mã hóa phần nguy hiểm và chỉ giải nén khi chạy. Điều này khiến các công cụ phân tích tĩnh(`static analysis`) như **antivirus** khó phát hiện, vì mã độc thật bị ẩn sau các `section` "rác" vô hại. Vì các phần mềm **antivirus** quét theo hướng tuyến tính, từ **DOS MZ Header**, **PE Header** rồi các `section` trong **section table**. Đồng thời, nó cũng gây khó khăn cho các chuyên gia phân tích mã khi cần reverse engineer hoặc debug file. Đây là kỹ thuật phổ biến để né tránh phát hiện và làm chậm việc phân tích `malware`.
### 1 vài Packer phổ biến
* `UPX`, `ASPack`, ...
### Dấu hiệu
* Để detect được khi nào file bị **pack**, ta có thể dựa vào mấy dấu hiệu sau:
* **Entry point** không nằm trong section `.text`.
* Chứa các section lạ như `.UPX`, `.ASPack`, `.petite`.
* Chạy bị lỗi trên `disassembler`.
* Tồn tại **section** có **entropy** > $7$. (Khi không `pack` thì < $6,7$). Đây khả năng cao là **section** chứa **Packed Data**.
* Hoặc đơn giản hơn thì dùng các công cụ **PEiD**, **Detect It Easy**, **Exeinfo PE**, ...
### Các bước cơ bản của kỹ thuật Unpack
* Điều đầu tiên, trong chương trình luôn có 1 **Entry point(`EP`)** (nằm trong `.text` và trỏ trực tiếp tới mã gốc) gọi là điểm xuất phát của chương trình, code sẽ được thực thi bắt đầu từ điểm này. Trong trường hợp file bị **pack**, thì **EP** này có thể sẽ thay đổi, nó thường không trỏ đến mã gốc của chương trình nữa mà trỏ đến một đoạn mã được **packer** chèn vào — gọi là `stub`. Mục đích của `stub` là giải nén hoặc giải mã phần **Packed Data**, nơi chứa mã gốc đã bị nén/mã hóa. Sau khi quá trình giải nén hoàn tất, luồng thực thi sẽ được chuyển tiếp tới **OEP (Original Entry Point)**, là vị trí ban đầu mà mã gốc thực sự bắt đầu chạy.

:::success
* Túm lại, hiểu 1 cách không lòng vòng thì : Đối với 1 file đã bị `pack`, nói đến **EP** ta sẽ hiểu là **Entry Point** của file sau khi `pack`. Còn **OEP** là **EP** của file gốc trước khi bị `pack`.
:::
* Vậy bước đầu tiên cần làm chính là tìm **OEP**, từ đó ta mới có thể trích xuất được mã gốc và phân tích nó.
#### Bước 1: Tìm OEP
* Dùng mấy debugger như: **x64dbg**, **OllyDbg** để theo dõi quá trình unpack tại runtime, đặt breakpoint tại vùng nghi ngờ.
* Khi thấy code chuyển sang mã gốc (thường recognizable như `push ebp`,`mov ebp, esp`...), thì chú ý, vì đó nhiều khả năng là **OEP**.
* Hoặc dùng **PE-Editor** để tìm **EP** hiện tại, rồi debug xem từ **EP** nó jump vào đâu thì khả năng địa chỉ đó là **OEP**.
* Ngoài ra, còn có **Scylla**, **ImpREC**, hoặc các plugin cho **x64dbg** cũng hỗ trợ mạnh mẽ cho bước đầu tiên này.
#### Bước 2: Fix IAT
* Nếu đặt câu hỏi tại sao sau khi tìm được **OEP**, ta lại phải fix
**IAT(`Import Address Table`)**?
* Theo như mình hiểu, khi một file bị `pack`, bảng **IAT** (chứa địa chỉ các hàm **API Windows** mà chương trình sử dụng, ví dụ **MessageBoxA**, **CreateFileA**,...) thường bị xóa,ẩn hoặc làm rối để tránh bị phân tích tĩnh bởi **IDA**, **Ghidra**. Thay vào đó, packer sẽ:
* Ẩn thông tin import thật.
* Tạm thời load **API** vào vùng nhớ riêng khi `unpack` tại runtime.
* Địa chỉ thật của các hàm chỉ xuất hiện trong quá trình chạy.
* ....
:::success
* Tức là, hiểu 1 cách đơn giản là bước này nhằm xác định các **API** mà chương trình sử dụng, từ đó phân tích được chính xác hành vi **malware**.
:::
* Có thể sử dụng plugin cho **OllyDbg** là **Scylla**, **ImportREC (`ImpREC`)**. Sau đó có thể sử dụng **PE-bear / CFF Explorer** (dùng để kiểm tra lại **IAT** sau khi rebuild).
* Có thể công nhân hơn là đặt breakpoint tại các **API** của **kernel32.dll**, **user32.dll**,... :)))
* Vui vậy thôi chứ nếu tools có vấn đề gì hoặc không detect hết các **API** thì mới làm thế 🤦♂️
#### Bước 3: Dump và hoàn thiện file sau unpack
* Sau khi fix **IAT** xong, ta có thể `dump` ngay vùng nhớ này ra và copy vào 1 file nào đó bằng công cụ như **Scylla** hoặc **x64dbg**.
* Tuy nhiên, đây ta mới chỉ gọi là lấy được file `dump`, chứ chưa phải là file **PE** `unpack` hoàn chỉnh. Vì nó thiếu thông tin quan trọng như **PE** header, size `section`, `offset`, ... Ngoài ra ta `dump` ra các `section` có thể vẫn dư thừa hoặc thiếu dữ liệu.
* Vậy nên chúng ta mới đến bước cuối cùng để gọi là hoàn thiện `unpack` file thành công.
* Vẫn là lựa chọn dùng **Scylla**, kết hợp với **x64dbg**, **OllyDbg**. Công nhân thì dùng **CFF Explorer** để `mukbang` bằng tay cũng được 🤣
#### Tổng hợp
* Trên đây là mình đưa ra các bước cơ bản nhất, có thể nói là không thế thiếu trong quá trình `unpack` 1 file **PE** bị `pack`.
* Ngoài ra, thực tế kể cả trong ngữ cảnh **malware** hay **CTF**, thì có thể sẽ khác, thậm chí bypass **anti-debug**, **anti-VM**,..., nhưng tựa chung vẫn sẽ theo trình tự này.
## Tổng kết
Vậy ở wu này mình đã trình bày **PE** file là gì, cấu trúc của 1 file **PE**. Sau đó giới thiệu thêm cơ bản về kỹ thuật `pack` và `unpack` **PE** file. 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://learn.microsoft.com/en-us/windows/win32/debug/pe-format
https://kienmanowar.wordpress.com/2014/01/16/tim-hieu-pe-file-qua-cac-vi-du-co-ban/
https://en.wikipedia.org/wiki/Portable_Executable
https://0xrick.github.io/win-internals/pe3/
https://hub.whitehub.net/tim-hieu-ve-cau-truc-pe-file/
https://hackmd.io/@3ud4jm0nj4/ByDRNMkHn
https://hackmd.io/@0r3o/BJrxqpvc6
https://hackmd.io/@Wh04m1/r1Pzr-M96
https://hackmd.io/@3ud4jm0nj4/BkOr12W0o
https://hackmd.io/@antoinenguyen09/Hy0a2mb0t
https://hackmd.io/@3ud4jm0nj4/BkOr12W0o