【漏洞解析】举个小栗子说明溢出漏洞利用原理及其检测原理

溢出漏洞利用,是指在存在缓存溢出安全漏洞的计算机中,攻击者可以用超出常规长度的字符数来填满一个域,通常是内存区地址。在某些情况下,这些过量的字符能够作为“可执行”代码来运行。从而使得攻击者可以不受安全措施的约束进行攻击行为。本文以实例详细说明溢出漏洞的利用过程,感兴趣的朋友们来动手操作一把吧!

一、溢出漏洞利用概述

溢出漏洞利用,是指在存在缓存溢出安全漏洞的计算机中,攻击者可以用超出常规长度的字符数来填满一个域,通常是内存区地址。在某些情况下,这些过量的字符能够作为“可执行”代码来运行。从而使得攻击者可以不受安全措施的约束进行攻击行为。

下面以栈布局说明溢出漏洞利用过程。

正常栈布局

RBP : 栈基地址寄存器,指向栈底地址;

RSP : 栈顶寄存器,指向栈顶地址;

RIP:指令地址寄存器,指向指令所在地址。

函数调用时,当前函数的下一个执行命令入栈(RIP,即RET返回地址),RBP入栈,然后把当前RSP的值给RBP;

RSP根据函数执行而移动,根据空间需要移动所指位置,例如入栈局部变量,RSP的值会跟着减小,保证RSP一直指向栈顶。

栈溢出利用过程说明

栈上的内容填充是从低地址向高地址填充的。

请注意,我们开始使坏啦:
1)给var1填充巧妙构造的超长字符串,超过var1高地址(0x7fffffffdee8),并继续覆盖更高地址的内容,可以看到会把RBP(0x7fffffffdef0)和RET返回地址(0x7fffffffdef8)都给覆盖了;
2)RET返回地址指向var1地址(0x7fffffffdee8),var1里面有我们精心制造的字符串,说白了,有我们的恶意代码;
3)被调用函数执行返回命令时,会读取RET返回地址,便会读取var1里面的恶意代码,并进行执行,无意中执行了恶意代码。

也就是,通过缓冲区溢出,我们能改变程序原有的执行流程,去执行我们准备好的代码。
直观示意请见下面的溢出栈布局图:

二、存有溢出漏洞的代码

  • 漏洞代码内容

代码很简单,可以看到这段代码用了危险函数strcpy!!

  • 漏洞编译并运行
  • 编译命令


注意后面两个参数必须要加上,要不然,后续的漏洞利用会比较麻烦,这两个参数是关闭linux系统的堆栈溢出保护机制。
-z execstack:表示允许栈中存放可执行代码;
-fno-stack-protector:表示关闭堆栈保护机制,可以任意覆盖RIP的值,如果编译没有加上这个参数,程序执行过程会进行值(被称为金丝雀值)校验,看RIP是否被篡改了,如果篡改了,说明发生了栈溢出,会调用__stack_chk_fail函数,进行安全退出并丢出一个错误。
我们这次是为了研究溢出漏洞利用的流程和原理,不是进行高级漏洞挖掘研究,所以,先关闭系统本身的保护机制。

  • 正常结果

  • 异常结果


字符串过长,程序崩溃了。

三、GDB下的漏洞利用执行

  • RIP控制
  • 设定断点后,执行查看寄存器内容和栈内容

  • 继续执行,填充288个B字符后的栈内容情况

  • 继续执行,strcpy执行完毕返回,查看寄存器和栈内容,可以看到rbp被覆盖了,但是rip并没有被覆盖,从栈中可以看出,是尝试把0x4242424242424242赋值给rip,然而linux 64位系统的rip最大值是0x00007fffffffffff,因而,rip控制失败。


  • 调整输入字符串,再次尝试控制RIP

可以计算返回地址的相对位置:0x7fffffffdd38- 0x7fffffffdc30=0x108=264(十进制)
因而,可以构造字符串:”B” * 264 + “C” * 6 通过gdb调试,可以看到,这次我们能控制rip的取值了,是我们尝试的0x434343434343。

基于我们不断探索的精神,我们继续尝试,把RIP指向缓存起始地址,即我们可以控制内容的地方。构造字符串:”B” * 264 + “\x7f\xff\xff\xff\xdc\x40″(反序),因为是小端模式,因此内存地址需要反序。

OK,目前为止,我们已经可以控制RIP指向我们的缓存空间了,下一步就是把shellcode注入到我们的缓存空间