# -*- coding: binary -*-
require 'rex/java/serialization'
require 'rex/text'

module Msf
  class Exploit
    class Remote
      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 [Integer] 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
            # @return [Integer] 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 [Integer, 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 [Integer, 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::Exploit::Remote::Java::Rmi::Client::Jmx:Server::Parser#parse_jmx_new_client_endpoint
            # @see Msf::Exploit::Remote::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

            # Register ports and services for autofilter support
            #
            def register_common_rmi_ports_and_services
              register_autofilter_ports([
                999, 1090, 1098, 1099, 1100, 1101, 1102, 1103, 1129, 1030, 1035, 1199, 1234, 1440, 3273, 3333, 3900,
                2199, 2809, 5520, 5580, 5521, 5999, 6060, 6789, 6996, 7700, 7800, 7878, 7890, 7801, 8050, 8051, 8085,
                8091, 8205, 8303, 8642, 8701, 8686, 8888, 8889, 8890, 8901, 8902, 8903, 8999, 9001, 9003, 9004, 9005,
                9050, 9090, 9099, 9300, 9500, 9711, 9809, 9810, 9811, 9812, 9813, 9814, 9815, 9875, 9910, 9991, 9999,
                10001, 10162, 10098, 10099, 11001, 11099, 11333, 12000, 13013, 14000, 15000, 15001, 15200, 16000,
                17200, 18980, 20000, 23791, 26256, 31099, 33000, 32913, 37718, 45230, 47001, 47002, 50050, 50500,
                50501, 50502, 50503, 50504
              ])
              register_autofilter_services(%W{ rmi rmid java-rmi rmiregistry })
            end

          end
        end
      end
    end
  end
end
