selph
selph
发布于 2024-03-03 / 21 阅读
0
0

[libc 2.35 源码学习] IO_FILE 篇 - vtable & IO_validate_vtable

源码分析

IO_validate_vtable

位于:libio/libioP.h

/* Perform vtable pointer validation.  If validation fails, terminate
   the process.  */
// 确保vtable指针合法,不然就结束进程
static inline const struct _IO_jump_t *
IO_validate_vtable(const struct _IO_jump_t *vtable)
{
    /* Fast path: The vtable pointer is within the __libc_IO_vtables
       section.  */
    // 指针在 __libc_IO_vtables 段里
    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;
    // 如果vtable指针超过了区段范围,进一步检查
    if (__glibc_unlikely(offset >= section_length))
        /* The vtable pointer is not in the expected section.  Use the
           slow path, which will terminate the process if necessary.  */
        _IO_vtable_check();
    return vtable;
}

用一个专门的段保存vtable信息

  [32] __libc_IO_vtables PROGBITS         00000000001e1980  001e0980
       0000000000000d68  0000000000000000  WA       0     0     32

计算vtable是否在这个段范围内,超过范围内就调用_IO_vtable_check检查该位置是否被允许,否则就关闭程序

_IO_vtable_check

位于:libio/vtables.c

void attribute_hidden
_IO_vtable_check(void)
{
#ifdef SHARED
	/* Honor the compatibility flag.  */
	// 函数指针,检查兼容性标志
	void (*flag)(void) = atomic_load_relaxed(&IO_accept_foreign_vtables);
#ifdef PTR_DEMANGLE
	PTR_DEMANGLE(flag);
#endif
	if (flag == &_IO_vtable_check)
		return;

	/* In case this libc copy is in a non-default namespace, we always
	   need to accept foreign vtables because there is always a
	   possibility that FILE * objects are passed across the linking
	   boundary.  */
	{
		Dl_info di;
		struct link_map *l;
		if (!rtld_active() || (_dl_addr(_IO_vtable_check, &di, &l, NULL) != 0 && l->l_ns != LM_ID_BASE))
			return;
	}

#else /* !SHARED */
	/* We cannot perform vtable validation in the static dlopen case
	   because FILE * handles might be passed back and forth across the
	   boundary.  Therefore, we disable checking in this case.  */
	if (__dlopen != NULL)
		return;
#endif

	__libc_fatal("Fatal error: glibc detected an invalid stdio handle\n");
}
  • 使用原子加载检查兼容性标志 (IO_accept_foreign_vtables),以确保跨线程的一致行为。
  • 如果标志指向自身 (_IO_vtable_check), 则表明内部调用有效,函数返回。
  • 否则,使用 rtld_active_dl_addr 检查当前 glibc 实例是否位于非默认命名空间。
  • 如果不在默认命名空间中,则接受外来 vtable 以处理潜在的跨链接场景。

总结

绕过方式则是,寻找在这个区段的其他可用的虚函数表,看有没有什么办法可以劫持其中的函数并触发,从而劫持控制流


评论