ruby/lib/metasploit/aggregator/tlv/uuid.rb
# -*- coding => binary -*-
require 'rex/arch'
#
# This class provides methods for calculating, extracting, and parsing
# unique ID values used by payloads.
#
module Metasploit
module Aggregator
module Tlv
class UUID
include Rex::Arch
#
# Constants
#
Architectures = {
0 => nil,
1 => ARCH_X86,
2 => ARCH_X64, # removed ARCH_X86_64, now consistent across the board
3 => ARCH_X64,
4 => ARCH_MIPS,
5 => ARCH_MIPSLE,
6 => ARCH_MIPSBE,
7 => ARCH_PPC,
8 => ARCH_PPC64,
9 => ARCH_CBEA,
10 => ARCH_CBEA64,
11 => ARCH_SPARC,
12 => ARCH_ARMLE,
13 => ARCH_ARMBE,
14 => ARCH_CMD,
15 => ARCH_PHP,
16 => ARCH_TTY,
17 => ARCH_JAVA,
18 => ARCH_RUBY,
19 => ARCH_DALVIK,
20 => ARCH_PYTHON,
21 => ARCH_NODEJS,
22 => ARCH_FIREFOX,
23 => ARCH_ZARCH,
24 => ARCH_AARCH64,
25 => ARCH_MIPS64,
26 => ARCH_PPC64LE
}
Platforms = {
0 => nil,
1 => 'windows',
2 => 'netware',
3 => 'android',
4 => 'java',
5 => 'ruby',
6 => 'linux',
7 => 'cisco',
8 => 'solaris',
9 => 'osx',
10 => 'bsd',
11 => 'openbsd',
12 => 'bsdi',
13 => 'netbsd',
14 => 'freebsd',
15 => 'aix',
16 => 'hpux',
17 => 'irix',
18 => 'unix',
19 => 'php',
20 => 'js',
21 => 'python',
22 => 'nodejs',
23 => 'firefox'
}
# The raw length of the UUID structure
RawLength = 16
# The base64url-encoded length of the UUID structure
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
#
#
# Parse a raw 16-byte payload UUID and return the payload ID, platform, architecture, and timestamp
#
# @param raw [String] The raw 16-byte payload UUID to parse
# @return [Hash] A hash containing the Payload ID, platform, architecture, and timestamp
#
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')
plat = find_platform_name(plat_xor ^ plat_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 = time_xor ^ tstamp
{ puid: puid, platform: plat, arch: arch, timestamp: time, xor1: plat_xor, xor2: arch_xor }
end
#
# Filter out UUIDs with obviously invalid fields and return either
# 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
#
# Look up the numeric platform ID given a string or PlatformList as input
#
# @param platform [String] The name of the platform to lookup
# @return [Fixnum] The integer value of this platform
#
def self.find_platform_id(platform)
name = name.first if name.kind_of? ::Array
( Platforms.keys.select{ |k|
Platforms[k] == name
}.first || Platforms[0] ).to_i
end
#
# Look up the numeric architecture ID given a string as input
#
# @param name [String] The name of the architecture to lookup
# @return [Fixnum] The integer value of this architecture
#
def self.find_architecture_id(name)
name = name.first if name.kind_of? ::Array
( Architectures.keys.select{ |k|
Architectures[k] == name
}.first || Architectures[0] ).to_i
end
def self.find_platform_name(num)
Platforms[num]
end
def self.find_architecture_name(num)
Architectures[num]
end
#
# Instance methods
#
def initialize(opts=nil)
opts = load_new if opts.nil?
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]
# Generate some sensible defaults
self.puid ||= SecureRandom.random_bytes(8)
self.xor1 ||= rand(256)
self.xor2 ||= rand(256)
self.timestamp ||= Time.now.utc.to_i
end
#
# Initializes a UUID object given a raw 16+ byte blob
#
# @param raw [String] The string containing at least 16 bytes of encoded data
# @return [Hash] The attributes encoded into this UUID
#
def load_raw(raw)
self.class.filter_invalid(self.class.parse_raw(raw))
end
def load_new
self.class.parse_raw(self.class.generate_raw())
end
#
# Provides a string representation of a UUID
#
# @return [String] The human-readable version of the UUID data
#
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
#
# Return a string that represents the Meterpreter arch/platform
#
def session_type
# mini-patch for x86 so that it renders x64 instead. This is
# mostly to keep various external modules happy.
arch = self.arch
if arch == ARCH_X86_64
arch = ARCH_X64
end
"#{arch}/#{self.platform}"
end
#
# Provides a hash representation of a UUID
#
# @return [Hash] The hash representation of the UUID suitable for creating a new one
#
def to_h
{
puid: self.puid,
arch: self.arch, platform: self.platform,
timestamp: self.timestamp,
xor1: self.xor1, xor2: self.xor2
}
end
#
# Provides a raw byte representation of a UUID
#
# @return [String] The 16-byte raw encoded version of the UUID
#
def to_raw
self.class.generate_raw(self.to_h)
end
#
# Provides a hex representation of the Payload UID of the UUID
#
# @return [String] The 16-byte hex string representing the Payload UID
#
def puid_hex
self.puid.unpack('H*').first
end
#
# Clears the two random XOR keys used for obfuscation
#
def xor_reset
self.xor1 = self.xor2 = nil
self
end
attr_accessor :arch
attr_accessor :platform
attr_accessor :timestamp
attr_accessor :puid
attr_accessor :xor1
attr_accessor :xor2
end
end
end
end