Chương 18: Packers và Unpacking
Tags
malware-analysis reverse-engineering packing unpacking OEP static-analysis dynamic-analysis
1. Packers là gì và tại sao malware dùng?
Packer là chương trình biến đổi một executable thành một executable mới, trong đó:
- Executable gốc được nén, mã hóa hoặc biến đổi và lưu dưới dạng data
- Executable mới chứa một unpacking stub được OS gọi thay vì code gốc
Mục đích sử dụng:
- Giảm kích thước file
- Qua mặt phần mềm diệt virus
- Cản trở phân tích (anti-reverse-engineering)
Lưu ý quan trọng: Static analysis thông thường hoàn toàn vô dụng với packed executable — bạn chỉ đang phân tích unpacking stub, không phải chương trình thật.
2. Cơ chế hoạt động của Packer
2.1 Unpacking Stub
Stub là phần code nhỏ, duy nhất có thể nhìn thấy trong packed file. Nó thực hiện 3 bước:
- Unpack executable gốc vào memory
- Resolve imports của chương trình gốc
- Transfer execution tới OEP (Original Entry Point)
2.2 Các chiến lược xử lý Import Table
| Chiến lược | Mô tả | Độ stealth |
|---|---|---|
Chỉ import LoadLibrary + GetProcAddress | Stub tự resolve toàn bộ | Cao |
| Giữ nguyên import table gốc | Windows loader tự xử lý | Thấp — lộ hết imports |
| Giữ 1 import/DLL | Stub resolve phần còn lại | Trung bình |
| Xóa hoàn toàn mọi import | Stub phải tự tìm functions | Rất cao, stub rất phức tạp |
3. Nhận diện Packed Program
Dấu hiệu nhận biết
Entropy Calculation
- Entropy đo mức độ “hỗn loạn” của dữ liệu
- File bị nén/mã hóa → entropy rất cao
- File thường → entropy thấp hơn
- Tool: Mandiant Red Curtain — tính threat score dựa trên entropy
4. Các phương pháp Unpacking
4.1 Automated Static Unpacking
- Không chạy executable
- Nhanh và hiệu quả nhất khi hoạt động
- Ví dụ tool: PE Explorer (hỗ trợ NSPack, UPack, UPX mặc định)
- Nhược điểm: chỉ hoạt động với packer đã biết, không qua được anti-analysis packers
4.2 Automated Dynamic Unpacking
- Chạy executable, để stub tự unpack, sau đó dump ra disk
- Khó khăn: xác định đúng điểm kết thúc stub (nếu sai → unpacking thất bại)
4.3 Manual Dynamic Unpacking
Quy trình chuẩn:
1. Load packed executable vào OllyDbg
2. Tìm OEP
3. Dump process ra memory (dùng OllyDump plugin)
4. Rebuild import table (dùng ImpRec nếu OllyDump thất bại)5. Tìm OEP (Original Entry Point)
5.1 Dùng Automated Tool — OllyDump “Find OEP by Section Hop”
- Unpacking stub thường nằm ở section khác với executable gốc
- OllyDbg phát hiện khi có transfer giữa 2 sections và dừng lại
- Có 2 chế độ:
| Chế độ | Ưu điểm | Nhược điểm |
|---|---|---|
| Step-over | Tránh false positive khi call sang section khác | Bỏ sót nếu call không return |
| Step-into | Tìm được OEP trong mọi trường hợp hơn | Nhiều false positive hơn |
Thực hành: Thử cả hai, bắt đầu với step-over.
5.2 Tìm Tail Jump thủ công
Tail jump là lệnh nhảy cuối cùng từ stub sang OEP. Đây là kỹ thuật thủ công cơ bản nhất.
; Ví dụ tail jump của UPX (Listing 18-1)
00416C31 PUSH EDI
00416C32 CALL EBP
00416C34 POP EAX
00416C35 POPAD
00416C36 LEA EAX, DWORD PTR SS:[ESP-80]
00416C3A PUSH 0
00416C3C CMP ESP, EAX
00416C3E JNZ SHORT Sample84.00416C3A
00416C40 SUB ESP, -80
00416C43 JMP Sample84.00401000 ; <-- ĐÂY LÀ TAIL JUMP
00416C48 DB 00
00416C49 DB 00
; ... nhiều 0x00 bytes tiếp theoĐặc điểm nhận diện tail jump:
- Là lệnh cuối trước một chuỗi bytes
0x00dài (padding) - Nhảy đến địa chỉ rất xa (hàng chục nghìn bytes) — không bình thường với loop/conditional
- Trong IDA Pro graph view: tail jump được tô màu đỏ vì IDA không thể resolve target
- Sau tail jump, code tại OEP chứa
ADD BYTE PTR DS:[EAX],AL(toàn0x00) trước khi unpack, và code hợp lệ sau khi unpack
5.3 Breakpoint trên Stack
1. Ghi nhớ địa chỉ stack nơi giá trị đầu tiên được PUSH
2. Đặt hardware breakpoint READ tại địa chỉ đó
3. Mọi thứ sau đó push lên stack cao hơn (địa chỉ thấp hơn)
4. Chỉ khi stub hoàn tất, địa chỉ gốc mới được POP → trigger breakpoint
5. Tail jump thường ngay sau lệnh POP đó5.4 Breakpoint sau mỗi Loop
- Scan qua code, đặt breakpoint sau mỗi vòng lặp (kể cả loop lồng nhau)
- Tránh phải trace qua cùng một code lặp đi lặp lại
- Nhược điểm: tốn thời gian, dễ đặt sai chỗ khiến program chạy đến completion
5.5 Breakpoint trên GetProcAddress
- Hầu hết unpackers dùng
GetProcAddressđể resolve imports - Đặt breakpoint tại đây → bypass phần đầu phức tạp nhất của stub
- Vẫn còn một đoạn code trước tail jump sau khi break
5.6 Breakpoint trên Windows wrapper functions
Dựa vào pattern: chương trình Windows luôn gọi một số functions nhất định ngay sau OEP.
| Loại chương trình | Function cần break |
|---|---|
| Command-line | GetVersion, GetCommandLineA |
| GUI | GetModuleHandleA |
1. Đặt breakpoint tại instruction đầu tiên của GetModuleHandleA (không phải call đến nó)
2. Khi break, xem stack frame trước → tìm function đã call GetModuleHandleA
3. Scroll lên tìm đầu function đó (thường bắt đầu bằng PUSH EBP / MOV EBP, ESP)
4. Đó rất có thể là OEP5.7 OllyDbg Run Trace
- Cho phép đặt breakpoint trên một vùng địa chỉ rộng
- OEP luôn nằm trong
.textsection của file gốc - Đặt break khi bất kỳ instruction nào trong
.textsection được thực thi → bắt được OEP
6. Dump và Rebuild Import Table
6.1 Quy trình cơ bản với OllyDump
1. Khi debugger đang ở OEP → ghi lại giá trị OEP
2. Plugins → OllyDump → Dump Debugged Process
3. OllyDump tự động:
- Set entry point = current instruction pointer (= OEP)
- Rebuild import table
4. Click Dump → xong6.2 Khi OllyDump thất bại — Dùng Import Reconstructor (ImpRec)
1. Chạy ImpRec, mở dropdown → chọn packed executable đang chạy
2. Nhập RVA của OEP vào ô OEP
Ví dụ: Image base = 0x400000, OEP = 0x403904 → nhập 0x3904
3. Click "IAT Autosearch" → ImpRec tìm Import Address Table
4. Click "Get Imports" → danh sách imports hiện ra bên trái
5. Kiểm tra: tất cả imports phải có "valid: YES"
6. Click "Fix Dump" → chọn file đã dump từ OllyDump
7. ImpRec xuất file mới với dấu underscore (_) ở tên file6.3 Rebuild Import Table thủ công
Khi cả OllyDump lẫn ImpRec đều thất bại (packer xóa bảng tên imports):
; Ví dụ call đến imported function khi import table chưa được rebuild (Listing 18-4)
push eax
call dword_401244 ; call gián tiếp qua DWORD pointer
...
dword_401244: 0x7c4586c8 ; địa chỉ nằm ngoài loaded programQuy trình:
1. Mở file trong IDA Pro (không có import info)
2. Khi gặp call đến imported function → navigate đến DWORD value
3. Mở OllyDbg → navigate đến địa chỉ đó → xem OllyDbg đã label gì
4. Label lại trong IDA Pro: vd imp_WriteFile
5. IDA Pro cross-reference sẽ tự label tất cả calls đến function đó
6. Lặp lại cho từng import gặp phải7. Phân tích khi không thể Unpack hoàn toàn
- Dùng IDA Pro analyze từng section code cụ thể bằng cách navigate đến địa chỉ memory và mark làm code
- Chạy Strings trên file dump để tìm imported functions và thông tin hữu ích
- Dùng ProcDump (Microsoft tool) dump process từ memory mà không cần debug — rất hữu ích khi packer có anti-debugging mạnh
- Tập trung vào dynamic analysis hơn static analysis
8. Packed DLLs
DLL có thêm một số phức tạp so với EXE:
- DLL có OEP tại đầu hàm
DllMain - Unpacking stub được đặt vào
DllMainthay vì main method - Vấn đề: khi load DLL bằng
loadDll.exetrong OllyDbg,DllMainđược gọi trước khi OllyDbg break → stub đã chạy xong rồi
Workaround:
1. Mở PE file bằng hex editor
2. Tìm field "Characteristics" trong IMAGE_FILE_HEADER
3. Bit tại vị trí 0x2000 = 1 (DLL), đổi thành 0 → file được treat như EXE
4. OllyDbg mở như EXE → apply các kỹ thuật unpack bình thường
5. Sau khi tìm OEP xong → đổi bit 0x2000 trở về 1