diff --git a/nselib/data/packetdecoders.lua b/nselib/data/packetdecoders.lua index 4c190a7b1..7fabfa5ab 100644 --- a/nselib/data/packetdecoders.lua +++ b/nselib/data/packetdecoders.lua @@ -1,947 +1,947 @@ -local bin = require "bin" -local packet = require "packet" -local stdnse = require "stdnse" -local tab = require "tab" -local table = require "table" -local target = require "target" - ---- The following file contains a list of decoders used by the --- broadcast-listener script. A decoder can be either "ethernet" based or IP --- based. As we're only monitoring broadcast traffic (ie. traffic not --- explicitly addressed to us) we're mainly dealing with: --- o UDP broadcast or multicast traffic --- o ethernet broadcast traffic --- --- Hence, the Decoder table defines two sub tables ether and udp. --- In order to match an incoming UDP packet the destination port number is --- used, therefore each function is indexed based on their destination port --- for the udp based decoders. For the ether table each decoder function is --- indexed according to a pattern that the decoding engine attempts to match. --- --- Each decoder defines three functions: --- o new - creates a new instance of the decoder --- o process - process a packet passed through the --- data argument. --- o getResults - retrieve any discovered results --- --- The discovery engine creates an instance of each decoder once it's needed. --- Then discovery engine stores this instance in a decoder table for reference --- once the next packet of the same type comes in. This allows the engine to --- discard duplicate packets and to request the collected results at the end --- of the session. --- --- Currently, the packet decoder decodes the following protocols: --- o Ether --- x ARP requests (IPv4) --- x CDP - Cisco Discovery Protocol --- x EIGRP - Cisco Enhanced Interior Gateway Routing Protocol --- x OSPF - Open Shortest Path First --- --- o UDP --- x DHCP --- x Netbios --- x SSDP --- x HSRP --- x DropBox --- x Logitech SqueezeBox Discovery --- x Multicast DNS/Bonjour/ZeroConf --- x Spotify --- --- --- @author "Patrik Karlsson " --- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html - --- Version 0.2 --- Created 07/25/2011 - v0.1 - created by Patrik Karlsson --- 02/12/2012 - v0.2 - added support for EIGRP - Tom Sellers --- 07/13/2012 - v0.3 - added support for OSPF - Hani Benhabiles - -local bin = require 'bin' -local target = require 'target' -local tab = require 'tab' -local packet = require 'packet' - -Decoders = { - - ether = { - - -- ARP IPv4 - ['^00..08000604'] = { - - new = function(self) - local o = { dups = {} } - setmetatable(o, self) - self.__index = self - return o - end, - - process = function(self, data) - local ipOps = require("ipOps") - local pos, hw, proto, hwsize, protosize, opcode = bin.unpack(">SSCCS", data) - - -- this shouldn't ever happen, given our filter - if ( hwsize ~= 6 ) then return end - local sender, target = {}, {} - - -- if this isn't an ARP request, abort - if ( opcode ~= 1 ) then return end - - pos, sender.mac, - sender.ip, - target.mac, - target.ip = bin.unpack("I", data) - for i=1, count do - pos, proto_type, proto_len = bin.unpack(">CC", data, pos) - pos, addr_proto = bin.unpack(">H" .. proto_len, data, pos) - if ( addr_proto == 'CC' ) then - -- IPv4 address, extract it - pos, addr_len = bin.unpack(">S", data, pos) - pos, dev_addr = bin.unpack("CCS", data, 9) - if ( ver ~= 2 ) then return end - if ( not(self.results) ) then - self.results = tab.new(5) - tab.addrow( self.results, 'ip', 'id', 'platform', 'version', 'notes' ) - end - - local result_part = {} - result_part.notes = '' - while ( pos < #data ) do - local typ, len, typdata - pos, typ, len = bin.unpack(">SS", data, pos) - pos, typdata = bin.unpack("A" .. len - 4, data, pos) - - -- Device ID - if ( typ == 1 ) then - result_part.id = typdata - -- Version - elseif ( typ == 5 ) then - result_part.version = typdata:match(", Version (.-),") - -- Platform - elseif ( typ == 6 ) then - result_part.platform = typdata - -- Address - elseif ( typ == 2 ) then - result_part.ip = self.getAddresses(typdata) - elseif ( typ == 10) then - local _, mgmt_vlan = bin.unpack(">S", data,pos - 2) - result_part.notes = result_part.notes .. 'native vlan:' .. mgmt_vlan .. ' ' - -- Management Address - elseif ( typ == 22 ) then - result_part.notes = result_part.notes .. 'mgmt ip:' .. self.getAddresses(typdata) .. ' ' - -- TODO: add more decoding of types here ... - end - end - - -- TODO: add code for dups check - if ( not(self.dups[result_part.ip]) ) then - self.dups[result_part.ip] = true - tab.addrow( self.results, result_part.ip, result_part.id, result_part.platform, result_part.version, result_part.notes ) - end - end, - - getResults = function(self) return { name = "CDP", (self.results and tab.dump(self.results) or "") } end, - }, - - - -- EIGRP Update - ['0201....0000'] = { - - new = function(self) - local o = { dups = {} } - setmetatable(o, self) - self.__index = self - return o - end, - - process = function(self, layer3) - local p = packet.Packet:new( layer3, #layer3 ) - -- EIGRP is IP protocol 88 (0x58), so verify this - if ( p.ip_p ~= 88 ) then return end - - local data = layer3:sub(p.ip_data_offset + 1) - local eigrp = require("eigrp") - local route_type, proto_name - -- Extract the EIGRP header - local response = eigrp.EIGRP.parse(data) - - if response then - -- Iterate over tlv tables - for _, tlv in pairs(response.tlvs) do - if eigrp.EIGRP.isRoutingTLV(tlv.type) then - if ( not(self.results) ) then - self.results = tab.new(7) - tab.addrow(self.results, 'Sender IP', 'AS#', 'Route Type', 'Destination', 'Next hop', 'Ext Protocol', 'Orig Router ID') - end - if tlv.type == 0x102 then - route_type = "Internal" - elseif tlv.type == 0x103 then - route_type = "External" - for name, value in pairs(eigrp.EXT_PROTO) do - if value == tlv.eproto then - proto_name = name - break - end - end - end - if ( not(self.dups[("%s:%s:s:%s"):format(p.ip_src, response.as, tlv.type, tlv.dst)]) ) then - if ( target.ALLOW_NEW_TARGETS ) then target.add(p.ip_src) end - self.dups[("%s:%s:%s:%s"):format(p.ip_src, response.as, tlv.type, tlv.dst)] = true - tab.addrow( self.results, p.ip_src, response.as, route_type, tlv.dst, tlv.nexth, proto_name or 'X', tlv.orouterid or 'X') - end - end - end - end - end, - - getResults = function(self) return { name = "EIGRP Update", (self.results and tab.dump(self.results) or "") } end, - }, - - ['0205....0000'] = { - - new = function(self) - local o = { dups = {} } - setmetatable(o, self) - self.__index = self - return o - end, - - process = function(self, layer3) - - local p = packet.Packet:new( layer3, #layer3 ) - -- EIGRP is IP protocol 88 (0x58), so verify this - if ( p.ip_p ~= 88 ) then return end - - local data = layer3:sub(p.ip_data_offset + 1) - local eigrp = require("eigrp") - -- Extract the EIGRP header - local response = eigrp.EIGRP.parse(data) - -- See if Software version TLV is included - local swvertlv - for num, tlv in pairs(response.tlvs) do - if tlv.type == eigrp.TLV.SWVER then - swvertlv = num - end - end - - if swvertlv then - if ( not(self.results) ) then - self.results = tab.new(5) - tab.addrow(self.results, 'Sender IP', 'AS number', 'EIGRP version', 'IOS version') - end - - if ( not(self.dups[("%s:%s"):format(p.ip_src,response.as)]) ) then - if ( target.ALLOW_NEW_TARGETS ) then target.add(p.ip_src) end - self.dups[("%s:%s"):format(p.ip_src,response.as)] = true - tab.addrow( self.results, p.ip_src, response.as, response.tlvs[swvertlv].majv .. '.' .. response.tlvs[swvertlv].minv, response.tlvs[swvertlv].majtlv .. '.' .. response.tlvs[swvertlv].mintlv) - end - end - end, - - getResults = function(self) return { name = "EIGRP Hello", (self.results and tab.dump(self.results) or "") } end, - }, - - -- OSPF - ['02010'] = { -- OSPFv2 Hello packet - - new = function(self) - local o = { dups = {} } - setmetatable(o, self) - self.__index = self - return o - end, - - process = function(self, layer3) - local p = packet.Packet:new( layer3, #layer3 ) - -- IP Protocol is 89 for OSPF - if p.ip_p ~= 89 then return end - - local ospf = require("ospf") - local data = layer3:sub(p.ip_data_offset + 1) - local header = ospf.OSPF.Header.parse(data) - if header then - if not(self.results) then - self.results = tab.new(5) - tab.addrow(self.results, 'Source IP', 'Router ID', 'Area ID', 'Auth Type', 'Password') - end - local srcip = p.ip_src - local areaid = header.area_id - local routerid = header.router_id - local authtype = header.auth_type - local authdata - - -- Format authentication type and data - if header.auth_type == 0 then - authtype = "None" - authdata = '' - elseif header.auth_type == 1 then - authtype = "Password" - authdata = header.auth_data.password - elseif header.auth_type == 2 then - authtype = "OSPF MD5" - authdata = "" -- Not really helpful, as the MD5 - -- is applied to the whole packet+password - else - -- Error - stdnse.print_debug("Unknown OSPF auth type %d", header.auth_type) - return - end - - if ( not(self.dups[("%s:%s"):format(routerid,areaid)]) ) then - if ( target.ALLOW_NEW_TARGETS ) then target.add(routerid) end - self.dups[("%s:%s"):format(routerid,areaid)] = true - tab.addrow( self.results, srcip, routerid, areaid, authtype, authdata) - end - else - return nil - end - end, - - getResults = function(self) return { name = "OSPF Hello", (self.results and tab.dump(self.results)) } end, - }, - }, - - udp = { - - -- DHCP - [68] = { - new = function(self) - local o = { dups = {} } - setmetatable(o, self) - self.__index = self - return o - end, - - getOption = function(options, name) - for _, v in ipairs(options) do - if ( v.name == name ) then - if ( type(v.value) == "table" ) then - return stdnse.strjoin(", ", v.value) - else - return v.value - end - end - end - end, - - process = function(self, layer3) - local dhcp = require("dhcp") - local p = packet.Packet:new( layer3, #layer3 ) - local data = layer3:sub(p.udp_offset + 9) - - -- the dhcp.parse function isn't optimal for doing - -- this, but it will do for now. First, we need to - -- extract the xid as the parse function checks that it - -- was the same as in the request, which we didn't do. - local pos, msgtype, _, _, _, xid = bin.unpack(" 1) then - table.insert(result, { name = "Registrations", tab.dump(self.reg_result) }) - end - if ( #self.query_result > 1 ) then - table.insert(result, { name = "Query", tab.dump(self.query_result) }) - end - return result - end, - }, - - -- BROWSER - [138] = { - - new = function(self) - local o = { dups = {} } - setmetatable(o, self) - self.__index = self - return o - end, - - process = function(self, layer3) - local bin = require('bin') - local netbios = require('netbios') - local tab = require('tab') - local ipOps = require("ipOps") - local p = packet.Packet:new( layer3, #layer3 ) - local data = layer3:sub(p.udp_offset + 9) - - local pos, ip, _, src, dst = 5 - pos, ip, _, _, _, src, dst = bin.unpack(" 0 and result or nil ) - end - - local results = split(data) - local uri = ( #results > 3 and results[3]:match('[^%"]+') ) - local loc = ( #results > 4 and results[4]:match('[^%"]+') or "") - local info = ( #results > 5 and results[5]:match('[^%"]+') or "") - local model = ( #results > 6 and results[6]:match('[^%"]+') or "") - - if ( not(self.results) ) then - self.results = tab.new(4) - tab.addrow(self.results, 'ip', 'uri', 'loc', 'model') - end - - stdnse.print_debug(1, "Decoded CUPS: %s, %s, %s, %s", p.ip_src, uri, loc, model) - if ( not(self.dups[p.ip_src]) or not(self.dups[p.ip_src][uri]) ) then - tab.addrow(self.results, p.ip_src, uri, loc, model) - self.dups[p.ip_src] = self.dups[p.ip_src] or {} - self.dups[p.ip_src][uri] = self.dups[p.ip_src][uri] or true - end - end, - - getResults = function(self) return { name = "CUPS", (self.results and tab.dump(self.results)) } end, - - }, - - -- SSDP - [1900] = { - - new = function(self) - local o = { dups = {} } - setmetatable(o, self) - self.__index = self - return o - end, - - process = function(self, layer3) - local p = packet.Packet:new( layer3, #layer3 ) - local data = layer3:sub(p.udp_offset + 9) - - local headers = stdnse.strsplit("\r\n", data) - for _, h in ipairs(headers) do - local st = "" - if ( h:match("^ST:.*") ) then - st = h:match("^ST:(.*)") - if ( not(self.results) ) then - self.results = tab.new(1) - tab.addrow( self.results, 'ip', 'uri' ) - end - if ( not(self.dups[("%s:%s"):format(p.ip_src,st)]) ) then - if ( target.ALLOW_NEW_TARGETS ) then target.add(p.ip_src) end - tab.addrow( self.results, p.ip_src, st ) - self.dups[("%s:%s"):format(p.ip_src,st)] = true - end - end - end - end, - - getResults = function(self) return { name = "SSDP", (self.results and tab.dump(self.results)) } end, - }, - - --- HSRP - [1985] = { - - new = function(self) - local o = { dups = {} } - setmetatable(o, self) - self.__index = self - return o - end, - - process = function(self, layer3) - local p = packet.Packet:new( layer3, #layer3 ) - local data = layer3:sub(p.udp_offset + 9) - local ipOps = require("ipOps") - - local State = { - [0] = "Initial", - [1] = "Learn", - [2] = "Listen", - [4] = "Speak", - [8] = "Standby", - [16] = "Active" - } - - local Op = { - [0] = "Hello", - [1] = "Coup", - [2] = "Resign", - } - - local pos, version, op, state, _, _, prio, group, _, secret = bin.unpack("CCCCCCCCz", data) - if ( version ~= 0 ) then return end - pos = pos + ( 7 - #secret ) - local virtip - pos, virtip = bin.unpack(" 0 ) then - name = dresp.questions[1].dname - elseif ( dresp.answers and #dresp.answers > 0 ) then - -- Identify MacBooks - local macbook, model, ip, ipv6 - - if ( p.ip_src:match(":") ) then - ipv6 = p.ip_src - else - ip = p.ip_src - end - - for i in ipairs(dresp.answers) do - if ( dresp.answers[i]['data'] ) then - local _, data = bin.unpack("p", dresp.answers[i]['data']) - if ( data ) then - model = data:match("^model=(.*)") - if ( model ) then - macbook = dresp.answers[i]['dname']:match("^(.-)%._") - end - end - elseif ( dresp.answers[i]['ip'] ) then - ip = dresp.answers[i]['ip'] - elseif ( dresp.answers[i]['ipv6'] ) then - ipv6 = dresp.answers[i]['ipv6'] - elseif ( not(macbook) and dresp.answers[i]['domain'] ) then - macbook = dresp.answers[i]['domain'] - end - end - if ( macbook and model ) then - self.macbooks[macbook] = self.macbooks[macbook] or {} - self.macbooks[macbook]['macbook'] = self.macbooks[macbook]['macbook'] or macbook - self.macbooks[macbook]['model'] = self.macbooks[macbook]['model'] or model - self.macbooks[macbook]['ip'] = self.macbooks[macbook]['ip'] or ip - self.macbooks[macbook]['ipv6'] = self.macbooks[macbook]['ipv6'] or ipv6 - stdnse.print_debug(1, "Decoded MDNS(MacBook): %s, %s, %s, %s", - (self.macbooks[macbook]['ip'] or ""), (self.macbooks[macbook]['ipv6'] or ""), - self.macbooks[macbook]['model'], self.macbooks[macbook]['macbook']) - else - name = dresp.answers[1].dname - if ( not(name) ) then return end - self.generic[name] = self.generic[name] or {} - self.generic[name]['name'] = self.generic[name]['name'] or name - if ( p.ip_src:match(":") ) then - self.generic[name]['ipv6'] = p.ip_src - else - self.generic[name]['ip'] = p.ip_src - end - stdnse.print_debug(1, "Decoded MDNS(Generic): %s, %s", name, p.ip_src) - end - end - end, - - getResults = function(self) - local tab = require('tab') - local result = { name = "MDNS" } - - -- build a macbooks table - local macbooks, generic - - if ( next(self.generic) ) then - table.sort(self.generic) - generic = tab.new(3) - tab.addrow(generic, 'ip', 'ipv6', 'name') - - for name, v in pairs(self.generic) do - tab.addrow(generic, (v.ip or ""), (v.ipv6 or ""), name) - end - table.insert(result, { name = 'Generic', tab.dump(generic) } ) - end - - if ( next(self.macbooks) ) then - table.sort(self.macbooks) - macbooks = tab.new(4) - tab.addrow(macbooks, 'ip', 'ipv6', 'name', 'model') - - for _, v in pairs(self.macbooks) do - tab.addrow(macbooks, (v.ip or ""), (v.ipv6 or ""), v.macbook, v.model) - end - table.insert(result, { name = 'Macbooks', tab.dump(macbooks) } ) - end - - return result - end, - }, - - [5355] = { -- LLMNR - new = function(self) - local o = { dups = {} } - setmetatable(o, self) - self.__index = self - return o - end, - - process = function(self, layer3) - local tab = require('tab') - local dns = require('dns') - local p = packet.Packet:new( layer3, #layer3 ) - local data = layer3:sub(p.udp_offset + 9) - - local resp = dns.decode(data) - if ( not(self.results) ) then - self.results = tab.new(2) - tab.addrow(self.results, 'ip', 'query') - end - - local name = (( resp.questions and #resp.questions > 0 ) and resp.questions[1].dname ) - if ( not(name) ) then return end - stdnse.print_debug(1, "Decoded LLMNR: %s, %s", p.ip_src, name) - - if ( not(self.dups[("%s:%s"):format(p.ip_src, name)]) ) then - self.dups[("%s:%s"):format(p.ip_src, name)] = true - tab.addrow(self.results, p.ip_src, name) - end - end, - - getResults = function(self) return { name = "LLMNR", (self.results and tab.dump(self.results)) } end, - }, - - --- Spotify - [57621] = { - - new = function(self) - local o = { dups = {} } - setmetatable(o, self) - self.__index = self - return o - end, - - process = function(self, layer3) - local p = packet.Packet:new( layer3, #layer3 ) - local data = layer3:sub(p.udp_offset + 9) - - if ( data:match("^SpotUdp") ) then - if ( not(self.results) ) then - self.results = tab.new(1) - tab.addrow( self.results, 'ip' ) - end - - if ( not(self.dups[p.ip_src]) ) then - tab.addrow( self.results, p.ip_src ) - self.dups[p.ip_src] = true - if ( target.ALLOW_NEW_TARGETS ) then target.add(p.ip_src) end - end - end - - end, - - getResults = function(self) return { name = "Spotify", (self.results and tab.dump(self.results)) } end, - - } - - } -} +local bin = require "bin" +local packet = require "packet" +local stdnse = require "stdnse" +local tab = require "tab" +local table = require "table" +local target = require "target" + +--- The following file contains a list of decoders used by the +-- broadcast-listener script. A decoder can be either "ethernet" based or IP +-- based. As we're only monitoring broadcast traffic (ie. traffic not +-- explicitly addressed to us) we're mainly dealing with: +-- o UDP broadcast or multicast traffic +-- o ethernet broadcast traffic +-- +-- Hence, the Decoder table defines two sub tables ether and udp. +-- In order to match an incoming UDP packet the destination port number is +-- used, therefore each function is indexed based on their destination port +-- for the udp based decoders. For the ether table each decoder function is +-- indexed according to a pattern that the decoding engine attempts to match. +-- +-- Each decoder defines three functions: +-- o new - creates a new instance of the decoder +-- o process - process a packet passed through the +-- data argument. +-- o getResults - retrieve any discovered results +-- +-- The discovery engine creates an instance of each decoder once it's needed. +-- Then discovery engine stores this instance in a decoder table for reference +-- once the next packet of the same type comes in. This allows the engine to +-- discard duplicate packets and to request the collected results at the end +-- of the session. +-- +-- Currently, the packet decoder decodes the following protocols: +-- o Ether +-- x ARP requests (IPv4) +-- x CDP - Cisco Discovery Protocol +-- x EIGRP - Cisco Enhanced Interior Gateway Routing Protocol +-- x OSPF - Open Shortest Path First +-- +-- o UDP +-- x DHCP +-- x Netbios +-- x SSDP +-- x HSRP +-- x DropBox +-- x Logitech SqueezeBox Discovery +-- x Multicast DNS/Bonjour/ZeroConf +-- x Spotify +-- +-- +-- @author "Patrik Karlsson " +-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html + +-- Version 0.2 +-- Created 07/25/2011 - v0.1 - created by Patrik Karlsson +-- 02/12/2012 - v0.2 - added support for EIGRP - Tom Sellers +-- 07/13/2012 - v0.3 - added support for OSPF - Hani Benhabiles + +local bin = require 'bin' +local target = require 'target' +local tab = require 'tab' +local packet = require 'packet' + +Decoders = { + + ether = { + + -- ARP IPv4 + ['^00..08000604'] = { + + new = function(self) + local o = { dups = {} } + setmetatable(o, self) + self.__index = self + return o + end, + + process = function(self, data) + local ipOps = require("ipOps") + local pos, hw, proto, hwsize, protosize, opcode = bin.unpack(">SSCCS", data) + + -- this shouldn't ever happen, given our filter + if ( hwsize ~= 6 ) then return end + local sender, target = {}, {} + + -- if this isn't an ARP request, abort + if ( opcode ~= 1 ) then return end + + pos, sender.mac, + sender.ip, + target.mac, + target.ip = bin.unpack("I", data) + for i=1, count do + pos, proto_type, proto_len = bin.unpack(">CC", data, pos) + pos, addr_proto = bin.unpack(">H" .. proto_len, data, pos) + if ( addr_proto == 'CC' ) then + -- IPv4 address, extract it + pos, addr_len = bin.unpack(">S", data, pos) + pos, dev_addr = bin.unpack("CCS", data, 9) + if ( ver ~= 2 ) then return end + if ( not(self.results) ) then + self.results = tab.new(5) + tab.addrow( self.results, 'ip', 'id', 'platform', 'version', 'notes' ) + end + + local result_part = {} + result_part.notes = '' + while ( pos < #data ) do + local typ, len, typdata + pos, typ, len = bin.unpack(">SS", data, pos) + pos, typdata = bin.unpack("A" .. len - 4, data, pos) + + -- Device ID + if ( typ == 1 ) then + result_part.id = typdata + -- Version + elseif ( typ == 5 ) then + result_part.version = typdata:match(", Version (.-),") + -- Platform + elseif ( typ == 6 ) then + result_part.platform = typdata + -- Address + elseif ( typ == 2 ) then + result_part.ip = self.getAddresses(typdata) + elseif ( typ == 10) then + local _, mgmt_vlan = bin.unpack(">S", data,pos - 2) + result_part.notes = result_part.notes .. 'native vlan:' .. mgmt_vlan .. ' ' + -- Management Address + elseif ( typ == 22 ) then + result_part.notes = result_part.notes .. 'mgmt ip:' .. self.getAddresses(typdata) .. ' ' + -- TODO: add more decoding of types here ... + end + end + + -- TODO: add code for dups check + if ( not(self.dups[result_part.ip]) ) then + self.dups[result_part.ip] = true + tab.addrow( self.results, result_part.ip, result_part.id, result_part.platform, result_part.version, result_part.notes ) + end + end, + + getResults = function(self) return { name = "CDP", (self.results and tab.dump(self.results) or "") } end, + }, + + + -- EIGRP Update + ['0201....0000'] = { + + new = function(self) + local o = { dups = {} } + setmetatable(o, self) + self.__index = self + return o + end, + + process = function(self, layer3) + local p = packet.Packet:new( layer3, #layer3 ) + -- EIGRP is IP protocol 88 (0x58), so verify this + if ( p.ip_p ~= 88 ) then return end + + local data = layer3:sub(p.ip_data_offset + 1) + local eigrp = require("eigrp") + local route_type, proto_name + -- Extract the EIGRP header + local response = eigrp.EIGRP.parse(data) + + if response then + -- Iterate over tlv tables + for _, tlv in pairs(response.tlvs) do + if eigrp.EIGRP.isRoutingTLV(tlv.type) then + if ( not(self.results) ) then + self.results = tab.new(7) + tab.addrow(self.results, 'Sender IP', 'AS#', 'Route Type', 'Destination', 'Next hop', 'Ext Protocol', 'Orig Router ID') + end + if tlv.type == 0x102 then + route_type = "Internal" + elseif tlv.type == 0x103 then + route_type = "External" + for name, value in pairs(eigrp.EXT_PROTO) do + if value == tlv.eproto then + proto_name = name + break + end + end + end + if ( not(self.dups[("%s:%s:s:%s"):format(p.ip_src, response.as, tlv.type, tlv.dst)]) ) then + if ( target.ALLOW_NEW_TARGETS ) then target.add(p.ip_src) end + self.dups[("%s:%s:%s:%s"):format(p.ip_src, response.as, tlv.type, tlv.dst)] = true + tab.addrow( self.results, p.ip_src, response.as, route_type, tlv.dst, tlv.nexth, proto_name or 'X', tlv.orouterid or 'X') + end + end + end + end + end, + + getResults = function(self) return { name = "EIGRP Update", (self.results and tab.dump(self.results) or "") } end, + }, + + ['0205....0000'] = { + + new = function(self) + local o = { dups = {} } + setmetatable(o, self) + self.__index = self + return o + end, + + process = function(self, layer3) + + local p = packet.Packet:new( layer3, #layer3 ) + -- EIGRP is IP protocol 88 (0x58), so verify this + if ( p.ip_p ~= 88 ) then return end + + local data = layer3:sub(p.ip_data_offset + 1) + local eigrp = require("eigrp") + -- Extract the EIGRP header + local response = eigrp.EIGRP.parse(data) + -- See if Software version TLV is included + local swvertlv + for num, tlv in pairs(response.tlvs) do + if tlv.type == eigrp.TLV.SWVER then + swvertlv = num + end + end + + if swvertlv then + if ( not(self.results) ) then + self.results = tab.new(5) + tab.addrow(self.results, 'Sender IP', 'AS number', 'EIGRP version', 'IOS version') + end + + if ( not(self.dups[("%s:%s"):format(p.ip_src,response.as)]) ) then + if ( target.ALLOW_NEW_TARGETS ) then target.add(p.ip_src) end + self.dups[("%s:%s"):format(p.ip_src,response.as)] = true + tab.addrow( self.results, p.ip_src, response.as, response.tlvs[swvertlv].majv .. '.' .. response.tlvs[swvertlv].minv, response.tlvs[swvertlv].majtlv .. '.' .. response.tlvs[swvertlv].mintlv) + end + end + end, + + getResults = function(self) return { name = "EIGRP Hello", (self.results and tab.dump(self.results) or "") } end, + }, + + -- OSPF + ['02010'] = { -- OSPFv2 Hello packet + + new = function(self) + local o = { dups = {} } + setmetatable(o, self) + self.__index = self + return o + end, + + process = function(self, layer3) + local p = packet.Packet:new( layer3, #layer3 ) + -- IP Protocol is 89 for OSPF + if p.ip_p ~= 89 then return end + + local ospf = require("ospf") + local data = layer3:sub(p.ip_data_offset + 1) + local header = ospf.OSPF.Header.parse(data) + if header then + if not(self.results) then + self.results = tab.new(5) + tab.addrow(self.results, 'Source IP', 'Router ID', 'Area ID', 'Auth Type', 'Password') + end + local srcip = p.ip_src + local areaid = header.area_id + local routerid = header.router_id + local authtype = header.auth_type + local authdata + + -- Format authentication type and data + if header.auth_type == 0 then + authtype = "None" + authdata = '' + elseif header.auth_type == 1 then + authtype = "Password" + authdata = header.auth_data.password + elseif header.auth_type == 2 then + authtype = "OSPF MD5" + authdata = "" -- Not really helpful, as the MD5 + -- is applied to the whole packet+password + else + -- Error + stdnse.print_debug("Unknown OSPF auth type %d", header.auth_type) + return + end + + if ( not(self.dups[("%s:%s"):format(routerid,areaid)]) ) then + if ( target.ALLOW_NEW_TARGETS ) then target.add(routerid) end + self.dups[("%s:%s"):format(routerid,areaid)] = true + tab.addrow( self.results, srcip, routerid, areaid, authtype, authdata) + end + else + return nil + end + end, + + getResults = function(self) return { name = "OSPF Hello", (self.results and tab.dump(self.results)) } end, + }, +}, + +udp = { + + -- DHCP + [68] = { + new = function(self) + local o = { dups = {} } + setmetatable(o, self) + self.__index = self + return o + end, + + getOption = function(options, name) + for _, v in ipairs(options) do + if ( v.name == name ) then + if ( type(v.value) == "table" ) then + return stdnse.strjoin(", ", v.value) + else + return v.value + end + end + end + end, + + process = function(self, layer3) + local dhcp = require("dhcp") + local p = packet.Packet:new( layer3, #layer3 ) + local data = layer3:sub(p.udp_offset + 9) + + -- the dhcp.parse function isn't optimal for doing + -- this, but it will do for now. First, we need to + -- extract the xid as the parse function checks that it + -- was the same as in the request, which we didn't do. + local pos, msgtype, _, _, _, xid = bin.unpack(" 1) then + table.insert(result, { name = "Registrations", tab.dump(self.reg_result) }) + end + if ( #self.query_result > 1 ) then + table.insert(result, { name = "Query", tab.dump(self.query_result) }) + end + return result + end, + }, + + -- BROWSER + [138] = { + + new = function(self) + local o = { dups = {} } + setmetatable(o, self) + self.__index = self + return o + end, + + process = function(self, layer3) + local bin = require('bin') + local netbios = require('netbios') + local tab = require('tab') + local ipOps = require("ipOps") + local p = packet.Packet:new( layer3, #layer3 ) + local data = layer3:sub(p.udp_offset + 9) + + local pos, ip, _, src, dst = 5 + pos, ip, _, _, _, src, dst = bin.unpack(" 0 and result or nil ) + end + + local results = split(data) + local uri = ( #results > 3 and results[3]:match('[^%"]+') ) + local loc = ( #results > 4 and results[4]:match('[^%"]+') or "") + local info = ( #results > 5 and results[5]:match('[^%"]+') or "") + local model = ( #results > 6 and results[6]:match('[^%"]+') or "") + + if ( not(self.results) ) then + self.results = tab.new(4) + tab.addrow(self.results, 'ip', 'uri', 'loc', 'model') + end + + stdnse.print_debug(1, "Decoded CUPS: %s, %s, %s, %s", p.ip_src, uri, loc, model) + if ( not(self.dups[p.ip_src]) or not(self.dups[p.ip_src][uri]) ) then + tab.addrow(self.results, p.ip_src, uri, loc, model) + self.dups[p.ip_src] = self.dups[p.ip_src] or {} + self.dups[p.ip_src][uri] = self.dups[p.ip_src][uri] or true + end + end, + + getResults = function(self) return { name = "CUPS", (self.results and tab.dump(self.results)) } end, + + }, + + -- SSDP + [1900] = { + + new = function(self) + local o = { dups = {} } + setmetatable(o, self) + self.__index = self + return o + end, + + process = function(self, layer3) + local p = packet.Packet:new( layer3, #layer3 ) + local data = layer3:sub(p.udp_offset + 9) + + local headers = stdnse.strsplit("\r\n", data) + for _, h in ipairs(headers) do + local st = "" + if ( h:match("^ST:.*") ) then + st = h:match("^ST:(.*)") + if ( not(self.results) ) then + self.results = tab.new(1) + tab.addrow( self.results, 'ip', 'uri' ) + end + if ( not(self.dups[("%s:%s"):format(p.ip_src,st)]) ) then + if ( target.ALLOW_NEW_TARGETS ) then target.add(p.ip_src) end + tab.addrow( self.results, p.ip_src, st ) + self.dups[("%s:%s"):format(p.ip_src,st)] = true + end + end + end + end, + + getResults = function(self) return { name = "SSDP", (self.results and tab.dump(self.results)) } end, + }, + + --- HSRP + [1985] = { + + new = function(self) + local o = { dups = {} } + setmetatable(o, self) + self.__index = self + return o + end, + + process = function(self, layer3) + local p = packet.Packet:new( layer3, #layer3 ) + local data = layer3:sub(p.udp_offset + 9) + local ipOps = require("ipOps") + + local State = { + [0] = "Initial", + [1] = "Learn", + [2] = "Listen", + [4] = "Speak", + [8] = "Standby", + [16] = "Active" + } + + local Op = { + [0] = "Hello", + [1] = "Coup", + [2] = "Resign", + } + + local pos, version, op, state, _, _, prio, group, _, secret = bin.unpack("CCCCCCCCz", data) + if ( version ~= 0 ) then return end + pos = pos + ( 7 - #secret ) + local virtip + pos, virtip = bin.unpack(" 0 ) then + name = dresp.questions[1].dname + elseif ( dresp.answers and #dresp.answers > 0 ) then + -- Identify MacBooks + local macbook, model, ip, ipv6 + + if ( p.ip_src:match(":") ) then + ipv6 = p.ip_src + else + ip = p.ip_src + end + + for i in ipairs(dresp.answers) do + if ( dresp.answers[i]['data'] ) then + local _, data = bin.unpack("p", dresp.answers[i]['data']) + if ( data ) then + model = data:match("^model=(.*)") + if ( model ) then + macbook = dresp.answers[i]['dname']:match("^(.-)%._") + end + end + elseif ( dresp.answers[i]['ip'] ) then + ip = dresp.answers[i]['ip'] + elseif ( dresp.answers[i]['ipv6'] ) then + ipv6 = dresp.answers[i]['ipv6'] + elseif ( not(macbook) and dresp.answers[i]['domain'] ) then + macbook = dresp.answers[i]['domain'] + end + end + if ( macbook and model ) then + self.macbooks[macbook] = self.macbooks[macbook] or {} + self.macbooks[macbook]['macbook'] = self.macbooks[macbook]['macbook'] or macbook + self.macbooks[macbook]['model'] = self.macbooks[macbook]['model'] or model + self.macbooks[macbook]['ip'] = self.macbooks[macbook]['ip'] or ip + self.macbooks[macbook]['ipv6'] = self.macbooks[macbook]['ipv6'] or ipv6 + stdnse.print_debug(1, "Decoded MDNS(MacBook): %s, %s, %s, %s", + (self.macbooks[macbook]['ip'] or ""), (self.macbooks[macbook]['ipv6'] or ""), + self.macbooks[macbook]['model'], self.macbooks[macbook]['macbook']) + else + name = dresp.answers[1].dname + if ( not(name) ) then return end + self.generic[name] = self.generic[name] or {} + self.generic[name]['name'] = self.generic[name]['name'] or name + if ( p.ip_src:match(":") ) then + self.generic[name]['ipv6'] = p.ip_src + else + self.generic[name]['ip'] = p.ip_src + end + stdnse.print_debug(1, "Decoded MDNS(Generic): %s, %s", name, p.ip_src) + end + end + end, + + getResults = function(self) + local tab = require('tab') + local result = { name = "MDNS" } + + -- build a macbooks table + local macbooks, generic + + if ( next(self.generic) ) then + table.sort(self.generic) + generic = tab.new(3) + tab.addrow(generic, 'ip', 'ipv6', 'name') + + for name, v in pairs(self.generic) do + tab.addrow(generic, (v.ip or ""), (v.ipv6 or ""), name) + end + table.insert(result, { name = 'Generic', tab.dump(generic) } ) + end + + if ( next(self.macbooks) ) then + table.sort(self.macbooks) + macbooks = tab.new(4) + tab.addrow(macbooks, 'ip', 'ipv6', 'name', 'model') + + for _, v in pairs(self.macbooks) do + tab.addrow(macbooks, (v.ip or ""), (v.ipv6 or ""), v.macbook, v.model) + end + table.insert(result, { name = 'Macbooks', tab.dump(macbooks) } ) + end + + return result + end, + }, + + [5355] = { -- LLMNR + new = function(self) + local o = { dups = {} } + setmetatable(o, self) + self.__index = self + return o + end, + + process = function(self, layer3) + local tab = require('tab') + local dns = require('dns') + local p = packet.Packet:new( layer3, #layer3 ) + local data = layer3:sub(p.udp_offset + 9) + + local resp = dns.decode(data) + if ( not(self.results) ) then + self.results = tab.new(2) + tab.addrow(self.results, 'ip', 'query') + end + + local name = (( resp.questions and #resp.questions > 0 ) and resp.questions[1].dname ) + if ( not(name) ) then return end + stdnse.print_debug(1, "Decoded LLMNR: %s, %s", p.ip_src, name) + + if ( not(self.dups[("%s:%s"):format(p.ip_src, name)]) ) then + self.dups[("%s:%s"):format(p.ip_src, name)] = true + tab.addrow(self.results, p.ip_src, name) + end + end, + + getResults = function(self) return { name = "LLMNR", (self.results and tab.dump(self.results)) } end, +}, + +--- Spotify +[57621] = { + + new = function(self) + local o = { dups = {} } + setmetatable(o, self) + self.__index = self + return o + end, + + process = function(self, layer3) + local p = packet.Packet:new( layer3, #layer3 ) + local data = layer3:sub(p.udp_offset + 9) + + if ( data:match("^SpotUdp") ) then + if ( not(self.results) ) then + self.results = tab.new(1) + tab.addrow( self.results, 'ip' ) + end + + if ( not(self.dups[p.ip_src]) ) then + tab.addrow( self.results, p.ip_src ) + self.dups[p.ip_src] = true + if ( target.ALLOW_NEW_TARGETS ) then target.add(p.ip_src) end + end + end + + end, + + getResults = function(self) return { name = "Spotify", (self.results and tab.dump(self.results)) } end, + +} + + } +}