diff --git a/CHANGELOG b/CHANGELOG index 173f0a4b0..f19d5a860 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Added eigrp.lua library which supports parsing and generating a small subset + of Cisco's EIGRP packets. [Hani Benhabiles] + o [NSE] Added llmnr-resolve script which resolves a hostname by using the LLMNR (Link-Local Multicast Name Resolution) protocol. [Hani Benhabiles] diff --git a/nselib/eigrp.lua b/nselib/eigrp.lua new file mode 100644 index 000000000..058b37f23 --- /dev/null +++ b/nselib/eigrp.lua @@ -0,0 +1,387 @@ +--- A library supporting parsing and generating a limited subset of the Cisco' EIGRP packets. +-- +-- @author "Hani Benhabiles" +-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html +-- Version 0.1 +-- 19/07/2012 - First version. + +local stdnse = require "stdnse" +local ipOps = require "ipOps" +local packet = require "packet" +_ENV = stdnse.module("eigrp", stdnse.seeall) + + +-- TLV Type constants +TLV = { + PARAM = 0x0001, + AUTH = 0x0002, + SEQ = 0x0003, + SWVER = 0x0004, + MSEQ = 0x0005, + STUB = 0x0006, + TERM = 0x0007, + TIDLIST = 0x0008, + REQ = 0x0101, + INT = 0x0102, + EXT = 0x0103, + COM = 0x0104, + INT6 = 0x0402, + EXT6 = 0x0403, + COM6 = 0x0404, +} + +-- External protocols constants +EXT_PROTO = { + NULL = 0x00, + IGRP = 0x01, + EIGRP = 0x02, + Static = 0x03, + RIP = 0x04, + HELLO = 0x05, + OSPF = 0x06, + ISIS = 0x07, + EGP = 0x08, + BGP = 0x09, + IDRP = 0x10, + Connected = 0x11, +} + +-- Packets opcode constants +OPCODE = { + UPDATE = 0x01, + RESERVED = 0x02, + QUERY = 0x03, + REPLY = 0x04, + HELLO = 0x05, +} + +-- The EIGRP Class +EIGRP = { + + --- Creates a new instance of EIGRP class. + -- @param opcode integer Opcode. Defaults to 5 (Hello) + -- @param as integer Autonomous System. Defaults to 0. + -- @param routerid integer virtual router ID. defaults to 0. + -- @param flags integer flags field value. Defaults to 0. + -- @param seq integer sequence value. Defaults to 0. + -- @param ack integer acknowledge value. Defaults to 0. + -- @param Checksum integer EIGRP packet checksum. Calculated automatically + -- if not manually set. + -- @param Table TLVs table. + -- @return o Instance of EIGRP + new = function(self, opcode, as, routerid, flags, seq, ack, checksum, tlvs) + local o = { + ver = 2, + opcode = opcode or TLV.HELLO, + as = as or 0, + routerid = routerid or 0, + flags = flags or 0, + seq = seq or 0x00, + ack = ack or 0x00, + checksum = checksum, + tlvs = tlvs or {}, + } + setmetatable(o, self) + self.__index = self + return o + end, + + --- Parses a raw eigrp packet and returns a structred response. + -- @param eigrp_raw string EIGRP Raw packet. + -- @return response table Structured eigrp packet. + parse = function(eigrp_raw) + if type(eigrp_raw) ~= 'string' then + stdnse.print_debug("eigrp.lua: parse input should be string.") + return + end + if #eigrp_raw < 20 then + stdnse.print_debug("eigrp.lua: raw packet size lower then 20.") + return + end + local tlv + local eigrp_packet = {} + local index = 1 + index, eigrp_packet.ver = bin.unpack(">C", eigrp_raw, index) + index, eigrp_packet.opcode = bin.unpack(">C", eigrp_raw, index) + index, eigrp_packet.checksum = bin.unpack(">S", eigrp_raw, index) + index, eigrp_packet.flags = bin.unpack(">I", eigrp_raw, index) + index, eigrp_packet.seq = bin.unpack(">I", eigrp_raw, index) + index, eigrp_packet.ack = bin.unpack(">I", eigrp_raw, index) + index, eigrp_packet.routerid = bin.unpack(">S", eigrp_raw, index) + index, eigrp_packet.as = bin.unpack(">S", eigrp_raw, index) + eigrp_packet.tlvs = {} + while index < #eigrp_raw do + tlv = {} + index, tlv.type = bin.unpack(">S", eigrp_raw, index) + index, tlv.length = bin.unpack(">S", eigrp_raw, index) + if tlv.length == 0x00 then + -- In case someone wants to DoS us :) + stdnse.print_debug("eigrp.lua: stopped parsing due to null TLV length.") + break + end + if tlv.type == TLV.PARAM then + -- Parameters + local k = {} + index, k[1], k[2], k[3], k[4], k[5], k[6] = bin.unpack(">CCCCCC", eigrp_raw, index) + index, tlv.htime = bin.unpack(">S", eigrp_raw, index) + index = index + tlv.length - 12 + elseif tlv.type == TLV.AUTH then + index, tlv.authtype = bin.unpack(">S", eigrp_raw, index) + index, tlv.authlen = bin.unpack(">S", eigrp_raw, index) + index, tlv.keyid = bin.unpack(">I", eigrp_raw, index) + index, tlv.keyseq = bin.unpack(">I", eigrp_raw, index) + -- Null pad == tlv.length - What was already parsed - authlen + index, tlv.digest = bin.unpack(">S", eigrp_raw, index + (tlv.length - tlv.authlen - index + 1)) + elseif tlv.type == TLV.SEQ then + -- Sequence + index, tlv.addlen = bin.unpack(">S", eigrp_raw, index) + index, tlv.address = bin.unpack("C", eigrp_raw, index) + index, tlv.minv = bin.unpack(">C", eigrp_raw, index) + index, tlv.majtlv = bin.unpack(">C", eigrp_raw, index) + index, tlv.mintlv = bin.unpack(">C", eigrp_raw, index) + index = index + tlv.length - 8 + elseif tlv.type == TLV.MSEQ then + -- Next Multicast Sequence + index, tlv.mseq = bin.unpack(">I", eigrp_raw, index) + index = index + tlv.length - 8 + elseif tlv.type == TLV.STUB then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + index = index + tlv.length - 4 + elseif tlv.type == TLV.TERM then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + index = index + tlv.length - 4 + elseif tlv.type == TLV.TIDLIST then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + index = index + tlv.length - 4 + elseif tlv.type == TLV.REQ then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + index = index + tlv.length - 4 + elseif tlv.type == TLV.INT then + -- Internal Route + index, tlv.nexth = bin.unpack("S", eigrp_raw, index + 15) + -- Destination varies in length + -- e.g trailing 0's are omitted + -- if length = 29 => destination is 4 bytes + -- if length = 28 => destination is 3 bytes + -- if length = 27 => destination is 2 bytes + -- if length = 26 => destination is 1 byte + local dst = {} + index, dst[1], dst[2], dst[3], dst[4] = bin.unpack(">C" .. 4 + tlv.length - 29, eigrp_raw, index) + for i=2,4 do + if not dst[i] then + dst[i] = '0' + end + end + tlv.dst = dst[1] .. '.' .. dst[2] .. '.' .. dst[3] .. '.' .. dst[4] + elseif tlv.type == TLV.EXT then + -- External Route + index, tlv.nexth = bin.unpack("I", eigrp_raw, index) + index, tlv.tag = bin.unpack(">I", eigrp_raw, index) + index, tlv.emetric = bin.unpack(">I", eigrp_raw, index) + -- Skip 2 reserved bytes + index, tlv.eproto = bin.unpack(">C", eigrp_raw, index + 2) + index, tlv.eflags = bin.unpack(">C", eigrp_raw, index) + index, tlv.lmetrics = bin.unpack(">L"..2, eigrp_raw, index) + index, tlv.mask = bin.unpack(">C", eigrp_raw, index) + -- Destination varies in length + -- if length = 49 => destination is 4 bytes + -- if length = 48 => destination is 3 bytes + -- if length = 47 => destination is 2 bytes + -- if length = 46 => destination is 1 byte + local dst = {} + index, dst[1], dst[2], dst[3], dst[4] = bin.unpack(">C" .. 4 + tlv.length - 49, eigrp_raw, index) + for i=2,4 do + if not dst[i] then + dst[i] = '0' + end + end + tlv.dst = dst[1] .. '.' .. dst[2] .. '.' .. dst[3] .. '.' .. dst[4] + elseif tlv.type == TLV.COM then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + index = index + tlv.length - 4 + elseif tlv.type == TLV.INT6 then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + index = index + tlv.length - 4 + elseif tlv.type == TLV.EXT6 then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + index = index + tlv.length - 4 + elseif tlv.type == TLV.COM6 then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + index = index + tlv.length - 4 + else + stdnse.print_debug("eigrp.lua: eigrp.lua: TLV type %d unknown.", tlv.type) + index = index + tlv.length - 4 + end + table.insert(eigrp_packet.tlvs, tlv) + end + return eigrp_packet + end, + + --- Adds a TLV table to the table of TLVs. + -- @param tlv TLV table. + addTLV = function(self, tlv) + if type(tlv) == 'table' then + table.insert(self.tlvs, tlv) + else + stdnse.print_debug("eigrp.lua: TLV should be a table, not %s", type(tlv)) + end + end, + + --- Checks if TLV type is one that should contain routing information. + -- @param tlvtype integer TLV type integer to check. + -- @return status true if tlvtype is a routing information tlv. + isRoutingTLV = function(tlvtype) + if tlvtype == 0x101 or tlvtype == 0x102 + or tlvtype == 0x103 or tlvtype == 0x104 + or tlvtype == 0x402 or tlvtype == 0x403 + or tlvtype == 0x404 then + return true + end + end, + + --- Sets the EIGRP version. + -- @param ver integer version to set. + setVersion = function(self, ver) + self.ver = ver + end, + --- Sets the EIGRP Packet opcode + -- @param opcode integer EIGRP opcode. + setOpcode = function(self, opcode) + self.opcode = opcode + end, + --- Sets the EIGRP packet checksum + -- @param integer checksum Checksum to set. + setChecksum = function(self, checksum) + self.checksum = checksum + end, + --- Sets the EIGRP packet flags field. + -- @param flags Flags integer value. + setFlags = function(self, flags) + self.flags = flags + end, + --- Sets the EIGRP packet sequence field. + -- @param seq EIGRP sequence. + setSequence = function(self, seq) + self.seq = seq + end, + --- Sets the EIGRP Packet acknowledge field. + -- @param ack EIGRP acknowledge. + setAcknowledge = function(self, ack) + self.ack = ack + end, + --- Sets the EIGRP Packet Virtual Router ID. + -- @param routerid EIGRP Virtual Router ID. + setRouterID = function(self, routerid) + self.routerid = routerid + end, + --- Sets the EIGRP Packet Autonomous System. + -- @param as EIGRP A.S. + setAS = function(self, as) + self.as = as + end, + --- Sets the EIGRP Packet tlvs + -- @param tlvs table of EIGRP tlvs. + setTlvs = function(self, tlvs) + self.tlvs = tlvs + end, + --- Converts the request to a string suitable to be sent over a socket. + -- @return data string containing the complete request to send over the socket + __tostring = function(self) + local data = bin.pack(">C", self.ver) -- Version 2 + data = data .. bin.pack(">C", self.opcode) -- Opcode: Hello + + -- If checksum not manually. + -- set to 0, then calculate it later + if self.checksum then + data = data .. bin.pack(">S", self.checksum) + else + data = data .. bin.pack(">S", 0x0000) -- Calculated later. + end + data = data .. bin.pack(">I", self.flags) -- Flags + data = data .. bin.pack(">I", self.seq) -- Sequence 0 + data = data .. bin.pack(">I", self.ack) -- Acknowledge 0 + data = data .. bin.pack(">S", self.routerid) -- Virtual Router ID 0 + data = data .. bin.pack(">S", self.as) -- Autonomous system + for _, tlv in pairs(self.tlvs) do + if tlv.type == TLV.PARAM then + data = data .. bin.pack(">S", TLV.PARAM) + data = data .. bin.pack(">S", 0x000c) -- Length: 12 + data = data .. bin.pack(">CCCCCC", tlv.k[1],tlv.k[2],tlv.k[3], + tlv.k[4],tlv.k[5],tlv.k[6]) + data = data .. bin.pack(">S", tlv.htime) + elseif tlv.type == TLV.AUTH then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.SEQ then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.SWVER then + data = data .. bin.pack(">S", TLV.SWVER) + data = data .. bin.pack(">S", 0x0008) + data = data .. bin.pack(">CC", tonumber(tlv.majv), tonumber(tlv.minv)) + data = data .. bin.pack(">CC", tonumber(tlv.majtlv), tonumber(tlv.mintlv)) + elseif tlv.type == TLV.MSEQ then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.STUB then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.TERM then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.TIDLIST then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.REQ then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.INT then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.EXT then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.COM then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.INT6 then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.EXT6 then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.COM6 then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + else + stdnse.print_debug("eigrp.lua: TLV type %d unknown.", tlv.type) + end + end + -- In the end, correct the checksum if not manually set + if not self.checksum then + data = data:sub(1,2) .. bin.pack(">S", packet.in_cksum(data)) .. data:sub(5) + end + return data + end, +} + +return _ENV;