缓冲区溢出
是指向程序缓冲区写入超出预分配固定长度数据的情况。这一漏洞可以被攻击者利用来改变程序的流程控制,甚至执行代码的任意片段。缓冲区溢出攻击成为远程攻击的主要手段,攻击者利用缓冲区溢出漏洞可以植入并且执行任意的攻击代码,缓冲区溢出漏洞给予了攻击者想要的一切,甚至得到被攻击计算机的控制权。
缓冲区溢出漏洞能够被利用的主要原因是计算机采用了“·冯·诺依曼体系结构
”,该体系结构把程序指令也当做数据来对待,指令和数据不加区分混合存储在同一个存储器中,数据和程序在内存中是没有区别的,它们都是内存中的数据,当EIP指针(call 之后的 地址即 函数返回时候执行的语句地址)指向哪 CPU就加载那段内存中的数据
。这就造成数据存在覆盖指令的情况,如果数据指向一个内存地址并且覆盖了EIP指针所指向的地址,那么程序就会跳转到攻击者指定的代码段执行。
计算机程序被CPU执行前需要先被载入内存,程序所在的内存由操作系统进行分配,程序内部使用的变量或者接收用户输入的数据都需要分配内存,这块内存区域也叫做缓冲区。
一个程序占用着一块内存区域,这块区域包含了程序函数的调用栈,数据,缓冲区等。·如果向缓冲区写入超出长度的数据,那么数据将会覆盖到程序的其他内存区域
,如果函数调用栈里某个函数地址被覆盖成其他函数地址,那么这个程序执行流程将会被改变,将会执行覆盖数据指定地址的函数。更为严重的是如果被植入精心构造的恶意代码并执行,攻击者将会得到运行该程序的用户权限,如果该程序以root角色执行,那么攻击者就可以得到目标计算机的完全控制权。
1,用C语言编写一个具有缓冲区溢出漏洞的程序,程序的功能是从main函数参数接收用户输入的数据,并且输出到控制台。,
2,使用gcc将源代码编译成32位的可执行程序,并使用gdb调试并断点分析程序运行。
3,运行程序并向程序输入超过缓冲区大小的数据,对第二步调试分析得到的目标函数地址进行覆盖,将函数地址覆盖成另一个函数地址,使程序执行其他函数。
4,使用的系统和工具:
Linux 16.04(64位)操作系统 vim,gcc,gdb,gcc-multilib(32位程序所需的编译工具)
其中gcc和gdb在CentOS 7中已经内置,vim和编译32位程序所需的gcc编译器需要另外安装
sudo apt-get install vim
sudo apt-get install gcc-multilib
使用vim新建一个main.c 源代码文件
#include
#include
void Bug()
{
printf("I should not show up here, I am a bug\n");
}
int fun(char *str)
{
char buf[10];
strcpy(buf, str);
printf("%s\n", buf);
return 0;
}
int main(int argc, char **argv)
{
char *str = argv[1];
fun(str);
return 0;
}
上面的代码通过main()函数参数接收用户输入数据,并调用fun()函数将输入的数据str拷贝给buf缓冲区,最后将buf缓冲区的字符输出。
可以看到上面的代码定义了一个字符数组为10个字节大小的buf缓冲区
,并使用strcpy将str数据直接拷贝到buf缓冲区,并没有对数据长度进行判断,通常,如果用户输入了超过10个字符的数据
,那么将会产生缓冲区溢出。但是在本人系统Centos 7中 ,gcc的优化,使得可以溢出17位,第18位认为出错,出现段错误。
上面的代码并没有调用函数Bug(),我们可以利用缓冲区漏洞,让程序调用函数Bug()。
使用gcc将源代码编译成可执行程序
gcc -m32 -z execstack -fno-stack-protector -g -o Buff Buff.c
为了方便实验进行,上面的gcc编译指令后面跟了一些编译参数:
-m32
将程序编译成32位程序
-z execstack
允许栈执行
-fno-stack-protector
关闭gcc的Stack Guard
-g
为了gdb程序能够调试程序(方便后续的结合源代码断点调试)
-o
输出目标文件为指定文件
编译后可以看到已经生成了可执行程序 main, 输入命令运行程序 并且后面跟上参数AAAA, 超过一定长度 ,就会出现段错误。即本18位报错
接下来使用gdb调试目标程序
gdb ./main
在gdb模式下,可以使用·disass
·命令查看程序中各个函数的汇编代码
依次查看程序中的 Bug()函数 fun()函数 main()函数的汇编代码
可以看到其中Bug函数的首地址是 0x0804843d,缓冲区溢出的时候会用到0x0804843d。
除此之外还可以看到main函数调用fun函数call的地址是0x08048495,
call后的下面一条指令地址是0x0804849a,这些指令地址都将会放在CPU寄存器里,待会断点调试的时候可以看到。
输入l命令列出程序源码:
使用 b命令进行断点,我们将断点设置在20行和27行,看看调用fun函数时和printf输出时寄存器内的数据。
输入数据AAAA运行,查看寄存器ebp和esp的数据:
可以看到程序先执行到了fun函数所在的断点位置,此时fun函数接收的参数str应该是输入的数据AAAA
使用p命令查看str的地址
使用si命令单步运行,然后查看寄存器数据
可以看到str地址`0xffffd6c0’已经压入栈中
继续si单步运行,查看寄存器数据
可以看到call后面的一条指令地址0x0804849a也已经压入栈中
使用n单步运行,到达20行断点位置,查看寄存器内容
可以看到寄存器中有4个41,因为A的ASCII码对应的就是41,我们传入4个A就会有4个41。
在命令行中输入12个A重新运行,然后再次到达20行断点位置,查看寄存器内容
为了方便操作,这里使用perl语言输出数据配合运行
可以看到寄存器中已经有12个A的ASCII码值41了,除此之外,还可以看到call指令下面的指令地址0x0804849a也在寄存器中了,我们要做的就是覆盖这个地址,只要将这个地址修改为Bug函数所在的地址
,
那么程序就会 执行Bug函数。
通过观察发现,要用A覆盖满底下的寄存器数据,还需要10个A,也就是总共需要22个A才能邻近目标地址。
在命令行中输入22个A + Bug函数地址
重新运行,我们使用perl对地址进行格式化输出,需要将这个16进制地址由后向前每两位进行一个\x拼接,得到的内容是 \x3d\x84\x04\x08。
在命令行中输入22个A加上Bug函数地址重新运行,一路输入n单步运行。
可以看到上面的程序已经执行到Bug函数了,我们可以输入q命令退出调试。
为了验证缓冲区溢出漏洞,我们现在直接运行程序,后面跟上我们之前调试分析时得到的结论数据进行运行。
可以看到程序调用了Bug函数,并且成功输出。
虽然最后提示程序段错误,但是我们已经成功的通过让程序接收数据的缓冲区溢出,改变了程序的执行逻辑,执行了其他函数。
如果这是个具有网络通信功能的程序,通过缓冲区漏洞可以使攻击者远程执行任意代码,得到目标系统的控制权。
缓冲区溢出攻击成为远程攻击的主要手段,攻击者通过目标ip地址扫描计算机的开放的端口,并对端口程序进行缓冲区溢出攻击,如果这个端口的程序存在缓冲区漏洞,那么这台计算机将会被攻陷。
现代操作系统对缓冲区溢出攻击做了防范,比如ASLR(地址空间布局随机化),是参与保护缓冲区溢出问题的一个计算机安全技术。是为了防止攻击者在内存中能够可靠地对跳转到特定利用函数。ASLR包括随机排列程序的关键数据区域的位置,包括可执行的部分、堆、栈及共享库的位置。
缓冲区溢出普遍存在于旧版系统等程序上,比如Windows xp的pop3服务就存在缓存区溢出漏洞,通过该漏洞可以注入shellocode,拿到控制权进行任意操作,比如开启3389端口添加用户等操作,随后就可以通过添加的用户进行远程登录,Windows XP 系统目前在国内的学校和一些政府机构依然在使用。