栈复习
以下均针对AMD 指令集
ROPgadget
1 | $ROPgadget --binary pwn --only 'pop|ret' | grep 'rdi' #控制寄存器的值 |
ret2
text
32位
栈传参,顺序从右向左,ebp占4个字节
栈布局
fun(a,b,c)
1 | ---- ebp |
pwn37 – 32位有完整后门函数
1 | from pwn import * |
pwn39 – 后门函数需要拼接
1 | from pwn import * |
小tips:
call指令会自动压入返回地址,而直接跳转system函数不会自动压入返回地址,64位程序就不用考虑这个问题
1 | 在使用call system地址作为backdoor地址时 |
64位
寄存器传参,顺序rdi,rsi,rdx,rcx,r8,r9,栈上,rbp占8字节,需要栈对齐
pwn38 – 有完整后门函数
1 | from pwn import * |
pwn40 – 后门函数需要拼接
64位程序与32位程序不同,前6个参数是使用寄存器传参,此时我们需要利用ROPgaget找到合适的gadget来改变参数调用函数
1 | from pwn import * |
栈对齐
详细讲解文章: CTFer成长日记12:栈对齐—获取shell前的临门一脚 - 知乎
在64位程序中,想要调用system('/bin/sh')需要栈对齐,在上书题目中,如若不压入p64(ret),运行程序会发现程序停在了下面这条指令中:
1 | movaps xmmword ptr [rsp + 0x50], xmm0 |
查询可知MOVAPS — Move Aligned Packed Single Precision Floating-Point Values这条指令的功能是:将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的前提下修改栈的结构
因为system函数内部执行到这一出错指令的过程中rsp的变化量是固定的,我们可以修改进入system函数前的栈结构,进而影响执行出错指令前的栈结构。
通过gdb调试,在进入backdoor函数前rsp指向0x7fff6a7f5c18在执行到报错指令时rsp指向0x7fff6a7f5868差值为0x3b0
当我们在backdoor函数之前加入一个 ret 指令后栈顶下移8个字节使之对齐 16Byte 为此满足了XMM寄存器的要求成功得到shell
libc
查libc的网站libc database search
32位
pwn45
libc:libc database search
1 | from pwn import * |
这里遇到了一个小问题,在接收时puts函数运行时got表地址时本地输出了这样一串
1 | @\x91\x0b\xf6\xf0\\x06\xf6\xb6\x83\x04\x08\xa0\x99\x0b\xf6 |
而连接远程服务器后则是输出这一段
1 | `\x03\xe5\xf7\x90\x1d\xe0\xf7\xb6\x83\x04\x08\xc0 |
0xf7只是 32 位 Linux 系统中 libc 地址的常见前缀,而非唯一特征(0xf6、0xf8等同样可能,取决于 ASLR 随机化结果和系统配置)。远程服务器一般都开启了ASLR这时候通常前缀为0xf7在不确定本地ASLR开启情况时0xf0~0xff均可能,这时候最保险的方式就是接收最后4个字节

