Tiếp tục series ghi chú từ “Malware Analysis Crash Course” (FLARE/Mandiant) — Chapter 2 nói về Stack, một cấu trúc cực kỳ nền tảng vì gần như mọi function call đều đụng tới nó.
The Stack
Stack là một vùng bộ nhớ hoạt động theo kiểu LIFO (Last In, First Out) — tạm giữ dữ liệu riêng cho từng lần gọi hàm: biến cục bộ, tham số, và địa chỉ trả về. Trên x86, stack phát triển về phía địa chỉ thấp hơn (dù khi vẽ hình người ta hay quy ước vẽ “đỉnh” ở trên).
ESP (Extended Stack Pointer) luôn tự động trỏ tới đỉnh hiện tại của stack. Toàn bộ thao tác với stack quy về 2 việc: đẩy giá trị vào (PUSH) và lấy giá trị ra (POP).
PUSH Instruction
PUSH chạy đúng theo trình tự 2 bước, thứ tự rất quan trọng:
- Giảm
ESPđi 4 trước (cấp phát chỗ trống) - Rồi mới ghi giá trị operand vào đúng chỗ
ESPvừa trỏ tới
Tương đương với:
sub esp, 4
mov [esp], <operand>Operand của PUSH có thể là immediate, thanh ghi, hoặc giá trị từ bộ nhớ. Nếu đẩy liên tiếp 1, 2, 3, thì 3 (giá trị đẩy sau cùng) sẽ nằm trên đỉnh stack. Muốn truy cập các giá trị đẩy trước đó thì dùng offset dương từ ESP (ví dụ [esp+4] là phần tử đẩy thứ nhì tính từ đỉnh).
POP Instruction
POP làm ngược lại, cũng đúng 2 bước theo thứ tự:
- Đọc giá trị tại vị trí
ESPhiện tại, ghi vào đích - Tăng
ESPlên 4 (coi như đã “giải phóng” phần tử đó)
Tương đương với:
mov <đích>, [esp]
add esp, 4Đích của POP có thể là bất kỳ thanh ghi hay vị trí bộ nhớ nào. Nếu đẩy 1, 2, 3 rồi pop liên tiếp, thứ tự lấy ra sẽ là 3, 2, 1 — đúng bản chất LIFO.
Vì sao điều này quan trọng khi đọc RE
Nắm chắc PUSH/POP (giảm trước-ghi sau, đọc trước-tăng sau) là điều kiện bắt buộc để hiểu 2 thứ lớn hơn ở các chapter sau:
- Function call (Chapter 3):
CALLthực chất tựPUSHđịa chỉ lệnh kế tiếp (return address) lên stack trước khi nhảy tới hàm;RETthìPOPđịa chỉ đó ra rồi nhảy tới. - Stack frame (Chapter 4): tham số hàm và biến cục bộ đều được định vị tương đối theo
ESP/EBP— hiểu đúng PUSH/POP làmESPdi chuyển thế nào mới tính đúng offset nào trỏ tới biến nào.
Một lỗi khá hay gặp khi mới học: nhầm hướng phát triển của stack dẫn tới tính sai offset khi đọc [ebp-8] hay [ebp+8] (âm là biến cục bộ, dương là tham số — nói kỹ hơn ở Chapter 4).
Chapter tới mình sẽ ghi về Flow Control — JMP, CALL, RET và cách hàm gọi lẫn nhau được thể hiện trong assembly.