Buổi 10: ROP & Heap Overflow
Mục lục
1. Tấn công ROP (Return Oriented Programming)
1.1 Nhắc lại – Buffer Overflow cơ bản
Khi khai thác lỗ hổng tràn bộ đệm (buffer overflow), trường hợp lý tưởng là:
- Kiểm soát được return address (không có stack canary).
- Có thể truyền shellcode vào stack và thực thi (biên dịch với
-z execstack).
Tuy nhiên, trong thực tế tồn tại nhiều ràng buộc hơn:
| Tình huống | Kỹ thuật tương ứng |
|---|---|
| Off-by-one, buffer nhỏ, không ghi đè được return addr | Cần kỹ thuật phức tạp hơn |
| Stack không cho phép thực thi code | Return-to-libc hoặc ROP |
| Shellcode quá dài, phức tạp | ROP |
1.2 Tại sao cần ROP? – DEP/NX
DEP (Data Execution Prevention) – hay còn gọi là NX (No-eXecute), W^X – là cơ chế bảo mật ngăn chặn việc thực thi code trên các vùng nhớ dữ liệu.
Phân loại permission vùng nhớ:
Không có DEP:
.text → R-X (Read, Execute)
.rodata → R-- (Read)
Heap → RWX (Read, Write, Execute) ← nguy hiểm!
Stack → RWX (Read, Write, Execute) ← nguy hiểm!
Có DEP:
.text → R-X (Read, Execute)
.rodata → R-- (Read)
Heap → RW- (Read, Write) ← không thực thi được
Stack → RW- (Read, Write) ← không thực thi đượcKhi DEP được bật, nếu attacker nhảy vào stack để thực thi shellcode → Segmentation Fault. Shellcode truyền vào stack không thể chạy.
Kiểm tra với checksec:
$ checksec ./binary
Arch: i386-32-little
CANARY: disabled
FORTIFY: disabled
NX: ENABLED ← DEP đang bật
PIE: disabled1.3 Bypass DEP – Ý tưởng ROP
Return-to-libc là tiền thân của ROP: nhảy vào các hàm sẵn có trong libc (ví dụ system()). Tuy nhiên có nhược điểm:
- Cần tìm đúng địa chỉ hàm libc.
- Có thể có tác dụng phụ không mong muốn.
- ASLR có thể làm địa chỉ libc thay đổi mỗi lần chạy.
ROP – Return Oriented Programming khắc phục bằng cách:
1.4 Gadget là gì?
Gadget là một chuỗi các lệnh assembly liên tiếp có ý nghĩa, và kết thúc bằng lệnh ret.
; Ví dụ các gadgets:
pop eax
ret
xor ebx, ebx
ret
mov edx, eax
ret
int 0x80
retNhiều gadgets được nối (chain) với nhau → tạo thành ROP Chain, tương đương với shellcode nhưng không cần vùng nhớ có quyền execute.
1.5 Tìm Gadget ở đâu?
Gadget có thể nằm ở:
- Trong các hàm thông thường của binary.
- Ẩn bên trong các byte của lệnh khác – do cách encode lệnh x86 (biến độ dài), một chuỗi byte có thể được decode theo nhiều cách.
Ví dụ kỹ thuật:
Lệnh gốc: e9 5a c3 ff ff → jmp PC+0xffffc35a
Nhưng nếu bắt đầu đọc từ byte thứ 2:
5a c3 → pop edx; ret→ Đây là một gadget hợp lệ dù không phải ý định ban đầu của lập trình viên!
Công cụ tìm gadget:
$ ROPgadget --binary ./smashme.bin --asm "popl %edx; ret"
Gadgets information
============================================================
0x0806eb0a : popl %edx; ret
0x08081736 : popl %edx; ret
Total opcodes found: 21.6 ROP Chain – Cơ chế hoạt động
Câu hỏi: Làm sao để chain các gadget lại với nhau?
Trả lời: Bằng cách sắp xếp địa chỉ của các gadget trên stack theo thứ tự thực thi mong muốn.
Mục tiêu: thực thi exit(0) tương đương shellcode:
xor eax, eax ; eax = 0 (syscall number cho exit)
xor ebx, ebx ; ebx = 0 (exit code)
inc eax ; eax = 1 (sys_exit = 1 trên x86 Linux 32-bit)
int 0x80 ; gọi syscallCác gadget tương ứng:
gadget1: xor eax, eax; ret @ 0x08054134
gadget2: xor ebx, ebx; ret @ 0x08053168
gadget3: inc eax; ret @ 0x08056243
gadget4: int 0x80 @ 0x08054390Layout của stack khi exploit:
Địa chỉ thấp (lower)
┌────────────────────────────┐
│ buf (filler) │
├────────────────────────────┤
│ saved ebp │
├────────────────────────────┤ ← return address bị ghi đè
│ 0x08054134 (gadget 1) │ ← esp trỏ vào đây khi hàm ret
├────────────────────────────┤
│ 0x08053168 (gadget 2) │
├────────────────────────────┤
│ 0x08056243 (gadget 3) │
├────────────────────────────┤
│ 0x08054390 (int 0x80) │
└────────────────────────────┘
Địa chỉ cao (higher)Cơ chế thực thi từng bước:
1.7 Ví dụ ROP nâng cao: sys_write
Mục tiêu: Gọi write(1, str_addr, 13) để in chuỗi ra stdout.
; Shellcode tương đương:
mov eax, 4 ; syscall number: sys_write
mov ebx, 1 ; fd = stdout
mov ecx, str_addr ; địa chỉ chuỗi cần in
mov edx, 13 ; độ dài chuỗi
int 0x80Các gadgets tìm được:
gadget1: pop eax; ret
gadget2: pop ebx; ret
gadget3: leal ecx, [esp+12]; ret ; ecx = esp+12 (trỏ vào data trên stack)
gadget4: pop edx; ret
gadget5: int 0x80; retLayout stack hoàn chỉnh:
┌────────────────────────────┐
│ buf (filler) │
├────────────────────────────┤
│ addr gadget1 (pop eax) │ ← return addr bị ghi đè
├────────────────────────────┤
│ 0x4 │ ← giá trị cho gadget1 pop vào eax
├────────────────────────────┤
│ addr gadget2 (pop ebx) │
├────────────────────────────┤
│ 0x1 │ ← giá trị cho gadget2 pop vào ebx
├────────────────────────────┤
│ addr gadget3 (lea ecx) │
├────────────────────────────┤
│ addr gadget4 (pop edx) │
├────────────────────────────┤
│ 0xd (= 13) │ ← giá trị cho gadget4 pop vào edx
├────────────────────────────┤
│ addr gadget5 (int 0x80) │
├────────────────────────────┤
│ "hello world\n" │ ← gadget3 dùng [esp+12] để trỏ vào đây
└────────────────────────────┘1.8 Stack Pivoting
2. Khai thác lỗ hổng trên bộ nhớ Heap
2.1 Cơ bản về bộ nhớ Heap
Heap là vùng bộ nhớ dùng cho cấp phát động tại runtime:
// Cấp phát vùng nhớ
char *ptr = malloc(0x100);
// Sử dụng
fgets(ptr, 0x100, stdin);
// Giải phóng
free(ptr);Vị trí trong không gian bộ nhớ (32-bit):
0xFFFFFFFF ─── End of memory
Stack (phát triển xuống ↓)
0xbfff0000 ─── Top of stack
...
0x09104000 ─── Top of heap
Heap (phát triển lên ↑)
.bss / .data
0x08048000 ─── .text segment (ELF)
Libraries (libc)
0x00000000 ─── Start of memory2.2 Cấu trúc Heap Chunk (ptmalloc)
Glibc sử dụng ptmalloc để quản lý heap. Mỗi vùng nhớ được cấp phát/giải phóng được gọi là một chunk.
Cấu trúc chunk đang sử dụng (allocated):
struct malloc_chunk {
size_t prev_size; // Kích thước chunk trước (chỉ hợp lệ khi chunk trước đã free, P=0)
size_t size; // Kích thước chunk hiện tại (byte, align 8-byte)
// Các bit thấp là flags:
// A: allocated in non-main arena
// M: mmapped
// P: previous-in-use (P=0 → chunk trước đã free)
// Phần payload (data):
// ...
};Bộ nhớ thực tế (allocated chunk):
┌──────────────────┐
│ prev_size │ ← *(ptr - 2)
├──────────────────┤
│ size | A M P=1 │ ← *(ptr - 1) [P=1: chunk trước đang dùng]
├──────────────────┤
│ │ ← ptr (con trỏ malloc() trả về)
│ payload │
│ (user data) │
│ │
└──────────────────┘Cấu trúc chunk đã giải phóng (freed):
Freed chunk (thêm 2 con trỏ fd/bk để liên kết danh sách):
┌──────────────────┐
│ prev_size │
├──────────────────┤
│ size | A M P=0 │ [P=0: chunk trước đã free]
├──────────────────┤
│ fd (forward) │ ← con trỏ tới chunk free tiếp theo
├──────────────────┤
│ bk (backward) │ ← con trỏ tới chunk free trước đó
├──────────────────┤
│ (unused) │
└──────────────────┘Mục đích của cấu trúc này:
fd/bktạo thành doubly linked list các chunk trống → giảm phân mảnh (fragmentation).- Cho phép
malloc()tìm chunk trống trong O(1). - Khi
free()được gọi, kiểm tra chunk liền kề có trống không → coalescing (gộp lại).
2.3 Các phiên bản Heap Allocator
| Allocator | Sử dụng trong |
|---|---|
| dlmalloc | Trình cấp phát tổng quát |
| ptmalloc | glibc (Linux phổ biến nhất) |
| tcmalloc | Google Chrome, Go |
| jemalloc | FreeBSD, Firefox |
| nedmalloc | Game engines |
| Hoard | Multi-threaded apps |
2.4 Heap Overflow
Cơ chế: Tương tự stack overflow – ghi dữ liệu vượt quá kích thước chunk được cấp phát, ghi đè lên dữ liệu của chunk kế tiếp.
Sự khác biệt so với stack overflow:
Ví dụ tấn công Heap Overflow qua function pointer:
struct toystr {
void (*message)(char *); // function pointer – 4 bytes trên x86
char buffer[20]; // dữ liệu
};// Cấp phát 2 object liên tiếp trên heap
struct toystr *coolguy = malloc(sizeof(struct toystr));
struct toystr *lameguy = malloc(sizeof(struct toystr));
coolguy->message = &print_cool;
lameguy->message = &print_meh;
// LỖI: fgets cho phép nhập tới 200 bytes nhưng buffer chỉ có 20 bytes!
printf("Input coolguy's name: ");
fgets(coolguy->buffer, 200, stdin); // ← HEAP OVERFLOW!
printf("Input lameguy's name: ");
fgets(lameguy->buffer, 20, stdin); // bình thường
coolguy->message(coolguy->buffer);
lameguy->message(lameguy->buffer); // ← con trỏ hàm đã bị ghi đè!
Phân tích layout heap:
Trước khi overflow:
┌─────────────────────────┐
│ chunk header coolguy │
├─────────────────────────┤
│ message = &print_cool │ ← 4 bytes
│ buffer[20] │ ← 20 bytes
├─────────────────────────┤
│ chunk header lameguy │
├─────────────────────────┤
│ message = &print_meh │ ← sẽ bị ghi đè!
│ buffer[20] │
└─────────────────────────┘
Sau khi nhập 200 bytes vào coolguy->buffer:
┌─────────────────────────┐
│ chunk header coolguy │
├─────────────────────────┤
│ message = &print_cool │
│ AAAAAAAAAAAAAAAAAAAAAA │ ← 20 bytes buffer
│ AAAA... (overflow) │ ← tràn sang chunk lameguy
├─────────────────────────┤
│ AAAA... (header hỏng) │ ← chunk header bị ghi đè
├─────────────────────────┤
│ 0x41414141 (= "AAAA") │ ← message pointer bị ghi đè!
│ ... │
└─────────────────────────┘→ Khi lameguy->message(lameguy->buffer) được gọi, chương trình nhảy tới địa chỉ 0x41414141 → crash, hoặc nếu attacker điền địa chỉ shellcode → code execution.
2.5 Use-After-Free (UAF)
Định nghĩa: Lỗ hổng xảy ra khi một vùng nhớ heap đã được giải phóng (free()) nhưng vẫn còn một con trỏ (dangling pointer) trỏ tới đó và tiếp tục được sử dụng.
Dangling Pointer / Stale Pointer / Wild Pointer:
Mức độ phổ biến:
UAF cực kỳ phổ biến – chỉ tính riêng Firefox, có hàng nghìn CVE liên quan. Thường xuất hiện nhiều nhất trong:
- Web browsers (Firefox, Chrome)
- PDF readers (Foxit Reader)
- Android kernel
- Bluetooth stack
Khai thác UAF:
struct toystr {
void (*message)(char *);
char buffer[20];
};
struct person {
int favorite_num; // 4 bytes
int age; // 4 bytes
char name[16]; // 16 bytes
};
// Bước 1: Cấp phát và free toystr → dangling pointer còn trỏ vào
struct toystr *victim = malloc(sizeof(struct toystr));
victim->message = &safe_function;
free(victim); // victim vẫn là dangling pointer!
// Bước 2: Cấp phát struct person vào CÙNG vùng nhớ đó
struct person *p = malloc(sizeof(struct person));
p->favorite_num = 0x41414141; // ghi vào cùng offset với message!
// Bước 3: Dùng dangling pointer để gọi message
// victim->message bây giờ = 0x41414141
victim->message(victim->buffer); // nhảy tới 0x41414141 → kiểm soát EIP!
Tại sao UAF là kỹ thuật khai thác ưa thích?
2.6 Heap Spraying
Nguyên lý:
Lấp đầy một lượng lớn heap bằng payload (shellcode hoặc ROP chain) → xác suất cao là bất kỳ địa chỉ nào trong heap cũng trỏ vào payload.
char *filler = "AAAAAAA..."; // payload (shellcode/ROP chain)
for (int i = 0; i < 3000; i++) {
char *temp = malloc(1000000); // mỗi lần 1MB
memcpy(temp, filler, 1000000);
}
// → 3GB của heap chứa đầy payload
// → địa chỉ 0x23456789 có 75% xác suất trỏ vào payload
Hiệu quả trên kiến trúc 32-bit:
- Không gian địa chỉ tối đa: 4GB (2³²).
- Spray 3GB → 75% xác suất địa chỉ bất kỳ là hợp lệ.
- Dùng trong browser exploit qua JavaScript:
// Ví dụ heap spray qua JavaScript trong trang HTML độc hại:
var memory = new Array();
for (var i = 0; i < 0x100; i++) {
memory[i] = ROPNOP + ROP_CHAIN;
}Hạn chế trên 64-bit:
2.7 Metadata Corruption
Nguyên lý: Khai thác bằng cách phá vỡ metadata của heap chunk (các trường size, prev_size, fd, bk) để lừa trình cấp phát thực hiện ghi tùy ý vào bộ nhớ.
Cấu trúc chunk và metadata:
Allocated chunk:
┌──────────────────────────────────┐
│ prev_size ← *(buffer - 2) │
│ size (flags) ← *(buffer - 1) │ ← Heap metadata
├──────────────────────────────────┤
│ payload ← *buffer │ ← User data
└──────────────────────────────────┘
Freed chunk:
┌──────────────────────────────────┐
│ prev_size ← *(buffer - 2) │
│ size (flags) ← *(buffer - 1) │ ← Heap metadata
├──────────────────────────────────┤
│ fd ← *buffer │ ← Forward pointer
│ bk ← *(buffer + 1) │ ← Backward pointer
└──────────────────────────────────┘Kỹ thuật Heap Unlink:
Khi free() được gọi và hai chunk liền kề đều trống, glibc sẽ coalesce (gộp) chúng bằng thao tác unlink từ doubly linked list:
// Thao tác unlink (đơn giản hóa):
// P->fd->bk = P->bk
// P->bk->fd = P->fd
Nếu attacker ghi đè fd và bk của một chunk đã free (qua overflow), thao tác unlink sẽ thực hiện ghi tùy ý:
fd = địa chỉ muốn ghi
bk = giá trị muốn ghi
→ *(fd + 12) = bk (ghi đè GOT entry, function pointer, v.v.)3. Câu hỏi trắc nghiệm
Câu 1. DEP (Data Execution Prevention) ngăn chặn điều gì trong khai thác buffer overflow?
- A. Ghi đè return address trên stack
- B. Thực thi shellcode được inject vào các vùng nhớ dữ liệu như stack, heap
- C. Dò tìm địa chỉ hàm libc
- D. Sử dụng stack canary
Câu 2. Nguyên tắc W^X có nghĩa là gì?
- A. Một vùng nhớ phải có cả quyền Write và Execute
- B. Không có vùng nhớ nào được phép vừa có quyền Write vừa có quyền Execute
- C. Chỉ vùng nhớ có quyền Execute mới được đọc
- D. Write trước, Execute sau
Câu 3. ROP (Return Oriented Programming) khác Return-to-libc ở điểm nào chính?
- A. ROP không cần biết địa chỉ của bất kỳ hàm nào
- B. ROP không cần dùng toàn bộ hàm, chỉ dùng các đoạn code nhỏ (gadget) kết thúc bằng
ret - C. ROP bypass được ASLR hoàn toàn
- D. ROP chỉ hoạt động trên kiến trúc 64-bit
Câu 4. Gadget trong ROP phải kết thúc bằng lệnh gì?
- A.
call - B.
jmp - C.
ret - D.
nop
Câu 5. Trong ROP chain, cơ chế nối (chain) các gadget với nhau dựa trên điều gì?
- A. Ghi địa chỉ của gadget tiếp theo vào thanh ghi
eax - B. Sắp xếp địa chỉ các gadget theo thứ tự trên stack
- C. Sử dụng lệnh
jmpđể nhảy giữa các gadget - D. Liên kết địa chỉ gadget qua linked list
Câu 6. Gadget ẩn (hidden gadget) trong x86 là gì?
- A. Gadget nằm trong vùng nhớ đã được mã hóa
- B. Gadget hình thành từ việc đọc giữa chừng các byte của lệnh assembly khác, do x86 dùng encoding biến độ dài
- C. Gadget chỉ có trong thư viện libc
- D. Gadget không có lệnh
ret
Câu 7. Công cụ nào sau đây thường được dùng để tìm ROP gadget trong binary?
- A.
gdb - B.
ROPgadget - C.
strace - D.
objdump(chỉ duy nhất)
Câu 8. Khi dùng gadget pop eax; ret trong ROP chain để gán giá trị 4 vào eax, attacker cần làm gì?
- A. Đặt giá trị
4vào thanh ghiebxtrước - B. Đặt giá trị
4ngay sau địa chỉ của gadget trên stack - C. Gọi hàm libc để set eax
- D. Không cần làm gì thêm
Câu 9. Stack Pivoting trong ROP là kỹ thuật gì?
- A. Tăng kích thước stack để chứa nhiều gadget hơn
- B. Thay đổi giá trị
espđể trỏ tới vùng nhớ khác mà attacker kiểm soát - C. Đặt gadget xen kẽ với dữ liệu trên stack
- D. Dùng nhiều gadget
retliên tiếp
Câu 10. Vì sao ROP chain bypass được DEP/NX?
- A. ROP vô hiệu hóa bit NX trong page table
- B. ROP không inject code mới mà tái sử dụng code đã có trong vùng nhớ
.text(có quyền execute) - C. ROP chạy trên kernel mode
- D. ROP dùng syscall trực tiếp không qua user space
Câu 11. Heap trong chương trình C phát triển theo hướng nào trong bộ nhớ?
- A. Từ địa chỉ cao xuống địa chỉ thấp (giống stack)
- B. Từ địa chỉ thấp lên địa chỉ cao
- C. Ngẫu nhiên
- D. Không phát triển, kích thước cố định
Câu 12. Trường prev_size trong heap chunk có ý nghĩa gì?
- A. Kích thước của chunk hiện tại
- B. Kích thước của chunk trước đó – chỉ hợp lệ khi chunk trước đã được free (P=0)
- C. Số lần chunk đã được cấp phát
- D. Con trỏ tới chunk trước
Câu 13. Trong ptmalloc, bit P trong trường size của heap chunk có ý nghĩa gì?
- A. Protected – chunk được bảo vệ
- B. Previous-in-use – P=1 nghĩa là chunk liền trước đang được sử dụng (allocated)
- C. Pointer – trỏ tới chunk tiếp theo
- D. Page – chunk được cấp phát từ mmap
Câu 14. Tại sao heap không có cơ chế bảo vệ như stack canary?
- A. Heap được bảo vệ bởi ASLR thay thế
- B. Heap không có return address để bảo vệ, và cấu trúc heap phức tạp hơn stack
- C. Stack canary quá tốn tài nguyên để áp dụng cho heap
- D. Heap không bao giờ bị overflow
Câu 15. Trong ví dụ Heap Overflow với struct toystr, fgets(coolguy->buffer, 200, stdin) gây ra lỗi vì lý do gì?
- A.
fgetskhông hoạt động với heap - B. Buffer chỉ có 20 bytes nhưng cho phép nhập tới 200 bytes
- C.
coolguychưa được khởi tạo - D.
stdinkhông hợp lệ
Câu 16. Use-After-Free (UAF) là lỗ hổng thuộc loại nào?
- A. Memory corruption (hỏng bộ nhớ)
- B. Logic error / pointer mismanagement (quản lý con trỏ sai)
- C. Integer overflow
- D. Format string vulnerability
Câu 17. “Dangling pointer” là gì?
- A. Con trỏ NULL
- B. Con trỏ trỏ tới vùng nhớ đã được
free()nhưng chưa được gán NULL - C. Con trỏ tới stack
- D. Con trỏ tới địa chỉ cao nhất trong bộ nhớ
Câu 18. Để khai thác UAF, attacker thường làm gì sau khi vùng nhớ bị free()?
- A. Ghi đè return address trên stack
- B. Gọi
malloc()với một kiểu struct khác để chiếm lại vùng nhớ đó, sau đó thao tác qua dangling pointer - C. Dùng heap spraying để lấp đầy heap
- D. Vô hiệu hóa stack canary
Câu 19. Tại sao UAF phổ biến trong web browser?
- A. Browser dùng ngôn ngữ unsafe như Assembly
- B. Browser quản lý nhiều đối tượng DOM/JS phức tạp có vòng đời khó quản lý, dễ xảy ra free() sớm trong các race condition hoặc event handler
- C. Browser không dùng ASLR
- D. Browser chạy với quyền root
Câu 20. Heap Spraying có phải là lỗ hổng bảo mật không?
- A. Có, là lỗ hổng rất nghiêm trọng
- B. Không, đây là kỹ thuật hỗ trợ khai thác, không phải lỗ hổng
- C. Chỉ là lỗ hổng trên hệ điều hành Windows
- D. Là lỗ hổng trong heap allocator
Câu 21. Heap Spraying giúp bypass cơ chế bảo mật nào?
- A. Stack Canary
- B. ASLR (Address Space Layout Randomization)
- C. DEP/NX
- D. RELRO
Câu 22. Trên kiến trúc 32-bit, nếu spray 3GB payload vào heap (tổng không gian địa chỉ 4GB), xác suất một địa chỉ bất kỳ trỏ vào payload là bao nhiêu?
- A. 25%
- B. 50%
- C. 75%
- D. 100%
Câu 23. Tại sao Heap Spraying toàn bộ không hiệu quả trên kiến trúc 64-bit?
- A. 64-bit có DEP mạnh hơn
- B. Không gian địa chỉ 64-bit (~18 exabyte) quá lớn để spray toàn bộ
- C. Heap allocator 64-bit dùng encryption
- D. ASLR trên 64-bit không thể bypass
Câu 24. Heap Spraying thường được dùng trong môi trường nào nhất?
- A. Kernel exploits
- B. Browser exploits qua JavaScript trong trang HTML độc hại
- C. Network protocol exploits
- D. Firmware exploits
Câu 25. Metadata Corruption khai thác điều gì trong heap?
- A. Con trỏ hàm trong struct người dùng
- B. Các trường metadata của chunk (
size,prev_size,fd,bk) để lừa heap allocator ghi tùy ý vào bộ nhớ - C. Stack frame của hàm
malloc() - D. Thanh ghi CPU
Câu 26. Thao tác “coalescing” trong heap allocator là gì?
- A. Tách một chunk lớn thành nhiều chunk nhỏ
- B. Gộp các chunk trống liền kề lại thành một chunk lớn hơn để giảm phân mảnh
- C. Sao chép dữ liệu giữa các chunk
- D. Sắp xếp lại các chunk theo kích thước
Câu 27. Trong heap unlink exploitation cổ điển, nếu attacker ghi đè fd = addr_target và bk = value, kết quả thao tác unlink là gì?
- A.
*addr_target = addr_target - B. Ghi
valuevào vùng nhớ gầnaddr_target(cụ thể là*(addr_target + 12) = valuetrên 32-bit) - C.
*value = addr_target - D. Xóa
addr_targetkhỏi bộ nhớ
Câu 28. Tại sao kỹ thuật heap unlink cổ điển không còn hoạt động trên glibc hiện đại?
- A. glibc hiện đại không có thao tác unlink
- B. glibc đã thêm kiểm tra tính hợp lệ:
fd->bk == chunkvàbk->fd == chunk - C. Heap hiện đại dùng singly linked list thay vì doubly linked list
- D. Kỹ thuật này bị patch ở kernel level
Câu 29. Lý do chính khiến UAF khó phát hiện bằng static analysis là gì?
- A. UAF code quá ngắn để phân tích
- B. UAF chỉ xảy ra ở một số trạng thái runtime cụ thể, không thể suy ra từ code tĩnh
- C. Compiler tự động loại bỏ dangling pointer khỏi code
- D. Static analyzer không đọc được heap
Câu 30. Phương pháp nào giúp tìm lỗi UAF hiệu quả hơn?
- A. Code review thủ công toàn bộ source code
- B. Symbolic Execution kết hợp Constraint Solver (như KLEE, SAGE)
- C. Kiểm tra checksec output
- D. Chạy thử với input ngẫu nhiên thông thường
Câu 31. Lệnh ret trong x86 thực chất tương đương với lệnh gì?
- A.
jmp [esp] - B.
pop eip(lấy giá trị từ[esp]vàoeip, tăngesplên 4) - C.
push eip - D.
mov eip, [esp+4]
Câu 32. checksec là công cụ dùng để làm gì?
- A. Kiểm tra memory leak
- B. Kiểm tra các cơ chế bảo vệ binary (canary, NX, PIE, RELRO…)
- C. Tìm gadget trong binary
- D. Debug chương trình
Câu 33. PIE (Position Independent Executable) ảnh hưởng thế nào đến khai thác ROP?
- A. PIE không ảnh hưởng đến ROP
- B. PIE ngẫu nhiên hóa địa chỉ load của binary → địa chỉ gadget thay đổi mỗi lần chạy, khó hardcode
- C. PIE vô hiệu hóa lệnh
ret - D. PIE bảo vệ stack
Câu 34. Phân bổ bộ nhớ nào sau đây KHÔNG thuộc heap?
- A.
malloc(100) - B.
calloc(10, sizeof(int)) - C.
int arr[100];(biến local) - D.
realloc(ptr, 200)
Câu 35. Trong ptmalloc, các trường fd và bk trong chunk đã free có tác dụng gì?
- A. Lưu địa chỉ của hàm
free()vàmalloc() - B. Tạo thành doubly linked list của các chunk trống –
fdtrỏ forward (chunk free tiếp theo),bktrỏ backward (chunk free trước đó) - C. Lưu kích thước chunk tiếp theo
- D. Bảo vệ chunk khỏi overflow
Câu 36. Kỹ thuật “Heap Spraying có chủ đích” (targeted spray) trên 64-bit là gì?
- A. Spray toàn bộ không gian địa chỉ 64-bit
- B. Spray vào một vùng heap nhỏ, cụ thể mà attacker đã biết offset hoặc đã leak địa chỉ
- C. Spray chỉ vào phần stack
- D. Spray bằng NOP sled
Câu 37. Trong ví dụ ROP sys_write, gadget leal ecx, [esp+12] có tác dụng gì?
- A. Gán địa chỉ string trực tiếp vào ecx
- B. Gán cho
ecxđịa chỉesp + 12– tức là tự tính địa chỉ của data trên stack (nơi string được đặt sẵn) - C. Đọc giá trị tại
esp+12vàoecx - D. Tăng
ecxlên 12
Câu 38. Điều kiện nào cần thiết để khai thác heap overflow ghi đè function pointer?
- A. Chương trình phải có ASLR tắt
- B. Có một buffer ghi (write buffer) trên heap và một function pointer nằm trong chunk liền kề phía sau
- C. Stack canary phải bị vô hiệu hóa
- D. Chương trình phải chạy với quyền root
Câu 39. Lỗ hổng “double free” thuộc loại nào?
- A. Stack overflow
- B. Incorrect use of heap – gọi
free()hai lần trên cùng một con trỏ - C. Integer overflow
- D. Use-after-free (hoàn toàn khác biệt)
Câu 40. Tại sao trong ví dụ ROP chain exit(0), cần dùng gadget inc eax thay vì mov eax, 1?
- A. Không tìm thấy gadget
mov eax, 1trong binary - B.
inc eaxnhanh hơn - C.
mov eax, 1bị cấm bởi DEP - D.
inc eaxan toàn hơn
Câu 41. ptmalloc là gì và được dùng ở đâu?
- A. Là thuật toán sắp xếp bộ nhớ trong Windows
- B. Là phiên bản heap allocator được dùng trong glibc (GNU C Library) – chuẩn trên các hệ thống Linux
- C. Là công cụ debug heap
- D. Là cơ chế ASLR cho heap
Câu 42. Vì sao khai thác Heap phức tạp hơn Stack overflow truyền thống?
- A. Heap allocator có stack canary riêng
- B. Heap không có địa chỉ return để ghi đè trực tiếp; phải ghi đè function pointer, metadata, hoặc tận dụng UAF – đòi hỏi hiểu sâu về allocator internals
- C. Heap được bảo vệ bởi NX mạnh hơn stack
- D. Heap luôn bật ASLR
Câu 43. Trong context bảo mật, “attack surface” là gì?
- A. Bề mặt vật lý của máy chủ
- B. Tập hợp tất cả các điểm trong hệ thống mà attacker có thể tương tác để tấn công
- C. Diện tích màn hình của ứng dụng
- D. Số lượng bug trong code
Câu 44. Kỹ thuật nào kết hợp ROP và Heap để thực hiện exploit phức tạp hơn?
- A. Chạy ROP chain từ vùng nhớ heap thay vì stack
- B. Spray ROP chain lên heap (heap spray), sau đó dùng bug để redirect EIP vào heap
- C. Đặt gadget trên heap
- D. Cả A và B đều đúng
Câu 45. Byte \x90 trong context shellcode thường được gọi là gì và có tác dụng gì trong heap spray?
- A. Null byte – kết thúc chuỗi
- B. NOP (No Operation) – tạo “NOP sled” giúp EIP trượt về phía shellcode thật dù không nhảy chính xác vào đầu payload
- C. RET byte – thực hiện return
- D. Interrupt byte – gọi syscall
Câu 46. Lỗ hổng nào sau đây KHÔNG thuộc nhóm “Heap-specific vulnerabilities”?
- A. Use-After-Free
- B. Heap Spraying
- C. Format String vulnerability
- D. Metadata Corruption
Câu 47. Tại sao function pointer trong struct trên heap là mục tiêu hấp dẫn cho heap overflow?
- A. Function pointer dễ tìm hơn return address
- B. Ghi đè function pointer và buộc code gọi nó → kiểm soát EIP mà không cần stack overflow
- C. Function pointer không được bảo vệ bởi bất kỳ cơ chế nào
- D. Function pointer luôn nằm ở đầu struct
Câu 48. Thuật ngữ nào mô tả đúng nhất về UAF trong context lập trình C++?
- A. Dangling reference sau khi object bị destroy
- B. Memory leak khi quên delete
- C. Stack corruption do recursion sâu
- D. Null pointer dereference
Câu 49. Nếu một binary có NX enabled nhưng không có ASLR, attacker có thể làm gì?
- A. Không thể khai thác được
- B. Dùng ROP với địa chỉ gadget cố định (không cần leak) vì binary load ở địa chỉ cố định
- C. Phải dùng heap spray
- D. Chỉ có thể gây crash
Câu 50. Tại sao UAF chiếm tỷ lệ cao nhất (khoảng 62%) trong các CVE khai thác browser (2009–2013) so với Heap Overflow và các loại khác?
- A. UAF dễ viết code khai thác hơn
- B. UAF không cần memory corruption, có thể dùng cho cả information leak lẫn code execution, và rất phổ biến trong engine JavaScript/DOM phức tạp
- C. Browser không có cơ chế bảo vệ chống UAF
- D. UAF bypass được tất cả các cơ chế bảo mật
Câu 51. Khi biên dịch với -z execstack, điều gì xảy ra?
- A. Stack được bảo vệ bằng canary mạnh hơn
- B. Stack được phép thực thi code (NX bị tắt cho stack) – cho phép shellcode trên stack chạy
- C. Stack size tăng lên
- D. ASLR bị tắt
Câu 52. Công cụ nào sau đây hỗ trợ tốt nhất cho việc khai thác binary (CTF/pwn)?
- A.
Wireshark - B.
pwntools(Python library) - C.
Nmap - D.
Burp Suite
Câu 53. Trong ví dụ khai thác UAF với struct toystr và struct person, điều kiện gì cần thiết để khai thác thành công?
- A. Hai struct phải có kích thước khác nhau hoàn toàn
- B.
sizeof(struct person)phải bằng (hoặc xấp xỉ)sizeof(struct toystr)để malloc() cấp phát lại cùng vùng nhớ - C.
struct personphải được khai báo trướcstruct toystr - D. Cần heap spray trước
Câu 54. “Bin” trong ngữ cảnh ptmalloc heap là gì?
- A. Thư mục chứa binary executable
- B. Danh sách (list) các chunk trống được nhóm theo kích thước, dùng để phân bổ lại nhanh
- C. Vùng nhớ dự phòng
- D. Bảng metadata của heap
Câu 55. Sự khác nhau cơ bản giữa heap overflow và stack overflow về tác động?
- A. Không có sự khác nhau
- B. Stack overflow thường ghi đè return address → kiểm soát EIP ngay lập tức; Heap overflow thường ghi đè dữ liệu/con trỏ trong các object → cần code path gọi đến dữ liệu bị hỏng
- C. Heap overflow luôn nguy hiểm hơn
- D. Stack overflow không thể bypass DEP