lib/metasploit/aggregator/tlv/packet.rb in metasploit-aggregator-0.2.3 vs lib/metasploit/aggregator/tlv/packet.rb in metasploit-aggregator-1.0.0
- old
+ new
@@ -1,6 +1,8 @@
# -*- coding: binary -*-
+require 'openssl'
+require 'rex/text'
module Metasploit
module Aggregator
module Tlv
@@ -78,11 +80,11 @@
TLV_TYPE_EXCEPTION_STRING = TLV_META_TYPE_STRING | 301
TLV_TYPE_LIBRARY_PATH = TLV_META_TYPE_STRING | 400
TLV_TYPE_TARGET_PATH = TLV_META_TYPE_STRING | 401
TLV_TYPE_MIGRATE_PID = TLV_META_TYPE_UINT | 402
- TLV_TYPE_MIGRATE_LEN = TLV_META_TYPE_UINT | 403
+ TLV_TYPE_MIGRATE_PAYLOAD_LEN = TLV_META_TYPE_UINT | 403
TLV_TYPE_MIGRATE_PAYLOAD = TLV_META_TYPE_STRING | 404
TLV_TYPE_MIGRATE_ARCH = TLV_META_TYPE_UINT | 405
TLV_TYPE_MIGRATE_BASE_ADDR = TLV_META_TYPE_UINT | 407
TLV_TYPE_MIGRATE_ENTRY_POINT = TLV_META_TYPE_UINT | 408
TLV_TYPE_MIGRATE_SOCKET_PATH = TLV_META_TYPE_STRING | 409
@@ -103,15 +105,27 @@
TLV_TYPE_TRANS_RETRY_WAIT = TLV_META_TYPE_UINT | 440
TLV_TYPE_TRANS_GROUP = TLV_META_TYPE_GROUP | 441
TLV_TYPE_MACHINE_ID = TLV_META_TYPE_STRING | 460
TLV_TYPE_UUID = TLV_META_TYPE_RAW | 461
+ TLV_TYPE_SESSION_GUID = TLV_META_TYPE_RAW | 462
- TLV_TYPE_CIPHER_NAME = TLV_META_TYPE_STRING | 500
- TLV_TYPE_CIPHER_PARAMETERS = TLV_META_TYPE_GROUP | 501
+ TLV_TYPE_RSA_PUB_KEY = TLV_META_TYPE_STRING | 550
+ TLV_TYPE_SYM_KEY_TYPE = TLV_META_TYPE_UINT | 551
+ TLV_TYPE_SYM_KEY = TLV_META_TYPE_RAW | 552
+ TLV_TYPE_ENC_SYM_KEY = TLV_META_TYPE_RAW | 553
#
+# Pivots
+#
+ TLV_TYPE_PIVOT_ID = TLV_META_TYPE_RAW | 650
+ TLV_TYPE_PIVOT_STAGE_DATA = TLV_META_TYPE_RAW | 651
+ TLV_TYPE_PIVOT_STAGE_DATA_SIZE = TLV_META_TYPE_UINT | 652
+ TLV_TYPE_PIVOT_NAMED_PIPE_NAME = TLV_META_TYPE_STRING | 653
+
+
+#
# Core flags
#
LOAD_LIBRARY_FLAG_ON_DISK = (1 << 0)
LOAD_LIBRARY_FLAG_EXTENSION = (1 << 1)
LOAD_LIBRARY_FLAG_LOCAL = (1 << 2)
@@ -127,18 +141,26 @@
TLV_TYPE_SID = TLV_META_TYPE_STRING | 1045
TLV_TYPE_DOMAIN = TLV_META_TYPE_STRING | 1046
TLV_TYPE_LOGGED_ON_USER_COUNT = TLV_META_TYPE_UINT | 1047
TLV_TYPE_LOCAL_DATETIME = TLV_META_TYPE_STRING | 1048
+#
+# Sane defaults
+#
+ GUID_SIZE = 16
+ NULL_GUID = "\x00" * GUID_SIZE
+
###
#
# Base TLV (Type-Length-Value) class
#
###
class Tlv
attr_accessor :type, :value, :compress
+ HEADER_SIZE = 8
+
##
#
# Constructor
#
##
@@ -252,11 +274,11 @@
raw = [0].pack("c")
end
end
# check if the tlv is to be compressed...
- if( @compress )
+ if @compress
raw_uncompressed = raw
# compress the raw data
raw_compressed = Rex::Text.zlib_deflate( raw_uncompressed )
# check we have actually made the raw data smaller...
# (small blobs often compress slightly larger then the origional)
@@ -268,11 +290,11 @@
# (we include the uncompressed data length as the C side will need to know this for decompression)
raw = [ raw_uncompressed.length ].pack("N") + raw_compressed
end
end
- return [raw.length + 8, self.type].pack("NN") + raw
+ [raw.length + HEADER_SIZE, self.type].pack("NN") + raw
end
#
# Translates the raw format of the TLV into a sanitize version.
#
@@ -284,23 +306,26 @@
# check if the tlv value has been compressed...
if( self.type & TLV_META_TYPE_COMPRESSED == TLV_META_TYPE_COMPRESSED )
# set this TLV as using compression
@compress = true
# remove the TLV_META_TYPE_COMPRESSED flag from the tlv type to restore the
- # tlv type to its origional, allowing for transparent data compression.
+ # tlv type to its original, allowing for transparent data compression.
self.type = self.type ^ TLV_META_TYPE_COMPRESSED
# decompress the compressed data (skipping the length and type DWORD's)
- raw_decompressed = Rex::Text.zlib_inflate( raw[8..length-1] )
- # update the length to reflect the decompressed data length (+8 for the length and type DWORD's)
- length = raw_decompressed.length + 8
- # update the raw buffer with the new length, decompressed data and updated type.
- raw = [length, self.type].pack("NN") + raw_decompressed
+
+ # disable raw_decompression for now - not needed by aggregator at this time
+ # raw_decompressed = Rex::Text.zlib_inflate( raw[HEADER_SIZE..length-1] )
+ #
+ # # update the length to reflect the decompressed data length (+HEADER_SIZE for the length and type DWORD's)
+ # length = raw_decompressed.length + HEADER_SIZE
+ # # update the raw buffer with the new length, decompressed data and updated type.
+ # raw = [length, self.type].pack("NN") + raw_decompressed
end
if (self.type & TLV_META_TYPE_STRING == TLV_META_TYPE_STRING)
if (raw.length > 0)
- self.value = raw[8..length-2]
+ self.value = raw[HEADER_SIZE..length-2]
else
self.value = nil
end
elsif (self.type & TLV_META_TYPE_UINT == TLV_META_TYPE_UINT)
self.value = raw.unpack("NNN")[2]
@@ -314,27 +339,28 @@
self.value = true
else
self.value = false
end
else
- self.value = raw[8..length-1]
+ self.value = raw[HEADER_SIZE..length-1]
end
- return length;
+ length
end
protected
- def htonq( value )
- if( [1].pack( 's' ) == [1].pack( 'n' ) )
+ def htonq(value)
+ if [1].pack( 's' ) == [1].pack('n')
return value
+ else
+ [value].pack('Q<').reverse.unpack('Q<').first
end
- return [ value ].pack( 'Q<' ).reverse.unpack( 'Q<' ).first
end
- def ntohq( value )
- return htonq( value )
+ def ntohq(value)
+ htonq(value)
end
end
###
@@ -356,11 +382,11 @@
# and creates an empty TLV array.
#
def initialize(type)
super(type)
- self.tlvs = [ ]
+ self.tlvs = []
end
##
#
# Group-based TLV accessors
@@ -397,22 +423,22 @@
#
# Returns an array of TLVs for the given type.
#
def get_tlvs(type)
- if (type == TLV_TYPE_ANY)
- return self.tlvs
+ if type == TLV_TYPE_ANY
+ self.tlvs
else
type_tlvs = []
self.tlvs.each() { |tlv|
if (tlv.type?(type))
type_tlvs << tlv
end
}
- return type_tlvs
+ type_tlvs
end
end
##
#
@@ -424,11 +450,11 @@
# Adds a TLV of a given type and value.
#
def add_tlv(type, value = nil, replace = false, compress=false)
# If we should replace any TLVs with the same type...remove them first
- if (replace)
+ if replace
each(type) { |tlv|
if (tlv.type == type)
self.tlvs.delete(tlv)
end
}
@@ -440,18 +466,18 @@
tlv = Tlv.new(type, value, compress)
end
self.tlvs << tlv
- return tlv
+ tlv
end
#
# Adds zero or more TLVs to the packet.
#
def add_tlvs(tlvs)
- if (tlvs != nil)
+ if tlvs
tlvs.each { |tlv|
add_tlv(tlv['type'], tlv['value'])
}
end
end
@@ -460,24 +486,25 @@
# Gets the first TLV of a given type.
#
def get_tlv(type, index = 0)
type_tlvs = get_tlvs(type)
- if (type_tlvs.length > index)
- return type_tlvs[index]
+ if type_tlvs.length > index
+ type_tlvs[index]
+ else
+ nil
end
- return nil
end
#
# Returns the value of a TLV if it exists, otherwise nil.
#
def get_tlv_value(type, index = 0)
tlv = get_tlv(type, index)
- return (tlv != nil) ? tlv.value : nil
+ (tlv != nil) ? tlv.value : nil
end
#
# Returns an array of values for all tlvs of type type.
#
@@ -487,11 +514,11 @@
#
# Checks to see if the container has a TLV of a given type.
#
def has_tlv?(type)
- return get_tlv(type) != nil
+ get_tlv(type) != nil
end
#
# Zeros out the array of TLVs.
#
@@ -514,31 +541,31 @@
self.each() { |tlv|
raw << tlv.to_r
}
- return [raw.length + 8, self.type].pack("NN") + raw
+ [raw.length + HEADER_SIZE, self.type].pack("NN") + raw
end
#
# Converts the TLV group container from raw to all of the individual
# TLVs.
#
def from_r(raw)
- offset = 8
+ offset = HEADER_SIZE
# Reset the TLVs array
self.tlvs = []
self.type = raw.unpack("NN")[1]
# Enumerate all of the TLVs
- while (offset < raw.length-1)
+ while offset < raw.length-1
tlv = nil
# Get the length and type
- length, type = raw[offset..offset+8].unpack("NN")
+ length, type = raw[offset..offset+HEADER_SIZE].unpack("NN")
if (type & TLV_META_TYPE_GROUP == TLV_META_TYPE_GROUP)
tlv = GroupTlv.new(type)
else
tlv = Tlv.new(type)
@@ -561,40 +588,94 @@
# The logical meterpreter packet class
#
###
class Packet < GroupTlv
attr_accessor :created_at
+ attr_accessor :raw
+ attr_accessor :session_guid
+ attr_accessor :encrypt_flags
+ attr_accessor :length
##
#
+ # The Packet container itself has a custom header that is slightly different to the
+ # typical TLV packets. The header contains the following:
+ #
+ # XOR KEY - 4 bytes
+ # Session GUID - 16 bytes
+ # Encrypt flags - 4 bytes
+ # Packet length - 4 bytes
+ # Packet type - 4 bytes
+ # Packet data - X bytes
+ #
+ # If the encrypt flags are zero, then the Packet data is just straight TLV values as
+ # per the normal TLV packet structure.
+ #
+ # If the encrypt flags are non-zer, then the Packet data is encrypted based on the scheme.
+ #
+ # Flag == 1 (AES256)
+ # IV - 16 bytes
+ # Encrypted data - X bytes
+ #
+ # The key that is required to decrypt the data is stored alongside the session data,
+ # and hence when the packet is initially parsed, only the header is accessed. The
+ # packet itself will need to be decrypted on the fly at the point that it is required
+ # and at that point the decryption key needs to be provided.
+ #
+ ###
+
+ XOR_KEY_SIZE = 4
+ ENCRYPTED_FLAGS_SIZE = 4
+ PACKET_LENGTH_SIZE = 4
+ PACKET_TYPE_SIZE = 4
+ PACKET_HEADER_SIZE = XOR_KEY_SIZE + GUID_SIZE + ENCRYPTED_FLAGS_SIZE + PACKET_LENGTH_SIZE + PACKET_TYPE_SIZE
+
+ AES_IV_SIZE = 16
+
+ ENC_FLAG_NONE = 0x0
+ ENC_FLAG_AES256 = 0x1
+
+ ##
+ #
# Factory
#
##
#
# Creates a request with the supplied method.
#
def Packet.create_request(method = nil)
- return Packet.new(PACKET_TYPE_REQUEST, method)
+ Packet.new(PACKET_TYPE_REQUEST, method)
end
#
# Creates a response to a request if one is provided.
#
def Packet.create_response(request = nil)
response_type = PACKET_TYPE_RESPONSE
method = nil
+ id = nil
if (request)
if (request.type?(PACKET_TYPE_PLAIN_REQUEST))
response_type = PACKET_TYPE_PLAIN_RESPONSE
end
method = request.method
+
+ if request.has_tlv?(TLV_TYPE_REQUEST_ID)
+ id = request.get_tlv_value(TLV_TYPE_REQUEST_ID)
+ end
end
- return Packet.new(response_type, method)
+ packet = Packet.new(response_type, method)
+
+ if id
+ packet.add_tlv(TLV_TYPE_REQUEST_ID, id)
+ end
+
+ packet
end
##
#
# Constructor
@@ -607,15 +688,16 @@
# created.
#
def initialize(type = nil, method = nil)
super(type)
- if (method)
+ if method
self.method = method
end
self.created_at = ::Time.now
+ self.raw = ''
# If it's a request, generate a random request identifier
if ((type == PACKET_TYPE_REQUEST) ||
(type == PACKET_TYPE_PLAIN_REQUEST))
rid = ''
@@ -624,42 +706,124 @@
add_tlv(TLV_TYPE_REQUEST_ID, rid)
end
end
+ def add_raw(bytes)
+ self.raw << bytes
+ end
+
+ def raw_bytes_required
+ # if we have the xor bytes and length ...
+ if self.raw.length >= PACKET_HEADER_SIZE
+ # return a value based on the length of the data indicated by
+ # the header
+ xor_key = self.raw.unpack('a4')[0]
+ decoded_bytes = xor_bytes(xor_key, raw[0, PACKET_HEADER_SIZE])
+ _, _, _, length, _ = decoded_bytes.unpack('a4a16NNN')
+ length + PACKET_HEADER_SIZE - HEADER_SIZE - self.raw.length
+ else
+ # Otherwise ask for the remaining bytes for the metadata to get the packet length
+ # So we can do the rest of the calculation next time
+ PACKET_HEADER_SIZE - self.raw.length
+ end
+ end
+
+ def aes_encrypt(key, data)
+ # Create the required cipher instance
+ aes = OpenSSL::Cipher.new('AES-256-CBC')
+ # Generate a truly random IV
+ iv = aes.random_iv
+
+ # set up the encryption
+ aes.encrypt
+ aes.key = key
+ aes.iv = iv
+
+ # encrypt and return the IV along with the result
+ return iv, aes.update(data) + aes.final
+ end
+
+ def aes_decrypt(key, iv, data)
+ # Create the required cipher instance
+ aes = OpenSSL::Cipher.new('AES-256-CBC')
+ # Generate a truly random IV
+
+ # set up the encryption
+ aes.decrypt
+ aes.key = key
+ aes.iv = iv
+
+ # decrypt!
+ aes.update(data) + aes.final
+ end
+
#
# Override the function that creates the raw byte stream for
# sending so that it generates an XOR key, uses it to scramble
# the serialized TLV content, and then returns the key plus the
# scrambled data as the payload.
#
- def to_r
- raw = super
- xor_key = rand(254) + 1
- xor_key |= (rand(254) + 1) << 8
- xor_key |= (rand(254) + 1) << 16
- xor_key |= (rand(254) + 1) << 24
- result = [xor_key].pack('N') + xor_bytes(xor_key, raw)
- result
+ def to_r(session_guid = nil, key = nil)
+ xor_key = (rand(254) + 1).chr + (rand(254) + 1).chr + (rand(254) + 1).chr + (rand(254) + 1).chr
+
+ raw = (session_guid || NULL_GUID).dup
+ tlv_data = GroupTlv.instance_method(:to_r).bind(self).call
+
+ if key && key[:key] && key[:type] == ENC_FLAG_AES256
+ # encrypt the data, but not include the length and type
+ iv, ciphertext = aes_encrypt(key[:key], tlv_data[HEADER_SIZE..-1])
+ # now manually add the length/type/iv/ciphertext
+ raw << [ENC_FLAG_AES256, iv.length + ciphertext.length + HEADER_SIZE, self.type, iv, ciphertext].pack('NNNA*A*')
+ else
+ raw << [ENC_FLAG_NONE, tlv_data].pack('NA*')
+ end
+
+ # return the xor'd result with the key
+ xor_key + xor_bytes(xor_key, raw)
end
#
+ # Decrypt the packet based on the content of the encryption flags.
+ #
+ def decrypt_packet(key, encrypt_flags, data)
+ # TODO: throw an error if the expected encryption isn't the same as the given
+ # as this could be an indication of hijacking or side-channel packet addition
+ # as highlighted by Justin Steven on github.
+ if key && key[:key] && key[:type] && encrypt_flags == ENC_FLAG_AES256 && encrypt_flags == key[:type]
+ iv = data[0, AES_IV_SIZE]
+ aes_decrypt(key[:key], iv, data[iv.length..-1])
+ else
+ data
+ end
+ end
+
+ def parse_header!
+ xor_key = self.raw.unpack('a4')[0]
+ data = xor_bytes(xor_key, self.raw[0..PACKET_HEADER_SIZE])
+ _, self.session_guid, self.encrypt_flags, self.length, self.type = data.unpack('a4a16NNN')
+ end
+
+ #
# Override the function that reads from a raw byte stream so
# that the XORing of data is included in the process prior to
# passing it on to the default functionality that can parse
# the TLV values.
#
- def from_r(bytes)
- xor_key = bytes[0,4].unpack('N')[0]
- super(xor_bytes(xor_key, bytes[4, bytes.length]))
+ def from_r(key=nil)
+ self.parse_header!
+ xor_key = self.raw.unpack('a4')[0]
+ data = xor_bytes(xor_key, self.raw[PACKET_HEADER_SIZE..-1])
+ raw = decrypt_packet(key, self.encrypt_flags, data)
+ super([self.length, self.type, raw].pack('NNA*'))
end
#
- # Xor a set of bytes with a given DWORD xor key.
+ # Xor a set of bytes with a given XOR key.
#
def xor_bytes(xor_key, bytes)
result = ''
- bytes.bytes.zip([xor_key].pack('V').bytes.cycle).each do |b|
+ bytes.bytes.zip(xor_key.bytes.cycle).each do |b|
result << (b[0].ord ^ b[1].ord).chr
end
result
end