diff --git a/CHANGELOG b/CHANGELOG index 2c2716cc2..79b412371 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] New script: pcworx-info retrieves PLC model, firmware version, and date + from Phoenix Contact PLCs. [Stephen Hilt] + Nmap 7.25BETA2 [2016-09-01] o [GH#376] Windows binaries are now code-signed with our "Insecure.Com LLC" diff --git a/nmap-service-probes b/nmap-service-probes index 84ba37f43..7a9fa54a5 100644 --- a/nmap-service-probes +++ b/nmap-service-probes @@ -14946,3 +14946,10 @@ ports 1194,443,500 rarity 9 match openvpn m|^@........\x01\0\0\0\0d\xc1x\x01\xb8\x9b\xcb\x8f\0\0\0\0$| p/OpenVPN/ +##############################NEXT PROBE############################## +# Phoenix Contact PCWorx +Probe TCP pcworx q|\x01\x01\x00\x1a\x00\x00\x00\x00x\x80\x00\x03\x00\x0cIBETH01N0_M\x00| +rarity 9 +ports 1962 + +match pcworx m|\x81\x01\0\x14\0\0\0\x01\0\0\0\0\0\x02\0\0\0.\0\0| p/Phoenix Contact PCWorx/ diff --git a/scripts/pcworx-info.nse b/scripts/pcworx-info.nse new file mode 100644 index 000000000..50d55a99c --- /dev/null +++ b/scripts/pcworx-info.nse @@ -0,0 +1,111 @@ +local string = require "string" +local nmap = require "nmap" +local shortport = require "shortport" +local stdnse = require "stdnse" + +description = [[ +This NSE script will query and parse pcworx protocol to a remote PLC. +The script will send a initial request packets and once a response is received, +it validates that it was a proper response to the command that was sent, and then +will parse out the data. PCWorx is a protocol and Program by Phoenix Contact. + + +http://digitalbond.com +]] +--- +-- @usage +-- nmap --script pcworx-info -p 1962 +-- +-- +-- @output +--| pcworx-info: +--| PLC Type: ILC 330 ETH +--| Model Number: 2737193 +--| Firmware Version: 3.95T +--| Firmware Date: Mar 2 2012 +--|_ Firmware Time: 09:39:02 + +-- +-- +-- @xmloutput +--ILC 330 ETH +--2737193 +--3.95T +--Mar 2 2012 +--09:39:02 + +author = "Stephen Hilt (Digital Bond)" +license = "Same as Nmap--See http://nmap.org/book/man-legal.html" +categories = {"discovery"} + +portrule = shortport.port_or_service(1962, "pcworx", "tcp") + +-- Safely extract a zero-terminated string if the blob is long enough +-- Returns nil if it is not. +local function get_string(blob, offset) + if #blob >= offset then + return string.unpack("z", blob, offset) + end +end +--- +-- Action Function that is used to run the NSE. This function will send the initial query to the +-- host and port that were passed in via nmap. The initial response is parsed to determine if host +-- is a pcworx Protocol device. If it is then more actions are taken to gather extra information. +-- +-- @param host Host that was scanned via nmap +-- @param port port that was scanned via nmap +action = function(host,port) + local init_comms = "\x01\x01\0\x1a\0\0\0\0x\x80\0\x03\0\x0cIBETH01N0_M\0" + + -- create table for output + local output = stdnse.output_table() + + -- create new socket + local socket = nmap.new_socket() + -- define the catch of the try statement + local catch = function() + socket:close() + end + local try = nmap.new_try(catch) + + try(socket:connect(host, port)) + try(socket:send(init_comms)) + local response = try(socket:receive()) + + if not response:match("^\x81") then + stdnse.debug1("Unexpected or unknown PCWorx message.") + return nil + end + -- pcworx has a session ID that is generated by the PLC + -- This will pull the SID so we can communicate further to the PLC + local sid = string.sub(response, 18, 18) + local init_comms2 = "\x01\x05\0\x16\0\x01\0\0\x78\x80\0" .. sid .. "\0\0\0\x06\0\x04\x02\x95\0\0" + try(socket:send(init_comms2)) + -- receive response + response = try(socket:receive()) + -- TODO: verify this + + -- this is the request that will pull all the information from the PLC + local req_info = "\x01\x06\0\x0e\0\x02\0\0\0\0\0" .. sid .. "\x04\0" + try(socket:send(req_info)) + -- receive response + response = try(socket:receive()) + + -- if the response starts with 0x81 then we will continue + if not response:match("^\x81") then + stdnse.debug1("Unexpected or unknown PCWorx message.") + socket:close() + return nil + end + + -- create output table with proper data + output["PLC Type"] = get_string(response, 31) + output["Model Number"] = get_string(response, 153) + output["Firmware Version"] = get_string(response, 67) + output["Firmware Date"] = get_string(response, 80) + output["Firmware Time"] = get_string(response, 92) + + -- close socket and return output table + socket:close() + return output +end diff --git a/scripts/script.db b/scripts/script.db index 34775ad91..947b3aecf 100644 --- a/scripts/script.db +++ b/scripts/script.db @@ -380,6 +380,7 @@ Entry { filename = "ovs-agent-version.nse", categories = { "version", } } Entry { filename = "p2p-conficker.nse", categories = { "default", "safe", } } Entry { filename = "path-mtu.nse", categories = { "discovery", "safe", } } Entry { filename = "pcanywhere-brute.nse", categories = { "brute", "intrusive", } } +Entry { filename = "pcworx-info.nse", categories = { "discovery", } } Entry { filename = "pgsql-brute.nse", categories = { "brute", "intrusive", } } Entry { filename = "pjl-ready-message.nse", categories = { "intrusive", } } Entry { filename = "pop3-brute.nse", categories = { "brute", "intrusive", } }