Chương 8 – DEBUGGING (Gỡ Lỗi Chương Trình)


1. Debugger là gì?

Debugger là phần mềm (hoặc phần cứng) dùng để kiểm tra và điều khiển quá trình thực thi của một chương trình khác.

  • Disassembler → chỉ cho ảnh tĩnh (snapshot) của chương trình trước khi chạy.
  • Debugger → cho cái nhìn động trong khi chương trình đang chạy: giá trị thanh ghi, bộ nhớ, đối số hàm thay đổi theo thời gian thực.

Tại sao quan trọng với malware analyst? Nhiều thứ không thể suy luận qua disassembly tĩnh: dữ liệu được mã hóa/giải mã tại runtime, tên file tạo ra, chuỗi bị XOR… Debugger tiết lộ tất cả.


2. Phân loại Debugger

2.1 Source-Level vs Assembly-Level

LoạiHoạt động trênDùng khi nào
Source-LevelMã nguồn (C, C++…)Lập trình viên bình thường, có source
Assembly-LevelMã assemblyMalware analyst — không có source

Assembly-level debugger vẫn cho phép: đặt breakpoint, step từng lệnh, xem bộ nhớ.

2.2 User-Mode vs Kernel-Mode Debugging

User-mode debugging:
  [Debugger] ←→ [Process đang debug]   ← cùng một máy, OS cô lập từng process

Kernel-mode debugging:
  [Máy A: chạy code debug] ←→ [Máy B: chạy debugger]   ← cần 2 máy

Lý do cần 2 máy cho kernel debugging: chỉ có một kernel duy nhất; nếu kernel dừng tại breakpoint, toàn bộ hệ thống đứng, không có gì chạy được.


3. Cách gắn Debugger vào chương trình

Có hai cách:

  1. Khởi chạy chương trình qua debugger → dừng ngay trước instruction đầu tiên (entry point), toàn quyền kiểm soát từ đầu.
  2. Attach vào process đang chạy → dừng tất cả thread, debug từ giữa chừng. Dùng khi muốn debug sau khi chương trình đã chạy một thời gian hoặc process bị malware tác động.

4. Single-Stepping

Single-stepping = chạy từng lệnh một, sau mỗi lệnh trả quyền kiểm soát về cho debugger.

Ví dụ thực tế: đoạn code XOR để decode chuỗi:

mov edi, DWORD_00406904   ; địa chỉ dữ liệu cần decode
mov ecx, 0x0d             ; đếm 13 vòng lặp
LOC_040106B2:
    xor [edi], 0x9C       ; XOR từng byte với key 0x9C
    inc edi
    loopw LOC_040106B2

Dữ liệu ban đầu tại 0x00406904: F8 FD F3 D0 — không có ý nghĩa rõ ràng.

Sau khi single-step qua từng vòng lặp:

4CF3FDF8 D0F5FEEE FDEEE5DD 9C   →  (L............)
4C6FFDF8 D0F5FEEE FDEEE5DD 9C   →  (Lo...........)
4C6F61F8 D0F5FEEE FDEEE5DD 9C   →  (Loa..........)
...
4C6F6164 4C696272 61727941 00   →  (LoadLibraryA.)

→ Hàm đang decode chuỗi LoadLibraryA bằng XOR đơn giản — điều không thể thấy qua static analysis.


5. Step-Over vs Step-Into

Hành độngÝ nghĩaKhi nào dùng
Step-OverBỏ qua lời gọi hàm, nhảy tới lệnh tiếp sau khi hàm returnHàm không quan trọng (vd: LoadLibrary)
Step-IntoĐi vào bên trong hàm được gọiHàm nghi ngờ cần phân tích sâu
Step-OutChạy đến khi hàm hiện tại returnĐã step-into rồi muốn thoát ra nhanh

6. Breakpoints

Breakpoint = điểm dừng để kiểm tra trạng thái chương trình (thanh ghi, bộ nhớ, tham số hàm) tại một thời điểm cụ thể.

Khi chương trình dừng tại breakpoint → gọi là “broken”.

6.1 Software Execution Breakpoints

Cơ chế hoạt động:

Debugger ghi đè byte đầu tiên của instruction bằng 0xCC (INT 3)
  → Khi CPU thực thi 0xCC → OS sinh exception → chuyển điều khiển về debugger
  → Debugger hiển thị instruction gốc (restore 0x55 trở lại trên giao diện)
Memory dump thực tế:    CC 8B EC 83 E4 F8 ...   ← 0xCC là breakpoint
Debugger hiển thị:      55 8B EC 83 E4 F8 ...   ← push ebp (instruction gốc)

Ưu điểm: Đặt được số lượng không giới hạn (user-mode).

Nhược điểm:

  • Nếu code tự sửa đổi (self-modifying code) → breakpoint bị xóa mất.
  • Code đọc bộ nhớ của chính nó sẽ thấy 0xCC thay vì byte gốc → có thể dùng để anti-debug.

6.2 Hardware Execution Breakpoints

Cơ chế: CPU có 4 debug register chuyên dụng (DR0DR3), CPU phần cứng so sánh instruction pointer với địa chỉ breakpoint sau mỗi lệnh.

Ưu điểm so với software breakpoint:

  • Không cần sửa byte nào trong bộ nhớ → hoạt động ngay cả với self-modifying code.
  • Có thể đặt breakpoint trên truy cập bộ nhớ (đọc/ghi), không chỉ thực thi.
Ví dụ: breakpoint khi có ghi vào địa chỉ 0x00403000
  → dù instruction ở đâu, debugger vẫn break ngay khi ghi xảy ra

Nhược điểm:

  • Chỉ có 4 hardware breakpoint cùng lúc.
  • Malware có thể sửa các debug register (DR0DR3, DR7) để phá breakpoint.

6.3 Conditional Breakpoints

Cơ chế: Là software breakpoint nhưng debugger tự động evaluate điều kiện; nếu điều kiện không thỏa → tiếp tục chạy mà không thông báo.

Ví dụ: Chỉ dừng khi GetProcAddress được gọi với tham số "RegSetValue":

# Điều kiện pseudo-code
if stack[esp+4] == "RegSetValue":
    BREAK
else:
    CONTINUE

So sánh 3 loại Breakpoint

Tiêu chíSoftwareHardwareConditional
Số lượngKhông giới hạnTối đa 4Không giới hạn
Self-modifying code❌ Không hoạt động✅ Hoạt động❌ Không hoạt động
Break on memory access❌ Không✅ Có❌ Không
Tốc độNhanhNhanhChậm
Anti-debug riskCao (0xCC bị detect)Cao (DR register bị sửa)Trung bình

7. Exceptions (Ngoại lệ)

Exception là cơ chế chính để debugger giành quyền kiểm soát chương trình đang chạy. Breakpoint thực ra cũng là exception (INT 3).

Các nguồn sinh exception thường gặp:

ExceptionNguyên nhân
INT 3Breakpoint — phổ biến nhất
Trap flagSingle-stepping — CPU sinh exception sau mỗi lệnh
Memory access violationTruy cập địa chỉ không hợp lệ hoặc không được phép
Privileged instructionChạy lệnh kernel-mode trong user-mode
Division by zeroPhép chia cho 0

First-chance vs Second-chance Exception

flowchart TD A[Exception xảy ra] --> B[Debugger nhận: First-chance] B --> C{Debugger xử lý?} C -- Có --> D[Tiếp tục chạy] C -- Không --> E{Program có exception handler?} E -- Có --> F[Handler của program xử lý] F --> D E -- Không --> G[Debugger nhận: Second-chance] G --> H[Program sẽ crash nếu không có debugger]

8. Modify Execution – Sửa đổi luồng thực thi

Debugger không chỉ quan sát mà còn cho phép thay đổi cách chương trình chạy:

Kỹ thuậtCách làmDùng để
Skip function callĐặt breakpoint tại call, sau đó đặt EIP nhảy qua lệnh callBỏ qua hàm anti-debug
Thay đổi return valueĐặt breakpoint sau call, sửa EAXGiả lập điều kiện khác
Gọi hàm thủ côngĐặt ESP+4 = tham số mong muốn, đặt EIP = địa chỉ hàmPhân tích một hàm độc lập
Sửa thanh ghi cờFlip bit trong EFLAGSThay đổi kết quả điều kiện nhảy

Ví dụ thực tế: Virus phụ thuộc ngôn ngữ hệ thống

call GetSystemDefaultLCID      ; trả về Locale ID trong EAX
mov  [ebp+var_4], eax
cmp  [ebp+var_4], 409h         ; English?
jnz  short loc_411360
call sub_411037                ; → hành vi với English

loc_411360:
cmp  [ebp+var_4], 411h         ; Japanese?
jz   short loc_411372
cmp  [ebp+var_4], 421h         ; Indonesian?
jnz  short loc_411377

loc_411372:
call sub_41100F                ; → hành vi với Japanese/Indonesian

loc_411377:
cmp  [ebp+var_4], 0C04h        ; Chinese?
jnz  short loc_411385
call sub_41100A                ;  tự gỡ cài đặt

Vấn đề: Máy đang dùng English (EAX = 0x0409). Muốn phân tích nhánh Japanese mà không đổi language setting hệ thống.

Giải pháp: Đặt breakpoint ngay sau GetSystemDefaultLCID → sửa EAX từ 0x0409 thành 0x0411 → tiếp tục chạy → chương trình đi vào nhánh Japanese.


9. Ứng dụng Breakpoint trong thực tế


10. Tóm tắt

Debugger
├── Loại
│   ├── Source-level  (IDE, có source code)
│   └── Assembly-level (malware analysis, không cần source)
├── Môi trường
│   ├── User-mode  (cùng máy, OllyDbg)
│   └── Kernel-mode (2 máy, WinDbg)
├── Kỹ thuật chính
│   ├── Single-stepping
│   ├── Step-over / Step-into / Step-out
│   └── Breakpoints
│       ├── Software  (0xCC, không giới hạn)
│       ├── Hardware  (DR0-DR3, tối đa 4, hỗ trợ access break)
│       └── Conditional (software + điều kiện, chậm)
├── Exceptions
│   ├── First-chance → có thể bỏ qua
│   └── Second-chance → bắt buộc xử lý
└── Modify Execution
    ├── Sửa EIP (nhảy qua/vào hàm)
    ├── Sửa thanh ghi (thay đổi return value)
    └── Sửa bộ nhớ (thay đổi data)