Refactor Msf::Payload::UUID, use this in reverse_http

bug/bundler_fix
HD Moore 2015-03-22 16:17:12 -05:00
parent 0d1fe37710
commit 378e867486
3 changed files with 219 additions and 57 deletions

View File

@ -212,14 +212,18 @@ protected
# #
def on_request(cli, req, obj) def on_request(cli, req, obj)
resp = Rex::Proto::Http::Response.new resp = Rex::Proto::Http::Response.new
print_status("#{cli.peerhost}:#{cli.peerport} Request received for #{req.relative_resource}...")
info = process_uri_resource(req.relative_resource) info = process_uri_resource(req.relative_resource)
uuid = info[:uuid] || Msf::Payload::UUID.new
# Configure the UUID architecture and payload if necessary
uuid.arch = obj.arch if uuid.arch.nil?
uuid.platform = obj.platform if uuid.platform.nil?
print_status "#{cli.peerhost}:#{cli.peerport} Request received for #{req.relative_resource}... (UUID:#{uuid.to_s})"
conn_id = nil conn_id = nil
if info[:mode] && info[:mode] != :connect if info[:mode] && info[:mode] != :connect
conn_id = generate_uri_connect_uuid(info[:uuid], obj.arch, obj.platform) conn_id = generate_uri_connect_uuid(uuid)
end end
# Process the requested resource. # Process the requested resource.
@ -255,6 +259,7 @@ protected
:expiration => datastore['SessionExpirationTimeout'].to_i, :expiration => datastore['SessionExpirationTimeout'].to_i,
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i, :comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
:ssl => ssl?, :ssl => ssl?,
:uuid => uuid
}) })
self.pending_connections += 1 self.pending_connections += 1
@ -282,7 +287,8 @@ protected
:url => url, :url => url,
:expiration => datastore['SessionExpirationTimeout'].to_i, :expiration => datastore['SessionExpirationTimeout'].to_i,
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i, :comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
:ssl => ssl? :ssl => ssl?,
:uuid => uuid
}) })
when :init_native when :init_native
@ -318,6 +324,7 @@ protected
:expiration => datastore['SessionExpirationTimeout'].to_i, :expiration => datastore['SessionExpirationTimeout'].to_i,
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i, :comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
:ssl => ssl?, :ssl => ssl?,
:uuid => uuid
}) })
when :connect when :connect
@ -333,6 +340,7 @@ protected
:expiration => datastore['SessionExpirationTimeout'].to_i, :expiration => datastore['SessionExpirationTimeout'].to_i,
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i, :comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
:ssl => ssl?, :ssl => ssl?,
:uuid => uuid
}) })
else else

View File

@ -46,48 +46,25 @@ module Msf
# Figure out the mode based on the checksum # Figure out the mode based on the checksum
uri_csum = Rex::Text.checksum8(uri_bare) uri_csum = Rex::Text.checksum8(uri_bare)
# Extract the UUID if the URI is long enough
uri_uuid = nil uri_uuid = nil
if uri_bare.length >= URI_CHECKSUM_UUID_MIN_LEN if uri_bare.length >= URI_CHECKSUM_UUID_MIN_LEN
uri_uuid = uri_uuid = Msf::Payload::UUID.new(uri: uri_bare)
Msf::Payload::UUID.payload_uuid_parse_raw(
Rex::Text.decode_base64url(
uri_bare[0, Msf::Payload::UUID::UriLength]))
# Verify the uri_uuid fields and unset everything but
# the unique ID itself unless it looks wonky.
if uri_uuid[:timestamp] > (Time.now.utc.to_i + (24*3600*365)) ||
uri_uuid[:timestamp] < (Time.now.utc.to_i - (24*3600*365)) ||
(uri_uuid[:arch].nil? && uri_uuid[:platform].nil?)
uri_uuid = { puid: uri_uuid[:puid] }
end
end end
uri_mode = URI_CHECKSUM_MODES[uri_csum] uri_mode = URI_CHECKSUM_MODES[uri_csum]
# Return a hash of URI attributes to the caller # Return a hash of URI attributes
{ { uri: uri_bare, sum: uri_csum, uuid: uri_uuid, mode: uri_mode }
uri: uri_bare,
sum: uri_csum,
uuid: uri_uuid,
mode: uri_mode
}
end end
# Create a URI that matches the :connect mode with optional UUID, Arch, and Platform # Create a URI that matches the :connect mode with optional UUID, Arch, and Platform
# #
# @param uuid [Hash] An optional hash with the UUID parameters # @param uuid [Msf::Payload::UUID] A valid UUID object
# @param arch [String] An optional architecture name to use if no UUID is provided # @return [String] The URI string for connections
# @param platform [String] An optional platform name to use if no UUID is provided def generate_uri_connect_uuid(uuid)
# @return [String] The URI string that checksums to the given value
def generate_uri_connect_uuid(uuid=nil, arch=nil, platform=nil)
curl_uri_len = URI_CHECKSUM_UUID_MIN_LEN+rand(URI_CHECKSUM_CONN_MAX_LEN-URI_CHECKSUM_UUID_MIN_LEN) curl_uri_len = URI_CHECKSUM_UUID_MIN_LEN+rand(URI_CHECKSUM_CONN_MAX_LEN-URI_CHECKSUM_UUID_MIN_LEN)
curl_prefix = Rex::Text.encode_base64url( curl_prefix = uuid.to_uri
Msf::Payload::UUID.payload_uuid_generate_raw(
uuid: uuid[:puid],
arch: uuid[:arch] || arch,
platform: uuid[:platform] || platform,
timestamp: uuid[:timestamp] ))
# Pad out the URI and make the checksum match :connect # Pad out the URI and make the checksum match :connect
"/" + generate_uri_checksum(URI_CHECKSUM_CONN, curl_uri_len, curl_prefix) "/" + generate_uri_checksum(URI_CHECKSUM_CONN, curl_uri_len, curl_prefix)

View File

@ -1,5 +1,6 @@
# -*- coding => binary -*- # -*- coding => binary -*-
require 'msf/core'
require 'msf/core/module/platform' require 'msf/core/module/platform'
require 'rex/constants' require 'rex/constants'
require 'rex/text' require 'rex/text'
@ -10,6 +11,11 @@ require 'rex/text'
# #
class Msf::Payload::UUID class Msf::Payload::UUID
#
# Constants
#
Architectures = { Architectures = {
0 => nil, 0 => nil,
1 => ARCH_X86, 1 => ARCH_X86,
@ -62,33 +68,57 @@ class Msf::Payload::UUID
23 => 'firefox' 23 => 'firefox'
} }
# The raw length of the UUID structure
RawLength = 16 RawLength = 16
# The base64url-encoded length of the UUID structure
UriLength = 22 UriLength = 22
# Validity constraints for UUID timestamps in UTC
TimestampMaxFuture = Time.now.utc.to_i + (30*24*3600) # Up to 30 days in the future
TimestampMaxPast = 1420070400 # Since 2015-01-01 00:00:00 UTC
#
# Class Methods
#
# #
# Generate a raw 16-byte payload UUID given a seed, platform, architecture, and timestamp # Generate a raw 16-byte payload UUID given a seed, platform, architecture, and timestamp
# #
# @options opts [String] :seed A optional string to use for generated the unique payload ID, deterministic # @options opts [String] :seed An optional string to use for generated the unique payload ID, deterministic
# @options opts [String] :puid An optional 8-byte string to use as the unique payload ID
# @options opts [String] :arch The hardware architecture for this payload # @options opts [String] :arch The hardware architecture for this payload
# @options opts [String] :platform The operating system platform for this payload # @options opts [String] :platform The operating system platform for this payload
# @options opts [String] :timestamp The timestamp in UTC Unix epoch format # @options opts [String] :timestamp The timestamp in UTC Unix epoch format
# @options opts [Fixnum] :xor1 An optional 8-bit XOR ID for obfuscation
# @options opts [Fixnum] :xor2 An optional 8-bit XOR ID for obfuscation
# @return [String] The encoded payoad UUID as a binary string # @return [String] The encoded payoad UUID as a binary string
# #
def self.payload_uuid_generate_raw(opts={}) def self.generate_raw(opts={})
plat_id = find_platform_id(opts[:platform]) || 0 plat_id = find_platform_id(opts[:platform]) || 0
arch_id = find_architecture_id(opts[:arch]) || 0 arch_id = find_architecture_id(opts[:arch]) || 0
seed = opts[:seed] || Rex::Text.rand_text(16)
tstamp = opts[:timestamp] || Time.now.utc.to_i tstamp = opts[:timestamp] || Time.now.utc.to_i
puid = opts[:puid]
plat_xor = rand(255) if opts[:seed]
arch_xor = rand(255) puid = seed_to_puid(opts[:seed])
end
puid ||= Rex::Text.rand_text(8)
if puid.length != 8
raise ArgumentError, "The :puid parameter must be exactly 8 bytes"
end
plat_xor = opts[:xor1] || rand(256)
arch_xor = opts[:xor2] || rand(256)
# Recycle the previous two XOR bytes to keep our output small # Recycle the previous two XOR bytes to keep our output small
time_xor = [plat_xor, arch_xor, plat_xor, arch_xor].pack('C4').unpack('N').first time_xor = [plat_xor, arch_xor, plat_xor, arch_xor].pack('C4').unpack('N').first
# Combine the last 64-bits of the SHA1 of seed with the arch/platform # Combine the payload UID with the arch/platform and use xor to
# Use XOR to obscure the platform, architecture, and timestamp # obscure the platform, architecture, and timestamp
Rex::Text.sha1_raw(seed)[12,8] + puid +
[ [
plat_xor, arch_xor, plat_xor, arch_xor,
plat_xor ^ plat_id, plat_xor ^ plat_id,
@ -103,23 +133,57 @@ class Msf::Payload::UUID
# @param raw [String] The raw 16-byte payload UUID to parse # @param raw [String] The raw 16-byte payload UUID to parse
# @return [Hash] A hash containing the Payload ID, platform, architecture, and timestamp # @return [Hash] A hash containing the Payload ID, platform, architecture, and timestamp
# #
def self.payload_uuid_parse_raw(raw) def self.parse_raw(raw)
if raw.to_s.length < 16
raise ArgumentError, "Raw UUID must be at least 16 bytes"
end
puid, plat_xor, arch_xor, plat_id, arch_id, tstamp = raw.unpack('A8C4N') puid, plat_xor, arch_xor, plat_id, arch_id, tstamp = raw.unpack('A8C4N')
plat = find_platform_name(plat_xor ^ plat_id) plat = find_platform_name(plat_xor ^ plat_id)
arch = find_architecture_name(arch_xor ^ arch_id) arch = find_architecture_name(arch_xor ^ arch_id)
time_xor = [plat_xor, arch_xor, plat_xor, arch_xor].pack('C4').unpack('N').first time_xor = [plat_xor, arch_xor, plat_xor, arch_xor].pack('C4').unpack('N').first
time = time_xor ^ tstamp time = time_xor ^ tstamp
{ puid: puid, platform: plat, arch: arch, timestamp: time } { puid: puid, platform: plat, arch: arch, timestamp: time, xor1: plat_xor, xor2: arch_xor }
end end
# Alias for the class method #
def payload_uuid_generate_raw(opts) # Generate a 8-byte payload ID given a seed string
self.class.payload_uuid_generate_raw(opts) #
# @param seed [String] The seed to use to calculate a deterministic payload ID
# @return [String] The 8-byte payload ID
#
def self.seed_to_puid(seed)
Rex::Text.sha1_raw(seed)[12,8]
end end
# Alias for the class method #
def parse_payload_uuid_raw(raw) # Filter out UUIDs with obviously invalid fields and return either
self.class.payload_uuid_parse_raw(raw) # a validated UUID or a UUID with the arch, platform, and timestamp
# fields strippped out.
#
# @param uuid [Hash] The UUID in hash format
# @return [Hash] The filtered UUID in hash format
#
def self.filter_invalid(uuid)
# Verify the UUID fields and return just the Payload ID unless the
# timestamp is within our constraints and the UUID has either a
# valid architecture or platform
if uuid[:timestamp] > TimestampMaxFuture ||
uuid[:timestamp] < TimestampMaxPast ||
(uuid[:arch].nil? && uuid[:platform].nil?)
return { puid: uuid[:puid] }
end
uuid
end
#
# Parse a 22-byte base64url-encoded payload UUID and return the hash
#
# @param uri [String] The 22-byte base64url-encoded payload UUID to parse
# @return [Hash] A hash containing the Payload ID, platform, architecture, and timestamp
#
def self.parse_uri(uri)
parse_raw(Rex::Text.decode_base64url(uri))
end end
def self.find_platform_id(platform) def self.find_platform_id(platform)
@ -130,17 +194,16 @@ class Msf::Payload::UUID
# Map a platform abbreviation to the real name # Map a platform abbreviation to the real name
name = Msf::Platform::Abbrev[platform] name = Msf::Platform::Abbrev[platform]
( Platforms.keys.select{ |k|
Platforms.keys.select{ |k|
Platforms[k] == name Platforms[k] == name
}.first || Platforms[0] }.first || Platforms[0] ).to_i
end end
def self.find_architecture_id(name) def self.find_architecture_id(name)
name = name.first if name.kind_of? ::Array name = name.first if name.kind_of? ::Array
Architectures.keys.select{ |k| ( Architectures.keys.select{ |k|
Architectures[k] == name Architectures[k] == name
}.first || Architectures[0] }.first || Architectures[0] ).to_i
end end
def self.find_platform_name(num) def self.find_platform_name(num)
@ -151,4 +214,118 @@ class Msf::Payload::UUID
Architectures[num] Architectures[num]
end end
#
# Instance methods
#
def initialize(opts=nil)
opts = load_new if opts.nil?
opts = load_uri(opts[:uri]) if opts[:uri]
opts = load_raw(opts[:raw]) if opts[:raw]
self.puid = opts[:puid]
self.timestamp = opts[:timestamp]
self.arch = opts[:arch]
self.platform = opts[:platform]
self.xor1 = opts[:xor1]
self.xor2 = opts[:xor2]
if opts[:seed]
self.puid = self.class.seed_to_puid(opts[:seed])
end
# Generate some sensible defaults
self.puid ||= Rex::Text.rand_text(8)
self.xor1 ||= rand(256)
self.xor2 ||= rand(256)
self.timestamp ||= Time.now.utc.to_i
end
def load_raw(raw)
self.class.filter_invalid(self.class.parse_raw(raw))
end
def load_uri(uri)
self.class.filter_invalid(self.class.parse_uri(uri))
end
def load_new
self.class.parse_raw(self.class.generate_raw())
end
def to_s
arch_id = self.class.find_architecture_id(self.arch).to_s
plat_id = self.class.find_platform_id(self.platform).to_s
[
self.puid_hex,
[ self.arch || "noarch", arch_id ].join("="),
[ self.platform || "noplatform", plat_id ].join("="),
Time.at(self.timestamp.to_i).utc.strftime("%Y-%m-%dT%H:%M:%SZ")
].join("/")
end
def to_h
{
puid: self.puid,
arch: self.arch, platform: self.platform,
timestamp: self.timestamp,
xor1: self.xor1, xor2: self.xor2
}
end
def to_raw
self.class.generate_raw(self.to_h)
end
def to_uri
Rex::Text.encode_base64url(self.to_raw)
end
def xor_reset
self.xor1 = self.xor2 = nil
self
end
def puid_hex
self.puid.unpack('H*').first
end
attr_reader :arch
attr_reader :platform
def arch=(arch_str)
if arch_str.nil?
@arch = nil
return
end
arch_id = self.class.find_architecture_id(arch_str)
if arch_id == 0
raise ArgumentError, "Invalid architecture: '#{arch_str}'"
end
arch_name = self.class.find_architecture_name(arch_id)
@arch = arch_name
end
def platform=(plat_str)
if plat_str.nil?
@platform = nil
return
end
plat_id = self.class.find_platform_id(plat_str)
if plat_id == 0
raise ArgumentError, "Invalid platform: '#{plat_str}'"
end
plat_name = self.class.find_platform_name(plat_id)
@platform = plat_name
end
attr_accessor :timestamp
attr_accessor :puid
attr_accessor :xor1
attr_accessor :xor2
end end