买不起的大house之house of emma

前言

这条链子由WJH’s Blog师傅首个发现,经典例题为2021湖湘杯的1解题 House_OF_Emma

适用范围:

glibc2.23-glibc.2.34

使用条件

  1. 可以任意写一个可控地址(LargeBin Attack、Tcache Stashing Unlink Attack…)
  2. 可以触发IO流(FSOP、 House of kiwi)

利用原理

vtable 的合法范围内,存在一个 _IO_cookie_jumps结构体
具体如下:

static const struct _IO_jump_t _IO_cookie_jumps libio_vtable = {
  JUMP_INIT_DUMMY,
  JUMP_INIT(finish, _IO_file_finish),
  JUMP_INIT(overflow, _IO_file_overflow),
  JUMP_INIT(underflow, _IO_file_underflow),
  JUMP_INIT(uflow, _IO_default_uflow),
  JUMP_INIT(pbackfail, _IO_default_pbackfail),
  JUMP_INIT(xsputn, _IO_file_xsputn),
  JUMP_INIT(xsgetn, _IO_default_xsgetn),
  JUMP_INIT(seekoff, _IO_cookie_seekoff),
  JUMP_INIT(seekpos, _IO_default_seekpos),
  JUMP_INIT(setbuf, _IO_file_setbuf),
  JUMP_INIT(sync, _IO_file_sync),
  JUMP_INIT(doallocate, _IO_file_doallocate),
  JUMP_INIT(read, _IO_cookie_read),
  JUMP_INIT(write, _IO_cookie_write),
  JUMP_INIT(seek, _IO_cookie_seek),
  JUMP_INIT(close, _IO_cookie_close),
  JUMP_INIT(stat, _IO_default_stat),
  JUMP_INIT(showmanyc, _IO_default_showmanyc),
  JUMP_INIT(imbue, _IO_default_imbue),
};

尽管 vtable 对具体位置有检测,但是依旧比较宽松,这使得我们可以在一定范围内对 vtable 表的起始位置进行偏移。我们可以通过偏移来调用 vtable 表中的任意函数,而 IO_cookie_jumps 这个虚表中恰好存在几个函数比较特殊可以利用

具体可利用函数如下
static ssize_t
_IO_cookie_read (FILE *fp, void *buf, ssize_t size)
{
  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;
  cookie_read_function_t *read_cb = cfile->__io_functions.read;
#ifdef PTR_DEMANGLE
  PTR_DEMANGLE (read_cb);
#endif
  if (read_cb == NULL)
return -1;
  return read_cb (cfile->__cookie, buf, size);
}
static ssize_t
_IO_cookie_write (FILE *fp, const void *buf, ssize_t size)
{
  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;
  cookie_write_function_t *write_cb = cfile->__io_functions.write;
#ifdef PTR_DEMANGLE
  PTR_DEMANGLE (write_cb);
#endif
  if (write_cb == NULL)
{
  fp->_flags |= _IO_ERR_SEEN;
  return 0;
}
  ssize_t n = write_cb (cfile->__cookie, buf, size);
  if (n < size)
fp->_flags |= _IO_ERR_SEEN;
  return n;
}
static off64_t
_IO_cookie_seek (FILE *fp, off64_t offset, int dir)
{
  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;
  cookie_seek_function_t *seek_cb = cfile->__io_functions.seek;
#ifdef PTR_DEMANGLE
  PTR_DEMANGLE (seek_cb);
#endif
  return ((seek_cb == NULL
   || (seek_cb (cfile->__cookie, &offset, dir)
   == -1)
   || offset == (off64_t) -1)
  ? _IO_pos_BAD : offset);
}
static int
_IO_cookie_close (FILE *fp)
{
  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;
  cookie_close_function_t *close_cb = cfile->__io_functions.close;
#ifdef PTR_DEMANGLE
  PTR_DEMANGLE (close_cb);
#endif
  if (close_cb == NULL)
return 0;
  return close_cb (cfile->__cookie);
}

这几个函数特殊之处在于内部存在任意函数指针调用,并且函数指针来源于 _IO_cookie_file结构体 这个结构体属于 _IO_FILE_plus 的拓展。那么显然,如果我们可以控制 IO 中的内容,那么大概率这部分的数据也是可控的,并且我们可以确认的是这些函数的第一个参数 FILE *fp
就是我们可控的

注意:
我们发现这些函数中都有一个共同的特点,它们拥有一个共同的函数 PTR_DEMANGLE(指针保护) 这意味着我们在利用这些函数的同时我们需要解决一个指针加密的问题

PTR_DEMANGLE的具体定义如下:

extern uintptr_t __pointer_chk_guard attribute_relro; 

#define PTR_MANGLE(var) \
  (var) = (__typeof (var)) ((uintptr_t) (var) ^ __pointer_chk_guard)

#define PTR_DEMANGLE(var) PTR_MANGLE (var)

根据 GLIBC Wiki 上的解释 Pointer Encryption,我们可以得知这个是 GLIBC 的一项安全功能,用于增加攻击者在 GLIBC 结构中操纵指针(尤其是函数指针)的难度。 同时,通过调试可以得知,这个值存在于 TLS 段上,将其 ROR 移位 0x11 后再与指针进行异或
![[Pasted image 20250307230627.png]]

简单来说就是在fs:[0x30]这个地方存放了一个值,这个值会用于异或加密,所以解密的时候我们需要这个值才能正确填充指针达到我们想要的效果
而这个 fs:[0x30] 的值位于与 libc 相邻的 ld 空间中,这个位置距离 Libc 地址的偏移固定,虽然我们无法泄露出这个位置随机值的内容,但是我们可以利用很多方法对其进行写入一个我们已知的值,从而达到绕过 PTR_DEMANGLE
常见的方法有:

  1. Fastbin Reverse Into Tcache
  2. Tcache Stashing Unlink Attack
  3. LargeBin Attack
    但无论使用什么方法,我们根本思想还是让这个本来是随机的、不确定的异或值,转变为已知的地址。而通常在满足能够利用 IO_File 的情况下,这个前置要求都能够被满足

emma利用方法总结

  1. 劫持stderr(比如 largebin attack)

  2. 劫持__pointer_chk_guard使得其为一个已知的地址(比如 largebin attack) 注:使用p &__pointer_chk_guard_local查看
    ![[Pasted image 20250307233552.png]]

  3. 在劫持的stderr中构造fake_IO,具体要求为_IO_write_ptr0xffffffffffffffff(也就是-1)以及_lock为一个不影响程序的流程的可写地址,一般选用堆地址(具体原因在最下面解释1)

  4. 然后在vtable处放上_IO_cookie_jumps+0x40(注:这里为什么是+0x40看下面解释2)用于调用_IO_cookie_write当然其他偏移也可以但是这里只介绍 _IO_cookie_write

  5. 进入_IO_cookie_jumps之后 rdi 会指向 被劫持的stderr

  6. 随后会进入 _IO_cookie_write 此时 rbx 指向 vtable指向的地址 也就是 _IO_cookie_jumps+0x40 先执行 mov rax, [rdi+0xf0] 所以我们需要在 stderr+0xf0 处放上 ROL(gadget_addr ^ (heap_base + 0x22a0), 0x11)(注:这里的ROL函数是什么看下面解释3) 这里的 gadget_addr 是下面这段 gadget 一般在glibc中都可以找到,如果没有完全相同的则需要寻找替代品,总之需要一个既可以利用rdi来控制rdx的gadget,又需要同时兼顾控制程序流程,不论是call还是jmp都可以

mov    rdx,QWORD PTR [rdi+0x8]
mov    QWORD PTR [rsp],rax
call   QWORD PTR [rdx+0x20]
  1. 接着会执行 call qword ptr [rbx+0x38] 也就是 call _IO_cookie_write
  2. 接在 _IO_cookie_write 执行 mov rdi,qword ptr [rdi + 0xe0] 所以我们需要在 被劫持stderr的地址+0xe0 处放上 另一个我们可控的地址记作 chunk2_addr
  3. 之后就会执行 jmp rax 也就是执行上面那三条gadget 为了让 rdx 也指向 chunk2_addr要在 chunk2_addr+0x8 的位置上放上 chunk2_addr,然后在 chunk2_addr+0x20 的地方放上 one_gadget 或者 setcontext_addr + 61
  4. 如果是 orw 的话那么就需要在 chunk2_addr+0xa0 处放上需要控制 rsp 所去的地址记作 rop_addr,然后在 chunk2_addr+0xa8 放上 ret 指令地址
  5. 记得提前布置好 ROP 然后就可以获取 flag

在先知社区的一个师傅的文章里发现一张思维导图,感觉很不错放在这里和大家分享,下面是原文链接文章 - house of emma源码及orw - 先知社区
![[Pasted image 20250219233013.png]]

解释说明:

解释1:
由于伪造的 IO_FILEflag_IO_USER_LOCK(0x8000)没有置位,因此在 __vfxprintf 函数中会执行如下代码
![[Pasted image 20250219233144.png]]
这段代码会使用指令给IO_FILE_lock指向的地址赋值,如果_lock指向的地址不可写的话程序就会报错

解释2:
注:执行到这附件的时候rbx = vtable的值
这是因为在调用的时候它并不是直接调用call qword ptr [rbx]它用的是call qword ptr [rbx+0x38]
![[Pasted image 20250219023051.png]]

那么我们可以通过gdb调试得到(并不是每个glibc版本都一样的)
![[Pasted image 20250308001637.png]]

_IO_cookie_jumps 在 0x7f2dde400ae0
_IO_cookie_write 在 0x7f2dde400b58
0x7f2dde400b58 - 0x7f2dde400ae0 = 0x78
0x78 - 0x38 = 0x40
所以我们在vtable填充的是 _IO_cookie_jumps+0x40
这样 rbp = _IO_cookie_jumps+0x40
call qword ptr [rbp+0x38] = call _IO_cookie_jumps+0x40+0x38 = call _IO_cookie_write

解释3:
自定义函数ROL的具体实现

def ROL(content, key):
    tmp = bin(content)[2:].rjust(64, '0')
    return int(tmp[key:] + tmp[:key], 2)

用法:ROL(gadget_addr ^ (heap_base + 0x22a0), 0x11)

使用emma时可能存在的问题

在实际操作中,可能因为 stderr 的指针存放在 bss 段上,从而导致无法篡改。只能使用 exit 来触发 FSOP,但是又会发现如果通过 exit 来触发 FSOP,会遇到在 exit 中也有调用指针保护的函数指针执行,但此时的异或内容被我们所篡改,使得无法执行正确的函数地址,且此位置在 FSOP 之前,从而导致程序没有进入 IO流 就发生了错误。

这种时候就可以考虑构造两个 IO_FILE,且后者指针处于前者的 _chains 处,前者用 GLIBC2.24 之前的 IO_FILE攻击 的思想在 __pointer_chk_guard 处写已知内容,后者再用 House_OF_Emma 来进行函数指针调用。

参考:
1.文章 - IO利用之House of kiwi & House of emma - 先知社区
2.[原创]【伽玛】第七届“湖湘杯” House _OF _Emma | 设计思路与解析-Pwn-看雪-安全社区|安全招聘|kanxue.com
3.libc2.34下的堆利用–House_of_emma分析 |
4.文章 - house of emma源码及orw - 先知社区
5.House of Emma - WJH’s Blog

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值