新手构造64位ROP链的拦路虎(?)。

作用原因

64位刚需16位栈对齐,也就是说,rbp,rsp等栈指针都应该是16的整数倍。也即是这些寄存器在正常运行指令时需保持最后一位是0。

对栈对齐造成影响的指令

主要是一对压栈、退栈指令,每出现一次这类命令,会破坏栈平衡的结构,使rsp前进或后退8(纠结其前进或后退实际对栈平衡无意义)。

push rbp
pop  rbp

以及这一对命令的衍生命令: call FUNC =

push rip
jmp  FUNC

leave =

mov  rsp, rbp
pop  rbp

ret / retn =

pop  rip

可以看到如上命令等价于一次压栈/退栈,栈顶改变8 。

正常程序运行时的栈对齐变化

参见: 母函数正常运行时,rsp的值正常。 $rsp = 0x???????????????0

在母函数时:

call FUNC

运行该命令,栈顶移动8,栈对齐被破坏。$rsp = 0x???????????????8

程序跳转到新的函数,新的函数开始初始化,其中新建栈帧中有这一行

push rbp

运行该命令,栈顶移动8,栈对齐恢复。$rsp = 0x???????????????0 ,程序得以正常运行。

函数运行将结束时,开始进行返回操作,其内容为

leave
ret

或者

pop  rbp
ret

其内容操作了2次栈,栈顶移动8 * 2 = 0x10,对栈对齐没有影响。$rsp = 0x???????????????0,程序以正常的栈对齐跳回到母函数继续运行。

ROP链利用中常见的栈操作gadgets

  • ret 栈移动8,影响栈对齐。
  • pop rdi ; ret 栈移动16,不影响栈对齐。
  • leave ; ret 栈移动16,不影响栈对齐。

新手在构建ROP链中常出现的问题

由上可知,由于call命令会破坏栈对齐,所以在函数开始会再操作一次栈来使栈平衡。但是,通过ret命令来调用属于异常调用,函数的返回结构对于栈平衡并没有影响,而函数开始的栈操作反而会使正常的栈平衡出现问题,从而使后面的程序运行出现问题。 此时可能的解决方案有2种。

  1. 跳转到程序流中间,跳过新建栈帧时的栈操作。
  2. 在rop链中加入ret命令的gadget,人为地再操作栈一次,破坏栈对齐,然后等开头设置栈帧的栈操作恢复栈平衡。