coolbins/system/usr/share/nmap/scripts/iec-identify.nse

162 lines
4.2 KiB
Lua

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