为了更好地对抗攻击行为,咱们需要了解攻击者的攻击手段是怎样的,做更有效的安全防护。本文介绍攻击者常用攻击手段之一:溢出漏洞利用手段。
文章目录
一、溢出漏洞利用概述二、存有溢出漏洞的代码三、GDB下的漏洞利用执行四、实际环境下的漏洞利用执行五、溢出漏洞利用样本六、溢出漏洞利用检测七、总结
一、溢出漏洞利用概述
溢出漏洞利用,是指在存在缓存溢出安全漏洞的计算机中,攻击者可以用超出常规长度的字符数来填满一个域,通常是内存区地址。在某些情况下,这些过量的字符能够作为“可执行”代码来运行。从而使得攻击者可以不受安全措施的约束进行攻击行为。
下面以栈布局说明溢出漏洞利用过程。
正常栈布局
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值作为参考地址。
我们已经了解了溢出漏洞利用原理,下面我们开始看看针对这种漏洞的检测原理。
转载于绿盟科技博客@举个小栗子说明溢出漏洞利用原理及其检测原理
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!