summaryrefslogtreecommitdiffstats
path: root/nselib/rpcap.lua
diff options
context:
space:
mode:
Diffstat (limited to 'nselib/rpcap.lua')
-rw-r--r--nselib/rpcap.lua435
1 files changed, 435 insertions, 0 deletions
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 <code>Request</code> and classes
+-- handling <code>Response</code>. The communication with the service is
+-- handled by the <code>Comm</code> class, and the main interface for script
+-- writers is kept under the <code>Helper</code> class.
+--
+-- The following code snippet illustrates how to connect to the service and
+-- extract information about network interfaces:
+-- <code>
+-- local helper = rpcap.Helper:new(host, port)
+-- helper:connect()
+-- helper:login()
+-- helper:findAllInterfaces()
+-- helper:close()
+-- </code>
+--
+-- For a more complete example, consult the rpcap-info.nse script.
+--
+-- @author Patrik Karlsson <patrik@cqure.net>
+
+
+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;