diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 07:42:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 07:42:04 +0000 |
commit | 0d47952611198ef6b1163f366dc03922d20b1475 (patch) | |
tree | 3d840a3b8c0daef0754707bfb9f5e873b6b1ac13 /scripts/iec-identify.nse | |
parent | Initial commit. (diff) | |
download | nmap-upstream.tar.xz nmap-upstream.zip |
Adding upstream version 7.94+git20230807.3be01efb1+dfsg.upstream/7.94+git20230807.3be01efb1+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'scripts/iec-identify.nse')
-rw-r--r-- | scripts/iec-identify.nse | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/scripts/iec-identify.nse b/scripts/iec-identify.nse new file mode 100644 index 0000000..81bcd61 --- /dev/null +++ b/scripts/iec-identify.nse @@ -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 |