1
0
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:
paulino
2019-01-08 21:26:06 +00:00
parent e3afc3f5a6
commit 65c0376c59

View File

@@ -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