From 0d47952611198ef6b1163f366dc03922d20b1475 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 09:42:04 +0200 Subject: Adding upstream version 7.94+git20230807.3be01efb1+dfsg. Signed-off-by: Daniel Baumann --- nselib/rpcap.lua | 435 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 nselib/rpcap.lua (limited to 'nselib/rpcap.lua') diff --git a/nselib/rpcap.lua b/nselib/rpcap.lua new file mode 100644 index 0000000..e524461 --- /dev/null +++ b/nselib/rpcap.lua @@ -0,0 +1,435 @@ +--- +-- This library implements the fundamentals needed to communicate with the +-- WinPcap Remote Capture Daemon. It currently supports authenticating to +-- the service using either NULL-, or Password-based authentication. +-- In addition it has the capabilities to list the interfaces that may be +-- used for sniffing. +-- +-- The library consist of classes handling Request and classes +-- handling Response. The communication with the service is +-- handled by the Comm class, and the main interface for script +-- writers is kept under the Helper class. +-- +-- The following code snippet illustrates how to connect to the service and +-- extract information about network interfaces: +-- +-- local helper = rpcap.Helper:new(host, port) +-- helper:connect() +-- helper:login() +-- helper:findAllInterfaces() +-- helper:close() +-- +-- +-- For a more complete example, consult the rpcap-info.nse script. +-- +-- @author Patrik Karlsson + + +local ipOps = require "ipOps" +local match = require "match" +local nmap = require "nmap" +local stdnse = require "stdnse" +local string = require "string" +local table = require "table" +_ENV = stdnse.module("rpcap", stdnse.seeall) + +RPCAP = { + + MessageType = { + ERROR = 1, + FIND_ALL_INTERFACES = 2, + AUTH_REQUEST = 8, + }, + + -- Holds the two supported authentication mechanisms PWD and NULL + Authentication = { + + PWD = { + + new = function(self, username, password) + local o = { + type = 1, + username = username, + password = password, + } + setmetatable(o, self) + self.__index = self + return o + end, + + __tostring = function(self) + local DUMMY = 0 + return string.pack(">I2I2I2I2", self.type, DUMMY, #self.username, #self.password) .. self.username .. self.password + end, + + }, + + NULL = { + + new = function(self) + local o = { + type = 0, + } + setmetatable(o, self) + self.__index = self + return o + end, + + __tostring = function(self) + local DUMMY = 0 + return string.pack(">I2I2I2I2", self.type, DUMMY, 0, 0) + end, + + } + + }, + + -- The common request and response header + Header = { + size = 8, + new = function(self, type, value, length) + local o = { + version = 0, + type = type, + value= value or 0, + length = length or 0 + } + setmetatable(o, self) + self.__index = self + return o + end, + + parse = function(data) + local header = RPCAP.Header:new() + header.version, header.type, header.value, header.length = string.unpack(">BBI2I4", data) + return header + end, + + __tostring = function(self) + return string.pack(">BBI2I4", self.version, self.type, self.value, self.length) + end, + + }, + + -- The implemented request types are kept here + Request = { + + Authentication = { + + new = function(self, data) + local o = { + header = RPCAP.Header:new(RPCAP.MessageType.AUTH_REQUEST, nil, #data), + data = data, + } + setmetatable(o, self) + self.__index = self + return o + end, + + __tostring = function(self) + return tostring(self.header) .. tostring(self.data) + end, + + }, + + FindAllInterfaces = { + + new = function(self) + local o = { + header = RPCAP.Header:new(RPCAP.MessageType.FIND_ALL_INTERFACES) + } + setmetatable(o, self) + self.__index = self + return o + end, + + __tostring = function(self) + return tostring(self.header) + end, + + + } + + }, + + -- Parsers for responses are kept here + Response = { + + Authentication = { + new = function(self) + local o = { } + setmetatable(o, self) + self.__index = self + return o + end, + + parse = function(data) + local resp = RPCAP.Response.Authentication:new() + local pos = RPCAP.Header.size + 1 + resp.header = RPCAP.Header.parse(data) + return resp + end + }, + + Error = { + new = function(self) + local o = { } + setmetatable(o, self) + self.__index = self + return o + end, + + parse = function(data) + local err = RPCAP.Response.Error:new() + local pos = RPCAP.Header.size + 1 + err.header = RPCAP.Header.parse(data) + err.error, pos = string.unpack("c" .. err.header.length, data, pos) + return err + end + + }, + + FindAllInterfaces = { + new = function(self) + local o = { } + setmetatable(o, self) + self.__index = self + return o + end, + + parse = function(data) + + -- Each address is made up of 4 128 byte fields, this function + -- parses these fields and return the response, if it + -- understands it. Otherwise it simply increases the pos by the + -- correct offset, to get us to the next field. + local function parseField(data, pos) + local offset = pos + local family, port + family, port, pos = string.unpack(">I2I2", data, pos) + + if ( family == 0x0017 ) then + -- not sure why... + pos = pos + 4 + + local ipv6 = ipOps.str_to_ip(data:sub(pos, pos + 16 - 1)) + return offset + 128, ipv6 + elseif ( family == 0x0002 ) then + local ipv4 = ipOps.str_to_ip(data:sub(pos, pos + 4 - 1)) + return offset + 128, ipv4 + end + + return offset + 128, nil + end + + -- Parses one of X addresses returned for an interface + local function parseAddress(data, pos) + local fields = {"ip", "netmask", "bcast", "p2p"} + local addr = {} + + for _, f in ipairs(fields) do + pos, addr[f] = parseField(data, pos) + end + + return pos, addr + end + + local resp = RPCAP.Response.FindAllInterfaces:new() + local pos = RPCAP.Header.size + 1 + resp.header = RPCAP.Header.parse(data) + resp.ifaces = {} + + for i=1, resp.header.value do + local name_len, desc_len, iface_flags, addr_count, dummy + name_len, desc_len, iface_flags, addr_count, dummy, pos = string.unpack(">I2I2I4I2I2", data, pos) + + local name, desc + name, desc, pos = string.unpack("c" .. name_len .. "c" .. desc_len, data, pos) + + local addrs = {} + for j=1, addr_count do + local addr + pos, addr = parseAddress(data, pos) + local cidr + if ( addr.netmask ) then + table.insert(addrs, addr.ip .. ipOps.subnet_to_cidr(addr.netmask)) + else + table.insert(addrs, addr.ip) + end + end + table.insert(resp.ifaces, { name = name, desc = desc, addrs = addrs }) + end + return resp + end, + } + + + } + +} + +-- Maps packet types to classes +RPCAP.TypeToClass = { + [1] = RPCAP.Response.Error, + [130] = RPCAP.Response.FindAllInterfaces, + [136] = RPCAP.Response.Authentication, +} + + +-- The communication class +Comm = { + + -- Creates a new instance of the Comm class + -- @param host table + -- @param port table + -- @return o instance of Comm + new = function(self, host, port, socket) + local o = { host = host, port = port, socket = socket or nmap.new_socket() } + setmetatable(o, self) + self.__index = self + return o + end, + + -- Connects the socket to the server + connect = function(self) + return self.socket:connect(self.host, self.port) + end, + + -- Sends an instance of the request class to the server + -- @param req class instance + -- @return status true on success, false on failure + -- @return err string containing error message if status is false + send = function(self, req) + return self.socket:send(req) + end, + + -- receives a packet and attempts to parse it if it has a supported parser + -- in RPCAP.TypeToClass + -- @return status true on success, false on failure + -- @return resp instance of a Response class or + -- err string containing the error message + recv = function(self) + local status, hdr_data = self.socket:receive_buf(match.numbytes(RPCAP.Header.size), true) + if ( not(status) ) then + return status, hdr_data + end + + local header = RPCAP.Header.parse(hdr_data) + if ( not(header) ) then + return false, "rpcap: Failed to parse header" + end + + local status, data = self.socket:receive_buf(match.numbytes(header.length), true) + if ( not(status) ) then + return false, "rpcap: Failed to read packet data" + end + + if ( RPCAP.TypeToClass[header.type] ) then + local resp = RPCAP.TypeToClass[header.type].parse(hdr_data .. data) + if ( resp ) then + return true, resp + end + end + + return false, "Failed to receive response from server" + end, + + -- Sends and request and receives the response + -- @param req the instance of the Request class to send + -- @return status true on success, false on failure + -- @return resp instance of a Response class or + -- err string containing the error message + exch = function(self, req) + local status, data = self:send(tostring(req)) + if ( not(status) ) then + return status, data + end + return self:recv() + end, + + -- closes the socket + close = function(self) + return self.socket:close() + end, + +} + + +Helper = { + + -- Creates a new instance of the Helper class + -- @param host table + -- @param port table + -- @return o instance of Helper + new = function(self, host, port) + local o = { + host = host, + port = port, + comm = Comm:new(host, port) + } + setmetatable(o, self) + self.__index = self + return o + end, + + -- Connects to the server + connect = function(self) + return self.comm:connect(self.host, self.port) + end, + + -- Authenticates to the service, in case no username or password is given + -- NULL authentication is assumed. + -- @param username [optional] + -- @param password [optional] + -- @return status true on success, false on failure + -- @return err string containing error message on failure + login = function(self, username, password) + local auth + + if ( username and password ) then + auth = RPCAP.Authentication.PWD:new(username, password) + else + auth = RPCAP.Authentication.NULL:new() + end + + local req = RPCAP.Request.Authentication:new(tostring(auth)) + local status, resp = self.comm:exch(req) + + if ( not(status) ) then + return false, resp + end + + if ( status and resp.error ) then + return false, resp.error + end + return true + end, + + -- Requests a list of all interfaces + -- @return table containing interfaces and addresses + findAllInterfaces = function(self) + local req = RPCAP.Request.FindAllInterfaces:new() + local status, resp = self.comm:exch(req) + + if ( not(status) ) then + return false, resp + end + + local results = {} + for _, iface in ipairs(resp.ifaces) do + local entry = {} + entry.name = iface.name + table.insert(entry, iface.desc) + table.insert(entry, { name = "Addresses", iface.addrs }) + table.insert(results, entry) + end + return true, results + end, + + -- Closes the connection to the server + close = function(self) + return self.comm:close() + end, +} + +return _ENV; -- cgit v1.2.3