文章总结: 本文系统梳理了CTFPwn挑战中常见防护机制(NX、RELRO、StackCanary、PIE等)的绕过思路与实战攻击技术,重点解析了ret2libc、ROP、栈迁移等核心利用手法。文档通过对比不同防护组合下的攻击路径,提供了从信息泄露到最终获取shell的完整链式利用方案,具有较强的实战指导价值。 综合评分: 85 文章分类: CTF,二进制安全,Pwn,漏洞分析,实战经验
《打造ctf-Pwn自动化利用框架》的基础知识点
原创
MicroPest MicroPest
MicroPest
2026年1月3日 20:20 安徽
在小说阅读器读本章
去阅读
2026,如期而至。
在《打造ctf-Pwn自动化利用框架的Pwn灵境》《打造ctf-Pwn自动化利用框架的Pwn灵境(二)(案例说明)》中有很多pwn方面的知识点,作了一些摘要笔记。
| | | | | | — | — | — | — | | 防护 | 核心作用 | 典型绕过思路 | 适用攻击方式 | | NX (DEP) | 栈/堆不可执行 | 无法直接跑 shellcode | → ret2libc / ret2csu / ROP / ret2syscall | | RELRO | – Partial → GOT 可写 – Full → GOT 只读 | Partial:直接改 GOT Full:不能改 GOT,只能改别的指针 | → Partial: ret2plt / GOT 覆写 → Full: ret2libc / ROP / 打 BSS 函数指针 / 利用 IO_FILE 等结构 | | GOT/PLT | 延迟绑定跳板 | GOT 可写时 → 劫持函数指针 | → Partial RELRO: GOT 覆写 → Full RELRO: 只能借 PLT 跳已解析函数,配合 ROP | | Stack Canary | 栈帧末尾 cookie,防栈溢出 | 泄露 or 绕过 | → SSP leak(打印 canary) → partial overwrite(只改低字节保 canary) → 栈迁移到 BSS/heap(避开 canary 检查) | | PIE | 代码段随机化 | 先泄代码基址 | → 信息泄露 → 计算偏移 → ret2libc/ROP | | Full RELRO + NX + PIE + Canary | 全家桶 | 无单点爆破 | → 全链条:信息泄露 → 栈迁移 → ROP → ret2libc |
一句话速记:
- 能塞 shellcode 的唯一场景:「NX 关」→ 直接 shellcode。
- NX 开 → 放弃 shellcode,立刻转 ret2libc / ROP。
- Partial RELRO → 优先 改 GOT(一次写,终身调用)。
- Full RELRO → GOT 只读,只能 ROP + ret2libc 或打 结构体指针。
- Canary → 先 泄 canary 或 partial overwrite,否则把栈迁走。
第一项攻击方式,→ ret2libc / ret2csu / ROP / ret2syscall,作如何理解?
答:把「ret2libc / ret2csu / ROP / ret2syscall」当成同一把“钥匙”的四种齿形——NX 开启后栈不能执行,就把代码段或 libc 里现成的机器指令拼成 chain,让 CPU 去替我们 execve(“/bin/sh”, 0, 0)。一句话:不自带 shellcode,只“借刀杀人”。
- ret2libc(最朴素)
栈上直接放 libc 里 system 的地址 + /bin/sh 字符串地址 + 假返回地址(rdi 参数)。
适用:libc 基址已知、rdi 可控、ASLR 关闭或已泄露。
- ret2csu(万能“传参器”)
x64 下前三个参数用寄存器 rdi/rsi/rdx,而 libc 里找不到一次 pop 三寄存器的 gadget?
所有 ELF 都自带 __libc_csu_init 函数,里面固定有两段“万能 gadget”:
pop rbx, rbp, r12, r13, r14, r15
mov rdx, r13; mov rsi, r14; mov edi, r15; call [r12 + rbx*8]
利用它把 rsi/rdx 传好,再 call 任意函数(read、write、mprotect、system)。
适用:64 位、前三个参数都要控、没有其他顺手可用的 pop 链。
- ROP(通用“拼图”)
把代码段/libc 里无数 pop reg; ret / add rsp, 0xXX; ret / xor rax, rax; ret 等小片段拼成一条长链:
布置参数 → 调函数 → 拿到输出 → 再布置下一组参数 → 调 system 或 one_gadget。
适用:任何架构、任何保护(只要代码段可执行且你能泄基址)。
- ret2syscall(“无 libc”版)
不用 libc 函数,直接找一段 syscall 指令 + 对应寄存器 gadget:
pop rax ; ret # 59 = execve
pop rdi ; ret # “/bin/sh”
pop rsi ; ret # 0
pop rdx ; ret # 0
syscall
适用:静态编译、libc 未知或被打补丁、one_gadget 全失效。
速记:
NX 开 → 不能执行栈 → 只能“借刀”:
ret2libc(借 system)、ret2csu(借传参器)、ROP(借任意 gadget)、ret2syscall(借 CPU 门)。
第二~三项攻击方式,作如何理解?
→ Partial: ret2plt / GOT 覆写
→ Full: ret2libc / ROP / 打 BSS 函数指针 / 利用 IO_FILE 等结构
→ Partial RELRO: GOT 覆写
→ Full RELRO: 只能借 PLT 跳已解析函数,配合 ROP
答:两者非常想像,归总如下,
把 RELRO 当成“能不能改函数指针”的开关,只看 GOT 是否可写——
- Partial RELRO
GOT 段 可写,而且函数第一次调用后 GOT 里就躺着真实地址。
攻击套路:
ret2plt – 用已有的 plt 条目触发任意函数(puts@plt、system@plt),但参数你得控制。
GOT 覆写 – 直接把 GOT 里某个已解析函数的地址改成 system 或 one_gadget ;下次程序再正常调用它时就等于调 system(“/bin/sh”) 。
一句话:有写权限就“偷梁换柱”,一次写终身受用。
- Full RELRO
GOT 段 只读(启动时就解析完并 mprotect 成不可写),你改不了函数指针。
思路:放弃 GOT,去找别的可写且会被调用的指针——
ret2libc / ROP – 正常栈里布置链,用 libc 里 system 等函数;GOT 不能改,但参数寄存器你能填。
打 BSS/数据段函数指针 – 很多程序把 handler / hook 结构放在 BSS,劫持这些指针。
_IO_FILE 结构 – libc 的 FILE 对象里藏着 vtable 指针( _IO_jump_t ),只要你能写这块内存并触发 fclose / printf ,就能把控制流拐进你的 gadget / one_gadget 。
一句话:GOT 只读就“另找指针”,在可写数据区重新造一张“假表”再让程序去用。
速记:
Partial – 改 GOT 本身;
Full – 放弃 GOT,改别的指针或干脆 ROP。
第四项攻击方式,作如何理解?
答:把 canary 当成“栈守护哨兵”——想溢出就必须先拿到或绕过它。三条路对应三句口诀:
- SSP leak(打印哨兵号)
利用格式化串、puts、write 等任意读,先把栈上的 canary 原值打印出来 → 溢出时原封不动写回去,哨兵不报警。
套路: %7$p 泄 canary → 溢出 payload 里把泄露值填到正确偏移。
- partial overwrite(只改低位,不动哨兵)
canary 位于栈帧末尾,后面才是 saved RBP/saved RIP。
用单字节/两字节溢出,只改 RBP/RIP 低字节 → canary 高字节原样保留,程序校验通过。
适用:PIE 开启时代码基址高字节不变,低字节改动即可跳转到附近 gadget。
- 栈迁移(换条街,不经过哨兵)
把栈指针整体搬到 BSS/heap 等可写段(用 leave ret 或 pop rsp )→ 后续 ROP 布置在新栈,原栈 canary 区不再参与溢出检查。
前提:能写目标地址且知道其地址(UAF、堆溢出、任意写)。
速记:
“要么抄答案(leak),要么只改尾巴(partial),要么整条街搬走(栈迁移)”。
第五项攻击方式,作如何理解?
答:把这条链想成“偷看试卷 → 算答案页码 → 翻到满分公式”三步走:
- 信息泄露(偷看试卷)
程序里只要有 printf/puts/write 的任意读,就能把内存“打印”出来——通常泄 1 行就够:
泄一个 代码段地址 → 算出 PIE 基址
泄一个 libc 函数地址(如 __libc_start_main ) → 算出 libc 基址
→ 两张“底牌”到手,ASLR 对你失效。
- 计算偏移(算答案页码)
用泄出的运行时地址 减去 其在 ELF/libc 文件里的静态偏移,得到 基址;
再把你想调用的 system 、 one_gadget 、 pop rdi 等静态偏移 加回去,就得到 运行时真实地址。
公式:
runtime_addr = file_offset + base
base = leaked_runtime – leaked_file_offset
- ret2libc / ROP(翻到满分公式)
现在所有地址已知,栈溢出时直接填:
[pop rdi; ret] → “/bin/sh” → [system]
或更长 ROP 链,最终 execve(“/bin/sh”, 0, 0) —— shell 到手。
速记:
“先偷地址算基址,再把 libc/代码段当成固定靶子打 ret2libc/ROP。”
第六项的攻击方式,作如何理解?
答:把「全链条」想成闯 4 道门,必须按顺序拿钥匙;缺一把就进不了下一关——
- 信息泄露(拿“地图”)
PIE + 全保护开启后,代码段、libc、栈地址全部随机。
先利用任意读(printf/puts/gets 溢出、格式化串、UAF 等)泄露出:
代码段基址(__text)
已解析的 libc 指针(如 __libc_start_main、free、puts…)
→ 算出 libc 基址 & 后续 gadget 地址,拿到第一张「地图」。
2.栈迁移(换“战场”)
Canary 堵死原栈溢出,或栈空间太小不够布置 ROP。
利用溢出/任意写把「rbp」或「栈上保存的 rsp」改成已知可写地址(bss/heap/data),再 leave; ret / pop rsp; ret 把栈切过去。
→ 新栈在「可写 + 可预知地址」区域,绕过 canary,且空间足够。
3.ROP(布置“跳板”)
在新栈上写入一连串 gadget 地址:
pop rdi; ret → 给将要调用的函数传参
pop rsi; ret / pop rdx; ret …
最终跳向 system / execve / one_gadget
→ 用代码段里已有的小指令拼出“调函数”逻辑,绕过 NX(栈不可执行)。
4.ret2libc(执行“致命一击”)
最后一条 ROP 链直接 call system(“/bin/sh”) 或 execve(“/bin/sh”, 0, 0) ;
因为第 1 步已拿到 libc 基址,所以 system 、字符串 /bin/sh 地址全部已知。
→ shell 到手,保护全部绕过。
速记:
“先拿地图再搬家,拼图跳板调 libc。”
可访问:http://8.137.125.6:14001/pwn.html
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:MicroPest MicroPest MicroPest《《打造ctf-Pwn自动化利用框架》的基础知识点》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论