2025年10月

源:MoeCTF 2025 call_it official writeup

其实 JOP 更像是对 ROP 的概念扩充而不是一个新的攻击方式,让我们先来回顾下熟悉的 amd64 ROP。在 ROP 中,我们大概可以将 gadgets 分为三类:

  • 传递参数或做准备的 gadgets(例如 pop rdi)、收集函数返回值的 gadgets(例如 mov rdi, rax
  • 执行有意义操作的 gadgets(例如 systemorwsyscallshellcode)。
  • 控制 ROP 本身的 gadgets(例如 retleave 栈迁移)

以上 gadgets 通过 ret 指令和 rsp 寄存器串联起来。

为了引出 JOP,我想到了一个好方法,一个“分解”x86 中较复杂指令的游戏:

(下文 reg 表示任意通用寄存器,tmp 表示内部临时寄存器。)我们来“分解”pop reg 指令,它“等效”于 mov reg, qword ptr [rsp]; add rsp, 8。(我们忽略了对标志位的影响,如果考虑的话第二个指令应为 lea rsp, [rsp + 8]。)JOP 链的位置完全可以不在栈上,最终我们可以提取出:

mov reg1, qword ptr [reg2 + offset]
或
mov qword ptr [reg2 + offset], reg1

(由于有 + offset,我们不需要 add,下同。)这就是 JOP 中类似 ROP 的“传参准备”或“收集结果”gadgets,例如考虑 reg1rdi 的情况。reg2 就是 JOP 链基地址。

但是可以看到这并不能构造链式执行。现在我们来“分解”ret 指令,它“等效”于 pop tmp; jmp tmp,由上文进而“展开”为 mov tmp, qword ptr [rsp]; add rsp, 8; jmp tmp,现在我们又提取出了:

mov reg1, qword ptr [reg2 + offset]
jmp reg1 或 call reg1 或 ...(跳转)

我们只需预先在 reg2 + offset 位置布置好下一个 JOP gadget,这就是 JOP 中类似 ROP 的“控制”gadgets。

把上述两部分拼起来,我们最终获得了和 ROP 相同的能力。它与栈无关,与函数返回无关。这就是 JOP。希望大家能通过本文从 ROP 流畅过渡到更广义的 JOP 概念。以上只是我个人极不严谨的理解,可能和学术界普遍的描述有较大偏差,仅供参考。