# -*- coding: binary -*-

module Msf
  class Exploit
    class Remote
      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::Exploit::Remote::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 name [String] 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::Exploit::Remote::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::Exploit::Remote::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 name [Hash] 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::Exploit::Remote::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::Exploit::Remote::Java::Rmi::Client::Jmx::OBJECT_NAME_UID, # serialVersionUID
                    flags: 3
                  )

                  data_binary = builder.new_array(
                    name: '[B',
                    serial: Msf::Exploit::Remote::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::Exploit::Remote::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::Exploit::Remote::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::Exploit::Remote::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
  end
end
