14 KiB
14 KiB
name, description, capabilities
| name | description | capabilities | |
|---|---|---|---|
| re-exploit | 利用开发:ROP 链构造、堆利用。 触发词:ROP、堆利用、fastbin、tcache、利用开发、exploit |
|
利用开发(ROP 链构造 / 堆利用)
何时使用 / 何时不用
- 用:拿到已定位的漏洞(越界读写 / 崩溃 / 危险输入)要升级为 RCE / 任意读写——漏洞分析(re-crash-triage / re-vuln)移交的利用开发环节
- 用:ROP 链构造——gadget 搜索、栈布局、ret2csu / SROP / ret2dlresolve
- 用:堆利用——fastbin / tcache 攻击、unsorted bin 泄露、double free 系列
- 用:seccomp 沙箱绕过(ORW:open / read / write 链)
- 不用:入门级 pwn(ret2win / 简单 ret2libc / 格式化字符串)→ re-pwn
- 不用:只有崩溃还没有定位(→ re-crash-triage);还没崩溃找 bug(→ re-fuzzing)
- 注意:利用验证是动态执行,默认在沙箱内跑(re-analyze/platform-tips 最高原则,见 re-sandbox);先规划再写(坑 4),组合绕过分步验证
工具准备
参考 re-analyze/platform-tips——利用验证默认沙箱;静态准备(gadget 搜索 / 规划)可免沙箱,动态验证进沙箱。
pwntools(pip 安装,见 re-pwn)—— 利用脚本主力
pip install pwntools(官方 PyPI,当前正式版 4.15.0)——Python 版本兼容性以 re-pwn 的「工具准备」为准(版本矩阵只在该处维护一份,避免两处漂移)- 发行版包:Debian/Ubuntu
sudo apt install python3-pwntools、Fedorasudo dnf install python3-pwntools、Archsudo pacman -S python-pwntools(Extra 仓库官方包) - Python 3.12+(Ubuntu 24.04 自带 3.12)直接 pip 装报 PEP 668
externally-managed-environment——对策:venv(python3 -m venv ~/venvs/pwn && source ~/venvs/pwn/bin/activate)或发行版包或--break-system-packages - 验证:
pwn --version/python3 -c "import pwn; print(pwn.version)"
ropper / ROPgadget —— gadget 搜索
- ropper:
pip install ropper(官方 PyPI);Archsudo pacman -S ropper(Extra 官方包);Debian/Ubuntu/Fedora 无官方包 → pip - ROPgadget:
pip install ROPgadget(官方 PyPI);Debian/Ubuntusudo apt install python3-ropgadget、Fedorasudo dnf install python3-ROPGadget、Archsudo pacman -S ropgadget(均为官方包) - 验证:
ropper --version;ROPgadget --binary ./target | head;常用搜索:ROPgadget --binary ./target | grep 'pop rdi'、ropper -f ./target --search "pop rdi"
gdb + gef/pwndbg(见 re-gdb)—— 栈/堆布局验证
- gdb: Debian/Ubuntu
sudo apt install gdb、Fedorasudo dnf install gdb、Archsudo pacman -S gdb、macOSbrew install gdb;32 位目标 Debian/Ubuntu 补gdb-multiarch+libc6-i386 - 堆利用调试建议装 pwndbg:
heap/bins(tcachebins / fastbins / unsorted)命令直接看 chunk 布局与 bin 链表(gef 也有heap bins,功能略弱);验证: gdb 里heap bins有输出 - pwndbg 内置
checksec(见 re-pwn 工具准备)
angr(可选,见 re-angr)—— 溢出大小 / 路径约束自动化
pip install angr——Python 版本兼容性以 re-angr 的「工具准备」为准(版本矩阵只在该处维护一份,避免两处漂移)- 用:溢出偏移不确定(符号化输入求覆盖返回地址的约束)、利用路径校验逻辑复杂
- 验证:
python3 -c "import angr; print(angr.__version__)"
操作步骤
按顺序执行;每步产物(利用面结论、gadget 清单、链布局、PoC)记录证据路径 + sha256(存证方法见 re-triage),供报告引用。
-
利用面分析(崩溃点 / 可控数据):
- 输入:re-crash-triage 的最小 PoC / 漏洞报告(崩溃点 + 输入通道)——确认可控字节范围与偏移(
cyclic定位崩溃时 rip 偏移,见 re-pwn 步骤 2) pwn checksec --file ./target:NX / PIE / Canary / RELRO——每项决定一条限制:栈不可执行 → 不能直接跑 shellcode;PIE → 地址要泄露 base;RELRO full → GOT 不可写(改函数指针 / 栈地址 / libc 钩子替代)- seccomp 检查:
seccomp-tools dump ./target(Gem:gem install seccomp-tools,官方途径;Arch 官方仓库无此包——仅 AUR 有seccomp-tools,直接用 gem 即可)或静态找prctl/seccomp调用——若禁execve则拿 shell 路线作废,改 ORW 链(步骤 4) - 记录:崩溃偏移、保护矩阵、seccomp 规则、可利用原语(任意写 / 栈溢出 / 堆越界)
- 输入:re-crash-triage 的最小 PoC / 漏洞报告(崩溃点 + 输入通道)——确认可控字节范围与偏移(
-
ROP 链(gadget 搜索、栈布局):
ROPgadget --binary ./target | grep -E 'pop rdi|pop rsi|pop rdx' # 64 位传参 gadget ROPgadget --binary ./target | grep 'ret' | head # 对齐用 ret ropper -f ./target --search "syscall" # SROP / shellcode 跳板- 布局模板(64 位,先泄露后执行两段式):
padding + pop_rdi_ret + arg1 + func_addr + [对齐 ret] + 下一段... - 参数顺序:rdi → rsi → rdx(SysV ABI);缺 rdx gadget 时用 ret2csu(
__libc_csu_init里的pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret+ 搬运 mov 段)或 ret2dlresolve - 16 字节对齐:
call时$rsp % 16 == 0才满足movaps(glibc 新版本system/printf内部会用),不对齐在 gadget 链前补一个ret - 栈布局产物:链注释图(偏移 + gadget 地址 + 参数)+ 算好的偏移表
- 布局模板(64 位,先泄露后执行两段式):
-
堆利用(fastbin / tcache 攻击模式):
- 先看 glibc 版本(
ldd ./target | grep libc)决定攻击面(坑 3):- tcache(glibc ≥ 2.26):tcache poisoning——double free 后改
fd到目标地址附近 → 下一次分配拿到任意地址(2.32+ 指针被 safe-linking 异或,坑 3);UAF 直接改 fd 更快 - fastbin(glibc < 2.26 / 老题):fastbin dup——double free 同一 chunk 两次 → 同一地址连续分配两次 → 伪造堆上任意写
- unsorted bin 泄露:free 大 chunk(> fastbin 上限)进 unsorted bin,fd/bk 指向 main_arena ——
show/格式化读泄露 libc base(libc_base = leaked - main_arena_offset)
- tcache(glibc ≥ 2.26):tcache poisoning——double free 后改
- 用 pwndbg 逐 chunk 验证:
(gdb) heap # 看 chunk 布局与地址 (gdb) bins # 看各 bin 链表(tcachebins/fastbins/unsorted) - 模式清单:double free → tcache dup;UAF → fd 覆写 / 泄露;off-by-one(单字节溢出)→ 改 next chunk size 造重叠 chunk;house of 系列(高版本利用)标注复杂度再上
- 目标落地:任意地址分配 → 改
__free_hook(≤2.33)/__malloc_hook/ GOT 可写项 / 栈返回地址 → 触发 get shell 或任意读写
- 先看 glibc 版本(
-
沙箱 / 缓解绕过(seccomp):
- 若步骤 1 发现禁
execve:ORW 链——ROP 依次调open(flag_path, 0)→read(fd, buf, n)→write(1, buf, n):ROPgadget --binary ./target | grep -E 'syscall|pop rdi|pop rsi|pop rdx' # 或 libc 里的 gadget - 找不到现成
syscallgadget 时用 libc 的(先泄露 libc base)或 SROP(rt_sigreturn构造寄存器帧) - 缓解绕过思路:PIE → 先泄露 base(格式化 / 堆泄露 / puts 泄露,见 re-pwn 步骤 3-4);Canary → 泄露后再溢出(payload = pad + canary + pad + ret,canary 低 1 字节为
\x00可直接截断处理);RELRO full → 不打 GOT,改栈 /__free_hook/environ泄栈地址 - 组合顺序纪律:先泄露(canary / libc base / heap base)→ 再覆盖(坑 4)
- 若步骤 1 发现禁
-
PoC 验证:
from pwn import * context.binary = './target'; context.log_level = 'debug' p = process('./target') # 沙箱内本地;远程: remote(host, port) p.sendline(payload) p.interactive()- 验证纪律:逐步验证每段链(泄露段先打,看到泄露值对得上再打覆盖段)——一次全链失败无法定位是哪段错(坑 4);gdb 断在链入口逐条
si确认跳转与栈布局 - 本地跑通 → 记录 PoC(文件 + sha256 + 运行输出)→ 远程最终验证;PoC hash 与漏洞特征可进 re-ioc
- 验证纪律:逐步验证每段链(泄露段先打,看到泄露值对得上再打覆盖段)——一次全链失败无法定位是哪段错(坑 4);gdb 断在链入口逐条
内核利用
与用户态同框架:fuzz(re-fuzzing)→ crash(re-crash-triage)→ 利用(本路径)。
- 提权原语:modprobe_path 覆写(触发内核执行任意路径)、cred 结构覆写(uid/gid 置 0)、io_uring / BPF 子系统利用面
- 堆喷与对象布局:堆喷策略(同尺寸对象占位)、对象重叠(UAF 后伪造对象)、SLUB 分配器行为(per-CPU 缓存)
- 内核 UAF 利用路径:漏洞触发(悬垂引用)→ 对象重用(占位伪造)→ 控制流劫持(函数指针 / ops 表覆写)
- 环境:调试内核(KASAN 开、KASLR 关闭或绕过)、gdb/kgdb 断点、模块化测试(只读分析 + 沙箱验证)
跨域联合
- re-vuln:本技能被漏洞挖掘网关引用——网关 4-5 步(逆向定位 → 报告)之后,可利用的漏洞进入本技能做利用开发(漏洞挖掘域"从崩溃到 PoC"的收口)
- re-crash-triage:崩溃分析 / 最小 PoC 输入交接本技能(利用面分析第一步的输入);本技能产出的 PoC 反过来验证漏洞可达性
- re-pwn:入门衔接——栈溢出 / 格式化字符串 / ret2libc 基础在本技能扩展为复杂 ROP 与堆利用
- re-ctf:pwn 赛题进阶(堆题 / 高难度 ROP 题)经网关调度本技能
- re-angr(可选):溢出偏移求约束、利用路径校验自动化
- re-gdb:栈 / 堆布局动态验证(pwndbg
heap/bins);re-binary-core 底座定位原语与后门 - re-sandbox / re-analyze/platform-tips:利用验证默认沙箱内跑(最高原则);远程目标仅做最终验证
常见坑与陷阱
- gadget 地址受 ASLR / PIE 影响(泄漏为先):现象——按静态地址(
objdump出的 gadget / 函数地址)写链,本地偶发成功远程必挂,或泄露后算出的 base 对不上;原因——PIE 开启时二进制基址随 ASLR 变化(低 12 位页内偏移不变),libc 基址同理;静态地址只在无 PIE(或已算 base)时成立;对策——泄漏为先:第一段链先泄露(puts/printf 打 GOT 或堆/格式化泄露),libc_base = leaked - offset、binary_base = leaked - offset,后续所有 gadget/函数地址 = base + 静态偏移(低 12 位直接拼);每次泄露后重新计算再发覆盖段,别复用上一轮的 base - tcache 双链表检查(safe-linking 异或 / key 检查):现象——glibc 2.32+ 上 classic tcache poisoning 崩(
malloc(): unaligned tcache chunk detected)或 double free 直接 abort(free(): double free detected in tcache 2);原因——2.32 起 tcache fd 被 safe-linking 异或(PROTECT_PTR(pos, ptr) = ptr ^ (pos >> 12),pos 是该 chunk 的 fd 字段自身存储地址(常规 chunk 即chunk_addr + 0x10),不是 heap_base);2.29 起 double free 检查 key 字段:free 进 tcache 时把 key 写成 tcache 结构地址,再 free 同 chunk 时发现 key 仍等于 tcache 指针就遍历整个 bin 找重复(命中直接malloc_printerrabort)——"中间插一次 free 别的 chunk"不清除首个 chunk 的 key 字段,绕不过;对策——写新 fd 前先泄露堆地址再编码:stored_fd = new_fd ^ (fd_field_addr >> 12)(UAF 读到的 fd 是已解密指针,但新值必须重新编码——PROTECT_PTR作用于每次写入,只有原样写回才免编码);double free 绕 key 检查只有两条路:① UAF 改写 key 字段(改成非 tcache 指针值)② 改 size 使 chunk 落入不同 bin(key 检查只看目标 bin 的链头,落别的 bin 不触发) - glibc 版本差异(check 变化):现象——本地(如 glibc 2.31)打通的堆利用,题目远程(2.35 / 2.27)行为完全不同:tcache 不存在 / 数量上限不同 /
__free_hook没了 / safe-linking 出现;原因——堆管理器每版本收紧检查:2.26 引入 tcache、2.29 加 key 检测、2.32 加 safe-linking、2.34 删__malloc_hook/__free_hook(2.34+ 用__libc_malloc指针或环境劫持)、2.37 起global_max_fast收窄为uint8_t(fastbin 上限随之收紧)、tcache 初始化改用__getrandom_nocancel;对策——ldd ./target | grep libc先确认版本,攻击面按版本选(步骤 3 清单);本地环境与远程不一致时用patchelf --set-interpreter ./ld-2.35.so --replace-needed libc.so.6 ./libc-2.35.so换 libc 本地复现;2.34+ 换目标(__malloc_hook不存在 → 打exit函数指针 /setcontext/ vtable 思路);每个 check 收紧点(key / safe-linking / count)列检查清单逐步验证 - 绕过组合复杂(先规划再写):现象——防护多个叠加时(NX+PIE+Canary+seccomp+高版本 glibc)脚本越改越乱,动不动全崩,调试半天发现是某一小段逻辑错;原因——每个保护是独立约束,组合后依赖链长(泄露 canary → 泄露 libc → 泄露堆 → ORW 链),一步错全盘错,边写边试没有全局规划;对策——先写利用规划表再写代码:列清(1)需要哪些原语(栈溢出 / UAF / 格式化写)(2)需要泄露什么、泄露顺序与依赖(canary 必须在覆盖前、libc base 必须在调 libc 函数前)(3)每段链的入口与出口;然后分步实现分步验证:泄露段单独跑通(值对得上)→ 覆盖段单独跑通(gdb 确认返回地址命中)→ 合链;gdb 断点逐段
si;每改一步先确认上一步仍通 - 对齐问题(movaps / 栈对齐):现象——ROP 链到
system或printf就段错误,gdb 看指令在movaps处崩,链前半段明明全对;原因——x86-64 ABI 要求call时栈 16 字节对齐,GCC 生成的 libc 函数(printf/system/execve)内部movaps假设对齐,不对齐直接 SIGSEGV;对策——在调用 gadget 链里、目标函数前补一个retgadget(ret使$rsp += 8调整对齐);或选对齐版 gadget(pop rdi; ret+ 偶数字节 padding);64 位所有 libc 函数调用前都按此检查