rapid7/metasploit-aggregator

View on GitHub
ruby/lib/metasploit/aggregator/tlv/uuid.rb

Summary

Maintainability
A
1 hr
Test Coverage
# -*- 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