PWN技巧-栈对齐
(Ubuntu 18.04版本及以上需考虑 )
罪魁祸首
movaps xmmword ptr [rsp + 0x50], xmm0 |
从 https://www.felixcloutier.com/x86/movaps 处我们可以知道,这条指令的功能是: 将xmm0中保存的单精度浮点数从xmm0移动至地址[rsp + 0x50]处 。
当然,更重要的是这条指令的执行条件,这直接关系到程序报错的原因。原文中对执行条件的描述如下:
When the source or destination operand is a memory operand, the operand must be aligned on a 16-byte (128-bit version), 32-byte (VEX.256 encoded version) or 64-byte (EVEX.512 encoded version) boundary or a general-protection exception (#GP) will be generated.
此时报错的原因就显而易见了:当内存地址作为操作数时,内存地址必须对齐 16Byte 、 32Byte 或 64Byte 。这里所说的对齐 xByte,就是指地址必须是 x 的倍数。
这时又出现了一个问题:到底是对齐多少字节呢?我们可以找到这样一段描述:
This instruction can be used to load an XMM, YMM or ZMM register from an 128-bit, 256-bit or 512-bit memory location, to store the contents of an XMM, YMM or ZMM register into a 128-bit, 256-bit or 512-bit memory location, or to move data between two XMM, two YMM or two ZMM registers.
基于此,我们可以推测:使用 XMM 时,需要 16Byte 对齐;使用 YMM 时,需要 32Byte 对齐;使用 ZMM 时,需要 64Byte 对齐。
而此处出错的指令使用了 XMM 寄存器,因此我们需要确保在执行这一指令时,rsp + 0x50 是 16 的倍数。直观地说,就是该地址必须以数字 0 结尾。
基于上面的分析,解决方案已经呼之欲出了:我们需要修改栈的结构,使得程序执行到出错指令时, rsp + 0x50 是16的整数倍。这时便出现了两个非常具体的问题:
- 我们该如何确保能获得shell的前提下修改栈的结构?
- 我们该将栈的结构修改成什么样子?
第1个问题很好回答——我们可以借助ROP的思想,再栈中添加若干汇编片段,以确保在能获得shell的前提下修改栈的结构。
接下来回答第二个问题:我们需要将栈修改成什么样子?我们知道,由于程序的代码是不变的,因此程序从进入 vulnerable_function 到执行到出错指令这一过程中,栈顶 rsp 的变化量是不变的。因此,我们可以通过修改进入 vulnerable_function 前的栈结构,进而影响执行出错指令前的栈结构。
那这个变化量具体是多少呢?通过GBD调试可以看到,在进入 vulnerable_function 前,rsp 中的值是 0x7fffffffdf38 ;而执行出错指令前,rsp 中的值是 0x7fffffffdba8 。因此,从进入 vulnerable_function 到执行到出错指令,栈顶的值减小了 0x390 。
为方便表述,记进入 vulnerable_function 前的栈顶为 origin_sp ,则有:(origin_sp - 0x390 + 0x50) % 0x10 == 0 。也即 origin_sp % 0x10 == 0 。
基于上述分析,我们可以在 vulnerable_function 的地址前增加一个新的地址,该地址恰好指向一个 ret 指令。这样一来,由于加入了一个新地址,栈顶被迫下移8个字节,使之对齐 16Byte ;同时,由于插入的地址指向了 ret 指令,程序的仍然可以顺利地进入 vulnerable_function 中。如下图所示:

至于指向 ret 指令的地址,可以在IDA中使用快捷键 alt + T 进行搜索。
综上,我们终于可以写出打通本地环境的payload了: