diff --git a/lib/msf/base/sessions/meterpreter_options.rb b/lib/msf/base/sessions/meterpreter_options.rb index 00058b4c7e..65d2697761 100644 --- a/lib/msf/base/sessions/meterpreter_options.rb +++ b/lib/msf/base/sessions/meterpreter_options.rb @@ -43,6 +43,8 @@ module MeterpreterOptions valid = true + session.aes_key = session.core.negotiate_aes + if datastore['AutoVerifySession'] if not session.is_valid_session?(datastore['AutoVerifySessionTimeout'].to_i) print_error("Meterpreter session #{session.sid} is not valid and will be closed") @@ -52,14 +54,14 @@ module MeterpreterOptions if valid # always make sure that the new session has a new guid if it's not already known - guid = session.core.get_session_guid + guid = session.session_guid + STDERR.puts("Session GUID is #{guid}\n") if guid == '00000000-0000-0000-0000-000000000000' guid = SecureRandom.uuid session.core.set_session_guid(guid) - session.guid = guid + session.session_guid = guid # TODO: New statgeless session, do some account in the DB so we can track it later. else - session.guid = guid # TODO: This session was either staged or previously known, and so we shold do some accounting here! end diff --git a/lib/msf/core/session.rb b/lib/msf/core/session.rb index 8ab8003ad4..f781f2864b 100644 --- a/lib/msf/core/session.rb +++ b/lib/msf/core/session.rb @@ -385,10 +385,6 @@ module Session # attr_accessor :machine_id # - # The guid that identifies an active Meterpreter session - # - attr_accessor :guid - # # The actual exploit module instance that created this session # attr_accessor :exploit diff --git a/lib/rex/post/meterpreter/client.rb b/lib/rex/post/meterpreter/client.rb index 4fc92b2aa6..d4bf04ec4f 100644 --- a/lib/rex/post/meterpreter/client.rb +++ b/lib/rex/post/meterpreter/client.rb @@ -77,7 +77,7 @@ class Client # Initializes the client context with the supplied socket through # which communication with the server will be performed. # - def initialize(sock,opts={}) + def initialize(sock, opts={}) init_meterpreter(sock, opts) end @@ -130,6 +130,9 @@ class Client # self.encode_unicode = opts.has_key?(:encode_unicode) ? opts[:encode_unicode] : true self.encode_unicode = false + self.aes_key = nil + self.session_guid = '00000000-0000-0000-0000-000000000000' + # The SSL certificate is being passed down as a file path if opts[:ssl_cert] if ! ::File.exist? opts[:ssl_cert] diff --git a/lib/rex/post/meterpreter/client_core.rb b/lib/rex/post/meterpreter/client_core.rb index ed00b9a38b..a181f52acf 100644 --- a/lib/rex/post/meterpreter/client_core.rb +++ b/lib/rex/post/meterpreter/client_core.rb @@ -75,6 +75,7 @@ class ClientCore < Extension begin response = self.client.send_packet_wait_response(request, self.client.response_timeout) rescue + STDERR.puts("Getting ext commands for #{extension_name} failed with an exception\n") # In the case where orphaned shells call back with OLD copies of the meterpreter # binaries, we end up with a case where this fails. So here we just return the # empty list of supported commands. @@ -210,7 +211,8 @@ class ClientCore < Extension image = f.read } - if !image.nil? + if image + STDERR.puts("Capabilities zlib: #{client.capabilities[:zlib]}\n") request.add_tlv(TLV_TYPE_DATA, image, false, client.capabilities[:zlib]) else raise RuntimeError, "Failed to serialize library #{library_path}.", caller @@ -287,6 +289,8 @@ class ClientCore < Extension raise RuntimeError, "No module of the name #{modname}.#{client.binary_suffix} found", caller end + STDERR.puts("Going to try to load #{mod} from #{path}\n") + # Load the extension DLL commands = load_library( 'LibraryFilePath' => path, @@ -680,10 +684,13 @@ class ClientCore < Extension end end + # + # Negotiates the use of AES256 encryption over the TLV packets. + # def negotiate_aes request = Packet.create_request('core_negotiate_aes') response = client.send_request(request) - response + response.get_tlv_value(TLV_TYPE_AES_KEY) end private diff --git a/lib/rex/post/meterpreter/packet.rb b/lib/rex/post/meterpreter/packet.rb index fb141c7c96..91cfcab6e0 100644 --- a/lib/rex/post/meterpreter/packet.rb +++ b/lib/rex/post/meterpreter/packet.rb @@ -1,4 +1,5 @@ # -*- coding: binary -*- +require 'openssl' module Rex module Post @@ -109,7 +110,6 @@ 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_AES_KEY = TLV_META_TYPE_RAW | 550 # @@ -127,6 +127,8 @@ LOAD_LIBRARY_FLAG_LOCAL = (1 << 2) class Tlv attr_accessor :type, :value, :compress + HEADER_SIZE = 8 + ## # # Constructor @@ -321,7 +323,7 @@ class Tlv end end - return [raw.length + 8, self.type].pack("NN") + raw + [raw.length + HEADER_SIZE, self.type].pack("NN") + raw end # @@ -340,16 +342,16 @@ class Tlv # tlv type to its origional, 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 + 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 @@ -367,23 +369,24 @@ class Tlv 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 @@ -409,7 +412,7 @@ class GroupTlv < Tlv def initialize(type) super(type) - self.tlvs = [ ] + self.tlvs = [] end ## @@ -450,8 +453,8 @@ class GroupTlv < Tlv # 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 = [] @@ -461,7 +464,7 @@ class GroupTlv < Tlv end } - return type_tlvs + type_tlvs end end @@ -477,7 +480,7 @@ class GroupTlv < Tlv 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) @@ -493,14 +496,14 @@ class GroupTlv < Tlv 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']) } @@ -513,11 +516,12 @@ class GroupTlv < Tlv 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 # @@ -526,7 +530,7 @@ class GroupTlv < Tlv def get_tlv_value(type, index = 0) tlv = get_tlv(type, index) - return (tlv != nil) ? tlv.value : nil + (tlv != nil) ? tlv.value : nil end # @@ -540,7 +544,7 @@ class GroupTlv < Tlv # 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 # @@ -567,7 +571,7 @@ class GroupTlv < Tlv raw << tlv.to_r } - return [raw.length + 8, self.type].pack("NN") + raw + [raw.length + HEADER_SIZE, self.type].pack("NN") + raw end # @@ -575,19 +579,19 @@ class GroupTlv < Tlv # 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) @@ -615,6 +619,56 @@ end class Packet < GroupTlv attr_accessor :created_at attr_accessor :raw + attr_accessor :session_guid + + ## + # + # 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 + # Encrypted flag - 1 byte + # Packet length - 4 bytes + # Packet type - 4 bytes + # Packet data - X bytes + # + # If the encrypted flag is NOT set, then the Packet data is just straight TLV values as + # per the normal TLV packet structure. + # + # If the encrypted flag IS set, then the Packet data is an AES256 encrypted block with + # the following format: + # + # 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_OFF = 0 + XOR_KEY_SIZE = 4 + + SESSION_GUID_OFF = XOR_KEY_OFF + XOR_KEY_SIZE + SESSION_GUID_SIZE = 16 + + ENCRYPTED_FLAG_OFF = SESSION_GUID_OFF + SESSION_GUID_SIZE + ENCRYPTED_FLAG_SIZE = 1 + + PACKET_LENGTH_OFF = ENCRYPTED_FLAG_OFF + ENCRYPTED_FLAG_SIZE + PACKET_LENGTH_SIZE = 4 + + PACKET_TYPE_OFF = PACKET_LENGTH_OFF + PACKET_LENGTH_SIZE + PACKET_TYPE_SIZE = 4 + + PACKET_DATA_OFF = PACKET_TYPE_OFF + PACKET_TYPE_SIZE + + REQUIRED_PREFIX_SIZE = XOR_KEY_SIZE + SESSION_GUID_SIZE + ENCRYPTED_FLAG_SIZE + PACKET_LENGTH_SIZE + + AES_IV_SIZE = 16 ## # @@ -626,7 +680,7 @@ class Packet < GroupTlv # 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 # @@ -644,7 +698,7 @@ class Packet < GroupTlv method = request.method end - return Packet.new(response_type, method) + Packet.new(response_type, method) end ## @@ -661,7 +715,7 @@ class Packet < GroupTlv def initialize(type = nil, method = nil) super(type) - if (method) + if method self.method = method end @@ -685,33 +739,86 @@ class Packet < GroupTlv def raw_bytes_required # if we have the xor bytes and length ... - if self.raw.length >= 8 + if self.raw.length >= REQUIRED_PREFIX_SIZE + p = [ self.raw[0,4].unpack('H*')[0], + self.raw[4,16].unpack('H*')[0], + self.raw[20].unpack('H*')[0], + self.raw[21,4].unpack('H*')[0] ].join('-') + STDERR.puts("Incoming packet: #{p}\n") + # return a value based on the length of the data indicated by # the header - xor_key = self.raw[0, 4] - length_bytes = xor_bytes(xor_key, raw[4, 4]) - length = length_bytes.unpack("N")[0] - # the raw buffer will always be 4 bytes longer than the length value - # given because of the xor key, so the number of bytes we need is ... - length - self.raw.length + 4 + xor_key = self.raw[XOR_KEY_OFF, XOR_KEY_SIZE] + decoded_bytes = xor_bytes(xor_key, raw[0, REQUIRED_PREFIX_SIZE]) + length_bytes = decoded_bytes[PACKET_LENGTH_OFF, PACKET_LENGTH_SIZE] + length_bytes.unpack("N")[0] + PACKET_DATA_OFF - HEADER_SIZE - self.raw.length else - # Otherwise ask for the remaining 8 bytes for the xor/length + # 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 - 8 - self.raw.length + REQUIRED_PREFIX_SIZE - self.raw.length end end + def aes_encrypt(aes_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 = aes_key + aes.iv = iv + + # encrypt and return the IV along with the result + return iv, aes.update(data) + aes.final + end + + def aes_decrypt(aes_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 = aes_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 + def to_r(session_guid, aes_key=nil) + STDERR.puts("AES key: #{aes_key.inspect}\n") xor_key = (rand(254) + 1).chr + (rand(254) + 1).chr + (rand(254) + 1).chr + (rand(254) + 1).chr - result = xor_key + xor_bytes(xor_key, raw) - result + + raw = [session_guid.gsub(/-/, '')].pack('H*') + tlv_data = GroupTlv.instance_method(:to_r).bind(self).call + + if aes_key + raw << "\x01" + STDERR.puts("Doing the encryption!\n") + # encrypt the data, but not include the length and type + iv, ciphertext = aes_encrypt(aes_key, tlv_data[HEADER_SIZE, tlv_data.length - HEADER_SIZE]) + STDERR.puts("Encryption done! IV is #{iv.unpack('H*')[0]}\n") + # now manually add the length/type/iv/ciphertext + STDERR.puts("len/type: #{[iv.length + ciphertext.length + HEADER_SIZE, self.type].inspect}\n") + raw << [iv.length + ciphertext.length + HEADER_SIZE, self.type].pack('NN') + raw << iv + raw << ciphertext + else + raw << "\x00" + raw << tlv_data + end + + # return the xor'd result with the key + xor_key + xor_bytes(xor_key, raw) end # @@ -720,10 +827,26 @@ class Packet < GroupTlv # passing it on to the default functionality that can parse # the TLV values. # - def from_r(bytes=nil) - bytes ||= self.raw - xor_key = bytes[0,4] - super(xor_bytes(xor_key, bytes[4, bytes.length])) + def from_r(aes_key=nil) + xor_key = self.raw[XOR_KEY_OFF, XOR_KEY_SIZE] + data = xor_key + xor_bytes(xor_key, self.raw[SESSION_GUID_OFF, self.raw.length - SESSION_GUID_OFF]) + self.session_guid = data[SESSION_GUID_OFF, SESSION_GUID_SIZE] + encrypted_flag = data[ENCRYPTED_FLAG_OFF, ENCRYPTED_FLAG_SIZE] == "\x01" + packet_length = data[PACKET_LENGTH_OFF, PACKET_LENGTH_SIZE] + packet_type = data[PACKET_TYPE_OFF, PACKET_TYPE_SIZE] + self.type = packet_type.unpack('N')[0] + if encrypted_flag + STDERR.puts("Packet is encrypted\n") + # TODO error when there's no aes key + iv = data[PACKET_DATA_OFF, AES_IV_SIZE] + raw = aes_decrypt(aes_key, iv, data[PACKET_DATA_OFF + AES_IV_SIZE, data.length - PACKET_DATA_OFF - AES_IV_SIZE]) + else + STDERR.puts("Packet isn't encrypted\n") + raw = data[PACKET_DATA_OFF, data.length - PACKET_DATA_OFF] + end + + super(packet_length + packet_type + raw) + STDERR.puts("Received packet: #{self.inspect}\n") end # diff --git a/lib/rex/post/meterpreter/packet_dispatcher.rb b/lib/rex/post/meterpreter/packet_dispatcher.rb index cb4a95702b..7c06abf097 100644 --- a/lib/rex/post/meterpreter/packet_dispatcher.rb +++ b/lib/rex/post/meterpreter/packet_dispatcher.rb @@ -57,6 +57,12 @@ module PacketDispatcher # active migration. Unused if this is a passive dispatcher attr_accessor :comm_mutex + # The guid that identifies an active Meterpreter session + attr_accessor :session_guid + + # This contains the AES key to use when handling TLV packet comms in an + # encrypted context + attr_accessor :aes_key # Passive Dispatching # @@ -71,10 +77,10 @@ module PacketDispatcher attr_accessor :recv_queue def initialize_passive_dispatcher - self.send_queue = [] - self.recv_queue = [] - self.waiters = [] - self.alive = true + self.send_queue = [] + self.recv_queue = [] + self.waiters = [] + self.alive = true # Ensure that there is only one leading and trailing slash on the URI resource_uri = "/" + self.conn_id.to_s.gsub(/(^\/|\/$)/, '') + "/" @@ -131,7 +137,7 @@ module PacketDispatcher resp.body = "" if req.body and req.body.length > 0 packet = Packet.new(0) - packet.from_r(req.body) + packet.add_raw(req.body) dispatch_inbound_packet(packet) end cli.send_response(resp) @@ -156,8 +162,10 @@ module PacketDispatcher add_response_waiter(packet, completion_routine, completion_param) end + STDERR.puts("Sending packet: #{packet.inspect}\n") + bytes = 0 - raw = packet.to_r + raw = packet.to_r(self.session_guid, self.aes_key) err = nil # Short-circuit send when using a passive dispatcher @@ -166,8 +174,7 @@ module PacketDispatcher return raw.size # Lie! end - if (raw) - + if raw self.comm_mutex.synchronize do begin bytes = self.sock.write(raw) @@ -420,7 +427,17 @@ module PacketDispatcher # once a full packet has been received. # def receive_packet - return parser.recv(self.sock) + packet = parser.recv(self.sock) + if packet + packet.from_r(self.aes_key) + STDERR.puts("Current GUID in packet dispatcher: #{self.session_guid}\n") + if self.session_guid == '00000000-0000-0000-0000-000000000000' + STDERR.puts("Packet Session GUID : #{packet.session_guid.inspect}\n") + parts = packet.session_guid.unpack('H*')[0] + self.session_guid = [parts[0, 8], parts[8, 4], parts[12, 4], parts[16, 4], parts[20, 12]].join('-') + end + end + packet end # diff --git a/lib/rex/post/meterpreter/packet_parser.rb b/lib/rex/post/meterpreter/packet_parser.rb index 82665c1a9a..0575fd11bc 100644 --- a/lib/rex/post/meterpreter/packet_parser.rb +++ b/lib/rex/post/meterpreter/packet_parser.rb @@ -31,6 +31,7 @@ class PacketParser # def recv(sock) bytes_left = self.packet.raw_bytes_required + STDERR.puts("Waiting for bytes: #{bytes_left}\n") if bytes_left > 0 raw = sock.read(bytes_left) @@ -42,8 +43,8 @@ class PacketParser end if self.packet.raw_bytes_required == 0 + STDERR.puts("Packet is ready ...\n") packet = self.packet - packet.from_r reset return packet end diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb index 49815f90d3..0240434a63 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb @@ -469,8 +469,9 @@ class Console::CommandDispatcher::Core # Get the session GUID # def cmd_guid(*args) - client.guid = client.core.get_session_guid unless client.guid - print_good("Session GUID: #{client.guid}") + parts = client.session_guid.unpack('H*')[0] + guid = [parts[0, 8], parts[8, 4], parts[12, 4], parts[16, 4], parts[20, 12]].join('-') + print_good("Session GUID: #{guid}") end #