# -*- coding: binary -*- require 'msf/core/payload/uuid' module Rex module Payloads module Meterpreter module UriChecksum # # Define 8-bit checksums for matching URLs # These are based on charset frequency # URI_CHECKSUM_INITW = 92 # Windows URI_CHECKSUM_INITN = 92 # Native (same as Windows) URI_CHECKSUM_INITP = 80 # Python URI_CHECKSUM_INITJ = 88 # Java URI_CHECKSUM_CONN = 98 # Existing session URI_CHECKSUM_INIT_CONN = 95 # New stageless session # Mapping between checksums and modes URI_CHECKSUM_MODES = Hash[ URI_CHECKSUM_INITN, :init_native, URI_CHECKSUM_INITP, :init_python, URI_CHECKSUM_INITJ, :init_java, URI_CHECKSUM_INIT_CONN, :init_connect, URI_CHECKSUM_CONN, :connect ] URI_CHECKSUM_MIN_LEN = 5 # Limit how long :connect URLs are to stay within 256 bytes when including # the hostname, colon, port, and leading slash URI_CHECKSUM_CONN_MAX_LEN = 128 URI_CHECKSUM_UUID_MIN_LEN = URI_CHECKSUM_MIN_LEN + Msf::Payload::UUID::UriLength # Map "random" URIs to static strings, allowing us to randomize # the URI sent in the first request. # # @param uri [String] The URI string from the HTTP request # @return [Hash] The attributes extracted from the URI def process_uri_resource(uri) # Ignore non-base64url characters in the URL uri_bare = uri.gsub(/[^a-zA-Z0-9_\-]/, '') # Figure out the mode based on the checksum uri_csum = Rex::Text.checksum8(uri_bare) # Extract the UUID if the URI is long enough uri_uuid = nil if uri_bare.length >= URI_CHECKSUM_UUID_MIN_LEN uri_uuid = Msf::Payload::UUID.new(uri: uri_bare) end uri_mode = URI_CHECKSUM_MODES[uri_csum] # Return a hash of URI attributes { uri: uri_bare, sum: uri_csum, uuid: uri_uuid, mode: uri_mode } end # Create a URI that matches the specified checksum and payload uuid # # @param sum [Fixnum] A checksum mode value to use for the generated url # @param uuid [Msf::Payload::UUID] A valid UUID object # @param len [Fixnum] An optional URI length value, including the leading slash # @return [String] The URI string for connections def generate_uri_uuid(sum, uuid, len=nil) curl_uri_len = URI_CHECKSUM_UUID_MIN_LEN+rand(URI_CHECKSUM_CONN_MAX_LEN-URI_CHECKSUM_UUID_MIN_LEN) curl_prefix = uuid.to_uri if len # Subtract a byte to take into account the leading / curl_uri_len = len - 1 end if curl_uri_len < URI_CHECKSUM_UUID_MIN_LEN raise ArgumentError, "Length must be #{URI_CHECKSUM_UUID_MIN_LEN+1} bytes or greater" end # Pad out the URI and make the checksum match the specified sum "/" + generate_uri_checksum(sum, curl_uri_len, curl_prefix) end # Create an arbitrary length URI that matches a given checksum # # @param sum [Fixnum] The checksum value that the generated URI should match # @param len [Fixnum] The length of the URI to generate # @param prefix [String] The optional prefix to use to build the URI # @return [String] The URI string that checksums to the given value def generate_uri_checksum(sum, len=5, prefix="") # Lengths shorter than 4 bytes are unable to match all possible checksums # Lengths of exactly 4 are relatively slow to find for high checksum values # Lengths of 5 or more bytes find a matching checksum fairly quickly (~80ms) if len < URI_CHECKSUM_MIN_LEN raise ArgumentError, "Length must be #{URI_CHECKSUM_MIN_LEN} bytes or greater" end gen_len = len-prefix.length if gen_len < URI_CHECKSUM_MIN_LEN raise ArgumentError, "Prefix must be at least {URI_CHECKSUM_MIN_LEN} bytes smaller than total length" end # Brute force a matching checksum for shorter URIs if gen_len < 40 loop do uri = prefix + Rex::Text.rand_text_base64url(gen_len) return uri if Rex::Text.checksum8(uri) == sum end end # The rand_text_base64url() method becomes a bottleneck at around 40 bytes # Calculating a static prefix flattens out the average runtime for longer URIs prefix << Rex::Text.rand_text_base64url(gen_len-20) loop do uri = prefix + Rex::Text.rand_text_base64url(20) return uri if Rex::Text.checksum8(uri) == sum end end # Return the numerical checksum for a given mode symbol # # @param mode [Symbol] The mode symbol to lookup (:connect, :init_native, :init_python, :init_java) # @return [Fixnum] The URI checksum value corresponding with the mode def uri_checksum_lookup(mode) sum = URI_CHECKSUM_MODES.keys.select{|ksum| URI_CHECKSUM_MODES[ksum] == mode}.first unless sum raise ArgumentError, "Unknown checksum mode: #{mode}" end sum end end end end end