Mã độc từ lâu đã được trang bị các kĩ thuật lẩn tránh nhằm mục đích qua mặt các giải pháp bảo mật. Một trong những kĩ thuật phổ biến nhất hiện nay mà các loại mã độc thường sử dụng đó là giải nén module/payload cuối của chúng vào bộ nhớ, sau đó thực thi module/payload này.
Có nhiều cách thức khác nhau để lấy được module/payload, nội dung dưới đây tập trung vào việc sử dụng công cụ IDA để dump payload từ bộ nhớ.
1. Cơ bản về PE file
Bài viết sẽ không trình bày chi tiết về cấu trúc của PE file bởi đã có rất nhiều tài liệu viết về nó. Các tài liệu đều cung cấp các thông tin giải thích chi tiết về cấu trúc cũng như ý nghĩa của từng trường/giá trị trong PE file. Hình dưới đây minh họa tổng quan nhất về cấu trúc thông thường của một PE file:
![]() |
Hình 1: Cấu trúc cơ bản của một PE file Nguồn: https://resources.infosecinstitute.com/2-malware-researchers-handbook-demystifying-pe-file |
Như vậy, về cơ bản thì PE file sẽ gồm một loạt các khối bộ nhớ đặt liền kề nhau. Trong đó, PE header chứa nhiều thông tin quan trọng như: địa chỉ entry point của file, thời gian biên dịch, số lượng section, địa chỉ các sections, thông tin về kích thước, bảng IAT v.v…
Để phục vụ cho mục tiêu của bài viết, cần quan tâm đến thông tin các sections, vì khi tính tổng địa chỉ của một section với kích thước của nó, sẽ nhận được địa chỉ của section tiếp theo. Và nếu lấy tổng của địa chỉ và kích thước của section cuối cùng, sẽ có được kích thước của file.
![]() |
Hình 2: Thông tin về các sections của PE file |
2. Phân tích file
Giả sử trong quá trình phân tích malware, gặp đoạn code thực hiện giải nén một PE file mới vào vùng nhớ như sau:
![]() |
Hình 3: Đoạn mã thực hiện giải nén một PE file mới vào bộ nhớ |
Sau khi thực hiện đoạn code trên, kết quả có được một PE file mới trong bộ nhớ:
![]() |
Hình 4: PE file mới sau khi được giải nén
|
Với thông tin trên, ta biết được địa chỉ bắt đầu của PE file trong bộ nhớ (ví dụ trong hình là 0x448490). Nhiệm vụ tiếp theo là cần phải phân tích các cấu trúc của file mới để tính ra kích thước. Công việc này được thực hiện một cách nhanh chóng nhờ vào sự hỗ trợ của IDA. Để phân tích, cần phải nạp các struct cần thiết liên quan tới PE file, bao gồm IMAGE_DOS_HEADER, IMAGE_NT_HEADERS và IMAGE_SECTION_HEADER.
Tại IDA, Nhấn Shift + F9 để chuyển tới tab Structures, sau đó nhấn Insert để thực hiện thêm các struct mới. Tại màn hình Create structure/union chọn Add standard structure để lựa chọn các cấu trúc chuẩn mà IDA đã định nghĩa trước và thêm các struct đã đề cập ở trên:
![]() |
Hình 5: Tạo một struct mới trong IDA |
![]() |
Hình 6: Bổ sung các struct cần thiết của PE file |
Như đã giải thích ở phần đầu, khi lấy địa chỉ của section cuối cùng cộng với kích thước của nó sẽ có được kích thước của file. Theo cách tổ chức của PE file thì các sections được bố trí nằm sau PE header. PE header được tìm thấy bằng cách cộng địa chỉ của MZ header (địa chỉ bắt đầu của PE file) với giá trị của trường e_lfanew.
Chuyển tới địa chỉ bắt đầu của file trong bộ nhớ (theo minh họa trong bài viết là 0x448490), đặt con trỏ tại MZ (DOS) header, nhấn Alt + Q, chọn IMAGE_DOS_HEADER. Kết quả sẽ tương tự như hình dưới đây:
![]() |
Hình 7: Kết quả sau khi áp IMAGE_DOS_HEADER |
PE header luôn bắt đầu tại MZ + e_lfanew, do vậy lấy địa chỉ bắt đầu (0x00448490) cộng với e_lfanew (trong ví dụ này là 0xD0) ra được địa chỉ của PE header tại 0x448560. Chuyển tới địa chỉ này, áp cấu trúc IMAGE_NT_HEADERS cho nó. Kết quả có được như sau:
![]() |
Hình 8: Kết quả sau khi áp IMAGE_NT_HEADERS |
Lưu ý về số lượng các sections như đã khoanh trên hình, cuộn xuống cuối cấu trúc sẽ thấy một loạt các bytes liền kề nhau, tất cả đều thuộc IMAGE_SECTION_HEADER. Trước tiên, đặt chuột tại byte đầu tiên và áp IMAGE_SECTION_HEADER cho nó. Căn cứ vào FileHeader.NumberOfSections, trong trường hợp này là 4, tiếp theo nhấn * để thực hiện tạo một mảng. Tại Array Size nhập thông tin là 4:
![]() |
Hình 9: Kết quả sau khi áp IMAGE_SECTION_HEADER |
![]() |
Hình 10: Thông tin đầy đủ các sections của PE File |
Tìm tới section cuối cùng, lấy giá trị tại các trường PointerToRawData và SizeOfRawData cộng với nhau sẽ có được kích thước của file cần dump:
![]() |
Hình 11: Tính toán kích thước của PE File |
3. Dump file ra disk
Để thực hiện dump file từ bộ nhớ ra disk, sử dụng câu lệnh IDAPython như sau:
open("payload.bin", "wb").write(get_bytes(mz_addr, size, 1))Trong đó:
· mz_addr: địa chỉ bắt đầu của MZ header (trong ví dụ này: 0x00448490)
· size: kích thước tính toán được ở trên (trong ví dụ này: 0x6000)
![]() |
Hình 12: Thực hiện dump file sau khi có đủ thông tin |
Cuối cùng, kiểm tra lại file đã dump bằng một công cụ PE bất kì:
![]() |
Hình 13: Kiểm tra lại file đã dump |
Tran Trung Kien (aka m4n0w4r)
R&D Center - VinCSS (a member of Vingroup)
No comments:
Post a Comment