112 lines
3.5 KiB
Lua
112 lines
3.5 KiB
Lua
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 <host>
|
|
--
|
|
--
|
|
-- @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
|
|
--<elem key="PLC Type">ILC 330 ETH</elem>
|
|
--<elem key="Model Number">2737193</elem>
|
|
--<elem key="Firmware Version">3.95T</elem>
|
|
--<elem key="Firmware Date">Mar 2 2012</elem>
|
|
--<elem key="Firmware Time">09:39:02</elem>
|
|
|
|
author = "Stephen Hilt (Digital Bond)"
|
|
license = "Same as Nmap--See https://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
|