Một vài ví dụ của định dạng PE là .exe.dll.sys và .scr.

Note

Các file có thể thực thi chẳng hạn như .exe hoặc .dll có thể được gọi là các image.

PE Structure

Cấu trúc tổng quan của định dạng PE:

Có thể thấy, định dạng PE bao gồm các header, các data directory và các section.

DOS Header (IMAGE_DOS_HEADER)

Header đầu tiên của file PE luôn bắt đầu bằng hai byte là 0x4D0x5A (ở dạng ASCII là MZ). Hai byte này chính là signature của DOS header.

Cấu trúc của DOS header được định nghĩa như sau:

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;                    // Offset to the NT header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

Hai thành phần quan trọng là:

  • e_magic: signature bytes
  • e_lfanew: offset đến NT header.

DOS Stub

Là một đoạn chương trình in ra thông điệp “This program cannot be run in DOS mode” trong trường hợp chương trình được nạp ở chế độ DOS (hay “Disk-based Operating System”). Thông điệp này có thể thay đổi bởi lập trình viên trong quá trình biên dịch. DOS stub không phải là một header.

NT Header (IMAGE_NT_HEADERS)

Là một header quan trọng vì nó là sự kết hợp của hai loại header khác: FileHeaderOptionalHeader cũng như là chứa một lượng lớn thông tin về file PE. Tương tự với DOS header, NT header cũng có signature bytes là chuỗi “PE” (0x500x45). Tuy nhiên, do signature có kiểu là DWORD nên nó được đệm thêm bằng 2 byte rỗng: 0x50450000.

NT header có thể được truy cập thông qua thành phần e_lfanew của DOS header.

Cấu trúc của NT header thay đổi tùy thuộc vào từng loại kiến trúc máy tính:

Phiên bản 32-bit:

typedef struct _IMAGE_NT_HEADERS {
  DWORD                   Signature;
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

Phiên bản 64-bit:

typedef struct _IMAGE_NT_HEADERS64 {
    DWORD                   Signature;
    IMAGE_FILE_HEADER       FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

Có thể thấy, hai phiên bản này khác nhau ở OptionalHeader.

File Header (IMAGE_FILE_HEADER)

Có thể được truy cập thông qua thành phần FileHeader của NT header và có cấu trúc như sau:

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;

Những thành phần quan trọng là:

  • NumberOfSections: số lượng các section ở trong file PE file.
  • Characteristics: chứa một số thuộc tính về file chẳng hạn như thuộc tính cho biết file là DLL hay console application.
  • SizeOfOptionalHeader: kích thước của optional header tiếp theo.
  • TimeDateStamp: thời gian và ngày tháng mà file PE được tạo ra.
  • PointerToSymbolTable: offset đến symbol table (nếu có).
  • NumberOfSymbols: số lượng các symbol có trong symbol table.

Seealso

Optional Header (IMAGE_OPTIONAL_HEADER)

Là thành phần quan trọng cho việc thực thi của file PE mặc dù nó có tên là “optional”. Nó được gọi là “optional” vì một số tệp định dạng PE không chứa header này.

Tương tự với file header, optional header cũng có hai phiên bản dành cho kiến trúc 32-bit và kiến trúc 64-bit. Cả hai đều có các thành phần tương tự nhau và sự khác biệt lớn nhất là kích thước của các thành phần này. Ngoài ra, phiên bản 32-bit có một số thành phần mà phiên bản 64-bit không có.

Một số thành phần quan trọng của IMAGE_OPTIONAL_HEADERIMAGE_OPTIONAL_HEADER64:

  • Magic: mô tả trạng thái của image file (32 hoặc 64-bit).
  • MajorLinkerVersionMinorLinkerVersion: phiên bản của linker được sử dụng để tạo ra file PE.
  • MajorOperatingSystemVersion: phiên bản hệ điều hành chính (chẳng hạn như 10, 11) tối thiểu cần thiết để chạy file PE.
  • MinorOperatingSystemVersion: phiên bản hệ điều hành phụ (chẳng hạn như 1511, 1507, 1607) tối thiểu cần thiết để chạy file PE.
  • MajorImageVersionMinorImageVersion: phiên bản của file PE.
  • SizeOfCode, SizeOfInitializedDataSizeOfUninitializedData: kích thước của .text section, các vùng dữ liệu đã được khởi tạo và các vùng dữ liệu chưa được khởi tạo.
  • AddressOfEntryPoint: offset đến entry point của file (thường là hàm main)
  • BaseOfCodeBaseOfData: offset đến phần .text và phần .data.
  • ImageBase: địa chỉ mà ứng dụng sẽ được nạp vào bộ nhớ. Tuy nhiên, do cơ chế ASLR (Address Space Layout Randomization) của Windows, địa chỉ này thường thay đổi mỗi lần chạy, vì Windows PE loader ánh xạ file tới một địa chỉ khác
  • SizeOfImage: kích thước của toàn bộ image
  • DataDirectory: một trong những thành phần quan trọng nhất của optional header. Nó là một mảng các IMAGE_DATA_DIRECTORY.

Cấu trúc của IMAGE_DATA_DIRECTORY:

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

Kích thước của DataDirectory là kích thước của IMAGE_NUMBEROF_DIRECTORY_ENTRIES với giá trị cố định là 16. Từng phần tử của trong mảng chứa thông tin chi tiết về một data directory.

Một data directory có thể được truy cập thông qua các chỉ số sau:

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

Hai data directory quan trọng là:

  • Export Directory: chứa địa chỉ của các hàm và biến được export từ file thực thi. Các file thực thi khác có thể sử dụng các địa chỉ này để truy cập đến các hàm và các biến. Data directory này thường được tìm thấy ở trong các DLL. Ví dụ: kernel32.dll export CreateFileA.
  • Import Address Table: chứa địa chỉ của các hàm và biến được import vào file thực thi. Chúng được sử dụng để truy cập vào các hàm và các biến của các file thực thi khác. Ví dụ: Application.exe import CreateFileA từ kernel32.dll.

PE Sections

Các section trong PE file chứa các thành phần như code, dữ liệu, hoặc resource (ví dụ: icon hoặc bitmap) tạo nên chương trình. Mỗi section có một tên duy nhất, và số lượng section không cố định vì chúng có thể được thêm, xóa, hoặc hợp nhất bởi các compiler tùy thuộc vào các thiết lập khác nhau. Ngoài ra, một số section có thể được thêm thủ công hoặc linh hoạt sau quá trình biên dịch.

Một số section phổ biến:

  • .text: Chứa mã máy (machine code) của chương trình.
  • .data: Lưu trữ dữ liệu đã khởi tạo, bao gồm các biến được khởi tạo trong code.
  • .rdata: Chứa dữ liệu chỉ đọc (read-only data), chẳng hạn các biến được khai báo với từ khóa const.
  • .idata: Chứa tham chiếu đến các hàm và biến được import từ các DLL.
  • .reloc: Chứa thông tin về relocation, giúp sửa chữa địa chỉ bộ nhớ để chương trình có thể nạp mà không gặp lỗi.
  • .rsrc: Lưu trữ resource, chẳng hạn như icon, bitmap, và các tài nguyên khác.

Mỗi PE section đều có một IMAGE_SECTION_HEADER nằm bên dưới các NT header chứa các thông tin liên quan đến section đó.

Cấu trúc của IMAGE_SECTION_HEADER:

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ột số thành phần quan trọng:

  • Name: tên của section (chẳng hạn như .text, .data, .rdata).
  • PhysicalAddress hoặc VirtualSize (Union Data Type): kích thước của section trong bộ nhớ.
  • VirtualAddress: offset của section trong bộ nhớ.
  • SizeOfRawData: kích thước của section trong file PE (bytes).
  • PointerToRelocations: offset đến các relocation của section.
  • NumberOfRelocations: số lượng các relocation của section.
  • Characteristics: chứa các flag giúp mô tả các thuộc tính của section.

Seealso

Tham khảo thêm 0xRick’s Blog:

list
from outgoing([[MalDev - Portable Executable Format]])
sort file.ctime asc

Resources