从 ROP 到 JOP 攻击(amd64)
笔者感觉 JOP 更像是对 ROP 的概念扩充而不是一个新的攻击方式,让我们先来回顾下熟悉的 amd64 ROP。在 ROP 中,我们大概可以将 gadgets 分为三类:
- 传递参数或做准备的 gadgets(例如
pop rdi)、收集函数返回值的 gadgets(例如mov rdi, rax) - 执行有意义操作的 gadgets(例如
system、orw、syscall、shellcode)。 - 控制 ROP 本身的 gadgets(例如
ret、leave栈迁移)
以上 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,例如考虑 reg1 是 rdi 的情况。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。