作者: CSysSec出品
CSysSec注: 本系列文章译自安全自由工作者Sploitfun的漏洞利用系列博客,从经典栈缓冲区漏洞利用堆漏洞利用,循序渐进,是初学者不可多得的好材料,本系列所有文章涉及的源码可以在这里找到。CSysSec计划在原基础上不断添加相关漏洞利用技术以及相应的Mitigation方法,欢迎推荐或自荐文章。
转载本文请务必注明,文章出处:《Linux(X86)漏洞利用系列-栈内Off-by-one漏洞利用》与作者信息:CSysSec出品
- 0X01 什么是off-by-one漏洞
- 0X02 如何实现任意代码执行
- 0X03 如果调用者的EBP不在目标缓冲区正上方,该怎么办
- 0X04 什么情况下调用者的EBP不在目标缓冲区正上方
阅读基础:
VM Setup: Ubuntu 12.04 (x86)
0X01 什么是off-by-one漏洞?
将源缓冲区复制到目标缓冲区时,以下情况可能导致Off-By-One漏洞:
源字符串长度等于目标缓冲区长度
当源字符串长度等于目标缓冲区长度时,单个NULL字节就会被复制到目标缓冲区上方。这种情况下,由于目标缓冲区存储在栈内,因此,仅凭单个NULL字节就能把栈内调用者EBP的最低有效位(LSB)覆盖掉。
依照惯例,未免定义过于枯燥,下面我们就来看一则Off-By-One漏洞代码。
漏洞代码:
|
|
编译命令:
|
|
上述漏洞代码的第[2]行就是Off-By-One溢出问题可能出现的地方。由于目标缓冲区长度为256,因此256字节的源字符串就可能导致任意代码执行。
注:本系列所有文章中第[N]行代码指的的代码中显示/*[N]*/的位置。
0X02 如何实现任意代码执行
任意代码执行是通过“EBP 覆盖(EBP overwrite)”方法实现的。如果调用者的EBP位于目标缓冲区上方,那么执行strcpy后,调用者的EBP的LSB很可能已然被单个NULL字节覆盖了。为了进一步了解off-by-one,我们来反汇编一则漏洞代码并且画出它的堆栈布局吧。
反汇编:
|
|
堆栈布局:
前面讲到,用户输入了256字节大小的数据,NULL字节就会覆盖foo的EBP的LSB。所以当存储于目标缓冲区‘buf’正上方的foo的EBP被单个NULL字节覆盖时,EBP就会由0xbffff2d8 变为0xbffff200。细看堆栈布局图,我们会发现栈地址0xbffff200就是目标缓冲区‘buf’的一部分,而既然用户输入值已经被复制进了这个目标缓冲区,那么攻击者就能得到这个栈地址(0xbffff200)的控制权,同时也得到了EIP的控制权,从而借此实现任意代码执行。我们来发送一串大小为256字节的“A”进行测试。
测试第一步:EBP覆盖后出现返回地址覆盖是否有可能?
|
|
上述输出结果显示,EBP覆盖会让我们得到EIP的控制权。
测试第二步:来自目标缓冲区的偏移量是什么?
现在我们需要在目标缓冲区‘buf’的起始端中找到偏移量。我们还需设置好返回地址。切记,在off-by-one漏洞中,我们并不是要覆盖栈中的实际返回地址(在栈缓冲区溢出漏洞利用代码中我们才覆盖实际返回地址),而是把攻击者控制的目标缓冲区‘buf’内的一个4字节内存区域视作返回地址位置,对这块区域进行覆盖(在off-by-one溢出之后)。因此,我们需要(从‘buf’中)找到这个返回地址位置的偏移量——而这个偏移量也是目标缓冲区‘buf’本身的一部分。
这段话有点绕,没关系,继续往下读就好。
我们先试着从 text 段地址0x0804840开始尝试理解CPU的执行:
- 0x08048490 - call strcpy – 执行这个指令会导致off-by-one溢出,因此(储存在栈地址0xbffff2cc中的)foo的EBP值将会由0xbffff2d8变为0xbffff200。
- 0x08048495 - leave - leave指令释放了这个函数的栈空间并且恢复了EBP。
|
|
- 0x08048495 - ret - 返回到foo的指令0x08048475。
- 0x08048475 - leave - leave指令释放了这个函数的栈空间并且恢复了EBP。
|
|
- 0x08048476 - ret - 返回到储存在ESP (0xbffff204)中的指令中。此时ESP指向被攻击者控制的缓冲区,因此攻击者可以回到任何他想要实现任意代码执行的地方。
现在我们回到“在目标缓冲区‘buf’中寻找返回地址的偏移量”的最初测试上。如堆栈布局图所示,‘buf’位于0xbffff158,并且由紧随其后的CPU执行中可知,目标缓冲区‘buf’内的返回地址位置是0xbffff204。因此目标缓冲区‘buf’中返回地址的偏移量是0xbffff204 – 0xbffff158 = 0xac,因此用户输入“A”172 + “B”4 + “A”*80,用“BBBB”覆盖了EIP。
|
|
上述输出结果显示,攻击者控制了返回地址。此时返回地址位于buf的偏移(0xac)处。有了上面这些信息,我们就可以写出能实现任意代码执行的漏洞利用程序了。
漏洞利用代码:
执行上述漏洞利用程序将会获取root shell,如下所示:
|
|
Off-by-one看上去是一个特别蠢的漏洞,而且程序开发者一个这么小的错误也能导致任意代码执行,这也太诡异了。那么,off-by-one漏洞是不是一定会导致任意代码执行呢?
0X03 如果调用者的EBP不在目标缓冲区上方,该怎么办
答案非常简单。如果那样的话,我们不能用“EBP覆盖”方法来利用这个漏洞了呗!(不过呢,毕竟这个漏洞在代码中是确实存在的,所以肯定有其他的漏洞利用方法啦。😛)
0X04 什么情况下调用者的EBP不在目标缓冲区上方
情况1: 一些其他的本地变量出现在目标缓冲区上方
|
|
因此在这种情况下,夹在缓冲区‘buf’末端和EBP之间的会是一个本地变量,这就不允许我们去覆盖EBP的LSB了。
情况2: 对齐空间——gcc对齐栈空间边界默认为16字节。即在创建栈空间之前,ESP的最后四个字节就被‘and’指令清零了。具体参见下方函数反汇编代码。
|
|
因此,在这种情况下,夹在缓冲区‘buf’末端和EBP之间的会是一个(最大为12字节的)对齐空间,这就不允许我们去覆盖EBP的LSB了。
由于这个原因,我们在编译漏洞利用代码(vuln.c)时添加了gcc参数“-mpreferred-stack-boundary=2”。
求助:如果在创建栈内容之前ESP边界已经对齐为16字节的话该怎么办?这种情况下,即使程序以gcc默认的16字节栈边界编译,按理来说“EBP覆盖”法也是可以用的。但是我一直都写不出有效代码。在我所有的试运行程序中,创建栈空间之前,ESP边界都没有对齐16字节。但是不管我多么小心地创建栈内容,gcc总是给本地变量添加额外空间,这样ESP边界就不能对齐16字节。如果任何人有有效代码或者知道为什么ESP总是无法对齐,麻烦告诉我!拜托了!
参考文章:
转载本文请务必注明,文章出处:《Linux(X86)漏洞利用系列-栈内Off-by-one漏洞利用》与作者信息:CSysSec出品