summaryrefslogtreecommitdiffstats
path: root/nselib/giop.lua
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--nselib/giop.lua633
1 files changed, 633 insertions, 0 deletions
diff --git a/nselib/giop.lua b/nselib/giop.lua
new file mode 100644
index 0000000..877f691
--- /dev/null
+++ b/nselib/giop.lua
@@ -0,0 +1,633 @@
+---
+-- GIOP Library supporting a very limited subset of operations
+--
+-- Summary
+-- -------
+-- The library currently provides functionality to connect and query the
+-- CORBA naming service for a list of available objects.
+--
+--
+-- Overview
+-- --------
+-- The library contains the following classes:
+--
+-- o Packet.*
+-- - The Packet classes contain specific packets and function to serialize
+-- them to strings that can be sent over the wire. Each class may also
+-- contain a function to parse the servers response.
+--
+-- o Comm
+-- - Implements a number of functions to handle communication over the
+-- the socket.
+--
+-- o Helper
+-- - A helper class that provides easy access to the rest of the library
+--
+--
+-- Example
+-- -------
+-- The following sample code illustrates how scripts can use the Helper class
+-- to interface the library:
+--
+-- <code>
+-- helper = giop.Helper:new(host, port)
+-- status, err = helper:Connect()
+-- status, ctx = helper:GetNamingContext()
+-- status, objs = helper:ListObjects(ctx)
+-- </code>
+--
+-- Additional information
+-- ----------------------
+-- The implementation is based on packet dumps and the decoding Wireshark
+-- provides.
+--
+-- This implementation is tested and known to work against:
+-- x Sun's JAVA orbd
+--
+-- @copyright Same as Nmap--See https://nmap.org/book/man-legal.html
+-- @author Patrik Karlsson <patrik@cqure.net>
+--
+
+--
+-- Version 0.1
+-- Created 08/07/2010 - v0.1 - created by Patrik Karlsson <patrik@cqure.net>
+--
+
+local match = require "match"
+local nmap = require "nmap"
+local stdnse = require "stdnse"
+local string = require "string"
+local table = require "table"
+_ENV = stdnse.module("giop", stdnse.seeall)
+
+-- A bunch of constants
+Constants = {
+
+ SyncScope = {
+ WITH_TARGET = 3,
+ },
+
+ ServiceContext = {
+ CODESETS = 1,
+ SENDING_CONTEXT_RUNTIME = 6,
+ NEO_FIRST_SERVICE_CONTEXT = 1313165056,
+ },
+
+ ReplyStatus = {
+ SYSTEM_EXCEPTION = 2,
+ },
+
+ VERSION_1_0 = 1,
+ VERSION_1_2 = 0x0201,
+
+ NAMESERVICE = "NameService\0",
+}
+
+
+Packet = {}
+
+Packet.GIOP = {
+
+ magic = "GIOP",
+ version = 0x0001,
+ byte_order = 0,
+
+ --- Creates a Packet.GIOP
+ --
+ -- @param msgtype number containing the message type
+ -- @param data string containing the message data
+ -- @return obj a new Packet.GIOP instance
+ new = function( self, msgtype, data )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.type = msgtype
+ o.data = data
+ o.size = data and #data or 0
+ return o
+ end,
+
+ --- Converts the class to a string suitable to send over the socket
+ --
+ -- @return string containing the instance data
+ __tostring = function( self )
+ return self.magic .. string.pack("<I2BB>I4", self.version, self.byte_order, self.type, self.size) .. self.data
+ end,
+
+ --- Sets the packet version
+ --
+ -- @param version number containing the version to use
+ setVersion = function( self, version ) self.version = version end,
+
+ --- Receives the packet over the socket
+ --
+ -- @param socket containing the already connected socket
+ -- @return status true on success, false on failure
+ -- @return err containing the error message if status is false
+ recv = function( self, socket )
+ local status, data = socket:receive_buf(match.numbytes(12), true)
+ local pos
+
+ if ( not(status) ) then return false, "Failed to read Packet.GIOP" end
+
+ self.magic, self.version, self.byte_order,
+ self.type, pos = string.unpack("<c4I2BB", data )
+
+ self.size, pos = string.unpack( ( self.byte_order == 0 and ">" or "<") .. "I4", data, pos )
+
+ status, data = socket:receive_buf(match.numbytes(self.size), true)
+ if ( not(status) ) then return false, "Failed to read Packet.GIOP" end
+
+ self.data = data
+ return true
+ end,
+}
+
+ServiceContext = {
+
+ --- Creates a ServiceContext
+ --
+ -- @param id number containing the context id
+ -- @param data the service context data
+ -- @param pad [optional] number used to pad after the service context
+ -- @return obj a new ServiceContext instance
+ new = function( self, id, data, pad )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.id = id
+ o.data = data or ""
+ o.pad = pad
+ return o
+ end,
+
+ --- Converts the class to a string suitable to send over the socket
+ --
+ -- @return string containing the instance data
+ __tostring = function( self )
+ if ( self.pad ) then
+ return string.pack(">I4s4I2", self.id, self.data, self.pad)
+ else
+ return string.pack(">I4s4", self.id, self.data)
+ end
+ end,
+}
+
+--- Creates a SendingContextRuntime
+SendingContextRuntime =
+{
+ --- Creates a SendingContextRuntime
+ --
+ -- @param lhost string containing the source ip address
+ -- @return obj a new SendingContextRuntime instance
+ new = function(self, lhost )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ lhost = lhost .. "\0"
+ o.data = stdnse.fromhex(
+ [[
+ 000000000000002849444c3a6f6d672e6f72672f53656e64696e67436f6e746
+ 578742f436f6465426173653a312e300000000001000000000000006e000102
+ 00
+ ]])
+ .. string.pack(">s4", lhost)
+ .. stdnse.fromhex(
+ [[
+ 00ec5100000019afabcb000000000249765d6900000008000000000000000014
+ 0000000000000200000001000000200000000000010001000000020501000100
+ 01002000010109000000010001010000000026000000020002
+ ]] )
+ return o
+ end,
+
+ --- Converts the class to a string suitable to send over the socket
+ --
+ -- @return string containing the instance data
+ __tostring = function( self ) return self.data end,
+}
+
+Packet.GIOP.reply = {
+
+ --- Creates a new Packet.GIOP.reply instance
+ --
+ -- @return obj a new Packet.GIOP.get instance
+ new = function( self )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ self.sc = {}
+ self.GIOP = Packet.GIOP:new()
+ return o
+ end,
+
+ --- Receives a Packet.GIOP.reply from the socket
+ --
+ -- @param socket already connected to the server
+ -- @return status true on success, false on failure
+ -- @return err error message if status is false
+ recv = function( self, socket )
+ local status, err = self.GIOP:recv( socket )
+ local pos, tmp
+ local bo = ( self.GIOP.byte_order == 0 and ">" or "<")
+
+ if( not(status) ) then return false, err end
+
+ if ( self.GIOP.version == Constants.VERSION_1_2 ) then
+ self.request_id, self.reply_status, pos = string.unpack(bo .. "I4I4", self.GIOP.data, pos )
+ tmp, pos = string.unpack( bo .. "I4", self.GIOP.data, pos )
+ elseif ( self.GIOP.version == Constants.VERSION_1_0 ) then
+ tmp, pos = string.unpack( bo .. "I4", self.GIOP.data )
+ else
+ local err = ("Unknown GIOP version: %s"):format(self.GIOP.version)
+ stdnse.debug2("recv: %s", err)
+ return false, err
+ end
+
+ for i=1, tmp do
+ local ctx_id, ctx_data
+ ctx_id, ctx_data, pos = string.unpack( bo .. "I4s4", self.GIOP.data, pos )
+ if ( i ~= tmp ) then pos = pos + 2 end
+ table.insert( self.sc, ServiceContext:new( ctx_id, ctx_data ) )
+ end
+
+ if ( self.GIOP.version == Constants.VERSION_1_0 ) then
+ self.request_id, self.reply_status, self.stub_data, pos = string.unpack( bo .. "I4I4c" .. ( #self.GIOP.data - pos - 8 ), self.GIOP.data, pos )
+ elseif ( pos < #self.GIOP.data ) then
+ -- TODO: Is this off-by-one? Unpacks (#data - pos) bytes, but there are (#data - pos + 1) bytes available.
+ -- If so, can replace with: self.data = self.GIOP.data:sub(pos)
+ self.data, pos = string.unpack("c" .. (#self.GIOP.data - pos), self.GIOP.data, pos )
+ end
+
+ return true
+ end,
+
+}
+
+Packet.GIOP.get = {
+
+ resp_expected = 1,
+ key_length = 4,
+ princ_len = 0,
+
+ --- Creates a new Packet.GIOP._is_a instance
+ --
+ -- @param id the packet identifier
+ -- @param key number containing the object key
+ -- @param data string containing the stub data
+ -- @return obj a new Packet.GIOP.get instance
+ new = function( self, id, key, data )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.op = "get\0"
+ o.id = id
+ o.key = key
+ o.data = data
+ o.sc = {}
+ return o
+ end,
+
+ --- Creates and adds a service context to the packet
+ --
+ -- @param id number containing the context id
+ -- @param data the service context data
+ -- @param pad [optional] number used to pad after the service context
+ addServiceContext = function( self, id, data, pad ) table.insert( self.sc, ServiceContext:new(id, data, pad) ) end,
+
+ --- Converts the class to a string suitable to send over the socket
+ --
+ -- @return string containing the packet data
+ __tostring = function( self )
+ local data = { string.pack(">I4", #self.sc) }
+
+ for i=1, #self.sc do
+ data[#data+1] = tostring( self.sc[i])
+ end
+
+ data[#data+1] = string.pack( ">I4BxxxI4I4s4I4", self.id, self.resp_expected,
+ self.key_length, self.key, self.op, self.princ_len)
+ data[#data+1] = self.data
+
+ return tostring( Packet.GIOP:new( 0, table.concat(data) ) )
+ end,
+
+}
+
+Packet.GIOP._is_a =
+{
+
+ --- Creates a new Packet.GIOP._is_a instance
+ --
+ -- @param id the packet identifier
+ -- @param flags [optional]
+ -- @param keyaddr string containing the keyaddr data
+ -- @return obj a new Packet.GIOP._is_a instance
+ new = function( self, id, flags, key_addr )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.op = "_is_a\0"
+ o.id = id
+ o.target_addr = 0 -- KeyAddr
+ o.key_addr = key_addr
+ o.flags = flags or Constants.SyncScope.WITH_TARGET -- SyncScope WITH_TARGET
+ o.sc = {}
+ return o
+ end,
+
+ --- Creates and adds a service context to the packet
+ --
+ -- @param id number containing the context id
+ -- @param data the service context data
+ -- @param pad [optional] number used to pad after the service context
+ addServiceContext = function( self, id, data, pad ) table.insert( self.sc, ServiceContext:new(id, data, pad) ) end,
+
+ --- Converts the class to a string suitable to send over the socket
+ --
+ -- @return string containing the packet data
+ __tostring = function( self )
+ local TYPE_ID = "IDL:omg.org/CosNaming/NamingContextExt:1.0\0"
+ local UNKNOWN, UNKNOWN2, UNKNOWN3 = 2, 1, 0
+ local data = {
+ string.pack(">I4BxxxI2I2s4s4I2I4", self.id, self.flags, self.target_addr,
+ UNKNOWN, self.key_addr, self.op, UNKNOWN2, #self.sc )
+ }
+
+ for i=1, #self.sc do
+ data[#data+1] = tostring( self.sc[i])
+ end
+
+ data[#data+1] = string.pack(">s4", TYPE_ID)
+
+ local packet = Packet.GIOP:new( 0, table.concat(data))
+ packet:setVersion( Constants.VERSION_1_2 )
+
+ return tostring( packet )
+ end,
+
+}
+
+Packet.GIOP.list =
+{
+ --- Creates a new Packet.GIOP.list instance
+ --
+ -- @param id the packet identifier
+ -- @param flags [optional]
+ -- @param keyaddr string containing the keyaddr data
+ -- @param how_many string containing the value to retrieve
+ -- @return obj a new Packet.GIOP.list instance
+ new = function( self, id, flags, keyaddr, how_many )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.op = "list\0"
+ o.id = id
+ o.flags = flags or Constants.SyncScope.WITH_TARGET
+ o.target_addr = 0 -- KeyAddr
+ o.key_addr = keyaddr
+ o.how_many = how_many or 1000
+ o.sc = {}
+ return o
+ end,
+
+ --- Creates and adds a service context to the packet
+ --
+ -- @param id number containing the context id
+ -- @param data the service context data
+ -- @param pad [optional] number used to pad after the service context
+ addServiceContext = function( self, id, data, pad ) table.insert( self.sc, ServiceContext:new(id, data, pad) ) end,
+
+ --- Converts the class to a string suitable to send over the socket
+ --
+ -- @return string containing the packet data
+ __tostring = function( self )
+ local UNKNOWN, UNKNOWN2, UNKNOWN3 = 2, 1, 6
+
+ local data = {
+ string.pack(">I4BxxxI2I2s4s4xxBI4", self.id, self.flags,
+ self.target_addr, UNKNOWN, self.key_addr,
+ self.op, UNKNOWN2, #self.sc )
+ }
+
+ for i=1, #self.sc do
+ data[#data+1] = tostring( self.sc[i])
+ end
+
+ data[#data+1] = string.pack(">I4I4", UNKNOWN3, self.how_many )
+ local packet = Packet.GIOP:new( 0, table.concat(data))
+ packet:setVersion( Constants.VERSION_1_2 )
+
+ return tostring( packet )
+ end,
+
+}
+
+-- Static class containing various message decoders
+MessageDecoder = {
+
+ --- Decodes a get response
+ --
+ -- @param packet the GIOP packet as received by the comm
+ -- <code>exchGIOPPacket</code> function
+ -- @return status true on success, false on failure
+ -- @return table containing <code>ip</code> and <code>ctx</code>
+ ["get"] = function( packet )
+ local bo = ( packet.GIOP.byte_order == 0 and ">" or "<")
+ local len, pos = string.unpack(bo .. "I4", packet.stub_data)
+ local ip, ctx
+
+ pos = pos + len + 16
+
+ ip, pos = string.unpack(bo .. "s4", packet.stub_data, pos)
+
+ pos = pos + 3
+ ctx, pos = string.unpack(bo .. "s4", packet.stub_data, pos)
+
+ return true, { ip = ip, ctx = ctx}
+ end,
+
+ --- Decodes a _is_a response (not implemented)
+ --
+ -- @param packet the GIOP packet as received by the comm
+ -- <code>exchGIOPPacket</code> function
+ -- @return status, always true
+ ["_is_a"] = function( packet )
+ return true
+ end,
+
+ --- Decodes a list response
+ --
+ -- @param packet the GIOP packet as received by the comm
+ -- <code>exchGIOPPacket</code> function
+ -- @return status true on success, false on failure
+ -- @return table containing <code>id</code>, <code>kind</code> and
+ -- <code>enum</code> or error message if status is false
+ ["list"] = function( packet )
+ local bo = ( packet.GIOP.byte_order == 0 and ">" or "<")
+ local seq_len, pos = string.unpack( bo .. "I4", packet.data, 7)
+ local objs = {}
+
+ for i=1, seq_len do
+ local seq_len_of_bind_name
+ local len, name
+ local obj = {}
+
+ seq_len_of_bind_name, pos = string.unpack( bo .. "I4", packet.data, pos)
+ if ( seq_len_of_bind_name ~= 1 ) then return false, "Sequence length of Binding_binding_name was greater than 1" end
+
+ len, pos = string.unpack( bo .. "I4", packet.data, pos )
+ obj.id, pos = string.unpack( "c" .. len - 1, packet.data, pos )
+
+ -- Account for terminating zero
+ pos = pos + 1
+
+ -- Account for undecoded data
+ pos = pos + ( ( len % 4 > 0 ) and ( 4 - ( len % 4 ) ) or 0 )
+ pos = pos + 3
+
+ obj.kind, pos = string.unpack("B", packet.data, pos)
+
+ -- Account for undecoded data
+ pos = pos + 4
+ obj.enum, pos = string.unpack( bo .. "I4", packet.data, pos )
+ table.insert( objs, obj )
+ end
+
+ return true, objs
+ end,
+
+}
+
+Comm = {
+
+ --- Creates a new Comm instance
+ --
+ -- @param socket containing a buffered socket connected to the server
+ -- @return a new Comm instance
+ new = function(self, socket)
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.socket = socket
+ return o
+ end,
+
+ --- Sends and receives a GIOP packet
+ --
+ -- @param packet containing a Packet.* object, the object must
+ -- implement the __tostring meta method
+ -- @return status true on success, false on failure
+ -- @return data decoder specific data, see the corresponding
+ -- MessageDecoder for more information.
+ exchGIOPPacket = function( self, packet )
+ local status, err = self.socket:send( tostring(packet) )
+ local op = packet.op:sub(1, -2)
+ local data
+
+ if( not(status) ) then return false, err end
+ packet = Packet.GIOP.reply:new()
+
+ status, err = packet:recv( self.socket )
+ if( not(status) ) then return false, err end
+
+ if ( MessageDecoder[op] ) then
+ status, data = MessageDecoder[op]( packet )
+ else
+ return false, ("No message decoder for op (%s)"):format(op)
+ end
+
+ return status, data
+ end,
+
+}
+
+
+Helper = {
+
+ new = function(self, host, port )
+ local o = {}
+ setmetatable(o, self)
+ self.__index = self
+ o.host = host
+ o.port = port
+ o.socket = nmap.new_socket()
+ return o
+ end,
+
+ GetNamingContext = function( self )
+ local packet = Packet.GIOP.get:new( 5, 0x494e4954, string.pack(">s4", Constants.NAMESERVICE) )
+ local status, ctx, lhost, pos, len, bo, tmp
+
+ packet:addServiceContext( 17, "\0\x02", 0)
+ packet:addServiceContext( Constants.ServiceContext.NEO_FIRST_SERVICE_CONTEXT, "\0\x14", 0)
+ packet:addServiceContext( Constants.ServiceContext.SENDING_CONTEXT_RUNTIME, tostring(SendingContextRuntime:new( self.lhost )), 0 )
+
+ status, packet = self.comm:exchGIOPPacket( packet )
+ if( not(status) ) then return status, packet end
+
+ return true, packet.ctx
+ end,
+
+ ListObjects = function( self, keyaddr )
+ -- SyncScope WITH_TARGET
+ local packet = Packet.GIOP._is_a:new( 5, Constants.SyncScope.WITH_TARGET, keyaddr )
+ local status, err, lhost
+
+ status, err = self:Reconnect()
+ if( not(status) ) then return false, err end
+
+ packet:addServiceContext( 17, "\0\2", 0x000d)
+ packet:addServiceContext( Constants.ServiceContext.CODESETS, "\0\0\0\0\0\1\0\1\0\1\1\9" )
+ packet:addServiceContext( Constants.ServiceContext.NEO_FIRST_SERVICE_CONTEXT, "\0\x14", 0x5d69)
+ packet:addServiceContext( Constants.ServiceContext.SENDING_CONTEXT_RUNTIME, tostring(SendingContextRuntime:new( self.lhost )), 0 )
+
+ status, packet = self.comm:exchGIOPPacket( packet )
+ if( not(status) ) then return status, packet end
+
+ packet = Packet.GIOP.list:new( Constants.ServiceContext.SENDING_CONTEXT_RUNTIME, Constants.SyncScope.WITH_TARGET, keyaddr, 1000 )
+ packet:addServiceContext( 17, "\0\2", 0x000d)
+ packet:addServiceContext( Constants.ServiceContext.CODESETS, "\0\0\0\0\0\1\0\1\0\1\1\9" )
+ packet:addServiceContext( Constants.ServiceContext.NEO_FIRST_SERVICE_CONTEXT, "\0\x14", 0x9c9b)
+
+ status, packet = self.comm:exchGIOPPacket( packet )
+ if( not(status) ) then return status, packet end
+
+ return true, packet
+ end,
+
+ --- Connects and performs protocol negotiation with the Oracle server
+ --
+ -- @return true on success, false on failure
+ -- @return err containing error message when status is false
+ Connect = function( self )
+ self.socket:set_timeout(10000)
+ local status, data = self.socket:connect( self.host.ip, self.port.number, "tcp" )
+ if( not(status) ) then return status, data end
+ self.comm = Comm:new( self.socket )
+
+ status, self.lhost = self.socket:get_info()
+ if ( not(status) ) then
+ self.socket:close()
+ return false, "Error failed to get socket information"
+ end
+
+ return true
+ end,
+
+ Close = function( self )
+ return self.socket:close()
+ end,
+
+ Reconnect = function( self )
+ local status = self:Close()
+ if( not(status) ) then return false, "Failed to close socket" end
+
+ status = self:Connect()
+ if( not(status) ) then return false, "Failed to re-connect socket" end
+
+ return true
+ end,
+}
+
+return _ENV;