mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 04:31:29 +00:00
Updated product codes, check for response length, update to string.unpack. (NothinRandom). Closes #1346
This commit is contained in:
@@ -9,8 +9,8 @@ This NSE script is used to send a EtherNet/IP packet to a remote device that
|
||||
has TCP 44818 open. The script will send a Request Identity Packet 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. Information that is parsed
|
||||
includes Vendor ID, Device Type, Product name, Serial Number, Product code,
|
||||
Revision Number, as well as the Device IP.
|
||||
includes Device Type, Vendor ID, Product name, Serial Number, Product code,
|
||||
Revision Number, status, state, as well as the Device IP.
|
||||
|
||||
This script was written based of information collected by using the the
|
||||
Wireshark dissector for CIP, and EtherNet/IP, The original information was
|
||||
@@ -26,43 +26,44 @@ http://digitalbond.com
|
||||
--
|
||||
--
|
||||
-- @output
|
||||
--44818/tcp open EtherNet/IP
|
||||
--PORT STATE SERVICE REASON
|
||||
--44818/tcp open EtherNet-IP-2 syn-ack
|
||||
--| enip-info:
|
||||
--| Vendor: Rockwell Automation/Allen-Bradley (1)
|
||||
--| Product Name: 1769-L32E Ethernet Port
|
||||
--| Serial Number: 0x000000
|
||||
--| Device Type: Communications Adapter (12)
|
||||
--| Product Code: 158
|
||||
--| Revision: 3.7
|
||||
--|_ Device IP: 192.168.1.1
|
||||
--| type: Communications Adapter (12)
|
||||
--| vendor: Rockwell Automation/Allen-Bradley (1)
|
||||
--| productName: 1769-L32E Ethernet Port
|
||||
--| serialNumber: 0x000000
|
||||
--| productCode: 158
|
||||
--| revision: 3.7
|
||||
--| status: 0x0030
|
||||
--| state: 0x03
|
||||
--|_ ipAddress: 192.168.1.123
|
||||
-- @xmloutput
|
||||
--<elem key="Vendor">Rockwell Automation/Allen-Bradley (1)</elem>
|
||||
--<elem key="Product Name">1769-L32E Ethernet Port</elem>
|
||||
--<elem key="Serial Number">0x000000</elem>
|
||||
--<elem key="Device Type">Communications Adapter (12)</elem>
|
||||
--<elem key="Product Code">158</elem>
|
||||
--<elem key="Revision">3.7</elem>
|
||||
--<elem key="Device IP">192.168.1.1</elem>
|
||||
--<elem key="type">Communications Adapter (12)</elem>
|
||||
--<elem key="vendor">Rockwell Automation/Allen-Bradley (1)</elem>
|
||||
--<elem key="productName">1769-L32E Ethernet Port</elem>
|
||||
--<elem key="serialNumber">0x000000</elem>
|
||||
--<elem key="productCode">158</elem>
|
||||
--<elem key="revision">3.7</elem>
|
||||
--<elem key="status">0x0030</elem>
|
||||
--<elem key="state">0x03</elem>
|
||||
--<elem key="ipAddress">192.168.1.1</elem>
|
||||
|
||||
|
||||
author = "Stephen Hilt (Digital Bond)"
|
||||
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
||||
categories = {"discovery", "version"}
|
||||
|
||||
--
|
||||
-- Function to define the portrule as per nmap standards
|
||||
--
|
||||
--
|
||||
-- IANA replaced the historical EtherNet/IP-2 name with EtherNet-IP-2
|
||||
portrule = shortport.version_port_or_service(44818, {"EtherNet-IP-2", "EtherNet/IP-2"}, {"tcp","udp"})
|
||||
|
||||
---
|
||||
-- Table to look up the Vendor Name based on Vendor ID
|
||||
-- Returns "Unknown Vendor Number" if Vendor ID not recognized
|
||||
-- Table data from Wireshark dissector ( link to unofficial mirror )
|
||||
-- https://github.com/avsej/wireshark/blob/master/epan/dissectors/packet-enip.c
|
||||
-- Fetched on 4/19/2014
|
||||
--
|
||||
|
||||
-- @key vennum Vendor number parsed out of the EtherNet/IP packet
|
||||
local vendor_id = {
|
||||
[0] = "Reserved",
|
||||
@@ -1560,12 +1561,12 @@ local vendor_id = {
|
||||
[1512] = "SAMSON AG",
|
||||
[1513] = "TGW Mechanics GmbH",
|
||||
}
|
||||
|
||||
--return vendor information
|
||||
local function vendor_lookup(vennum)
|
||||
return vendor_id[vennum] or "Unknown Vendor Number"
|
||||
end
|
||||
|
||||
---
|
||||
-- Table to look up the Device Type based on Device ID Number
|
||||
-- Returns "Unknown Device Type" if Device ID Number not recognized
|
||||
-- Table data from Wireshark dissector ( link to unofficial mirror )
|
||||
@@ -1575,23 +1576,31 @@ end
|
||||
-- @key devtype Device ID number parsed out of the EtherNet/IP packet
|
||||
local device_type = {
|
||||
[0] = "Generic Device (deprecated)",
|
||||
[2] = "AC Drive",
|
||||
[1] = "Control Station (deprecated)",
|
||||
[2] = "AC Drive Device",
|
||||
[3] = "Motor Overload",
|
||||
[4] = "Limit Switch",
|
||||
[5] = "Inductive Proximity Switch",
|
||||
[6] = "Photoelectric Sensor",
|
||||
[7] = "General Purpose Discrete I/O",
|
||||
[8] = "Encoder (deprecated)",
|
||||
[9] = "Resolver",
|
||||
[10] = "General Purpose Analog I/O (deprecated)",
|
||||
[12] = "Communications Adapter",
|
||||
[13] = "Barcode Scanner (deprecated)",
|
||||
[14] = "Programmable Logic Controller",
|
||||
[16] = "Position Controller",
|
||||
[17] = "Weigh Scale (deprecated)",
|
||||
[18] = "Message Display (deprecated)",
|
||||
[19] = "DC Drive",
|
||||
[20] = "Servo Drives (deprecated)",
|
||||
[21] = "Contactor",
|
||||
[22] = "Motor Starter",
|
||||
[23] = "Soft Start",
|
||||
[23] = "Softstart Starter",
|
||||
[24] = "Human-Machine Interface",
|
||||
[25] = "Pneumatic Valve(s) (deprecated)",
|
||||
[26] = "Mass Flow Controller",
|
||||
[27] = "Pneumatic Valve",
|
||||
[27] = "Pneumatic Valve(s)",
|
||||
[28] = "Vacuum Pressure Gauge",
|
||||
[29] = "Process Control Value",
|
||||
[30] = "Residual Gas Analyzer",
|
||||
@@ -1608,32 +1617,34 @@ local device_type = {
|
||||
[41] = "CIP Modbus Translator",
|
||||
[42] = "Safety Analog I/O Device",
|
||||
[43] = "Generic Device (keyable)",
|
||||
[44] = "Managed Switch",
|
||||
[59] = "ControlNet Physical Layer Component"
|
||||
[44] = "Managed Ethernet Switch",
|
||||
[50] = "ControlNet Physical Layer Component",
|
||||
[59] = "ControlNet Physical Layer Component",
|
||||
[100] = "In-Sight 2000 Series",
|
||||
[150] = "PowerFlex 525",
|
||||
[773] = "DataMan Series Reader"
|
||||
}
|
||||
|
||||
--return device type information
|
||||
function device_type_lookup (devtype)
|
||||
return device_type[devtype] or "Unknown Device Type"
|
||||
end
|
||||
|
||||
---
|
||||
-- Function to set the nmap output for the host, if a valid EtherNet/IP packet
|
||||
-- is received then the output will show that the port as EtherNet/IP instead of
|
||||
-- <code>unknown</code>
|
||||
--
|
||||
|
||||
-- @param host Host that was passed in via nmap
|
||||
-- @param port port that EtherNet/IP is running on (Default TCP/44818)
|
||||
function set_nmap(host, port)
|
||||
|
||||
--set port Open
|
||||
port.state = "open"
|
||||
-- set version name to EtherNet/IP
|
||||
port.version.name = "EtherNet-IP-2"
|
||||
nmap.set_port_version(host, port)
|
||||
nmap.set_port_state(host, port, "open")
|
||||
|
||||
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 EtherNet/IP device. If it is then more actions are taken to gather extra information.
|
||||
@@ -1641,68 +1652,115 @@ end
|
||||
-- @param host Host that was scanned via nmap
|
||||
-- @param port port that was scanned via nmap
|
||||
action = function(host,port)
|
||||
-- pack the request identity packet (0x63)
|
||||
local enip_req_ident = stdnse.fromhex("63000000000000000000000000000000c1debed100000000")
|
||||
-- create table for output
|
||||
local output = stdnse.output_table()
|
||||
-- create local vars for socket handling
|
||||
local socket, try, catch
|
||||
|
||||
-- create new socket
|
||||
socket = nmap.new_socket()
|
||||
|
||||
-- set timeout
|
||||
socket:set_timeout(stdnse.get_timeout(host))
|
||||
|
||||
-- define the catch of the try statement
|
||||
catch = function()
|
||||
socket:close()
|
||||
end
|
||||
|
||||
-- create new try
|
||||
try = nmap.new_try(catch)
|
||||
|
||||
-- connect to port on host
|
||||
try(socket:connect(host, port))
|
||||
-- send Req Identity packet
|
||||
try(socket:send(enip_req_ident))
|
||||
|
||||
-- send the request identity packet (0x63)
|
||||
local enipQuery = stdnse.fromhex("63000000000000000000000000000000c1debed100000000")
|
||||
try(socket:send(enipQuery))
|
||||
|
||||
-- receive response
|
||||
local rcvstatus, response = socket:receive()
|
||||
if(rcvstatus == false) then
|
||||
return false, response
|
||||
end
|
||||
-- unpack the response command
|
||||
local command = string.byte(response, 1)
|
||||
-- unpack the response type id
|
||||
local typeid = string.byte(response, 27)
|
||||
-- if command is 0x63
|
||||
if ( command == 0x63) then
|
||||
-- if typeid == 0x0c (req ident)
|
||||
if( typeid == 0x0c) then
|
||||
-- vendor number
|
||||
local vennum = string.unpack("<I2", response, 49)
|
||||
-- look up vendor number and store in output table
|
||||
output["Vendor"] = vendor_lookup(vennum) .. " (" .. vennum .. ")"
|
||||
-- unpack product name into output table
|
||||
output["Product Name"] = string.unpack("s1", response, 63)
|
||||
-- unpack the serial number
|
||||
local serial = string.unpack("<I4", response, 59)
|
||||
-- print it out in hex format
|
||||
output["Serial Number"] = string.format("%#0.8x", serial)
|
||||
-- device type number
|
||||
local devnum = string.unpack("<I2", response, 51)
|
||||
-- lookup device type based off number, return to output table
|
||||
output["Device Type"] = device_type_lookup(devnum) .. " (" .. devnum .. ")"
|
||||
-- unpack product code as a two byte int
|
||||
output["Product Code"] = string.unpack("<I2", response, 53)
|
||||
-- Revision Nuumber
|
||||
local char1, char2 = string.unpack("BB", response, 55)
|
||||
output["Revision"] = char1 .. "." .. char2
|
||||
-- Device IP, this could be the same, as the IP scanning, or may be actual IP behind NAT
|
||||
local ip = string.unpack("c4", response, 37)
|
||||
output["Device IP"] = ipOps.str_to_ip(ip)
|
||||
-- set Nmap output
|
||||
set_nmap(host, port)
|
||||
|
||||
-- close socket
|
||||
socket:close()
|
||||
|
||||
-- abort if no response
|
||||
if(rcvstatus == false) then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- print raw bytes
|
||||
stdnse.print_debug(1, "Raw hex: %s", stdnse.tohex(response))
|
||||
|
||||
-- unpack the response command
|
||||
local command = string.unpack("B", response, 1)
|
||||
stdnse.print_debug(1, "command 0x%s", stdnse.tohex(command))
|
||||
|
||||
-- abort if command != 0x63 or bad response
|
||||
if (command ~= 0x63 or string.len(response) < 27) then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- unpack the response type id
|
||||
local typeId = string.unpack("B", response, 27)
|
||||
stdnse.print_debug(1, "command 0x%s", stdnse.tohex(typeId))
|
||||
|
||||
-- abort if typeId != 0x0c
|
||||
if (typeId ~= 0x0c) then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Device IP, this could be the same, as the IP scanning, or may be actual IP behind NAT
|
||||
local dword = string.unpack(">I4", response, 37)
|
||||
local deviceIp = ipOps.fromdword(dword)
|
||||
|
||||
-- get vendor number
|
||||
local vendorNumber, Index = string.unpack("<I2", response, 49)
|
||||
-- look up vendor number and store in output table
|
||||
vendorNumber = vendor_lookup(vendorNumber) .. " (" .. vendorNumber .. ")"
|
||||
|
||||
-- get device type number
|
||||
local deviceNumber, Index = string.unpack("<I2", response, Index)
|
||||
-- lookup device type based off number, return to output table
|
||||
deviceNumber = device_type_lookup(deviceNumber) .. " (" .. deviceNumber .. ")"
|
||||
|
||||
-- unpack product code as a two byte int
|
||||
local productCode, Index = string.unpack("<I2", response, Index)
|
||||
|
||||
-- get revision number
|
||||
local major, minor, Index = string.unpack("BB", response, Index)
|
||||
local revision = major .. "." .. minor
|
||||
|
||||
-- get status in hex format
|
||||
local status, Index = string.unpack("<I2", response, Index)
|
||||
status = string.format("%#0.4x", status)
|
||||
|
||||
-- get serial number in hex format
|
||||
local serialNumber, Index = string.unpack("<I4", response, Index)
|
||||
serialNumber = string.format("%#0.8x", serialNumber)
|
||||
|
||||
-- get product name
|
||||
local productName, Index = string.unpack("s1", response, Index)
|
||||
|
||||
-- get state in hex format
|
||||
local state, Index = string.unpack(">B", response, Index)
|
||||
state = string.format("%#0.2x", state)
|
||||
|
||||
-- create table for output
|
||||
local output = stdnse.output_table()
|
||||
|
||||
-- populate output table
|
||||
output["type"] = deviceNumber
|
||||
output["vendor"] = vendorNumber
|
||||
output["productName"] = productName
|
||||
output["serialNumber"] = serialNumber
|
||||
output["productCode"] = productCode
|
||||
output["revision"] = revision
|
||||
output["status"] = status
|
||||
output["state"] = state
|
||||
output["deviceIp"] = deviceIp
|
||||
|
||||
-- set Nmap output
|
||||
set_nmap(host, port)
|
||||
|
||||
-- return output table to Nmap
|
||||
return output
|
||||
end
|
||||
else
|
||||
socket:close()
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user