1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-06 04:31:29 +00:00

Add iec-identify script

This commit is contained in:
dmiller
2017-07-06 20:54:04 +00:00
parent baac7e705e
commit 8470351025
3 changed files with 170 additions and 0 deletions

View File

@@ -1,5 +1,8 @@
# Nmap Changelog ($Id$); -*-text-*-
o [NSE] iec-identify probes for the IEC 60870-5-104 SCADA protocol.
[Aleksandr Timorin, Daniel Miller]
o [GH#910] added libssh2 support, ssh-brute, ssh-run, ssh-auth-methods,
ssh-publickey-acceptance [Evangelos Deirmentzoglou]

161
scripts/iec-identify.nse Normal file
View File

@@ -0,0 +1,161 @@
local shortport = require "shortport"
local comm = require "comm"
local stdnse = require "stdnse"
local string = require "string"
local match = require "match"
description = [[
Attempts to identify IEC 60870-5-104 ICS protocol.
After probing with a TESTFR (test frame) message, a STARTDT (start data
transfer) message is sent and general interrogation is used to gather the list
of information object addresses stored.
]]
---
-- @output
-- | iec-identify:
-- | ASDU address: 105
-- |_ Information objects: 30
--
author = {"Aleksandr Timorin", "Daniel Miller"}
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "intrusive"}
portrule = shortport.port_or_service(2404, "iec-104", "tcp")
local function get_asdu(socket)
local status, data = socket:receive_buf(match.numbytes(2), true)
if not status then
return nil, data
end
if data:byte(1) ~= 0x68 then
return nil, "Not IEC-104"
end
local len = data:byte(2)
status, data = socket:receive_buf(match.numbytes(len), true)
if not status then
return nil, data
end
local apcitype = data:byte(1)
return apcitype, data
end
action = function(host, port)
local output = stdnse.output_table()
local socket, err = comm.opencon(host, port)
if not socket then
stdnse.debug1("Connect error: %s", err)
return nil
end
-- send TESTFR ACT command
-- Test frame, like "ping"
local TESTFR = "\x68\x04\x43\0\0\0"
local status, err = socket:send( TESTFR )
if not status then
stdnse.debug1("Failed to send: %s", err)
return nil
end
-- receive TESTFR answer
local apcitype, recv = get_asdu(socket)
if not apcitype then
stdnse.debug1("protocol error: %s", recv)
return nil
end
if apcitype ~= 0x83 then
stdnse.print_debug(1, "Not IEC-104. TESTFR response: %#x", apcitype)
return nil
end
-- send STARTDT ACT command
local STARTDT = "\x68\x04\x07\0\0\0"
status, err = socket:send( STARTDT )
if not status then
stdnse.debug1("Failed to send: %s", err)
return nil
end
-- receive STARTDT answer
apcitype, recv = get_asdu(socket)
if not apcitype then
stdnse.debug1("protocol error: %s", recv)
return nil
end
if apcitype ~= 0x0b then
stdnse.debug1("STARTDT ACT did not receive STARTDT CON: %#x", apcitype)
return nil
end
-- May also receive ME_EI_NA_1 (End of initialization), so check for that in the buffer after sending the next part
-- send C_IC_NA_1 command
-- type: 0x64, C_IC_NA_1,
-- numix: 1
-- TNCause: 6, Act
-- Originator address; 0
-- ASDU address: 0xffff
-- Information object address: 0
-- QOI: 0x14 (20), Station interrogation (global)
local C_IC_NA_1_broadcast = "\x68\x0e\0\0\0\0\x64\x01\x06\0\xff\xff\0\0\0\x14"
status, err = socket:send( C_IC_NA_1_broadcast )
if not status then
stdnse.debug1("Failed to send: %s", err)
return nil
end
local asdu_address
local ioas = 0
-- Have to draw the line somewhere.
local limit = 10
while limit > 0 do
limit = limit - 1
apcitype, recv = get_asdu(socket)
if not apcitype then
stdnse.debug1("Error in C_IC_NA_1: %s", recv)
break
end
if apcitype & 0x01 == 0 then -- Type I, numbered information transfer
-- skip 2 bytes Tx, 2 bytes Rx
local typeid = recv:byte(5)
if typeid == 70 then
-- ME_EI_NA_1, End of Initialization. Skip.
else
local numix = recv:byte(6) & 0x7f
local cause = recv:byte(7) & 0x3f
asdu_address = string.unpack("<I2", recv, 9)
stdnse.debug2("Got asdu=%d, type %d, cause %d, numix %d.", asdu_address, typeid, cause, numix)
if typeid == 100 then
-- C_IC_NA_1
if cause == 7 then
-- ActCon. Skip.
elseif cause == 10 then
-- ActTerm. The end!
break
else
-- TODO: do something!
end
else
if cause >= 20 and cause <= 36 then
-- Inrogen, response to general interrogation
ioas = ioas + numix
end
end
end
end
end
socket:close()
if asdu_address then
output["ASDU address"] = asdu_address
output["Information objects"] = ioas
else
output = "IEC-104 endpoint did not respond to C_IC_NA_1 request"
end
return output
end

View File

@@ -278,6 +278,7 @@ Entry { filename = "http-xssed.nse", categories = { "discovery", "external", "sa
Entry { filename = "iax2-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "iax2-version.nse", categories = { "version", } }
Entry { filename = "icap-info.nse", categories = { "discovery", "safe", } }
Entry { filename = "iec-identify.nse", categories = { "discovery", "intrusive", } }
Entry { filename = "ike-version.nse", categories = { "default", "discovery", "safe", "version", } }
Entry { filename = "imap-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "imap-capabilities.nse", categories = { "default", "safe", } }
@@ -394,6 +395,7 @@ Entry { filename = "omp2-enum-targets.nse", categories = { "discovery", "safe",
Entry { filename = "omron-info.nse", categories = { "discovery", "version", } }
Entry { filename = "openlookup-info.nse", categories = { "default", "discovery", "safe", "version", } }
Entry { filename = "openvas-otp-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "openwebnet-discovery.nse", categories = { "discovery", "safe", } }
Entry { filename = "oracle-brute-stealth.nse", categories = { "brute", "intrusive", } }
Entry { filename = "oracle-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "oracle-enum-users.nse", categories = { "auth", "intrusive", } }
@@ -499,7 +501,11 @@ Entry { filename = "snmp-win32-users.nse", categories = { "auth", "default", "sa
Entry { filename = "socks-auth-info.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "socks-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "socks-open-proxy.nse", categories = { "default", "discovery", "external", "safe", } }
Entry { filename = "ssh-auth-methods.nse", categories = { } }
Entry { filename = "ssh-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "ssh-hostkey.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "ssh-publickey-acceptance.nse", categories = { } }
Entry { filename = "ssh-run.nse", categories = { "intrusive", } }
Entry { filename = "ssh2-enum-algos.nse", categories = { "discovery", "safe", } }
Entry { filename = "sshv1.nse", categories = { "default", "safe", } }
Entry { filename = "ssl-ccs-injection.nse", categories = { "safe", "vuln", } }