Nmap Development mailing list archives

Another SCADA/ICS NMAP NSE script - Electro Industries / Guagetech 'Nexus' smart meter enumeration script


From: Bob Radvanovsky <rsradvan () unixworks net>
Date: Tue, 07 Dec 2010 15:19:47 -0600

This is one of several enumeration scripts that I have written for the SCADA/industrial control systems community.  
This checks/validates the web-based traffic for the Electro Industries / Guagetech 'Nexus' smart meter device.  NOTE: 
This has been ONLY tested with a limited number of models from the 'Nexus' series, and I am still adding more models 
(such as the Shark 100S).  

All of these devices utilize an Ethernet connection to 'talk back' to a (probably) centralized controller.  All devices 
are capable of running both Modbus TCP and DNP3.  The Shark 100S and Shark 200S provide wireless (via WiFi) 
capabilities to poll these devices.  All devices that have web server capabilities DO NOT utilize any "secured web 
servers" (port 443), ONLY UN-secured web servers (port 80).  If I encounter any 'Nexus' devices that do provide/offer 
"secured web servers", that will be incorporated into the NSE enumeration script.

Here is sample output from the modified NMAP NSE script (execute it as "nmap --script=./eig.nse <IP address> -PN -p80 
-v"; use the "-PN" flag needs to be used on older TCP/IP device stacks, as NMAP has a tendency to lock up the TCP/IP 
stack).

The same script is shown below; if you wish to download the script, the script may be accessed here:
http://www.infracritical.com/enum-scripts/eig.nse

===============================================

description = "Confirms/verifies that target device is Electro Industries device."
author      = "Bob Radvanovsky <rsradvan at infracritical dot com>"
license     = "Refer to: http://nmap.org/book/man-legal.html for license."
categories  = {"safe", "discovery"}

--
--  Filename:      eig.nse
--
--  Purpose:       Checks for the following elements confirming said device:
-- 
--  1.  PHASE I - HTML pattern matching & confirmation.
--      a.  STEP 1:  EIG device confirmation.
--      b.  STEP 2:  HTML device detailed information.
--                
--  2.  PHASE II - Documentation.
--
--  ==========================================================================
--                      
--  Version(s):    N/A
--
--  Usage:         nmap --script=./eig.nse <IP> -PN -v
--
--  Author(s):     Bob Radvanovsky - Infracritical
--                 <rsradvan at infracritical dot com>
--
--  Initwritten:   September 2010
--
--  DATE----  INIT DESCRIPTION------------------------------------------------
--  10.24.09  rsr  Inital development - VERSION 001.
--  10.24.09  rsr  Added script argument feature; argument called "VERBOSE".
--
--  NOTE: We try and verify, running tests on as many versions of this device
--        as possible.  If you encounter an "UNKNOWN", this may mean that you
--        have scanned a device version/variant that was not tested.
--
--        Since we are providing this script free-of-charge to everyone,
--        would help us - and the community - if you would report the variance
--        to us.  Submit your findings to "report () infracritical com".  Thanks!
--

require("nmap")
require("nsedebug")
require("datafiles")
require("stdnse")
require("shortport")
require("http")
require("url")
require("strbuf")

--
-- NOTE: Much of what is determined, may be found simply through interrogating
--       the onboard web server; if the web server is DISABLED, or if the
--       verification is performed through the EXTERNAL port, the results may
--       vary, depending on how the firewall is configured.
--
portrule = shortport.port_or_service({80},{"http"})

action = function(host, port)

  result = "CONFIRM DEVICE AS ELECTRO INDUSTRIES GAUGETECH"

  local a0, a1, a2, a3, a4, a5, device, buildinfo, eigweb

  a0 = http.get(host, port, "/"); eigdev = 0
  if a0.status == 200 then
    if string.match(a0.body, "EIG Embedded Web Server") then
      eigweb = 1
    end
  else
    eigweb = 0
    if nmap.verbosity() > 1 then
      result = "File does not exist: index.htm"
    end
  end
    
  a1 = http.get(host, port, "/diag_firmware.htm"); eigdevice = 0
  if a1.status == 200 then
    local s11 = ""; local s12 = ""; local s13 = ""; local s14 = ""
    local s15 = ""; local s16 = ""; local s17 = ""; local s18 = ""
    local s1a = ""; local s1b = ""; local s1c = ""; local s19 = ""
    local s1d = ""; local s1e = ""; local s1f = ""; local s10 = ""
    if string.match(a1.body, "Electro Industries/GaugeTech") or string.match(a1.body,"Shark Netcard") then
      eigdevice = 1
    end
  else
    eigdevice = 0
    if nmap.verbosity() > 1 then
      result = "File does not exist: diag_firmware.htm"
    end
  end
  
  if eigweb == 1 or eigdevice == 1 then
    if nmap.verbosity() > 1 then
      result = result .. "\n** PHASE 1: HTML verification"
      result = result .. "\n....Step 1: EIG device info        : CONFIRMED"
    else
      result = result .. "\n** IF YOU REQUIRE MORE INFO, USE THE \"-v\" OPTION"
    end

    if string.match(a1.body,"Build info:") then
      if string.match(a1.body,"uild info: ") then
        s15 = stdnse.strsplit("uild info: ",a1.body)
        s16 = stdnse.strsplit("<BR>",s15[2]); s1c = s16[1]; buildinfo = s1c
      end
      if buildinfo == "Electro Industries/GaugeTech" then
        if string.match(a1.body,"time version: ") then
          s11 = stdnse.strsplit("time version: ", a1.body)
          s12 = stdnse.strsplit("<BR>",s11[2]); s1a = s12[1]
        end
        if string.match(a1.body,"oot version: ") then
          s13 = stdnse.strsplit("oot version: ",a1.body)
          s14 = stdnse.strsplit("<BR>",s13[2]); s1b = s14[1]
        end
        if string.match(a1.body,"oot ID: ") then
          s17 = stdnse.strsplit("oot ID: ",a1.body)
          s18 = stdnse.strsplit("<BR>",s17[2]); s1d = s18[1]
        end
        if string.match(a1.body,"time ID: ") then
          s19 = stdnse.strsplit("time ID: ",a1.body)
          s10 = stdnse.strsplit("<BR>",s19[2]); s1e = s10[1]
        end
      end

      if buildinfo == "Shark Netcard" then
        if string.match(a1.body,"Time version: ") then
          s11 = stdnse.strsplit("Time version: ", a1.body)
          s12 = stdnse.strsplit("<BR>",s11[2]); s1a = s12[1]
        end
        if string.match(a1.body,"oot version: ") then
          s13 = stdnse.strsplit("oot version: ",a1.body)
          s14 = stdnse.strsplit("<BR>",s13[2]); s1b = s14[1]
        end
        if string.match(a1.body,"oot ID: ") then
          s17 = stdnse.strsplit("oot ID: ",a1.body)
          s18 = stdnse.strsplit("<BR>",s17[2]); s1d = s18[1]
        end
        if string.match(a1.body,"time ID: ") then
          s19 = stdnse.strsplit("time ID: ",a1.body)
          s10 = stdnse.strsplit("<BR>",s19[2]); s1e = s10[1]
        end
      end

      if nmap.verbosity() > 1 then
        result = result .. "\n............Version S/W            : " .. s1a
        result = result .. "\n............Boot Version S/W       : " .. s1b
        result = result .. "\n....Step 2: HTML device detailed information"
        result = result .. "\n............Manufacturer name      : " .. s1c
        result = result .. "\n............Boot ID model number   : " .. s1d
        result = result .. "\n............Run ID model number    : " .. s1e
      else
        result = result .. "\n............EIG device info        : CONFIRMED"
        result = result .. "\n............Version S/W            : " .. s1a
      end
    end
  else
    if nmap.verbosity() > 1 then result = "Fingerprint not found."; end
  end

  a2 = http.get(host, port, "/meter_information.htm")
  if a2.status == 200 then
    local s21 = ""; local s22 = ""; local s23 = ""
    local s24 = ""; local s25 = ""; local s26 = ""
    if string.match(a2.body, "   Type") then
      s21 = stdnse.strsplit("   Type",a2.body)
      if string.match(s21[2],"Verdana") then
        s22 = stdnse.strsplit("Verdana",s21[2])
        s23 = stdnse.strsplit(string.char(0x22,0x3e,0x20,0x0d,0x0a),s22[2])
--      s23 = stdnse.strsplit(string.char(0x0d,0x0a),s22[2])
        if buildinfo == "Shark Netcard" then
          s24 = stdnse.strsplit("                                            ",s23[2])
          device = "Shark"
        else
          s24 = stdnse.strsplit("                          ",s23[2])
          s25 = string.sub(s24[2],1,16)
          device = string.sub(s24[2],1,16)
        end
      end
      if nmap.verbosity() > 1 then
        result = result .. "\n............Device type            : " .. s25
      end
    end

    local s31 = ""; local s32 = ""; local s33 = ""
    local s34 = ""; local s35 = ""; local s36 = ""
    if string.match(a2.body, "Serial #") then
      s31 = stdnse.strsplit("Serial #",a2.body)
      if string.match(s31[2],"Verdana") then
        s32 = stdnse.strsplit("Verdana",s31[2])
        s33 = stdnse.strsplit(string.char(0x22,0x3e,0x20,0x0d,0x0a),s32[2])
        s34 = stdnse.strsplit("                          ",s33[2])
        s35 = string.sub(s34[2],1,8)
      end
      if nmap.verbosity() > 1 then
        result = result .. "\n............Serial number          : " .. s35
      end
    end

    local s41 = ""; local s42 = ""; local s43 = ""
    local s44 = ""; local s45 = ""; local s46 = ""
    if string.match(a2.body, "MAC") then
      s41 = stdnse.strsplit("Address",a2.body)
      if string.match(s41[2],"000000") then
        s42 = stdnse.strsplit("000000",s41[2])
--      s43 = stdnse.strsplit(string.char(0x22,0x3e,0x0d,0x0a),s42[2])
        s43 = stdnse.strsplit(string.char(0x0d,0x0a),s42[2])
        s44 = stdnse.strsplit("                          ",s43[2])
        s45 = string.sub(s44[2],1,17)
      end
      if nmap.verbosity() > 1 then
        result = result .. "\n............Network MAC Address    : " .. s45
      end
    end
  else
    if nmap.verbosity() > 1 then
      result = "File does not exist: meter_information.htm"
    end
  end

  a3 = http.get(host, port, "/diag_modbus_tcp_server.htm")
  if a3.status == 200 then
    local s51 = ""; local s52 = ""; local s5a = ""

    if string.match(a3.body,"Modbus TCP sockets: ") then
      s51 = stdnse.strsplit("Modbus TCP sockets: ", a3.body)
      s52 = stdnse.strsplit("<BR>",s51[2]); s5a = s52[1]
      if s5a == 0 then
        s5a = "DISABLED"
      else
        s5a = "ENABLED"
      end
    end

    if nmap.verbosity() > 1 then
      result = result .. "\n............Modbus TCP server      : " .. s5a
    end
  end

  a4 = http.get(host, port, "/diag_dnp_lan_wan.htm")
  if a4.status == 200 then
    local s61 = ""; local s62 = ""; local s6a = ""

    if string.match(a4.body,"Mode: ") then
      s61 = stdnse.strsplit("Mode: ", a4.body)
      s62 = stdnse.strsplit("</font>",s61[2]); s6a = string.upper(s62[1])
    end

    if nmap.verbosity() > 1 then
      result = result .. "\n............DNP TCP Server         : " .. s6a
    end
  end

  result = result .. "\n** PHASE 2: Documentation"
  if string.match(device,"Nexus 1250") then
    result = result .. "\n....Step 1: Documentation exist?   : YES"
    result = result .. "\n............ninja.infracritical.com/dox/gaugetech/nexus1250.pdf"
  else
    if string.match(device,"Nexus 1252") then
      result = result .. "\n....Step 1: Documentation exist?   : YES"
      result = result .. "\n............ninja.infracritical.com/dox/gaugetech/nexus1252.pdf"
    else
      if string.match(device,"Nexus 1262") then
        result = result .. "\n....Step 1: Documentation exist?   : YES"
        result = result .. "\n............ninja.infracritical.com/dox/gaugetech/nexus1262.pdf"
      else
        if string.match(device,"Nexus 1272") then
          result = result .. "\n....Step 1: Documentation exist?   : YES"
          result = result .. "\n............ninja.infracritical.com/dox/gaugetech/nexus1272.pdf"
        else
          result = result .. "\n....Step 1: Documentation exist?   : NO"
        end
      end
    end
  end

  return result
    
end

===============================================

Sample output from the NSE script.  NOTE: The IP address, serial number, and MAC address for this sample output has 
been sanitized.

[root@server nmap]# nmap --script=./eig.nse xxx.xxx.xxx.xxx -PN -p80 -v

Starting Nmap 5.35DC1 ( http://nmap.org ) at 2010-12-07 14:55 CST
NSE: Loaded 1 scripts for scanning.
Initiating Parallel DNS resolution of 1 host. at 14:55
Completed Parallel DNS resolution of 1 host. at 14:55, 0.20s elapsed
Initiating SYN Stealth Scan at 14:55
Scanning xxx.xxx.xxx.xxx [1 port]
Discovered open port 80/tcp on xxx.xxx.xxx.xxx
Completed SYN Stealth Scan at 14:55, 1.84s elapsed (1 total ports)
NSE: Script scanning xxx.xxx.xxx.xxx.
NSE: Starting runlevel 1 (of 1) scan.
Initiating NSE at 14:55
Completed NSE at 14:55, 2.17s elapsed
Nmap scan report for xxx.xxx.xxx.xxx
Host is up (0.82s latency).
PORT   STATE SERVICE
80/tcp open  http
| eig: CONFIRM DEVICE AS ELECTRO INDUSTRIES GAUGETECH
| ** PHASE 1: HTML verification
| ....Step 1: EIG device info        : CONFIRMED
| ............Version S/W            : 1.0 (Build 34)
| ............Boot Version S/W       : 1.0 (Build 50)
| ....Step 2: HTML device detailed information
| ............Manufacturer name      : Electro Industries/GaugeTech
| ............Boot ID model number   : Boot 100 Base-T
| ............Run ID model number    : Run 100 Base-T
| ............Device type            : 0107 Nexus 1252
| ............Serial number          : 00000000
| ............Network MAC Address    : 00-00-00-00-00-00
| ............Modbus TCP server      : ENABLED
| ............DNP TCP Server         : DISABLED
| ** PHASE 2: Documentation
| ....Step 1: Documentation exist?   : YES
|_............ninja.infracritical.com/dox/gaugetech/nexus1252.pdf

Read data files from: /usr/local/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 4.48 seconds
           Raw packets sent: 2 (88B) | Rcvd: 1 (44B)
_______________________________________________
Sent through the nmap-dev mailing list
http://cgi.insecure.org/mailman/listinfo/nmap-dev
Archived at http://seclists.org/nmap-dev/


Current thread: