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

flowchart TD A[Executable gốc] -->|Packer xử lý| B[Packed Executable trên disk] B -->|OS load| C[Unpacking Stub chạy] C --> D[Giải nén code gốc vào memory] D --> E[Resolve imports] E --> F[Chuyển execution tới OEP] F --> G[Chương trình gốc chạy bình thường]

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:

  1. Unpack executable gốc vào memory
  2. Resolve imports của chương trình gốc
  3. Transfer execution tới OEP (Original Entry Point)

2.2 Các chiến lược xử lý Import Table

Chiến lượcMô tảĐộ stealth
Chỉ import LoadLibrary + GetProcAddressStub tự resolve toàn bộCao
Giữ nguyên import table gốcWindows loader tự xử lýThấp — lộ hết imports
Giữ 1 import/DLLStub resolve phần còn lạiTrung bình
Xóa hoàn toàn mọi importStub phải tự tìm functionsRấ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

flowchart LR A[Packed Malware] --> B{Phương pháp} B --> C[Automated Static Unpacking] B --> D[Automated Dynamic Unpacking] B --> E[Manual Dynamic Unpacking] C --> F[Nhanh nhất, không chạy malware\nChỉ hoạt động với packer cụ thể] D --> G[Chạy malware, dump kết quả\nKhó xác định điểm kết thúc stub] E --> H[Linh hoạt nhất\nTốn thời gian nhất]

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ểmNhược điểm
Step-overTránh false positive khi call sang section khácBỏ sót nếu call không return
Step-intoTìm được OEP trong mọi trường hợp hơnNhiề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ệnh cuối trước một chuỗi bytes 0x00 dà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àn 0x00) 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ìnhFunction cần break
Command-lineGetVersion, GetCommandLineA
GUIGetModuleHandleA
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à OEP

5.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 .text section của file gốc
  • Đặt break khi bất kỳ instruction nào trong .text section đượ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 → xong

6.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 file

6.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 program

Quy 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ải

7. 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 DllMain thay vì main method
  • Vấn đề: khi load DLL bằng loadDll.exe trong 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

9. Các Packer phổ biến và cách xử lý

UPX (Ultimate Packer for eXecutables)

PECompact

ASPack

Petite

WinUpack / UPack

Themida


10. Double-Packed Malware


Tổng kết quy trình Manual Unpacking

flowchart TD A[Load packed executable vào OllyDbg] --> B[Tìm OEP] B --> C{Automated tool hoạt động?} C -->|Có| D[OllyDump: Find OEP by Section Hop] C -->|Không| E[Manual: tail jump / stack breakpoint\n/ GetProcAddress BP / Windows wrappers] D --> F[Dump process với OllyDump] E --> F F --> G{Import table OK?} G -->|Có| H[Chạy và phân tích file unpacked] G -->|Không| I[Thử ImpRec] I --> J{ImpRec OK?} J -->|Có| H J -->|Không| K[Label thủ công từng import trong IDA Pro\nhoặc rebuild import table bằng tay] K --> L[Static analysis với IDA Pro\nDynamic analysis với file gốc]