Chương 21: Mã độc 64-bit (64-Bit Malware)
1. Tại sao cần viết mã độc 64-bit?
Mã độc 32-bit có thể chạy trên cả hệ điều hành 32-bit và 64-bit. Tuy nhiên, không thể chạy mã 32-bit bên trong tiến trình 64-bit. Khi processor đang ở chế độ 32-bit, nó không thể thực thi mã 64-bit, và ngược lại.
Nguyên tắc cốt lõi: Nếu mã độc cần chạy bên trong không gian tiến trình của một ứng dụng 64-bit, nó bắt buộc phải là 64-bit.
Các trường hợp bắt buộc phải dùng 64-bit
2. Sự khác biệt kiến trúc x64 so với x86
2.1. Thanh ghi (Registers)
| Đặc điểm | x86 (32-bit) | x64 (64-bit) |
|---|---|---|
| Thanh ghi đa năng | EAX, EBX, ECX… (32-bit) | RAX, RBX, RCX… (64-bit) |
| Số lượng thanh ghi | 8 | 16 (thêm R8–R15) |
| Instruction pointer | EIP | RIP |
| Truy cập thanh ghi mới theo kích thước | — | R8D (32-bit), R8W (16-bit), R8L (8-bit) |
| Truy cập byte thấp nhất của RSP/RBP/RDI/RSI | Không có | SPL, BPL, DIL, SIL |
Tóm tắt: RAX là phiên bản 64-bit của EAX. Vẫn có thể truy cập EAX (32-bit thấp) trong môi trường 64-bit.
2.2. RIP-Relative Addressing (Địa chỉ tương đối so với Instruction Pointer)
Đây là khác biệt quan trọng nhất giữa x64 và x86 liên quan đến shellcode và PIC (Position-Independent Code).
Trong x86 (absolute addressing):
; Địa chỉ tuyệt đối được mã hóa cứng vào lệnh
00401004 A1 74 33 40 00 mov eax, dword_403374
; Bytes 74 33 40 00 = địa chỉ 0x00403374
; Nếu file load ở địa chỉ khác → lệnh này SAI, phải sửa lạiTrong x64 (RIP-relative addressing):
; Địa chỉ được lưu dưới dạng OFFSET so với RIP hiện tại
0000000140001058 8B 05 A2 D3 00 00 mov eax, dword_14000E400
; Bytes A2 D3 00 00 = offset 0x0000D3A2 tính từ RIP
; File load ở bất kỳ đâu → lệnh vẫn trỏ đúng → Position-Independent3. Calling Convention và Stack Usage trong x64
3.1. Quy ước truyền tham số
x64 Windows dùng convention gần giống fastcall của 32-bit:
| Tham số | Thanh ghi |
|---|---|
| Tham số 1 | RCX |
| Tham số 2 | RDX |
| Tham số 3 | R8 |
| Tham số 4 | R9 |
| Tham số 5+ | Stack |
3.2. Sự khác biệt về Stack
- 32-bit: Stack có thể tăng/giảm tự do bằng
push/popở giữa hàm. - 64-bit: Stack chỉ được thay đổi ở đầu và cuối hàm. Không có
push/popở giữa.
Quy tắc này không được processor bắt buộc, nhưng model xử lý exception của Microsoft 64-bit phụ thuộc vào nó. Vi phạm có thể gây crash khi có exception.
3.3. Phân biệt Leaf và Nonleaf Function
| Loại | Định nghĩa | Đặc điểm |
|---|---|---|
| Leaf function | Không gọi hàm nào khác | Stack đơn giản hơn |
| Nonleaf function | Có gọi ít nhất một hàm khác | Phải cấp phát thêm 0x20 bytes shadow space |
Shadow space (0x20 bytes): Cho phép hàm được gọi lưu lại 4 thanh ghi tham số (RCX, RDX, R8, R9) nếu cần.
3.4. So sánh disassembly: printf call
32-bit — rõ ràng, dễ đếm tham số:
004113C0 mov eax, [ebp+arg_0]
004113C3 push eax ; tham số 4
004113C4 mov ecx, [ebp+arg_C]
004113C7 push ecx ; tham số 3
004113C8 mov edx, [ebp+arg_8]
004113CB push edx ; tham số 2
004113CC mov eax, [ebp+arg_4]
004113CF push eax ; tham số 1
004113D0 push offset aDDDD_ ; format string
004113D5 call printf
004113DB add esp, 14h ; 0x14 = 5 tham số × 4 bytes → rõ ràng!64-bit — khó đếm tham số hơn:
0000000140002C96 mov ecx, [rsp+38h+arg_0]
0000000140002C9A mov eax, [rsp+38h+arg_0]
0000000140002C9E mov [rsp+38h+var_18], eax ; local var hay tham số? Khó biết!
0000000140002CA2 mov r9d, [rsp+38h+arg_18] ; tham số 4
0000000140002CA7 mov r8d, [rsp+38h+arg_10] ; tham số 3
0000000140002CAC mov edx, [rsp+38h+arg_8] ; tham số 2
0000000140002CB0 lea rcx, aDDDD_ ; tham số 1 (format string)
0000000140002CB7 call cs:printf4. Prologue và Epilogue trong 64-bit
Mã 64-bit có cấu trúc prologue (đầu hàm) và epilogue (cuối hàm) rõ ràng:
; === PROLOGUE ===
00000001400010A5 mov [rsp+arg_0], ecx ; lưu tham số 1 (32-bit → ECX)
00000001400010A9 push rdi ; lưu thanh ghi được callee bảo toàn
00000001400010AA sub rsp, 20h ; cấp phát shadow space (nonleaf)5. Exception Handling trong x64
| 32-bit | 64-bit | |
|---|---|---|
| Cơ chế | Dùng stack (fs:[0] trỏ đến handler frame) | Dùng bảng tĩnh trong PE file |
| Nguy cơ | Exploit có thể ghi đè exception info trên stack | An toàn hơn, không lưu trên stack |
| Cấu trúc | — | _IMAGE_RUNTIME_FUNCTION_ENTRY trong .pdata section |
Hệ quả: Kỹ thuật khai thác SEH overwrite (phổ biến trong 32-bit) không áp dụng được trong 64-bit.
6. WOW64 — Windows 32-bit on Windows 64-bit
6.1. WOW64 là gì?
Subsystem cho phép ứng dụng 32-bit chạy trên OS 64-bit, xử lý các vấn đề tương thích về filesystem và registry.
6.2. Filesystem Redirection
Khi ứng dụng 32-bit truy cập C:\Windows\System32, WOW64 tự động redirect sang C:\Windows\SysWOW64.
Để ứng dụng 32-bit truy cập thư mục System32 thật:
C:\Windows\Sysnative ← bypass redirectHoặc gọi API:
Wow64DisableWow64FsRedirection() // tắt redirect cho thread hiện tại
6.3. Registry Redirection
HKEY_LOCAL_MACHINE\Software
↓ (32-bit app tự động redirect sang)
HKEY_LOCAL_MACHINE\Software\Wow6432NodeCác API registry có flag để chỉ định rõ muốn truy cập view 32-bit hay 64-bit:
RegCreateKeyEx(..., KEY_WOW64_64KEY, ...) // truy cập view 64-bit
RegCreateKeyEx(..., KEY_WOW64_32KEY, ...) // truy cập view 32-bit
7. Gợi ý phân tích mã độc 64-bit
7.1. Phân biệt Pointer và Data Value
Trong 64-bit, dễ hơn để nhận biết kiểu dữ liệu:
| Thanh ghi | Kích thước | Ý nghĩa |
|---|---|---|
| RDX, RCX, R8, R9 (64-bit) | 64-bit | Rất có thể là pointer |
| ECX, EDX, R8D (32-bit) | 32-bit | Không phải pointer (pointer phải 64-bit) |
Ví dụ so sánh:
; 32-bit: không biết tham số nào là pointer
004114F5 push eax ; 32-bit, pointer hay integer?
004114F9 push ecx ; 32-bit, pointer hay integer?
004114FA call sub_411186
; 64-bit: có thể suy luận kiểu
140001148 mov rdx, [rsp+38h+var_18] ; RDX = 64-bit → rất có thể là POINTER
14000114D mov ecx, [rsp+38h+var_10] ; ECX = 32-bit → KHÔNG phải pointer
140001151 call sub_14000100A8. Công cụ hỗ trợ phân tích 64-bit
| Công cụ | Hỗ trợ 64-bit |
|---|---|
| OllyDbg | Không hỗ trợ |
| WinDbg | Hỗ trợ |
| IDA Pro (Standard) | Không hỗ trợ x64 |
| IDA Pro (Advanced) | Hỗ trợ x64 |