源码分析
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 以处理潜在的跨链接场景。
总结
绕过方式则是,寻找在这个区段的其他可用的虚函数表,看有没有什么办法可以劫持其中的函数并触发,从而劫持控制流