Ở các bài trước Tôi đã giới thiệu đến các bạn một loạt các bài viết liên quan đến mã độc như hiểm họa của mã độc đến an ninh mạng đồng thời cũng giới thiệu cho các bạn về các phương pháp phân tích mã độc. Ở chủ đề lần này xin tiếp tục gửi đến các bạn chủ đề:”Cấu trúc PE & COFF File và ý nghĩa trong việc phân tích mã độc”.
Sau đây tôi sẽ trình bày cho các bạn những thông tin cần lưu ý khi làm việc với PE file và đặc biệt là ý nghĩa khi sử dụng cấu trúc PE file trong quá trình phân tích mã độc. Bài viết này tôi sẽ chia làm 03 nội dung chính:
- Tầm quan trọng của PE & COFF file
- Cấu trúc PE & COFF file
- Ý nghĩa trong quá trình phân tích mã độc
Trong đó, tôi sẽ cố gắng kết hợp phân tích cấu trúc PE và COFF file trên cả hai nền tảng 64 bit và 32 bit. Qua đó, các bạn sẽ thấy được sự khác biệt khi làm việc trên 2 nền tảng này.
Tầm quan trọng của PE và COFF file
Như chúng ta đã biết, virus nói chung và mã độc nói riêng chủ yếu tập trung trên hệ điều hành Windows. Cấu trúc các file thực thi hoặc các file đối tượng (COM) trên windows được xây dựng trên cấu trúc PE và COFF file.
Định dạng này được hệ điều hành sử dụng để khởi tạo môi trường, cung cấp các thư viện cần thiết và tiến hành thực thi chương trình.
Định dạng này được các chương trình nén, đóng gói (pack/unpack, archive/unarchiv, compress) dữ liệu hoặc đóng gói mã thực thi.
Do đó, hiểu được cấu trúc và cách vận hành sẽ giúp quá trình phân tích mã độc nhanh và hiệu quả hơn
Cấu trúc PE file
Hai hình vẽ trên phần nào giúp các bạn hình dung được phần nào hai loại cấu trúc file PE và COFF. Sự khác biệt nằm ở phần trên Section Headers. Trong khi PE file gồm nhiều thành phần khác nhau thì COFF file chỉ gồm MS COFF Header.
Tôi sử dụng PEInsider để xem nội dung file calc.exe. Bạn cũng có thể sử dụng PEInsider để xem cấu trúc của các file .DLL, file COM…
Như trong hình trên các bạn có thể thấy trình tự từ trên xuống dưới có Dos Header, Rich Signature, Nt Headers (PE Header), Section Headers và một loại các thư mục liên quan khác.
Rich Signature là phần không nằm trong tài liệu chính thống. Phần này được sinh ra bởi trình biên dịch của Microsoft và chỉ có trong các chương trình được build bằng trình biên.
dịch của Microsoft, nằm giữa Dos Stub và PE Header chứa thông tin liên quan tới trình biên dịch mà không phải thông tin quan trọng, chúng ta có thể bỏ qua trường này.
Dos Header là phần bắt đầu của PE file. Phần này gồm 64 bytes, các thành phần của Dos Header như sau:
struct IMAGE_DOS_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 DWORD e_lfanew; // File address of new exe header }; |
Trong phần này, thông thường chúng ta không quan tâm quá nhiều. Chúng ta thường để ý tới phần e_magic và e_lfanew.
- e_magic: giá trị text “MZ”, thường gọi là chữ kí của file PE. Đây là viết tắt tên một trong những người quan trọng tạo ra hệ điều hành MS-DOS.
- e_lfanew: nằm ở cuối cùng của Dos Header, đây là địa chỉ tương đối của PE header so với đầu file.
Ngoài ra, còn một số trường khác liên quan tới giá trị khởi tạo khi file được load lên bộ nhớ: e_ss, e_sp, e_ip, e_cs.
Tiếp theo là phần File Header, File Header gồm 03 phần chính: Microsoft MS-DOS stub, PE signature, COFF file header và optional header.
MS-DOS Stub là ứng dụng MS-DOS, thực thi và in ra dòng chữ “This program cannot be run in DOS mode”.
Signature nằm ở offset 0x3C sau MS-DOS stub, đây là 4 bytes chữ kí xác định định dạng một file PE, giá trị: “PE\0\0”.
COFF File Header gồm 20 bytes với thứ tự và các trường như sau:
Offset | Kích thước | Tên trường | Mô tả |
0 | 2 | Machine | Giá trị xác định loại máy mà file được build. |
2 | 2 | NumberOfSections | Số lượng section, số này sẽ chỉ ra kích thước của bảng section. |
4 | 4 | TimeDateStamp | 32 bit thấp của số giây tính từ 00:00 1/1/1970 theo thời gian tạo ra file. |
8 | 4 | PointerToSymbolTable | Vị trí offset của bảng COFF symbol hoặc bằng 0 nếu ko có bảng này. |
12 | 4 | NumberOfSymbols | Số lượng đầu vào của bảng symbol. |
16 | 2 | SizeOfOptionalHeader | Kích thước của Optional header, trường này quan trọng đối với file thực thi. |
18 | 2 | Characteristics | Giá trị là cờ xác định thuộc tính của file. |
Optional Header: là phần tiếp sau COFF File Header, phần này gồm 224 bytes (PE32) và 240 bytes (PE32+). 8 trường đầu tiên trong Optional Header là các trường cố định. Các trường sau thay đổi tùy thuộc vào loại file.
Vị trí | Kích thước | Tên trường | Mô tả |
0 | 2 | Magic | Số nguyên không dấu xác định trạng thái của file.
|
2 | 1 | MajorLinkerVersion | Số phiên bản chính của linker. |
3 | 1 | MinorLinkerVersion | Số phiên bản phụ của linker. |
4 | 4 | SizeOfCode | Kích thước của section .text hoặc tổng kích thước của các section .text. |
8 | 4 | SizeOfInitializedData | Kích thước của section khởi tạo hoặc tổng kích thước. |
12 | 4 | SizeOfUninitializedData | Kích thước của phần dữ liệu chưa khởi tạo (BSS), hoặc tổng của tất cả các phần nếu có nhiều phần BSS. |
16 | 4 | AddressOfEntryPoint | Địa chỉ của điểm bắt đầu chương trình, chính là điểm mà chương trình bắt đầu được thực thi. |
20 | 4 | BaseOfCode | Địa chỉ liên quan tới image base của điểm bắt đầu của code section khi nó được nạp lên bộ nhớ. |
File PE32 sẽ có thêm một phần không có trong PE32+ là BaseOfData, phần này có kích thước 4 bytes, theo ngay sau BaseOfCode.
Tiếp theo là 21 trường thuộc phần mở rộng của COFF Optional Header. Các trường này chứa các thông tin cần thiết phục vụ linker và loader trên Windows. Danh sách các trường: ImageBase, SectionAlignment, FileAlignment, MajorOperatingSystemVersion, MinorOperatingSystemVersion, MajorImageVersion, MinorImageVersion, MajorSubsystemVersion, MinorSubsystemVersion, Win32VersionValue, SizeOfImage, SizeOfHeaders, CheckSum, Subsystem, DllCharacteristics, SizeOfStackReserve,
SizeOfStackCommit, SizeOfHeapReserve, SizeOfHeapCommit, LoaderFlags, NumberOfRvaAndSizes.
Vị trí (PE32/ PE32+) | Kích thước (PE32/ PE32+) | Tên trường | Mô tả |
28/24 | 4/8 | ImageBase | Địa chỉ của byte đầu tiên của image khi được load lên bộ nhớ. Giá trị này phải chia hết cho 64K. Với mỗi hệ điều hành sẽ có giá trị mặc định khác nhau. |
32/32 | 4 | 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. |
36/36 | 4 | 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. |
40/40 | 2 | MajorOperatingSystemVersion | Số phiên bản chính của hệ điều hành yêu cầu. |
42/42 | 2 | MinorOperatingSystemVersion | Số phiên bản phụ của hệ điều hành yêu cầu. |
44/44 | 2 | MajorImageVersion | Số hiệu phiên bản chính của image. |
46/46 | 2 | MinorImageVersion | Số hiệu phiên bản phụ của image. |
48/48 | 2 | MajorSubsystemVersion | Số phiên bản chính của hệ thống subsystem. |
50/50 | 2 | MinorSubsystemVersion | Số phiên bản phụ của hệ thống subsystem. |
52/52 | 4 | Win32VersionValue | Phần dành riêng. |
56/56 | 4 | SizeOfImage | Kích thước theo bytes của image, bao gồm tất cả headers khi image được load lên bộ nhớ. Giá trị này phải là bội số của SectionAlignment. |
60/60 | 4 | SizeOfHeaders | Kích thước của MS-DOS stub, PE header và section header làm tròn ra số chia hết cho FileAlignment. |
64/64 | 4 | CheckSum | Checksum của file. |
68/68 | 2 | Subsystem | Sybsystem được yêu cầu để thực thi file. |
70/70 | 2 | DllCharacteristics | |
72/72 | 4/8 | SizeOfStackReserve | Kích thước của stack được dự trữ. |
76/80 | 4/8 | SizeOfStackCommit | Kích thước của stack dùng để cam kết. |
80/88 | 4/8 | SizeOfHeapReserve | Kích thước khai báo không gian bộ nhớ heap. |
84/96 | 4/8 | SizeOfHeapCommit | Kích thước không gian bộ nhớ heap dùng để cam kết. |
88/104 | 4 | LoaderFlags | Phần dành riêng. |
92/108 | 4 | NumberOfRvaAndSizes | Số lượng đầu vào của data-directory. |
Cuối cùng là phần 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.
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; |
Chi tiết các bạn có thể tham khảo thêm tại: PE Format
Phần tiếp theo là bảng Section thuộc Section Headers. Số lượng đầu vào của bảng chính bằng giá trị của trường NumberOfSections trong File Header. Mỗi section Header gồm 40 bytes với các trường:
Vị trí | Kích thước | Tên trường | Mô tả |
0 | 8 | Name | 8 byte tên section |
8 | 4 | VirtualSize | Kích thước khi được load lên bộ nhớ. |
12 | 4 | VirtualAddress | Địa chỉ byte đầu tiên của section tương ứng với Image base khi section được load lên bộ nhớ (File thực thi). |
16 | 4 | SizeOfRawData | Kích thước trên ổ đĩa. |
20 | 4 | PointerToRawData | Con trỏ tới trang đầu tiên của section với COFF file. |
24 | 4 | PointerToRelocations | Con trỏ file tới vị trí cấp phát mới cho các section. |
28 | 4 | PointerToLinenumbers | Con trỏ bắt đầu nhập số dòng cho section. |
32 | 2 | NumberOfRelocations | Số lượng section cần thực hiện cấp phát lại. |
34 | 2 | NumberOfLinenumbers | Số lượng đầu vào line-number cho section. |
36 | 4 | Characteristics | Giá trị cờ xác định đặc tính của section. |
Ngoài ra, còn có các phần khác: Section Data, COFF Relocations… Các bạn có thể tìm hiểu thêm tại mục: Other Contents of the file
Chúng ta có thể thấy có rất nhiều trường trong cấu trúc phức tạp của định dạng PE. Như vậy, khi phân tích mã độc thì đâu là các trường chúng ta cần quan tâm?
Sau đây, tôi xin tổng hợp lại một số trường mà chúng ta cần lưu ý trong quá trình phân tích file PE nói chung cũng như phân tích mã độc nói riêng.
- Trong phần DOS Header, chúng ta cần lưu ý tới 02 trường: e_magic và e_lfanew.
- Trong phần COFF File Header, chúng ta cần lưu ý tới trường: NumberOfSections.
- Trong phần Optional Header, chúng ta cần quan tâm tới một số trường: AddressOfEntryPoint (RVA), ImageBase, SectionAlignment, FileAlignment, SizeOfImage, SizeOfHeaders và DataDirectory.
- Cuối cùng, trong bảng section, một số trường cần quan tâm: VirtualSize, VirtualAddress, SizeOfRawData, PointerToRawData, Characteristics.
Ngoài ra, chúng ta cũng cần quan tâm tới vị trí tương đối của các trường và kích thước tương ứng của chúng trên PE file.
Vậy tại sao các thành phần trên có ý nghĩa quan trọng đối với việc phân tích file PE và đặc biệt là phân tích mã độc?
- Trước tiên, nắm bắt được cấu trúc PE file chúng ta sẽ biết được cách thức hệ điều hành quản lí file, quản lí chương trình, nạp chương trình lên bộ nhớ, thực thi chương trình, vị trí bắt đầu thực thi.
- Hiểu cơ chế thực thi của hệ điều hành từ đó chúng ta có thể thay đổi, chỉnh sửa file PE để được thực thi các phần như ý muốn.
- Nắm bắt được cấu trúc, vị trí tương đối và kích thước các phần sẽ giúp quá trình phân tích mã độc trở lên nhanh chóng hơn, chúng ta sẽ dễ dàng phát hiện cách thức mã độc thực hiện tác động lên một file. Đặc biệt là đối với virus (Mã độc có chức năng lây nhiễm).
Như vậy, trong bài này tôi đã giới thiệu sơ bộ cho các bạn các thành phần quan trọng của cấu trúc PE file. Tôi cũng đã khái quát một số thành phần quan trọng mà chúng ta cần hết sức lưu ý trong quá trình phân tích file PE nói chung và phân tích mã độc nói riêng.
Do không có thời gian đi sâu hơn nên tôi hy vọng các kiến thức cơ bản này sẽ giúp các bạn sẽ tiếp tục nghiên cứu sâu hơn nữa về PE Format để phục vụ cho loạt bài sắp tới khi chúng ta đi sâu hơn vào quá trình phân tích mã độc.
XEM THÊM: Một số công cụ kiểm tra mã độc website được chuyên gia tin dùng.
Theo chuyên gia an ninh mạng: Bùi Đình Cường