一.缓冲区溢出的危险性
缓冲区溢出是一种在各种操作系统、应用软件中广泛存在普遍且危险的漏洞,利用缓冲区溢出攻击可以导致程序运行失败、系统崩溃等后果。更为严重的是,可以利用它执行非授权指令,甚至可以取得系统特权,进而进行各种非法操作。
二.缓冲区溢出的原理:
看一个小程序来理解它:
/*the overflow of the stack*/
void Func(char *str)
{
char buffer[4];
strcpy(buffer,str);
}
int main(int argc,char *argv[])
{
int i; char largestr[128];
for(i=0;i<128;i++)
largestr[i] = 'A';
Func(largestr);
return 0;
}
这个程序就会产生缓冲区溢出.明显的,buffer只有4字节,却用一个128字节的来填充它.于是溢出就发生了.
缓冲区溢出破坏了程序的堆栈,使程序出现特殊的问题转而执行其它指令.一般的溢出只是让程序运行失败.(常出现“分段错误”(Segmentation fault),)
但如果黑客们"精心打造"的溢字符串,则可以达到攻击的目的啦.
最常见的手段是通过制造缓冲区溢出使程序运行一个用户shell,再通过shell执行其它命令。如果该程序属于root且有suid权限的话,攻击者就获得了一个有root权限的shell,可以对系统进行任意操作了。(win32下也一样)
在执行溢出的机子上开DOS(shell).只要很简单的一段程序:
/* running in windows open command.com*/
#include <windows.h>
#include <winbase.h>
typedef void (*MYPROC)(LPTSTR);
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle = (MYPROC) GetProcAddress(LibHandle,"System");
//查找system函数地址
(ProcAdd)("command.com");//相当于执行system("command.com")
return 0;
}
//////////////////////////////////////////////////////////
/* open a shell --for linux */
#include<stdio.h>
void main()
{
char *name[2];
name[0] = "/bin/sh"; //开个bash
name[1] = NULL;
execve(name[0],name,NULL);//调用程序
}
只要到这里就获得了个shell了,再通过shell执行其它命令,终于你激动的拥有了一台肉鸡.
缓冲区溢出攻击之所以成为一种常见安全攻击手段其原因在于缓冲区溢出漏洞普遍并且易于实现!而且缓冲区溢出成为远程攻击的主要手段其原因在于缓冲区溢出漏洞给予了攻击者他所想要的一切:植入并且执行攻击代码。被植入的攻击代码以一定的权限运行有缓冲区溢出漏洞的程序,从而得到被攻击主机的控制权。
下面来看一下攻击的过程:
(1)对ROOT程序进行试探性攻击,然后执行类似exec(sh)的执行代码来获得具有(root)权限的shell。
此步可再分二步走:
a.在程序的地址空间里安排适当的代码
b.通过适当初始化寄存器和内存,让程序跳到安排的地址空间执行预先设定好的程序.(有预谋哈)
总的来说攻击分为"代码安排"和"控制程序执行流程"两步:
1、在程序的地址空间里安排适当的代码的方法:
A>植入法:
攻击者向被攻击的程序输入一个字符串,程序会把这个字符串放到缓冲区里。这个字符串包含的资料是可以在这个被攻击的硬件平台上运行的指令序列。在这里,攻击者用被攻击程序的缓冲区来存放攻击代码.(前面已有示例)
B>利用已经存在的代码:
前提:攻击者想要的代码已经在被攻击的程序中了.
攻击者所要做的只是对代码传递一些参数。如攻击代码要求执行exec (“/bin/sh”),
而在libc库中的代码执行exec (arg),其中arg使一个指向一个字符串的指针参数,那么攻击者只要把传入的参数指针改向指向/bin/sh。
2、控制程序转移到攻击代码的方法:
通过溢出一个没有边界检查或者其它弱点的缓冲区,扰乱了程序的正常的执行顺序。
通过溢出一个缓冲区,攻击者可以用暴力的方法改写相邻的程序空间而直接跳过了系统的检查。
用暴力的方法来寻求改变程序指针的三种方法:
1)堆栈溢出攻击:强制改变函数结束时返回的地址.这样当函数调用结束时,程序就跳转到攻击者设定的地址,而不是原先的地址.此为最常用的缓冲区溢出攻击方式.
2)函数指针:通过改变函数指针来定位任何地址空间.
如:void (* foo)()
声明了一个返回值为void的函数指针变量foo。所以攻击者只需在任何空间内的函数指针附近找到一个能够溢出的缓冲区,然后溢出这个缓冲区来改变函数指针。在某一时刻,当程序通过函数指针调用函数时,程序的流程就按攻击者的意图实现了。
3)长跳转缓冲区
有点类似函数指针.setjmp/longjmp.也是跳转吧.我不太清楚.
三.缓冲区溢出攻击的防范方法:
目前有三种基本的方法保护缓冲区免受缓冲区溢出的攻击和影响:
1、通过操作系统使得缓冲区不可执行,从而阻止攻击者植入攻击代码;
2、强制写正确的代码的方法;
3、利用编译器的边界检查来实现缓冲区的保护,使得缓冲区溢出不可能出现,从而完全消除了缓冲区溢出的威胁。(使用运行检查器也是不错的选择.)
我常想我们或许应该感谢黑客们,正是因为他们,一切才能朝着完美的方向发展.事物都是矛盾着的.有攻击就有防范,这也是给我们程序员的一个压力,当感觉自己的程序非常完美并且欣喜若狂的时候,不妨问一下自己,它会有漏洞吗?溢出检查过吗?呵呵...
注:关于shellcode 的构造,(需要汇编和C的知识.)下次有时间再写一篇.互相交流.敬请关注!