879 words
4 minutes
FLARE Learning Hub - Chapter 5: Conditional Branching

Chapter 5 của “Malware Analysis Crash Course” (FLARE/Mandiant) là chương mình thấy quan trọng nhất tính tới giờ — vì gần như toàn bộ “logic” của một chương trình (if/else, vòng lặp, switch) đều quy về rẽ nhánh có điều kiện ở cấp assembly. Assembly vốn không có khái niệm if/else thật sự — compiler biến nó thành cặp so sánh + nhảy có điều kiện.

mandiant
/
flare-learning-hub
Waiting for api.github.com...
00K
0K
0K
Waiting...

Comparison Instructions: CMP và TEST#

Cả hai đều không ghi kết quả vào đâu cả, không sửa operand — chỉ âm thầm cập nhật EFLAGS để các lệnh nhảy có điều kiện phía sau dựa vào đó mà quyết định.

  • CMP a, b — tính nội bộ a - b (phép trừ, kết quả bị vứt đi), chỉ giữ lại cờ. Zero Flag (ZF) được set nếu kết quả bằng 0 (tức a == b).
  • TEST a, b — tính nội bộ a AND b (phép AND theo bit, kết quả cũng bị vứt). Pattern cực kỳ phổ biến: test reg, reg (AND một thanh ghi với chính nó) — ZF chỉ set khi thanh ghi đó bằng 0, hay dùng để kiểm tra con trỏ NULL.
cmp eax, ebx         ; tính EAX - EBX nội bộ, EAX/EBX không đổi
test ecx, ecx         ; tính ECX AND ECX nội bộ, ZF=1 nếu ECX == 0

Common Conditional Jumps#

Chia làm 2 nhóm rõ rệt — signed và unsigned — vì cùng ý nghĩa “lớn hơn/nhỏ hơn” nhưng diễn giải bit dấu khác nhau nên dùng 2 bộ lệnh hoàn toàn riêng:

Nhóm signed (so sánh số có dấu):

LệnhÝ nghĩa
JL<
JLE≤
JG>
JGE≥

Nhóm unsigned (so sánh số không dấu):

LệnhÝ nghĩa
JB<
JBE≤
JA>
JAE≥

Dùng chung cho cả hai (chỉ xét bằng/khác, không quan tâm dấu):

LệnhÝ nghĩa
JZ / JE== (Zero Flag = 1)
JNZ / JNE!=

JZ/JE và JNZ/JNE thực chất là cùng một opcode, chỉ khác tên mnemonic hiển thị — disassembler tự chọn tên nào dễ đọc hơn theo ngữ cảnh.

Mẹo đọc rẽ nhánh “kiểu dễ hiểu”#

Không cần nhớ cơ chế EFLAGS chi tiết, chỉ cần áp dụng 2 bước:

  1. Dịch mnemonic của lệnh nhảy thành toán tử so sánh (JG → >)
  2. Đọc thành câu: “nhảy nếu [operand1] toán_tử [operand2]”

Ví dụ: sau cmp eax, 50 rồi jle somewhere → đọc thành “nhảy tới somewhere nếu eax ≤ 50”. Cách này bỏ qua hoàn toàn việc phải hiểu cờ nào được set ra sao, chỉ cần dịch trực tiếp mnemonic sang ngôn ngữ toán học.

If Statements — compiler sinh ra gì#

Ví dụ với code nguồn if (a == b) { ... } else { ... }, assembly tương ứng thường có dạng:

mov eax, [ebp-4]      ; load biến a
cmp eax, [ebp-8]       ; so sánh a với b
jnz short else_block    ; nhảy nếu KHÁC (lưu ý: điều kiện bị ĐẢO NGƯỢC)
; --- thân if (a == b) ---
call printf_equal
jmp short merge         ; nhảy qua phần else
else_block:
; --- thân else (a != b) ---
call printf_not_equal
merge:
; --- code tiếp theo ---

Điểm quan trọng nhất cần nhớ: compiler thường đảo ngược điều kiện trong lệnh nhảy — thay vì “nhảy nếu điều kiện true”, nó sinh ra “nhảy nếu điều kiện false” để bỏ qua thân if, cho phép code rơi tự nhiên vào thân if khi điều kiện đúng. Một jmp không điều kiện ở cuối thân if dùng để nhảy qua hẳn phần else.

Static Analysis with IDA#

Static analysis là đọc hiểu chương trình mà không chạy nó. Khi mở một binary, IDA tự động chạy auto-analysis: disassemble toàn bộ, xác định ranh giới hàm, và dựng sẵn bảng cross-reference — xong hết trước khi mình kịp tương tác.

Giao diện mặc định gồm:

  • Disassembly pane — code dạng tuyến tính hoặc graph
  • Functions list — toàn bộ hàm IDA nhận diện được, kèm metadata
  • Imports — các hàm từ DLL bên ngoài được import vào
  • Exports — các hàm chính binary này export ra
  • Strings — toàn bộ chuỗi được trích xuất sẵn
  • Navigation band — thanh tổng quan toàn bộ không gian địa chỉ, có mã màu

Quy trình thực tế hay dùng: lướt Imports tìm các hàm đáng ngờ (file I/O, networking…), dùng xref (phím X) để tìm nơi gọi chúng — đây là cách nhanh nhất để nắm bắt hành vi chương trình mà không cần đọc tuần tự. Tương tự với Strings — một chuỗi lạ (URL, đường dẫn, thông báo lỗi) lần theo xref thường dẫn thẳng tới routine đáng chú ý nhất.

Các thao tác “để lại dấu vết” khi phân tích (document trực tiếp trong IDB):

  • Rename (phím N) — đặt lại tên cho hàm/biến/địa chỉ thay vì để tên mặc định sub_401000
  • Comment thường (phím :) — chú thích tại đúng 1 vị trí
  • Repeatable comment (phím ;) — chú thích xuất hiện lại ở mọi nơi tham chiếu tới vị trí đó
  • Repeatable function comment — comment đặt ở đầu hàm, tự hiện ra tại mọi điểm gọi hàm đó

Document dần dần theo cách này — đổi tên, thêm comment từng chút một — là cách biến một mớ assembly khó đọc thành logic có thể hiểu được ở mức cao hơn, thay vì phải nhớ từng địa chỉ trong đầu.


Hiểu rẽ nhánh có điều kiện là chìa khóa để đọc hiểu logic thật sự của một chương trình — chapter tiếp theo mình chuyển sang mảng hoàn toàn khác: các kỹ thuật debug cơ bản (forcing execution, patching).

FLARE Learning Hub - Chapter 5: Conditional Branching
https://stewmalwarehunter.id.vn/posts/flare-macc-ch5---conditional-branching/
Author
Stew
Published at
2026-09-05
License
CC BY-NC-SA 4.0