Chinaunix首页 | 论坛 | 博客
  • 博客访问: 364291
  • 博文数量: 102
  • 博客积分: 2000
  • 博客等级: 大尉
  • 技术积分: 1116
  • 用 户 组: 普通用户
  • 注册时间: 2007-03-29 16:21
文章分类

全部博文(102)

文章存档

2014年(10)

2011年(1)

2008年(2)

2007年(89)

我的朋友

分类: C/C++

2007-09-12 04:31:21

缓冲区溢出 (Buffer Overflow)大概是最常见的漏洞了,在N年前(N=1),我看过一篇文章比较缓冲区溢出漏洞与格式化字符串漏洞(format String),文章提到已有上千例缓冲区溢出被黑客们成功地Exploit,而格式化字符串漏洞被Exploit仅有几十例。我会在下面第七章介绍Exploit格式化字符串漏洞的例子,这一章就从容易的开始,讲我们如何在本地(Local) Exploit Solaris/Intel X86上缓冲区溢出。

Intel X86处理器(Processor)是典型的CISC(Complex Instruction Set Computer)处理器,这是与RISC(Reduced Instruction Set Computer)型处理器相比较。顾名思义,CISC有庞大的、包罗万象的机器指令群(Instruction Set)、各种各样的寻址方法,所以它的机器指令长短不一。另一方面,CISC仅有有限的通用寄存器,比如X86处理器,若不算上它的浮点寄存器,只有EAXEBXECXEDDX四个通用寄存器和一些专用的堆栈寄存器、指针寄存器、段寄存器等等。

CISC处理器相比较,RISC处理器的机器指令群(Instruction Set)只包括有限但通用的指令,其寻址方法也相对较少,机器指令长度固定(32位或更高级一些的64)。但是RISC却拥有大量的通用寄存器。这些架构上的区别决定了CISCRISC有不同的Exploit方法,我们后面一章会介绍如何Exploit RISC处理器中的代表人物Sparc,届时你们可以比较Exploit两种处理器的不同之处。关于CISCRICS详细的介绍,有兴趣的请到网上查查。

我要用到的机器配置:PENTIUM III800MHZ 运行Solaris 8/Intel X86版本。机器名叫Shanghai----上海,那是我生活与战斗了七年的地方。就在那七年中,改革开放的春风风向先朝着南方几个城市吹,然后终于吹到了上海,最明显的就是欣欣向荣的房地产市场。我当时顶着太阳炙热的关照,奋战在一个又一个位于地平面以下的基坑里。


Solaris/Intel X86
缓冲区在堆栈中的分配情况:


扯远了,还是回到我们的主题内容:如何Local Exploit Solaris/Intel X86上缓冲区溢出。请看下面这个程序vul.c。这个演示程序非常简单,它把用户输入的参数在函数Foo中用printf输出来。但请大家注意,这个vul.c是有缝的臭鸡蛋,结果引得天下英雄尽来叮。这条缝就是Foo中的函数strcpy,它把输入字符串s拷贝到缓冲区buf中,就是这个函数会造成缓冲区溢出!为什么它会造成如此严重的后果呢?

<==========================vul.c============================>
i nclude
Foo(char* s)
{
char buf[16]="";
strcpy(buf, s);
printf("The input String is %s\n", buf);
}
main(int argc, char* argv[])
{
if(argc == 2)
{
Foo(argv[1]);
}
else
{
printf("Usage: %s \n", argv[0]);
}

}
<===========================================================>

不入虎穴,焉得虎子。要知道为什么strcpy会导致缓冲区溢出,还得深入到基层(内存)中去。不过深入基层之前,请大家先到GNU的网站上去下载一些GNU的工具,象gccgdb等等,因为我们后面要经常用到它们来作程序编译(需要gcc)Debug(需要gdb)。不熟悉这些工具的朋友请尽量发挥你们的悟性理解。

先把程序vul.cDebug模式编译:

shanghai =>gcc vul.c -o vul -g

再用gdb运行它:

shanghai =>gdb vul
GNU gdb 5.2
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-pc-solaris2.8"...
/*
在程序入口处main设置一个断点
*/
(gdb) b main
Breakpoint 1 at 0x8050956: file vul.c, line 12.
/*
我以一个字符串--10'A'作为程序vul的输入参数。
*/
(gdb) r AAAAAAAAAA
Starting program: /export/home/moda/buf_of/vul AAAAAAAAAA
Breakpoint 1, m12 if(argc == 2)
/*
程序在main处中断,我们接着单步执行(Single Step)Foo
*/
(gdb) s
14 Foo(argv[1]);
/*
程序即将要进入Foo,我们看看将要执行的汇编指令。
*/
(gdb) x/10i $eip
0x805095c : add $0xfffffff4,%esp
0x805095f : mov 0xc(%ebp),%eax
0x8050962 : add $0x4,%eax
0x8050967 : mov (%eax),%edx
0x8050969 : push %edx
0x805096a : call 0x8050904
0x805096f : add $0x10,%esp
0x8050972 : jmp 0x805098a
0x8050974 : add $0xfffffff8,%esp
0x8050977 : mov 0xc(%ebp),%eax
/*
函数Foo在地址0x805096a处被main调用:"call 0x8050904 "。而0x805096fFoo调用后的返回地址,大家记住这个地址,因为下面要提到它。

接着我们用si(Single Instruction)一个指令一个指令地执行--------在情况不明的时候,摸着石子一步一步过河总是对的:
*/
(gdb) si
0x0805095f 14 Foo(argv[1]);
(gdb) si
0x08050962 14 Foo(argv[1]);
(gdb) si
0x08050967 14 Foo(argv[1]);
(gdb) si
0x08050969 14 Foo(argv[1]);
(gdb) si
0x0805096a 14 Foo(argv[1]);
/*
在这里程序即将要进入Foo,我们看看寄存器当前的内容:
*/
(gdb) i reg
eax 0x8047bf8 134511608
ecx 0x0 0
edx 0x8047d0d 134511885
ebx 0xdfbfb000 -541085696
esp 0x8047bb4 0x8047bb4
ebp 0x8047bcc 0x8047bcc
esi 0x8047bb0 134511536
edi 0x8047c74 134511732
eip 0x805096a 0x805096a
eflags 0x202 514
cs 0x17 23
ss 0x1f 31
ds 0x1f 31
es 0x1f 31
fs 0x0 0
gs 0x0 0
fctrl 0x137f 4991
fstat 0x0 0
ftag 0xffff 65535
fiseg 0x0 0
fioff 0x0 0
foseg 0x0 0
fooff 0x0 0
---Type to continue, or q to quit---q
Quit
/*
在寄存器ESP中的是当前堆栈栈顶指针0x8047bb4 EBPmain函数堆栈栈底指针0x8047bcc,这个0x8047bcc是要大家记住的第二个地址,因为下面也要提到它。

继续向下执行
*/
(gdb) si
Foo (s=0x2

) at vul.c:4
4 {
(gdb) x/5i $eip
0x8050904 : push %ebp
0x8050905 : mov %esp,%ebp
0x8050907 : sub $0x18,%esp
0x805090a : mov 0x8050a28,%al
0x805090f : mov %al,0xfffffff0(%ebp)
(gdb) s
Foo (s=0x8047d0d "AAAAAAAAAA") at vul.c:5
5 char buf[16]="";
(gdb) s
6 strcpy(buf, s);
(gdb) s
7 printf("The input String is %s\n", buf);
/*
说时迟那时快,程序已经进入Foo中,并把输入的字符串拷贝到了缓冲区buf中。我们来看一下当前堆栈的内容:
*/
(gdb) x/20x $esp
0x8047b94: 0xdfb35d90 0x00000210 0x41414141 0x41414141
0x8047ba4: 0x00004141 0x00000000 0x08047bcc 0x0805096f
0x8047bb4: 0x08047d0d 0xdfb3d3a7 0xdfbf137f 0x08047bb0
0x8047bc4: 0xdfbfb000 0xdfbf137f 0x08047be8 0x0805081b
0x8047bd4: 0x00000002 0x08047bf4 0x08047c00 0x08050a10
(
以下程序执行省略。。。)

从地址0x8047b9c0x8047bab的区域是系统分配给buf的缓冲区,刚好16个字节,分别对应buf16个字符;前面的10个字节已经填上了10个刚拷贝进来的字符'A'ASCII41。紧接着这16个字节的是0x08047bcc,就是我刚才要大家记住的调用函数main的堆栈栈底地址;再后面的是我要大家记住的被调用函数Foo的返回地址0x0805096f

根据上面的分析我们可以画一个缓冲区在内存中的分布图:

|<--buf: 16 Byte-->|<--Calling Function $EBP: 4 Byte-->|<--Called Function RetAddr: 4 Byte -->|

大家看到,buf缓冲区被分配在堆栈中,而且是和重要的系统管理数据如:调用函数堆栈栈底地址、被调用函数的返回地址紧紧地靠在一块----这,就是悲剧的DNA根源。

Foo中,bufstrcpy拷贝字符串的目标,而源字符串为sstrcpy忠实地把源字符串s完整地拷贝到以buf为开端的内存后面。如果s的长度超过buf的长度,也就是超过16个字节的话,调用函数main的堆栈栈底地址和被调用函数Foo的返回地址就会被覆盖掉,於是缓冲区溢出的悲剧就发生了。悲剧的结果轻则产生coredump 重则被一大堆天下英雄Exploit


 

 

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