新手构造64位ROP链的拦路虎(?)。
作用原因
64位刚需16位栈对齐,也就是说,rbp,rsp等栈指针都应该是16的整数倍。也即是这些寄存器在正常运行指令时需保持最后一位是0。
对栈对齐造成影响的指令
主要是一对压栈、退栈指令,每出现一次这类命令,会破坏栈平衡的结构,使rsp前进或后退8(纠结其前进或后退实际对栈平衡无意义)。
push rbp
pop rbp以及这一对命令的衍生命令:
call FUNC =
push rip
jmp FUNCleave =
mov rsp, rbp
pop rbpret / 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种。
- 跳转到程序流中间,跳过新建栈帧时的栈操作。
- 在rop链中加入
ret命令的gadget,人为地再操作栈一次,破坏栈对齐,然后等开头设置栈帧的栈操作恢复栈平衡。