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