Buổi 10: ROP & Heap Overflow

Mục lục

  1. Tấn công ROP (Return Oriented Programming)
  2. Khai thác lỗ hổng trên bộ nhớ Heap
  3. Câu hỏi trắc nghiệm

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ốngKỹ thuật tương ứng
Off-by-one, buffer nhỏ, không ghi đè được return addrCần kỹ thuật phức tạp hơn
Stack không cho phép thực thi codeReturn-to-libc hoặc ROP
Shellcode quá dài, phức tạpROP

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 được

Khi 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:      disabled

1.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
ret

Nhiề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 ở:

  1. Trong các hàm thông thường của binary.
  2. Ẩ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: 2

1.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 syscall

Cá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            @ 0x08054390

Layout 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:

sequenceDiagram participant S as Stack participant EIP as EIP (instruction pointer) participant ESP as ESP (stack pointer) Note over S: Hàm bị khai thác gọi ret S->>EIP: pop addr gadget1 → EIP = 0x08054134 Note over ESP: ESP tăng 4, trỏ đến addr gadget2 Note over EIP: gadget1 thực thi: xor eax,eax EIP->>S: gadget1 gọi ret S->>EIP: pop addr gadget2 → EIP = 0x08053168 Note over ESP: ESP tăng 4, trỏ đến addr gadget3 Note over EIP: gadget2 thực thi: xor ebx,ebx EIP->>S: gadget2 gọi ret S->>EIP: pop addr gadget3 → EIP = 0x08056243 Note over ESP: ESP tăng 4, trỏ đến addr int 0x80 Note over EIP: gadget3 thực thi: inc eax EIP->>S: gadget3 gọi ret S->>EIP: pop addr int0x80 → EIP = 0x08054390 Note over EIP: int 0x80 → syscall exit(0) ✓

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 0x80

Cá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; ret

Layout 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 memory

2.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/bk tạ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

AllocatorSử dụng trong
dlmallocTrình cấp phát tổng quát
ptmallocglibc (Linux phổ biến nhất)
tcmallocGoogle Chrome, Go
jemallocFreeBSD, Firefox
nedmallocGame engines
HoardMulti-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.

flowchart LR A["malloc() → ptr"] --> B["Sử dụng ptr"] B --> C["free(ptr)"] C --> D["Vùng nhớ trống\n(có thể được cấp phát lại)"] B --> E["ptr vẫn trỏ vào\nvùng nhớ cũ\n(dangling pointer)"] E --> F["Sử dụng ptr lần nữa\n← USE-AFTER-FREE!"] D --> G["malloc() lần 2\n→ cùng địa chỉ"] G --> H["Ghi dữ liệu mới\nvào vùng nhớ"] H --> F

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 đè fdbk 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ị 4 vào thanh ghi ebx trước
  • B. Đặt giá trị 4 ngay 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 ret liê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. fgets khô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. coolguy chưa được khởi tạo
  • D. stdin khô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_targetbk = value, kết quả thao tác unlink là gì?

  • A. *addr_target = addr_target
  • B. Ghi value vào vùng nhớ gần addr_target (cụ thể là *(addr_target + 12) = value trên 32-bit)
  • C. *value = addr_target
  • D. Xóa addr_target khỏ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 == chunkbk->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ào eip, tăng esp lê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 fdbk trong chunk đã free có tác dụng gì?

  • A. Lưu địa chỉ của hàm free()malloc()
  • B. Tạo thành doubly linked list của các chunk trống – fd trỏ forward (chunk free tiếp theo), bk trỏ 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+12 vào ecx
  • D. Tăng ecx lê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, 1 trong binary
  • B. inc eax nhanh hơn
  • C. mov eax, 1 bị cấm bởi DEP
  • D. inc eax an 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 toystrstruct 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 person phải được khai báo trước struct 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