rapid7 / mettle

This is an implementation of a native-code Meterpreter, designed for portability, embeddability, and low resource utilization.
421 stars 122 forks source link

loadlib doesn't appear to return an extensions supported command IDs #262

Closed sfewer-r7 closed 2 months ago

sfewer-r7 commented 3 months ago

I am experimenting with the sniffer extension on an embedded ARM based Linux device. I can load the extension (Thanks to #261), but the sniffer commands fail, for example:

meterpreter > sniffer_interfaces
[-] sniffer_interfaces: Operation failed: The command is not supported by this Meterpreter type (armle/linux)

On the framework side in Rex::Post::Meterpreter::PacketDispatcher#send_packet_wait_response we have this:

  def send_packet_wait_response(packet, timeout)
    if packet.type == PACKET_TYPE_REQUEST && commands.present?
      # XXX: Remove this condition once the payloads gem has had another major version bump from 2.x to 3.x and
      # rapid7/metasploit-payloads#451 has been landed to correct the `enumextcmd` behavior on Windows. Until then, skip
      # proactive validation of Windows core commands. This is not the only instance of this workaround.
      windows_core = base_platform == 'windows' && (packet.method - (packet.method % COMMAND_ID_RANGE)) == Rex::Post::Meterpreter::ClientCore.extension_id

      unless windows_core || commands.include?(packet.method)
        if (ext_name = Rex::Post::Meterpreter::ExtensionMapper.get_extension_name(packet.method))
          unless ext.aliases.include?(ext_name)
            raise RequestError.new(packet.method, "The command requires the #{ext_name} extension to be loaded")
          end
        end
        raise RequestError.new(packet.method, "The command is not supported by this Meterpreter type (#{session_type})")
      end
    end

An exception ("The command is not supported by this Meterpreter type") is thrown if the request packets method ID is not present in the array of know commands, via commands.include?(packet.method)

I looked at the PHP Meterpreter (here) and Java Meterpreter (here), and their loadlib implementations both return a series of TLV_TYPE_UINT values in the response packet from a loadlib request. I cannot see where this happens for Mettle based extensions. It seems like it does not happen. The result is no new command IDs are added framework side after the extension is loaded, so you cannot then issue any extension commands successfully.

On the framework side, in Rex::Post::Meterpreter::ClientCore#use we have the below, which loads the extension and then registers the commands returned by load_library:

      # Load the extension DLL
      commands = load_library(
          'LibraryFilePath'  => path,
          'LibraryFileImage' => image,
          'UploadLibrary'    => true,
          'Extension'        => true,
          'SaveToDisk'       => opts['LoadFromDisk'])
    end

    # wire the commands into the client
    client.add_extension(mod, commands)

In the case of loading a Mettle extension, no command IDs are returned from the call to load_library.

This was only observed with the sniffer extension, The stdapi is not loaded in the same way as other extensions, so its command IDs are retrieved via a call to Rex::Post::Meterpreter::ClientCore#get_loaded_extension_commands, which correctly returns the expected results. Perhaps, rather than modifying the C side of loadlib, the Ruby side, during load_library, can leverage get_loaded_extension_commands as this function issues a COMMAND_ID_CORE_ENUMEXTCMD request which I think Mettle supports

smcintyre-r7 commented 2 months ago

I started looking into this and I think it's related to a larger problem of core_loadlib returning before the extension has been fully initialized which doesn't seem great. Because of how stdlib is a special case that's always built in, this only affects the sniffer extension as far as I know. It seems to me like we should update core_loadlib to not return a status value until the extension's main method has been called which is where the commands are registered. This would ensure that if the main method failed for some reason, a successful result is not returned to Metasploit.

adfoster-r7 commented 2 months ago

Another report of this over here: https://github.com/rapid7/metasploit-framework/issues/19320 :+1: