842 words
4 minutes
FLARE Learning Hub - Chapter 4: Function Constructs

Chapter 4 của “Malware Analysis Crash Course” (FLARE/Mandiant) ghép những gì học ở Chapter 2 (Stack) và Chapter 3 (CALL/RET) lại thành một bức tranh hoàn chỉnh: một hàm tổ chức tham số và biến cục bộ của nó ra sao.

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

Stack Frame là gì#

Stack frame là vùng bộ nhớ cấp phát riêng cho một lần gọi hàm cụ thể, chứa toàn bộ dữ liệu của lần gọi đó. Khi một hàm gọi hàm khác, một frame mới được dựng chồng lên trên frame của caller. Frame tồn tại suốt thời gian hàm chạy và bị dọn sạch ngay khi hàm return.

Thứ tự layout chuẩn của một frame (từ địa chỉ cao xuống thấp):

  1. Tham số hàm — do caller push trước khi CALL
  2. Return address — tự động được CALL push
  3. EBP cũ (đã lưu) — callee tự push để giữ lại EBP của caller
  4. Biến cục bộ — vùng callee tự cấp phát cho riêng mình

Prologue và Epilogue#

Prologue — luôn là những lệnh đầu tiên của một hàm, dựng frame mới:

push ebp            ; lưu EBP của caller
mov ebp, esp         ; EBP mới = đỉnh stack hiện tại -> điểm neo cố định
sub esp, N           ; cấp N byte cho biến cục bộ

Đoạn này biến EBP (vốn dĩ cố định suốt thân hàm) thành điểm neo đáng tin cậy, trong khi ESP liên tục thay đổi theo từng PUSH/POP bên trong hàm.

Epilogue — ngược lại, dọn frame trước khi return:

mov esp, ebp         ; giải phóng biến cục bộ, trả ESP về đúng vị trí
pop ebp              ; khôi phục EBP của caller
ret [n]               ; pop return address, nhảy về, có thể kèm tự dọn thêm n byte

Thấy đúng cặp push ebp / mov ebp, esp ở đầu và pop ebp / ret ở cuối một block gần như chắc chắn là ranh giới của một hàm — IDA dùng chính pattern này để tự động xác định điểm bắt đầu/kết thúc hàm.

Function Arguments và Local Variables#

Vì EBP cố định ngay sau prologue, offset từ nó luôn ổn định suốt thân hàm:

  • Tham số nằm ở offset dương: vì chúng được push trước CALL, nên nằm “phía trên” cả return address lẫn EBP cũ trên stack. Return address nằm ở [ebp+4], nên tham số đầu tiên ở [ebp+8], tham số thứ hai ở [ebp+0xC], cứ thế cộng thêm 4.
  • Biến cục bộ nằm ở offset âm: vì được cấp phát sau khi dựng frame (qua sub esp, N), nằm “phía dưới” EBP. Nếu hàm cấp phát 8 byte, biến đầu ở [ebp-4], biến thứ hai ở [ebp-8].

Mẹo nhớ nhanh: dương = thứ có sẵn trước khi hàm bắt đầu (tham số, do caller cung cấp, callee chỉ đọc), âm = thứ hàm tự tạo ra cho riêng mình.

Calling Conventions: STDCALL và CDECL#

Cả hai cùng chung vài quy ước: tham số được push theo thứ tự phải sang trái, giá trị trả về luôn nằm trong EAX, và cấu trúc prologue/epilogue giống hệt nhau. Khác biệt duy nhất — nhưng cực kỳ quan trọng — nằm ở ai chịu trách nhiệm dọn stack sau khi gọi hàm xong:

STDCALLCDECL
Ai dọn stackCallee tự dọnCaller tự dọn
Cách dọnRET n (xem Chapter 3) — pop return address rồi tự cộng thêm n byteRET trơn, caller phải tự ADD ESP, n ngay sau CALL
Thường thấy ởWindows API (WINAPI)Hàm C chuẩn, đặc biệt hàm tham số biến đổi (printf)
; STDCALL
ret 8                ; callee tự pop return address VÀ cộng thêm 8 (dọn 2 tham số 4-byte)

; CDECL
call sub_401000
add esp, 8            ; caller tự dọn ngay sau khi gọi xong

Lý do CDECL bắt buộc cho hàm tham số biến đổi như printf: lúc compile, callee không hề biết trước sẽ nhận bao nhiêu tham số, nên không thể tự RET n với n cố định — chỉ có caller (nơi biết chính xác đã push bao nhiêu) mới tự dọn đúng được.

Local Variables và Global Variables#

  • Biến cục bộ: sống trên stack frame, truy cập qua [ebp - offset], vòng đời giới hạn trong 1 lần gọi hàm — epilogue dọn sạch (mov esp, ebp) ngay khi hàm return, coi như biến “biến mất”.
  • Biến toàn cục: nằm cố định trong vùng .data của binary, tồn tại suốt vòng đời chương trình, không phụ thuộc lần gọi hàm nào. Truy cập qua địa chỉ tuyệt đối thay vì offset theo EBP — assembler thường tự gán label dễ đọc cho các địa chỉ này (ví dụ dword_40CF60).
mov eax, [ebp - 4]        ; đọc biến cục bộ
mov ecx, dword_40CF60      ; đọc biến toàn cục (địa chỉ cố định, có label)

Phân biệt được 2 loại này giúp đoán nhanh ý nghĩa: biến toàn cục hay dùng cho state cần giữ xuyên suốt chương trình (config, flag trạng thái), biến cục bộ thường chỉ phục vụ logic tạm thời trong 1 lần gọi hàm.


Nắm vững stack frame + calling convention là nền tảng để Chapter 5 (Conditional Branching) và các chapter sau dễ đọc hơn nhiều — vì từ giờ nhìn vào [ebp+8] là biết ngay đang đọc tham số đầu tiên của hàm.

FLARE Learning Hub - Chapter 4: Function Constructs
https://stewmalwarehunter.id.vn/posts/flare-macc-ch4---function-constructs/
Author
Stew
Published at
2026-08-29
License
CC BY-NC-SA 4.0