local bin = require "bin" local nmap = require "nmap" local shortport = require "shortport" local stdnse = require "stdnse" local string = require "string" local table = require "table" description = [[ Modicon is a brand of Programmable Logic Controller (PLC) that is put out by Schneider Electric. This NSE is designed to use Modbus to communicate to the PLC via Normal queries that are performed via engineering software. The information that is collected via Modbus is done in two separate function codes. First, Function Code 43 is utilized to pull the Vendor Name, Network Module, and the Firmware Version. Second, Schneider uses function code 90 for communications as well. Via Function Code 90 it is possible to pull information such as the CPU Module, Memory Card Model, and some information about the project that is loaded into the PLC. http://digitalbond.com ]] --- -- @usage -- nmap --script modicon-info -p 502 -- -- -- @output --502/tcp open Modbus --| modicon-info: --| Vendor Name: Schneider Electric --| Network Module: BMX NOE 0100 --| CPU Module: BMX P34 2000 --| Firmware: V2.60 --| Memory Card: BMXRMS008MP --| Project Information: Project - V4.0 --| Project File Name: Project.STU --| Project Revision: 0.0.9 --|_ Project Last Modified: 7/11/2013 5:55:33 -- @xmloutput --Schneider Electric --BMX NOE 0100 --BMX P34 2000 --V2.60 --BMXRMS008MP --Project - V4.0 --Project.STU --0.0.9 --7/11/2013 5:55:33 author = "Stephen Hilt (Digital Bond)" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"discovery", "intrusive","digitalbond"} -- -- Function to define the portrule as per nmap standards -- -- -- portrule = shortport.portnumber(502, "tcp") --- -- Function to trim white space off the beginning and ending of a string -- -- @param s a string passed in that needs white space trimmed off function trim(s) -- remove white spaces from beginning and ending of the string return (s:gsub("^%s*(.-)%s*$", "%1")) end --- -- Function to set the nmap output for the host, if a valid Modbus packet -- is received then the output will show that the port as Modbus. -- -- @param host Host that was passed in via nmap -- @param port port that Modbus is running on (Default TCP/502) function set_nmap(host, port) --set port Open port.state = "open" -- set version name to Modbus port.version.name = "Modbus" nmap.set_port_version(host, port) nmap.set_port_state(host, port, "open") end --- -- Function to setup the communications to the Modicon. This is where alot -- of the function code 90 information is sent and parsed for information -- about the Modicon itself. -- -- @param socket Socket passed in via Action to communicate to remote device -- @param output The output table to add information that is collected --- function init_comms(socket, output) -- decelerations local pos local payload = bin.pack("H","000100000004005a0002") socket:send(payload) -- recv packet, however not going to do anything with it local rcvstatus, response = socket:receive() -- send and receive, not going to do anything with this packet. payload = bin.pack("H","000200000005005a000100") socket:send(payload) local rcvstatus, response = socket:receive() -- create a string with 249 T (0x54) local count = 0 local ice = "54" while (count < 248) do ice = ice .. "54" count = count + 1 end -- send packet with 249 T's (0x54), recv packet and do nothing as well payload = bin.pack("H","0003000000fe005a00fe00" .. ice) socket:send(payload) local rcvstatus, response = socket:receive() -- send packet that request the project information payload = bin.pack("H","000400000005005a000300") socket:send(payload) local rcvstatus, response = socket:receive() -- unpack the Project Name, this is configured by the engineers local pos, project_name = bin.unpack("z", response, 50) -- unpack the year that the project was last modified -- define the next sections we are going to unpack -- Each one is to support time stamp local project_hour local project_min local project_sec local project_month local project_day -- define the 3 vars for the project revision number local project_rev_1 local project_rev_2 local project_rev_3 -- unpack the time stamp, as well as the revision numbers -- unpack the seconds pos, project_sec = bin.unpack("C", response, 38) -- unpack the min pos, project_min = bin.unpack("C", response, pos) -- unpack the hour pos, project_hour = bin.unpack("C", response, pos) -- unpack the day pos, project_day = bin.unpack("C", response, pos) -- unpack the month pos, project_month = bin.unpack("C", response, pos) pos, project_year = bin.unpack("