PE là định dạng riêng của Win32. Hầu hết các file thực thi trên Win32 đều thuộc dạng PE (Trừ các tập tin VxDs và các file DLL 16 bít). Chúng ta hãy cùng tìm hiểu về cấu trúc PE file.

Tại sao cần hiểu cấu trúc PE file?

Để có thể thực thi trên máy tính, nội dung file PE được chia thành các thành phần và có mối liên hệ mật thiết với nhau. Nắm rõ cấu trúc PE sẽ giúp chúng ta hiểu được cơ chế thực thi của một chương trình, từ việc tổ chức tới việc load lên bộ nhớ, các tài nguyên sử dụng, …. Hơn nữa, khi chúng ta muốn sửa đổi một file, ví dụ như thêm vào một số đoạn mã, chỉnh sửa một số thành phần nhưng vẫn muốn chương trình thực thi bình thường. Do đó, cần phải nắm rõ cấu trúc PE file, mối liên hệ giữa các thành phần trong file để có thể nhanh chóng thay đổi file và thoả mãn yêu cầu đề ra.

Cấu trúc cơ bản của PE file

Từ hình vẽ trên, chúng ta thấy cấu trúc PE có thể gồm nhiều section, trong đó tối thiểu cần 2 section: data và code. Một số section thông dụng hay được gặp ở các chương trình:

  1. Executable Code Section, có tên là .text (Micro$oft) hoc là CODE (Borland).
  2. Data Sections, có nhng tên nh .data, .rdata hoc .bss (Micro$oft) hay DATA (Borland)
  3. Resources Section, có tên là .rsrc
  4. Export Data Section, có tên là .edata
  5. Import Data Section. có tên là .idata
  6. Debug Information Section, có tên là .debug

Cấu trúc các section trên bộ nhớ và trên ổ đĩa là như nhau, tuy nhiên khi được nạp lên bộ nhớ, các Windows loader sẽ quyết định thứ tự và vị trí nạp các phần, do đó vị trí các phần trên ổ đĩa và trên bộ nhớ sẽ có sự khác biệt. Tiếp theo chúng ta sẽ đi chi tiết hơn về các phần cụ thể trong PE: DOS MZ header, DOS stub, PE header, Section table

1. DOS MZ header

Tất cả các file PE bắt đầu bằng DOS Header, vùng này chiếm 64 bytes đầu tiên của files. 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. DOS Header là một cấu trúc được định nghĩa trong file windows.inc hoặc winnt.h. Cấu trúc này gồm 19 thành phần.

Cơ bản về cấu trúc PE file - dos_header

Trong đó chúng ta cần quan tâm tới hai trường:

  • 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.
Cơ bản về cấu trúc PE file - dos_header - mz_pe

2. 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

Cơ bản về cấu trúc PE file - pe_header
  • Signature: là 1 DWORD bắt đầu PE Header chứa chữ ký PE: 50h, 45h, 00h, 00h
  • FileHeader: 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. Trong trường này chúng ta cần chú ý tới trường 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.
Cơ bản về cấu trúc PE file - image_file_header
  • OptionalHeader: bao gồm 224 bytes tiếp theo sau FileHeader. Cấu trúc này đượ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.
Cơ bản về cấu trúc PE file - optional_header32
    • AddressOfEntryPoint (RVA): địa chỉ ảo tương đối của câu lệnh đầu tiên sẽ được thực thi. 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.
    • ImageBase: Địa chỉ được ưu tiên nạp cho PE file.
    • SectionAlignment: Phần liên kết 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.
    • FileAlignment: Phần liên kết 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 PE file trong bộ nhớ, là tổng của tất cả các headers và sections được liên kết tới SectionAlignment.
    • SizeOfHeaders: Kích thước của tất cả headers và section table, bằng kích thước file trừ đi tổng kích thước của các section trong file.
    • DataDirectory: là một mảng gồm 16 cấu trúc IMAGE_DATA_DIRECTORY, mỗi cấu trúc liên quan tới 1 cấu trúc dữ liệu trong PE file

Hình dưới là 16 cấu trúc IMAGE_DATA_DIRECTORY có thể xem được thông qua LordPE.

Cơ bản về cấu trúc PE file - lordpe

3. 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. Cấu trúc này được định nghĩa trong file windows.inc như hình dưới đây:

Cơ bản về cấu trúc PE file - section_table

Thông tin về một số trường quan trọng:

  • 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
  • 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 …
Chia sẻ bài viết này