mirror of
https://github.com/nmap/nmap.git
synced 2026-01-22 06:09:01 +00:00
add bjnp library and the scripts bjnp-discover and broadcast-bjnp-discover
commit d41a28813e4e4d26aeaab300ad30ad7c4116e37d Merge: a45e4e2 23fc8f1 Author: Patrik Karlsson <patrik@cqure.net> Date: Sun Aug 5 20:53:04 2012 +0200 Merge branch 'master' into bjnp Conflicts: CHANGELOG commit a45e4e2fd0c2579afc8d5b162bb5484327494b72 Author: Patrik Karlsson <patrik@cqure.net> Date: Sun Aug 5 20:44:19 2012 +0200 add bjnp library and the scripts bjnp-discover and broadcast-bjnp-discover
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
# Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [NSE] Added a BJNP library and the scripts broadcast-bjnp-discover and
|
||||
bjnp-discover. [Patrik Karlsson]
|
||||
|
||||
o [NPING] Nping now prints out an error and exists when the user tries to use
|
||||
the -p flag for a scan option where that is meaningless. [Sean Rivera]
|
||||
|
||||
|
||||
363
nselib/bjnp.lua
Normal file
363
nselib/bjnp.lua
Normal file
@@ -0,0 +1,363 @@
|
||||
---
|
||||
-- An implementation of the Canon BJNP protocol used to discover and query
|
||||
-- Canon network printers and scanner devices.
|
||||
--
|
||||
-- The implementation is pretty much based on Wireshark decoded messages
|
||||
-- the cups-bjnp implementation and the usual guesswork.
|
||||
--
|
||||
-- @author "Patrik Karlsson <patrik [at] cqure.net>"
|
||||
--
|
||||
|
||||
local bin = require("bin")
|
||||
local bit = require("bin")
|
||||
local nmap = require("nmap")
|
||||
local os = require("os")
|
||||
local stdnse = require("stdnse")
|
||||
local string = require("string")
|
||||
local table = require("table")
|
||||
|
||||
_ENV = stdnse.module("bjnp", stdnse.seeall)
|
||||
|
||||
BJNP = {
|
||||
|
||||
-- The common BJNP header
|
||||
Header = {
|
||||
|
||||
new = function(self, o)
|
||||
o = o or {}
|
||||
o = {
|
||||
id = o.id or "BJNP",
|
||||
type = o.type or 1,
|
||||
code = o.code,
|
||||
seq = o.seq or 1,
|
||||
session = o.session or 0,
|
||||
length = o.length or 0,
|
||||
}
|
||||
assert(o.code, "code argument required")
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
parse = function(data)
|
||||
local hdr = BJNP.Header:new({ code = -1 })
|
||||
local pos
|
||||
|
||||
pos, hdr.id, hdr.type, hdr.code,
|
||||
hdr.seq, hdr.session, hdr.length = bin.unpack(">A4CCISI", data)
|
||||
return hdr
|
||||
end,
|
||||
|
||||
__tostring = function(self)
|
||||
return bin.pack(">ACCISI",
|
||||
self.id,
|
||||
self.type,
|
||||
self.code,
|
||||
self.seq,
|
||||
self.session,
|
||||
self.length
|
||||
)
|
||||
end
|
||||
},
|
||||
|
||||
-- Scanner related code
|
||||
Scanner = {
|
||||
|
||||
Code = {
|
||||
DISCOVER = 1,
|
||||
IDENTITY = 48,
|
||||
},
|
||||
|
||||
Request = {
|
||||
|
||||
Discover = {
|
||||
|
||||
new = function(self)
|
||||
local o = { header = BJNP.Header:new( { type = 2, code = BJNP.Scanner.Code.DISCOVER }) }
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
__tostring = function(self)
|
||||
return tostring(self.header)
|
||||
end,
|
||||
},
|
||||
|
||||
|
||||
Identity = {
|
||||
|
||||
new = function(self)
|
||||
local o = { header = BJNP.Header:new( { type = 2, code = BJNP.Scanner.Code.IDENTITY, length = 4 }), data = 0 }
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
__tostring = function(self)
|
||||
return tostring(self.header) .. bin.pack(">I", self.data)
|
||||
end,
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
Response = {
|
||||
|
||||
Identity = {
|
||||
|
||||
new = function(self)
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
parse = function(data)
|
||||
local identity = BJNP.Scanner.Response.Identity:new()
|
||||
identity.header = BJNP.Header.parse(data)
|
||||
|
||||
local pos = #tostring(identity.header) + 1
|
||||
local pos, len = bin.unpack(">S", data, pos)
|
||||
if ( len ) then
|
||||
pos, identity.data = bin.unpack("A" .. len - 2, data, pos)
|
||||
return identity
|
||||
end
|
||||
end,
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
-- Printer related code
|
||||
Printer = {
|
||||
|
||||
Code = {
|
||||
DISCOVER = 1,
|
||||
IDENTITY = 48,
|
||||
},
|
||||
|
||||
Request = {
|
||||
|
||||
Discover = {
|
||||
new = function(self)
|
||||
local o = { header = BJNP.Header:new( { code = BJNP.Printer.Code.DISCOVER }) }
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
__tostring = function(self)
|
||||
return tostring(self.header)
|
||||
end,
|
||||
},
|
||||
|
||||
Identity = {
|
||||
|
||||
new = function(self)
|
||||
local o = { header = BJNP.Header:new( { code = BJNP.Printer.Code.IDENTITY }) }
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
__tostring = function(self)
|
||||
return tostring(self.header)
|
||||
end,
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
Response = {
|
||||
|
||||
Identity = {
|
||||
|
||||
new = function(self)
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
parse = function(data)
|
||||
local identity = BJNP.Printer.Response.Identity:new()
|
||||
identity.header = BJNP.Header.parse(data)
|
||||
|
||||
local pos = #tostring(identity.header) + 1
|
||||
local pos, len = bin.unpack(">S", data, pos)
|
||||
if ( len ) then
|
||||
pos, identity.data = bin.unpack("A" .. len - 2, data, pos)
|
||||
return identity
|
||||
end
|
||||
end,
|
||||
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-- Helper class, the main script writer interface
|
||||
Helper = {
|
||||
|
||||
-- Creates a new Helper instance
|
||||
-- @param host table
|
||||
-- @param port table
|
||||
-- @param options table containing one or more of the following fields;
|
||||
-- <code>timeout</code> - the timeout in milliseconds for socket communication
|
||||
-- <code>bcast</code> - instructs the library that the host is a broadcast
|
||||
-- address
|
||||
-- @return o new instance of Helper
|
||||
new = function(self, host, port, options)
|
||||
local o = {
|
||||
host = host, port = port, options = options or {}
|
||||
}
|
||||
o.options.timeout = o.options.timeout or 5000
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end,
|
||||
|
||||
-- Connects the socket to the device
|
||||
-- This should always be called, regardless if the broadcast option is set
|
||||
-- or not.
|
||||
--
|
||||
-- @return status, true on success, false on failure
|
||||
-- @return err string containing the error message if status is false
|
||||
connect = function(self)
|
||||
self.socket = nmap.new_socket(( self.options.bcast and "udp" ))
|
||||
self.socket:set_timeout(self.options.timeout)
|
||||
if ( not(self.options.bcast) ) then
|
||||
return self.socket:connect(self.host, self.port)
|
||||
end
|
||||
return true
|
||||
end,
|
||||
|
||||
-- Discover network devices using either broadcast or unicast
|
||||
-- @param packet discovery packet (printer or scanner)
|
||||
-- @return status, true on success, false on failure
|
||||
-- @return devices table containing discovered devices when status is true
|
||||
-- errmsg string containing the error message when status is false
|
||||
discoverDevice = function(self, packet)
|
||||
if ( not(self.options.bcast) ) then
|
||||
if ( not(self.socket:send(tostring(packet))) ) then
|
||||
return false, "Failed to send request to server"
|
||||
end
|
||||
else
|
||||
if ( not(self.socket:sendto(self.host, self.port, tostring(packet))) ) then
|
||||
return false, "Failed to send request to server"
|
||||
end
|
||||
end
|
||||
-- discover run in loop
|
||||
local devices, tmp = {}, {}
|
||||
local start = os.time()
|
||||
while( true ) do
|
||||
local status, data = self.socket:receive()
|
||||
if ( not(status) or ( os.time() - start > ( self.options.timeout/1000 - 1 ) )) then
|
||||
break
|
||||
end
|
||||
local status, _, _, rhost = self.socket:get_info()
|
||||
tmp[rhost] = true
|
||||
end
|
||||
for host in pairs(tmp) do table.insert(devices, host) end
|
||||
return true, ( self.options.bcast and devices or ( #devices > 0 and devices[1] ))
|
||||
end,
|
||||
|
||||
-- Discover BJNP supporting scanners
|
||||
discoverScanner = function(self)
|
||||
return self:discoverDevice(BJNP.Scanner.Request.Discover:new())
|
||||
end,
|
||||
|
||||
-- Discover BJNP supporting printers
|
||||
discoverPrinter = function(self)
|
||||
return self:discoverDevice(BJNP.Printer.Request.Discover:new())
|
||||
end,
|
||||
|
||||
-- Gets a printer identity (additional information)
|
||||
-- @param devtype string containing either the string printer or scanner
|
||||
-- @return status, true on success, false on failure
|
||||
-- @return attribs table containing device attributes when status is true
|
||||
-- errmsg string containing the error message when status is false
|
||||
getDeviceIdentity = function(self, devtype)
|
||||
-- Were currenlty only decoding this as I don't know what the other cruft is
|
||||
local attrib_names = {
|
||||
["scanner"] = {
|
||||
{ ['MFG'] = "Manufacturer" },
|
||||
{ ['MDL'] = "Model" },
|
||||
{ ['DES'] = "Description" },
|
||||
{ ['CMD'] = "Command" },
|
||||
},
|
||||
["printer"] = {
|
||||
{ ['MFG'] = "Manufacturer" },
|
||||
{ ['MDL'] = "Model" },
|
||||
{ ['DES'] = "Description" },
|
||||
{ ['VER'] = "Firmware version" },
|
||||
{ ['CMD'] = "Command" },
|
||||
}
|
||||
}
|
||||
local identity
|
||||
if ( "printer" == devtype ) then
|
||||
identity = BJNP.Printer.Request.Identity:new()
|
||||
elseif ( "scanner" == devtype ) then
|
||||
identity = BJNP.Scanner.Request.Identity:new()
|
||||
end
|
||||
assert(not(self.options.bcast), "getIdentity is not supported for broadcast")
|
||||
if ( not(self.socket:send(tostring(identity))) ) then
|
||||
return false, "Failed to send request to server"
|
||||
end
|
||||
local status, data = self.socket:receive()
|
||||
if ( not(status) ) then
|
||||
return false, "Failed to receive response from server"
|
||||
end
|
||||
|
||||
local identity
|
||||
if ( "printer" == devtype ) then
|
||||
identity = BJNP.Printer.Response.Identity.parse(data)
|
||||
elseif ( "scanner" == devtype ) then
|
||||
identity = BJNP.Scanner.Response.Identity.parse(data)
|
||||
end
|
||||
if ( not(identity) ) then
|
||||
return false, "Failed to parse identity"
|
||||
end
|
||||
local attrs, kvps = {}, {}
|
||||
|
||||
for k, v in ipairs(stdnse.strsplit(";", identity.data)) do
|
||||
local nm, val = v:match("^([^:]*):(.*)$")
|
||||
if ( nm ) then kvps[nm] = val end
|
||||
end
|
||||
|
||||
for _, attrib in ipairs(attrib_names[devtype]) do
|
||||
local short, long = next(attrib)
|
||||
if ( kvps[short] ) then
|
||||
table.insert(attrs, ("%s: %s"):format(long, kvps[short]))
|
||||
end
|
||||
end
|
||||
|
||||
return true, attrs
|
||||
end,
|
||||
|
||||
-- Retrieves information related to the printer
|
||||
getPrinterIdentity = function(self)
|
||||
return self:getDeviceIdentity("printer")
|
||||
end,
|
||||
|
||||
-- Retrieves information related to the scanner
|
||||
getScannerIdentity = function(self)
|
||||
return self:getDeviceIdentity("scanner")
|
||||
end,
|
||||
|
||||
-- Closes the connection
|
||||
-- @return status, true on success, false on failure
|
||||
-- @return errmsg string containing the error message when status is false
|
||||
close = function(self)
|
||||
return self.socket:close()
|
||||
end
|
||||
|
||||
}
|
||||
|
||||
return _ENV;
|
||||
49
scripts/bjnp-discover.nse
Normal file
49
scripts/bjnp-discover.nse
Normal file
@@ -0,0 +1,49 @@
|
||||
description = [[
|
||||
Retrievs printer or scanner information from a remote device supporting the
|
||||
BJNP protocol. The protocol is known to be supported by network based Canon
|
||||
devices.
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage
|
||||
-- sudo nmap -sU -p 8611,8612 --script bjnp-discover <ip>
|
||||
--
|
||||
-- @output
|
||||
-- PORT STATE SERVICE
|
||||
-- 8611/udp open canon-bjnp1
|
||||
-- | bjnp-discover:
|
||||
-- | Manufacturer: Canon
|
||||
-- | Model: MG5200 series
|
||||
-- | Description: Canon MG5200 series
|
||||
-- | Firmware version: 1.050
|
||||
-- |_ Command: BJL,BJRaster3,BSCCe,NCCe,IVEC,IVECPLI
|
||||
-- 8612/udp open canon-bjnp2
|
||||
-- | bjnp-discover:
|
||||
-- | Manufacturer: Canon
|
||||
-- | Model: MG5200 series
|
||||
-- | Description: Canon MG5200 series
|
||||
-- |_ Command: MultiPass 2.1,IVEC
|
||||
--
|
||||
|
||||
categories = {"safe", "discovery"}
|
||||
|
||||
local bjnp = require("bjnp")
|
||||
local shortport = require("shortport")
|
||||
local stdnse = require("stdnse")
|
||||
|
||||
portrule = shortport.portnumber({8611, 8612}, "udp")
|
||||
|
||||
action = function(host, port)
|
||||
local helper = bjnp.Helper:new(host, port)
|
||||
if ( not(helper:connect()) ) then
|
||||
return "\n ERROR: Failed to connect to server"
|
||||
end
|
||||
local status, attrs
|
||||
if ( port.number == 8611 ) then
|
||||
status, attrs = helper:getPrinterIdentity()
|
||||
else
|
||||
status, attrs = helper:getScannerIdentity()
|
||||
end
|
||||
helper:close()
|
||||
return stdnse.format_output(true, attrs)
|
||||
end
|
||||
172
scripts/broadcast-bjnp-discover.nse
Normal file
172
scripts/broadcast-bjnp-discover.nse
Normal file
@@ -0,0 +1,172 @@
|
||||
description = [[
|
||||
Attempts to discover Canon devices (Printers/Scanners) supporting the BJNP
|
||||
protocol. Discovery is performed by sending BJNP Discover requests to the
|
||||
network broadcast address for both ports associated with the protocol.
|
||||
|
||||
The script then attempts to retrieve the model, version and some additional
|
||||
information for all discovered devices.
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage
|
||||
-- nmap --script broadcast-bjnp-discover
|
||||
--
|
||||
-- @output
|
||||
-- | broadcast-bjnp-discover:
|
||||
-- | 192.168.0.10
|
||||
-- | Printer
|
||||
-- | Manufacturer: Canon
|
||||
-- | Model: MG5200 series
|
||||
-- | Description: Canon MG5200 series
|
||||
-- | Firmware version: 1.050
|
||||
-- | Command: BJL,BJRaster3,BSCCe,NCCe,IVEC,IVECPLI
|
||||
-- | Scanner
|
||||
-- | Manufacturer: Canon
|
||||
-- | Model: MG5200 series
|
||||
-- | Description: Canon MG5200 series
|
||||
-- |_ Command: MultiPass 2.1,IVEC
|
||||
--
|
||||
|
||||
author = "Patrik Karlsson"
|
||||
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||
categories = {"safe", "broadcast"}
|
||||
|
||||
local bjnp = require("bjnp")
|
||||
local stdnse = require("stdnse")
|
||||
local coroutine = require("coroutine")
|
||||
local nmap = require("nmap")
|
||||
local table = require("table")
|
||||
|
||||
local printer_port = { number = 8611, protocol = "udp"}
|
||||
local scanner_port = { number = 8612, protocol = "udp"}
|
||||
local arg_timeout = tonumber(stdnse.get_script_args(SCRIPT_NAME .. ".timeout"))
|
||||
|
||||
prerule = function()
|
||||
if ( nmap.address_family() ~= 'inet' ) then
|
||||
stdnse.print_debug("%s is IPv4 compatible only.", SCRIPT_NAME)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function identifyDevices(devices, devtype)
|
||||
local result
|
||||
local port = ( "printers" == devtype and printer_port or scanner_port )
|
||||
for _, ip in ipairs(devices or {}) do
|
||||
local helper = bjnp.Helper:new({ ip = ip }, port)
|
||||
if ( helper:connect() ) then
|
||||
local status, attrs
|
||||
if ( "printers" == devtype ) then
|
||||
status, attrs = helper:getPrinterIdentity()
|
||||
end
|
||||
if ( "scanners" == devtype ) then
|
||||
status, attrs = helper:getScannerIdentity()
|
||||
end
|
||||
if ( status ) then
|
||||
result = result or {}
|
||||
result[ip] = attrs
|
||||
end
|
||||
end
|
||||
helper:close()
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
local function identifyScanners(scanners)
|
||||
return identifyDevices(scanners, "scanners")
|
||||
end
|
||||
|
||||
local function identifyPrinters(printers)
|
||||
return identifyDevices(printers, "printers")
|
||||
end
|
||||
|
||||
local function getKeys(devices)
|
||||
local dupes = {}
|
||||
local function iter()
|
||||
for k, _ in pairs(devices) do
|
||||
for k2, _ in pairs(devices[k]) do
|
||||
if ( not(dupes[k2]) ) then
|
||||
dupes[k2] = true
|
||||
coroutine.yield(k2)
|
||||
end
|
||||
end
|
||||
end
|
||||
coroutine.yield(nil)
|
||||
end
|
||||
return coroutine.wrap(iter)
|
||||
end
|
||||
|
||||
local function getPrinters(devices)
|
||||
local condvar = nmap.condvar(devices)
|
||||
local helper = bjnp.Helper:new( { ip = "255.255.255.255" }, printer_port, { bcast = true, timeout = arg_timeout } )
|
||||
if ( not(helper:connect()) ) then
|
||||
condvar "signal"
|
||||
return
|
||||
end
|
||||
local status, printers = helper:discoverPrinter()
|
||||
helper:close()
|
||||
if ( status ) then
|
||||
devices["printers"] = identifyPrinters(printers)
|
||||
end
|
||||
condvar "signal"
|
||||
end
|
||||
|
||||
local function getScanners(devices)
|
||||
local condvar = nmap.condvar(devices)
|
||||
local helper = bjnp.Helper:new( { ip = "255.255.255.255" }, scanner_port, { bcast = true, timeout = arg_timeout } )
|
||||
if ( not(helper:connect()) ) then
|
||||
condvar "signal"
|
||||
return
|
||||
end
|
||||
local status, scanners = helper:discoverScanner()
|
||||
helper:close()
|
||||
if ( status ) then
|
||||
devices["scanners"] = identifyScanners(scanners)
|
||||
end
|
||||
condvar "signal"
|
||||
end
|
||||
|
||||
|
||||
action = function()
|
||||
arg_timeout = ( arg_timeout and arg_timeout * 1000 or 5000)
|
||||
local devices, result, threads = {}, {}, {}
|
||||
local condvar = nmap.condvar(devices)
|
||||
|
||||
local co = stdnse.new_thread(getPrinters, devices)
|
||||
threads[co] = true
|
||||
|
||||
co = stdnse.new_thread(getScanners, devices)
|
||||
threads[co] = true
|
||||
|
||||
while(next(threads)) do
|
||||
for t in pairs(threads) do
|
||||
threads[t] = ( coroutine.status(t) ~= "dead" ) and true or nil
|
||||
end
|
||||
if ( next(threads) ) then
|
||||
condvar "wait"
|
||||
end
|
||||
end
|
||||
|
||||
for ip in getKeys(devices) do
|
||||
local result_part = {}
|
||||
local printer = ( devices["printers"] and devices["printers"][ip] )
|
||||
local scanner = ( devices["scanners"] and devices["scanners"][ip] )
|
||||
|
||||
if ( printer ) then
|
||||
printer.name = "Printer"
|
||||
table.insert(result_part, printer)
|
||||
end
|
||||
if ( scanner ) then
|
||||
scanner.name = "Scanner"
|
||||
table.insert(result_part, scanner)
|
||||
end
|
||||
if ( #result_part > 0 ) then
|
||||
result_part.name = ip
|
||||
table.insert(result, result_part)
|
||||
end
|
||||
end
|
||||
|
||||
if ( result ) then
|
||||
return stdnse.format_output(true, result)
|
||||
end
|
||||
end
|
||||
@@ -21,8 +21,10 @@ Entry { filename = "bitcoin-getaddr.nse", categories = { "discovery", "safe", }
|
||||
Entry { filename = "bitcoin-info.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "bitcoinrpc-info.nse", categories = { "default", "discovery", "safe", } }
|
||||
Entry { filename = "bittorrent-discovery.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "bjnp-discover.nse", categories = { "discovery", "safe", } }
|
||||
Entry { filename = "broadcast-ataoe-discover.nse", categories = { "broadcast", "safe", } }
|
||||
Entry { filename = "broadcast-avahi-dos.nse", categories = { "broadcast", "dos", "intrusive", "vuln", } }
|
||||
Entry { filename = "broadcast-bjnp-discover.nse", categories = { "broadcast", "safe", } }
|
||||
Entry { filename = "broadcast-db2-discover.nse", categories = { "broadcast", "safe", } }
|
||||
Entry { filename = "broadcast-dhcp-discover.nse", categories = { "broadcast", "safe", } }
|
||||
Entry { filename = "broadcast-dhcp6-discover.nse", categories = { "broadcast", "safe", } }
|
||||
|
||||
Reference in New Issue
Block a user