From b34e05d4fb28a01d9bf68d0effd7db7d31689164 Mon Sep 17 00:00:00 2001 From: dmiller Date: Mon, 3 Sep 2018 19:38:01 +0000 Subject: [PATCH] Move common code to knx.lua, remove bin.lua dependency --- CHANGELOG | 3 + nselib/knx.lua | 83 +++++++++++++++++++++ scripts/knx-gateway-discover.nse | 121 ++++++++++--------------------- scripts/knx-gateway-info.nse | 111 +++++++++------------------- 4 files changed, 156 insertions(+), 162 deletions(-) create mode 100644 nselib/knx.lua diff --git a/CHANGELOG b/CHANGELOG index 3ecf03256..54f092e38 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,9 @@ o Emergency fix to Nmap's birthday announcement so Nmap wishes itself a "Happy 21st Birthday" rather than "Happy 21th" in verbose mode (-v) on September 1, 2018. [Daniel Miller] +o [NSE] New knx.lua library holds common functions and definitions for + communicating with KNX/Konnex devices. [Daniel Miller] + o [NSE] Completely removed the bit.lua NSE library. All of its functions are replaced by native Lua bitwise operations, except for `arshift` (arithmetic shift) which has been moved to the bits.lua library. [Daniel Miller] diff --git a/nselib/knx.lua b/nselib/knx.lua new file mode 100644 index 000000000..37b7a2a95 --- /dev/null +++ b/nselib/knx.lua @@ -0,0 +1,83 @@ +--- Functions for communicating with Konnex (KNX) devices + + +local ipOps = require "ipOps" +local string = require "string" +local _ENV = {} + +knxServiceFamilies = { + [0x02]="KNXnet/IP Core", + [0x03]="KNXnet/IP Device Management", + [0x04]="KNXnet/IP Tunnelling", + [0x05]="KNXnet/IP Routing", + [0x06]="KNXnet/IP Remote Logging", + [0x08]="KNXnet/IP Object Server", + [0x07]="KNXnet/IP Remote Configuration and Diagnosis" +} + +knxDibDescriptionTypes = { + [0x01]="Device Information", + [0x02]="Supp_Svc_families", + [0x03]="IP_Config", + [0x04]="IP_Cur_Config", + [0x05]="IP_Config" +} + +knxMediumTypes = { + [0x01]="reserved", + [0x02]="KNX TP1", + [0x04]="KNX PL110", + [0x08]="reserved", + [0x10]="KNX RF", + [0x20]="KNX IP" +} + +--- Returns a raw knx request +-- @param service KNX service type of the request +-- @param ip_address IP address of the sending host +-- @param port Port where gateways should respond to +query = function(service, ip_address, port) + return string.pack(">BB I2 I2 BB I4 I2", + 0x06, -- Header length + 0x10, -- Protocol version + service, -- Service type + 0x000e, -- Total length + 0x08, -- Structure length + 0x01, -- Host protocol + ipOps.todword(ip_address), + port + ) +end + +--- Parse a KNX address from raw bytes +-- @param addr Unpacked 2 bytes +-- @return KNX address in dotted-decimal format +parseKnxAddress = function(addr) + local a = (addr & 0xf000) >> 12 + local b = (addr & 0x0f00) >> 8 + local c = addr & 0xff + return a..'.'..b..'.'..c +end + +--- Parse a KNX header +-- @param knxMessage A KNX message packet as a string +-- @return knx_header_length, or nil on error +-- @return knx_protocol_version, or error message +-- @return knx_service_type +-- @return knx_total_length +-- @return pos The position just after the header +parseHeader = function(knxMessage) + if #knxMessage < 6 then + return nil, "Message too short for KNX header" + end + local knx_header_length, knx_protocol_version, knx_service_type, knx_total_length, pos = string.unpack(">BB I2 I2", knxMessage) + + -- TODO: Should this be 'or' instead of 'and'? + if knx_header_length ~= 0x06 and knx_protocol_version ~= 0x10 and knx_service_type ~= 0x0204 then + return nil, "Unknown KNX header format" + end + + return knx_header_length, knx_protocol_version, knx_service_type, knx_total_length, pos +end + +return _ENV diff --git a/scripts/knx-gateway-discover.nse b/scripts/knx-gateway-discover.nse index ddf8ce427..7446cbd54 100644 --- a/scripts/knx-gateway-discover.nse +++ b/scripts/knx-gateway-discover.nse @@ -2,11 +2,11 @@ local nmap = require "nmap" local coroutine = require "coroutine" local stdnse = require "stdnse" local table = require "table" -local bin = require "bin" local packet = require "packet" local ipOps = require "ipOps" local string = require "string" local target = require "target" +local knx = require "knx" description = [[ Discovers KNX gateways by sending a KNX Search Request to the multicast address @@ -57,49 +57,6 @@ prerule = function() return true end -local knxServiceFamilies = { - [0x02]="KNXnet/IP Core", - [0x03]="KNXnet/IP Device Management", - [0x04]="KNXnet/IP Tunnelling", - [0x05]="KNXnet/IP Routing", - [0x06]="KNXnet/IP Remote Logging", - [0x08]="KNXnet/IP Object Server", - [0x07]="KNXnet/IP Remote Configuration and Diagnosis" -} - -local knxDibDescriptionTypes = { - [0x01]="Device Information", - [0x02]="Supp_Svc_families", - [0x03]="IP_Config", - [0x04]="IP_Cur_Config", - [0x05]="IP_Config" -} - -local knxMediumTypes = { - [0x01]="reserved", - [0x02]="KNX TP1", - [0x04]="KNX PL110", - [0x08]="reserved", - [0x10]="KNX RF", - [0x20]="KNX IP" -} - ---- Returns a raw knx search request --- @param ip_address IP address of the sending host --- @param port Port where gateways should respond to -local knxQuery = function(ip_address, port) - return bin.pack(">C2S2C2IS", - 0x06, -- Header length - 0x10, -- Protocol version - 0x0201, -- Service type - 0x000e, -- Total length - 0x08, -- Structure length - 0x01, -- Host protocol - ipOps.todword(ip_address), - port - ) -end - --- Sends a knx search request -- @param query KNX search request message -- @param mcat Multicast destination address @@ -116,19 +73,10 @@ local knxSend = function(query, mcast, mport) sock:close() end --- Parse a KNX address from raw bytes --- @param addr Unpacked 2 bytes -local parseKnxAddress = function(addr) - local a = (addr & 0xf000) >> 12 - local b = (addr & 0x0f00) >> 8 - local c = addr & 0xff - return a..'.'..b..'.'..c -end - local fam_meta = { __tostring = function (self) return ("%s version %d"):format( - knxServiceFamilies[self.service_id] or self.service_id, + knx.knxServiceFamilies[self.service_id] or self.service_id, self.Version ) end @@ -137,44 +85,49 @@ local fam_meta = { --- Parse a Search Response -- @param knxMessage Payload of captures UDP packet local knxParseSearchResponse = function(ips, results, knxMessage) - local _, knx_header_length = bin.unpack('>C', knxMessage) - local _, knx_protocol_version = bin.unpack('>C', knxMessage, _) - local _, knx_service_type = bin.unpack('>S', knxMessage, _) - local _, knx_total_length = bin.unpack('>S', knxMessage, _) + local knx_header_length, knx_protocol_version, knx_service_type, knx_total_length, pos = knx.parseHeader(knxMessage) - if knx_header_length ~= 0x06 and knx_protocol_version ~= 0x10 and knx_service_type ~= 0x0202 then + if not knx_header_length then + stdnse.debug1("KNX header error: %s", knx_protocol_version) return end - local _, knx_hpai_structure_length = bin.unpack('>C', knxMessage, _) - local _, knx_hpai_protocol_code = bin.unpack('>A1', knxMessage, _) - local _, knx_hpai_ip_address = bin.unpack('>A4', knxMessage, _) - knx_hpai_ip_address = ipOps.str_to_ip(knx_hpai_ip_address) - local _, knx_hpai_port = bin.unpack('>S', knxMessage, _) + local message_format = '>B c1 c4 I2 BBB c1 I2 c2 c6 c4 c6 c30 BB' + if #knxMessage - pos + 1 < string.packlen(message_format) then + stdnse.debug1("Message too short for KNX message") + return + end - local _, knx_dib_structure_length = bin.unpack('>C', knxMessage, _) - local _, knx_dib_description_type = bin.unpack('>C', knxMessage, _) - knx_dib_description_type = knxDibDescriptionTypes[knx_dib_description_type] - local _, knx_dib_knx_medium = bin.unpack('>C', knxMessage, _) - knx_dib_knx_medium = knxMediumTypes[knx_dib_knx_medium] - local _, knx_dib_device_status = bin.unpack('>A1', knxMessage, _) - local _, knx_dib_knx_address = bin.unpack('>S', knxMessage, _) - local _, knx_dib_project_install_ident = bin.unpack('>A2', knxMessage, _) - local _, knx_dib_dev_serial = bin.unpack('>A6', knxMessage, _) - local _, knx_dib_dev_multicast_addr = bin.unpack('>A4', knxMessage, _) + local knx_hpai_structure_length, + knx_hpai_protocol_code, + knx_hpai_ip_address, + knx_hpai_port, + knx_dib_structure_length, + knx_dib_description_type, + knx_dib_knx_medium, + knx_dib_device_status, + knx_dib_knx_address, + knx_dib_project_install_ident, + knx_dib_dev_serial, + knx_dib_dev_multicast_addr, + knx_dib_dev_mac, + knx_dib_dev_friendly_name, + knx_supp_svc_families_structure_length, + knx_supp_svc_families_description, pos = string.unpack(message_format, knxMessage, pos) + + knx_hpai_ip_address = ipOps.str_to_ip(knx_hpai_ip_address) + + knx_dib_description_type = knx.knxDibDescriptionTypes[knx_dib_description_type] + knx_dib_knx_medium = knx.knxMediumTypes[knx_dib_knx_medium] knx_dib_dev_multicast_addr = ipOps.str_to_ip(knx_dib_dev_multicast_addr) - local _, knx_dib_dev_mac = bin.unpack('>A6', knxMessage, _) knx_dib_dev_mac = stdnse.format_mac(knx_dib_dev_mac) - local _, knx_dib_dev_friendly_name = bin.unpack('>A30', knxMessage, _) local knx_supp_svc_families = {} - local _, knx_supp_svc_families_structure_length = bin.unpack('>C', knxMessage, _) - local _, knx_supp_svc_families_description = bin.unpack('>C', knxMessage, _) - knx_supp_svc_families_description = knxDibDescriptionTypes[knx_supp_svc_families_description] or knx_supp_svc_families_description + knx_supp_svc_families_description = knx.knxDibDescriptionTypes[knx_supp_svc_families_description] or knx_supp_svc_families_description - for i=0,(knx_total_length-_),2 do + for i=0,(knx_total_length - pos),2 do local family = {} - _, family.service_id, family.Version = bin.unpack('CC', knxMessage, _) + family.service_id, family.Version, pos = string.unpack('BB', knxMessage, pos) setmetatable(family, fam_meta) knx_supp_svc_families[#knx_supp_svc_families+1] = family end @@ -197,7 +150,7 @@ local knxParseSearchResponse = function(ips, results, knxMessage) search_response.Body.DIB_DEV_INFO["Description type"] = knx_dib_description_type search_response.Body.DIB_DEV_INFO["KNX medium"] = knx_dib_knx_medium search_response.Body.DIB_DEV_INFO["Device status"] = stdnse.tohex(knx_dib_device_status) - search_response.Body.DIB_DEV_INFO["KNX address"] = parseKnxAddress(knx_dib_knx_address) + search_response.Body.DIB_DEV_INFO["KNX address"] = knx.parseKnxAddress(knx_dib_knx_address) search_response.Body.DIB_DEV_INFO["Project installation identifier"] = stdnse.tohex(knx_dib_project_install_ident) search_response.Body.DIB_DEV_INFO["Decive serial"] = stdnse.tohex(knx_dib_dev_serial) search_response.Body.DIB_DEV_INFO["Multicast address"] = knx_dib_dev_multicast_addr @@ -210,7 +163,7 @@ local knxParseSearchResponse = function(ips, results, knxMessage) search_response.Body.HPAI["Port"] = knx_hpai_port search_response.Body.DIB_DEV_INFO = stdnse.output_table() - search_response.Body.DIB_DEV_INFO["KNX address"] = parseKnxAddress(knx_dib_knx_address) + search_response.Body.DIB_DEV_INFO["KNX address"] = knx.parseKnxAddress(knx_dib_knx_address) search_response.Body.DIB_DEV_INFO["Decive serial"] = stdnse.tohex(knx_dib_dev_serial) search_response.Body.DIB_DEV_INFO["Multicast address"] = knx_dib_dev_multicast_addr search_response.Body.DIB_DEV_INFO["Device MAC address"] = knx_dib_dev_mac @@ -314,7 +267,7 @@ action = function() -- Launch listener thread stdnse.new_thread(knxListen, interface, timeout, ips, results) -- Craft raw query - local query = knxQuery(interface.address, lport) + local query = knx.query(0x0201, interface.address, lport) -- Small sleep so the listener doesn't miss the response stdnse.sleep(0.5) -- Send query diff --git a/scripts/knx-gateway-info.nse b/scripts/knx-gateway-info.nse index 52d58592a..c59f1ee57 100644 --- a/scripts/knx-gateway-info.nse +++ b/scripts/knx-gateway-info.nse @@ -1,8 +1,9 @@ local nmap = require "nmap" local shortport = require "shortport" -local bin = require "bin" local ipOps = require "ipOps" local stdnse = require "stdnse" +local string = require "string" +local knx = require "knx" description = [[ Identifies a KNX gateway on UDP port 3671 by sending a KNX Description Request. @@ -34,62 +35,11 @@ portrule = shortport.port_or_service(3671, "efcp", "udp") -- |_ KNXnet/IP Object Server version 1 -- -local knxServiceFamilies = { - [0x02]="KNXnet/IP Core", - [0x03]="KNXnet/IP Device Management", - [0x04]="KNXnet/IP Tunneling", - [0x05]="KNXnet/IP Routing", - [0x06]="KNXnet/IP Remote Logging", - [0x08]="KNXnet/IP Object Server", - [0x07]="KNXnet/IP Remote Configuration and Diagnosis" -} - -local knxDibDescriptionTypes = { - [0x01]="Device Information", - [0x02]="Supp_Svc_families", - [0x03]="IP_Config", - [0x04]="IP_Cur_Config", - [0x05]="IP_Config" -} - -local knxMediumTypes = { - [0x01]="reserved", - [0x02]="KNX TP1", - [0x04]="KNX PL110", - [0x08]="reserved", - [0x10]="KNX RF", - [0x20]="KNX IP" -} - ---- Returns a raw knx description request --- @param ip_address IP address of the sending host --- @param port Port where gateways sends response packets to -local knxQuery = function(ip_address, port) - return bin.pack(">C2S2C2IS", - 0x06, -- Header length - 0x10, -- Protocol version - 0x0203, -- Service type - 0x000e, -- Total length - 0x08, -- Structure length - 0x01, -- Host protocol - ipOps.todword(ip_address), - port - ) -end - --- Parse a KNX address from raw bytes --- @param addr Unpacked 2 bytes -local parseKnxAddress = function(addr) - local a = (addr & 0xf000) >> 12 - local b = (addr & 0x0f00) >> 8 - local c = addr & 0xff - return a..'.'..b..'.'..c -end local fam_meta = { __tostring = function (self) return ("%s version %d"):format( - knxServiceFamilies[self.service_id] or self.service_id, + knx.knxServiceFamilies[self.service_id] or self.service_id, self.Version ) end @@ -98,38 +48,43 @@ local fam_meta = { --- Parse a Description Response -- @param knxMessage UDP response packet local knxParseDescriptionResponse = function(knxMessage) - local _, knx_header_length = bin.unpack('>C', knxMessage) - local _, knx_protocol_version = bin.unpack('>C', knxMessage, _) - local _, knx_service_type = bin.unpack('>S', knxMessage, _) - local _, knx_total_length = bin.unpack('>S', knxMessage, _) + local knx_header_length, knx_protocol_version, knx_service_type, knx_total_length, pos = knx.parseHeader(knxMessage) - if knx_header_length ~= 0x06 and knx_protocol_version ~= 0x10 and knx_service_type ~= 0x0204 then + if not knx_header_length then + stdnse.debug1("KNX header error: %s", knx_protocol_version) return end - local _, knx_dib_structure_length = bin.unpack('>C', knxMessage, _) - local _, knx_dib_description_type = bin.unpack('>C', knxMessage, _) - knx_dib_description_type = knxDibDescriptionTypes[knx_dib_description_type] - local _, knx_dib_knx_medium = bin.unpack('>C', knxMessage, _) - knx_dib_knx_medium = knxMediumTypes[knx_dib_knx_medium] - local _, knx_dib_device_status = bin.unpack('>A1', knxMessage, _) - local _, knx_dib_knx_address = bin.unpack('>S', knxMessage, _) - local _, knx_dib_project_install_ident = bin.unpack('>A2', knxMessage, _) - local _, knx_dib_dev_serial = bin.unpack('>A6', knxMessage, _) - local _, knx_dib_dev_multicast_addr = bin.unpack('>A4', knxMessage, _) + local message_format = '>BBB c1 I2 c2 c6 c4 c6 c30 BB' + if #knxMessage - pos + 1 < string.packlen(message_format) then + stdnse.debug1("Message too short for KNX message") + return + end + + local knx_dib_structure_length, + knx_dib_description_type, + knx_dib_knx_medium, + knx_dib_device_status, + knx_dib_knx_address, + knx_dib_project_install_ident, + knx_dib_dev_serial, + knx_dib_dev_multicast_addr, + knx_dib_dev_mac, + knx_dib_dev_friendly_name, + knx_supp_svc_families_structure_length, + knx_supp_svc_families_description, pos = string.unpack(message_format, knxMessage, pos) + + knx_dib_description_type = knx.knxDibDescriptionTypes[knx_dib_description_type] + knx_dib_knx_medium = knx.knxMediumTypes[knx_dib_knx_medium] knx_dib_dev_multicast_addr = ipOps.str_to_ip(knx_dib_dev_multicast_addr) - local _, knx_dib_dev_mac = bin.unpack('>A6', knxMessage, _) knx_dib_dev_mac = stdnse.format_mac(knx_dib_dev_mac) - local _, knx_dib_dev_friendly_name = bin.unpack('>A30', knxMessage, _) local knx_supp_svc_families = {} - local _, knx_supp_svc_families_structure_length = bin.unpack('>C', knxMessage, _) - local _, knx_supp_svc_families_description = bin.unpack('>C', knxMessage, _) - knx_supp_svc_families_description = knxDibDescriptionTypes[knx_supp_svc_families_description] or knx_supp_svc_families_description + knx_supp_svc_families_description = knx.knxDibDescriptionTypes[knx_supp_svc_families_description] or knx_supp_svc_families_description - for i=0,(knx_total_length-_),2 do + for i=0,(knx_total_length - pos),2 do local family = {} - _, family.service_id, family.Version = bin.unpack('CC', knxMessage, _) + family.service_id, family.Version, pos = string.unpack('BB', knxMessage, pos) setmetatable(family, fam_meta) knx_supp_svc_families[#knx_supp_svc_families+1] = family end @@ -148,7 +103,7 @@ local knxParseDescriptionResponse = function(knxMessage) description_response.Body.DIB_DEV_INFO["Description type"] = knx_dib_description_type description_response.Body.DIB_DEV_INFO["KNX medium"] = knx_dib_knx_medium description_response.Body.DIB_DEV_INFO["Device status"] = stdnse.tohex(knx_dib_device_status) - description_response.Body.DIB_DEV_INFO["KNX address"] = parseKnxAddress(knx_dib_knx_address) + description_response.Body.DIB_DEV_INFO["KNX address"] = knx.parseKnxAddress(knx_dib_knx_address) description_response.Body.DIB_DEV_INFO["Project installation identifier"] = stdnse.tohex(knx_dib_project_install_ident) description_response.Body.DIB_DEV_INFO["Decive serial"] = stdnse.tohex(knx_dib_dev_serial) description_response.Body.DIB_DEV_INFO["Multicast address"] = knx_dib_dev_multicast_addr @@ -158,7 +113,7 @@ local knxParseDescriptionResponse = function(knxMessage) else description_response.Body = stdnse.output_table() description_response.Body.DIB_DEV_INFO = stdnse.output_table() - description_response.Body.DIB_DEV_INFO["KNX address"] = parseKnxAddress(knx_dib_knx_address) + description_response.Body.DIB_DEV_INFO["KNX address"] = knx.parseKnxAddress(knx_dib_knx_address) description_response.Body.DIB_DEV_INFO["Decive serial"] = stdnse.tohex(knx_dib_dev_serial) description_response.Body.DIB_DEV_INFO["Multicast address"] = knx_dib_dev_multicast_addr description_response.Body.DIB_DEV_INFO["Device friendly name"] = knx_dib_dev_friendly_name @@ -178,7 +133,7 @@ action = function(host, port) end local _, lhost, lport, _, _ = sock:get_info() - sock:send(knxQuery(lhost, lport)) + sock:send(knx.query(0x0203, lhost, lport)) local status, data = sock:receive() if not status then