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

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

一、溢出漏洞利用概述

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

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

正常栈布局

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注入到我们的缓存空间中,见下面小节。

  • Shellcode代码注入

Shellcode是一串机器可以直接识别的操作码,一般短小精悍,可以实现很多关键功能,是溢出漏洞利用的完美搭档。

Shellcode可以自己编写,也可以使用MetaSploit的shellcode生成器来生成。
当然,也可以直接从网上找现成的使用。

推荐个网站:https://www.exploit-db.com/shellcode/,里面有各种操作系统的各种shellcode代码,可以用来研究学习。

攻击者可以利用shellcode实现不同的攻击用途。

例如:可以实现蠕虫木马的下载;可以打开被沦陷系统的端口,以便接收C&C控制服务器的指令;也可以提权运行系统的命令行shell;以及获取系统关键资料文件等等。

我们这次用shellcode实现对系统的关键文件(/etc/passwd)的读取。

在gdb设定断点后,开始执行查看栈内容,可以看到shellcode内容和返回地址都已经填充成我们期望的数据。


继续执行,可以看到我们成功调出来了系统的关键文件(/etc/passwd)的内容!

细心的小伙伴会发现,我在shellcode之前填充了好多”\x90”,这是NOP空命令,作为缓冲作用,rip的值落在这个范围内都会顺利下滑到shellcode正常执行。
也就是说,就算rip地址稍微有点偏差,也会正常利用成功。

四、实际环境下的漏洞利用执行

  • 实际环境执行gdb调试好的字符串参数

直接运行,失败!

检查打印信息,我们会发现缓存地址变化非常随机,无法提前预知,就算咱们有NOP缓冲区也失效了。


这正是linux的另一种保护机制,通过地址随机化,防止溢出利用攻击。

那先关闭这个机制,再次验证。

关闭这个机制的命令:sudo bash -c “echo 0 > /proc/sys/kernel/randomize_va_space”

  • 关闭randomize_va_space后的实际环境运行情况

OK,利用成功!

五、溢出漏洞利用样本

在这个小节,我们自己编写个溢出漏洞利用样本,并进行漏洞利用攻击。

  • 溢出漏洞利用样本程序
  • 计算缓存大小,并获取rsp寄存器的值,用这个值预估返回地址(retAddr = rsp + RSP_RET_DIFF+ adjustment)。

  • 申请缓存存储空间,并在其中输入我们期望的内容(NOP段+shellcode+填充内容+返回地址),填充内容是为了构造合适的字符串长度,保证栈上返回地址内容被覆盖成我们期望的返回地址。

  • 调用我们之前编译好的有漏洞程序victim。

一点小提醒:为了防止内存泄漏,malloc分配的空间使用完毕要记得释放,对应的指针也要置为NULL,防止指针误用。

  • 溢出漏洞利用程序编译运行
  • 编译

  • 运行


直接运行发现出问题了,不能正常执行!

查看打印信息,可得知我们用的返回地址(0x7fffffffde60)跟缓存的起始地址(0x7fffffffdc80)差距很大,因而程序不能进入shellcode代码区正常执行。

因此,需要调整下返回地址,返回地址构成是:(retAddr = rsp + RSP_RET_DIFF+ adjustment),我们可以通过参数调整这个adjustment值,见下面小节。

  • 调整返回地址偏差,继续运行样本


OK,正常执行我们的shellcode代码!

对比返回地址(0x7fffffffdc9c)和缓存起始地址(0x7fffffffdc80),可以发现两者并不一致,返回地址是在缓存起始地址之后,但也正常执行了。

原因很简单,因为我们在缓存里面填充了很多NOP作为缓冲,返回地址只要落在NOP缓冲区任何一个地方,都能下滑到shellcode代码区进行正常运行。

可能有人会问,都能看到缓存起始地址了,干嘛还折腾用rsp预估返回地址呢,多麻烦呀?

大家可以想想实际情况,应该没有哪个应用程序会主动暴露缓存起始地址(我们的漏洞程序纯粹为了调试,把缓存的起始地址给打印出来了),因而需要一个参考地址进行预估返回地址是多少,所以我们就用了rsp值作为参考地址。

我们已经了解了溢出漏洞利用原理,下面我们开始看看针对这种漏洞的检测原理。

六、溢出漏洞利用检测

  • 溢出漏洞利用检测思路

目前比较常用的主要有三种:基于漏洞特征检测、基于Exploit特征检测、基于攻击特征检测。
1)基于漏洞特征检测:对漏洞攻击手段和技术细节比较了解后,研究其触发攻击的必要条件,可以分析出其对应的识别规则,例如缓存区溢出检测、目录遍历检测、远程命令注入检测、远程文件包含检测等;
2)基于Exploit特征检测:从漏洞利用程序中分析独特特征,做为识别规则,例如返回地址检测、ROP Chain检测等;
3)基于攻击特征检测:通过检测漏洞利用相关的相对独立的组件来发现攻击,例如shellcode代码检测、畸形参数攻击检测等等。

  • 基于漏洞特征检测
  • 本文中的漏洞程序检测

从前文可知,victim被攻击的触发条件正是输入一段超长的参数,导致溢出而被利用攻击;
检测方法就是对victim的输入参数进行检查,如果超过一定值(例如256),就要产生告警发现攻击行为。
下图的红线之间的就是输入参数。

  • 实际案例的检测机制:Microsoft Windows DCOM RPC接口长主机名远程缓冲区溢出漏洞(MS03-026)(CVE-2003-0352)

冲击波蠕虫就是利用这个漏洞进行传播攻击的,Windows Dcom RPC的主机名字段一般不是很长,服务端用栈缓存区存储这个值,但是拷贝时没有做长度检查,导致一个超长的串触发溢出执行任意指令。

检测方法:解码RPC协议,获取主机名,检查其长度,如果超过一定值,就要告警发现攻击行为。

下图的红线之间的就是主机名(够长吧。。)

  • 基于Exploit特征检测
  • 本文中的漏洞利用检测

从victim输入参数进行分析,可以看到返回地址非常明显,可以作为检测规则。
发现输入参数有返回地址,就要告警有攻击行为。

下图的红线所标识的就是返回地址特征。

  • 实际案例的检测机制:Microsoft Windows工作站服务远程缓冲区溢出漏洞(MS03-049)(CVE-2003-0812)

可以分析出带有返回地址的匹配特征。

  • 基于攻击特征检测
  • 本文中的漏洞利用检测

可以通过识别输入参数的shellcode特征进行匹配,如果参数包含shellcode肯定是异常行为。

同样,对于我们的溢出漏洞利用样本,进行静态分析,如果检测到有shellcode,就可以说明这个样本文件存在异常。

  • 实际案例的检测机制:Microsoft Windows DCOM RPC接口长主机名远程缓冲区溢出漏洞(MS03-026)

Exploit中夹带的是典型的自解码MetaSploit Shellcode,对于这种Shellcode的解码器做粗略的静态匹配检测其实只要匹配“\xd9\x74\x24\xf4”就可以了。

七、总结

  • 有兴趣的小伙伴们,可以亲自动手操作一把漏洞利用过程,一定会有不一样的收获~
  • 研发同学们在后续的开发过程中,除了实现功能和性能,还要留意自己代码的安全性,做好输入检查,防止被黑客利用。

Spread the word. Share this post!

Meet The Author

Leave Comment