前言
这条链子由WJH’s Blog师傅首个发现,经典例题为2021湖湘杯
的1解题 House_OF_Emma
适用范围:
glibc2.23-glibc.2.34
使用条件
- 可以任意写一个可控地址(LargeBin Attack、Tcache Stashing Unlink Attack…)
- 可以触发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
后再与指针进行异或
简单来说就是在fs:[0x30]
这个地方存放了一个值,这个值会用于异或加密
,所以解密的时候我们需要这个值才能正确填充指针达到我们想要的效果
而这个 fs:[0x30]
的值位于与 libc
相邻的 ld
空间中,这个位置距离 Libc
地址的偏移固定,虽然我们无法泄露出这个位置随机值的内容,但是我们可以利用很多方法对其进行写入一个我们已知的值,从而达到绕过 PTR_DEMANGLE
常见的方法有:
- Fastbin Reverse Into Tcache
- Tcache Stashing Unlink Attack
- LargeBin Attack
但无论使用什么方法,我们根本思想还是让这个本来是随机的、不确定的异或值,转变为已知的地址。而通常在满足能够利用IO_File
的情况下,这个前置要求都能够被满足
emma利用方法总结
-
劫持
stderr
(比如largebin attack
) -
劫持
__pointer_chk_guard
使得其为一个已知的地址(比如largebin attack
) 注:使用p &__pointer_chk_guard_local
查看
-
在劫持的
stderr
中构造fake_IO
,具体要求为_IO_write_ptr
为0xffffffffffffffff(也就是-1)
以及_lock
为一个不影响程序的流程的可写地址,一般选用堆地址(具体原因在最下面解释1
) -
然后在
vtable
处放上_IO_cookie_jumps+0x40
(注:这里为什么是+0x40看下面解释2
)用于调用_IO_cookie_write
当然其他偏移也可以但是这里只介绍_IO_cookie_write
-
进入
_IO_cookie_jumps
之后rdi
会指向被劫持的stderr
-
随后会进入
_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]
- 接着会执行
call qword ptr [rbx+0x38]
也就是call _IO_cookie_write
- 接在
_IO_cookie_write
执行mov rdi,qword ptr [rdi + 0xe0]
所以我们需要在被劫持stderr的地址+0xe0
处放上 另一个我们可控的地址记作chunk2_addr
- 之后就会执行
jmp rax
也就是执行上面那三条gadget 为了让rdx
也指向chunk2_addr
要在chunk2_addr+0x8
的位置上放上chunk2_addr
,然后在chunk2_addr+0x20
的地方放上one_gadget
或者setcontext_addr + 61
- 如果是
orw
的话那么就需要在chunk2_addr+0xa0
处放上需要控制rsp
所去的地址记作rop_addr
,然后在chunk2_addr+0xa8
放上ret
指令地址 - 记得提前布置好
ROP
然后就可以获取flag
了
在先知社区的一个师傅的文章里发现一张思维导图,感觉很不错放在这里和大家分享,下面是原文链接文章 - house of emma源码及orw - 先知社区
解释说明:
解释1:
由于伪造的 IO_FILE
的 flag
的 _IO_USER_LOCK
(0x8000)没有置位,因此在 __vfxprintf
函数中会执行如下代码
这段代码会使用指令给IO_FILE
中_lock
指向的地址赋值,如果_lock
指向的地址不可写的话程序就会报错
解释2:
注:执行到这附件的时候rbx = vtable的值
这是因为在调用的时候它并不是直接调用call qword ptr [rbx]
它用的是call qword ptr [rbx+0x38]
那么我们可以通过gdb调试得到(并不是每个glibc版本都一样的)
_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