From b8848698d3c57c2b50ce3045f16696f772f983cc Mon Sep 17 00:00:00 2001 From: Adam Maddock Date: Mon, 14 Mar 2022 13:49:16 +0000 Subject: [PATCH 1/2] Updates to TLS module --- dshell/plugins/ssl/tls.py | 185 ++++++++++++++++++++++++++++---------- 1 file changed, 139 insertions(+), 46 deletions(-) diff --git a/dshell/plugins/ssl/tls.py b/dshell/plugins/ssl/tls.py index a1ff2d2..cb74a0d 100644 --- a/dshell/plugins/ssl/tls.py +++ b/dshell/plugins/ssl/tls.py @@ -53,6 +53,7 @@ class UnsupportedOption(Exception): SSL3_VERSION = 0x0300 TLS1_VERSION = 0x0301 TLS1_2_VERSION = 0x0303 +TLS1_3_VERSION = 0x0304 # Record type SSL3_RT_CHANGE_CIPHER_SPEC = 20 @@ -72,6 +73,14 @@ class UnsupportedOption(Exception): SSL3_MT_CLIENT_KEY_EXCHANGE = 16 SSL3_MT_FINISHED = 20 +# SSL/TLS Version Text Strings +tls_version_text = { + 0x0300 : 'SSL3', + 0x0301 : 'TLS1.0', + 0x0303 : 'TLS1.2', + 0x0304 : 'TLS1.3' +} + # Cipher Suit Text Strings ciphersuit_text = { 0x0000: 'TLS_NULL_WITH_NULL_NULL', @@ -440,7 +449,7 @@ def __init__(self, data): ######################################################################### self.Handshakes = [] if self.ContentType == SSL3_RT_HANDSHAKE: - + ############################### # Loop Through Handshakes # ############################### @@ -560,6 +569,7 @@ def l24(self, data): class TLSClientHello(TLSHandshake): def __init__(self, HandshakeType, HandshakeLength, data): + global ja3_available TLSHandshake.__init__(self, HandshakeType, HandshakeLength) data_length = len(data) offset = 0 @@ -649,8 +659,11 @@ def __init__(self, HandshakeType, HandshakeLength, data): # Slice Off the Extensions ################################ if ja3_available: - self.ja3_data.extend(ja3.ja3.process_extensions( - ja3.ja3.dpkt.ssl.TLSClientHello(data))) + try: + self.ja3_data.extend(ja3.ja3.process_extensions( + ja3.ja3.dpkt.ssl.TLSClientHello(data))) + except ja3.ja3.dpkt.ssl.SSL3Exception: + ja3_available = False self.extensions = {} self.raw_extensions = [] # ordered list of tuples (ex_type, ex_data) # self.extensions_length @@ -669,47 +682,7 @@ def __init__(self, HandshakeType, HandshakeLength, data): raise InsufficientData('%d bytes received by TLSClientHello, expected %d for extensions' % ( data_length, offset + self.extensions_length)) - ########################### - # Iterate the Extensions - ########################### - extension_server_name_list = [] - while len(extensions_data) >= 4: - - (ex_type, length) = struct.unpack('!HH', extensions_data[:4]) - if len(extensions_data) > length+4: - this_extension_data = extensions_data[4:4+length] - extensions_data = extensions_data[4+length:] - else: - this_extension_data = extensions_data[4:] - extensions_data = '' - - self.raw_extensions.append((ex_type, this_extension_data)) - - # server_name extension - # this_extension_data is defined on page 8 of RFC 3546 - # It is essentially a list of hostnames - if ex_type == 0: - server_name_list_length = struct.unpack( - '!H', this_extension_data[:2])[0] - if server_name_list_length > len(this_extension_data) - 2: - raise Error("Malformed ServerNameList") - server_name_list = this_extension_data[2:] - # Iterate the list - while len(server_name_list) > 0: - (name_type, name_length) = struct.unpack( - '!BH', server_name_list[0:3]) - name_data = server_name_list[3:name_length + 3] - if len(server_name_list) > name_length + 3: - server_name_list = server_name_list[name_length + 3:] - else: - server_name_list = '' - if name_type == 0: - extension_server_name_list.append(name_data) - else: - raise UnsupportedOption("Unknown NameType") - # After Loop - # add extension information to dictionary - self.extensions['server_name'] = extension_server_name_list + parse_tls_extensions(self, extensions_data) def ja3(self): if ja3_available: @@ -788,10 +761,92 @@ def __init__(self, HandshakeType, HandshakeLength, data): raise InsufficientData( '%d bytes received by TLSServerHello, expected %d for compression_method' % (data_length, offset + 1)) + ################################ + # TLS Extensions + ################################ + self.extensions = {} + self.raw_extensions = [] # ordered list of tuples (ex_type, ex_data) + # self.extensions_length + if data_length >= offset+2: + self.extensions_length = struct.unpack( + '!H', data[offset:offset+2])[0] + offset += 2 + else: + # No extensions + return + + # Copy Extension Blob into a new working variable + try: + extensions_data = data[offset:offset+self.extensions_length] + except: + raise InsufficientData('%d bytes received by TLSServerHello, expected %d for extensions' % ( + data_length, offset + self.extensions_length)) + + parse_tls_extensions(self, extensions_data) + ############################################################################## # Some Utility Functions ############################################################################## +def parse_tls_extensions(helloObj, extensions_data): + + ########################### + # Iterate the Extensions + ########################### + extension_server_name_list = [] + extension_supported_versions_list = [] + while len(extensions_data) >= 4: + + (ex_type, length) = struct.unpack('!HH', extensions_data[:4]) + if len(extensions_data) > length+4: + this_extension_data = extensions_data[4:4+length] + extensions_data = extensions_data[4+length:] + else: + this_extension_data = extensions_data[4:] + extensions_data = '' + + helloObj.raw_extensions.append((ex_type, this_extension_data)) + + # server_name extension + # this_extension_data is defined on page 8 of RFC 3546 + # It is essentially a list of hostnames + if len(this_extension_data) > 2 and ex_type == 0: + server_name_list_length = struct.unpack( + '!H', this_extension_data[:2])[0] + if server_name_list_length > len(this_extension_data) - 2: + raise Error("Malformed ServerNameList") + server_name_list = this_extension_data[2:] + # Iterate the list + while len(server_name_list) > 0: + (name_type, name_length) = struct.unpack( + '!BH', server_name_list[0:3]) + name_data = server_name_list[3:name_length + 3] + if len(server_name_list) > name_length + 3: + server_name_list = server_name_list[name_length + 3:] + else: + server_name_list = '' + if name_type == 0: + extension_server_name_list.append(name_data) + else: + raise UnsupportedOption("Unknown NameType") + # supported_versions extension + elif ex_type == 43: + if length == 2: + extension_supported_versions_list.append(struct.unpack('!H', this_extension_data[0:2])[0]) + continue + supported_versions_length = this_extension_data[0] + # Iterate the list + for offset in range(0,supported_versions_length,2): + extension_supported_versions_list.append(struct.unpack('!H', this_extension_data[offset+1:offset+3])[0]) + + # After Loop + # add extension information to dictionary + if len(extension_server_name_list): + helloObj.extensions['server_name'] = extension_server_name_list + if len(extension_supported_versions_list): + helloObj.extensions['supported_versions'] = extension_supported_versions_list + + def keyTypeToString(kt): global keytypes if kt in keytypes: @@ -882,9 +937,9 @@ def connection_handler(self, conn): certs_sc = [] server_cipher = None client_cipher_list = [] + tls_version = 0 for blob in conn.blobs: - blob.reassemble(allow_overlap=True, allow_padding=True) data = blob.data offset = 0 @@ -896,6 +951,9 @@ def connection_handler(self, conn): tlsrecord = TLS(data[offset:]) offset += tlsrecord.recordbytes + if tlsrecord.ProtocolVersion > tls_version: + tls_version = tlsrecord.ProtocolVersion + if tlsrecord.ContentType == SSL3_RT_HANDSHAKE: for hs in tlsrecord.Handshakes: # @@ -908,6 +966,10 @@ def connection_handler(self, conn): for server in hs.extensions['server_name']: client_names.add( server.decode('utf-8')) + if 'supported_versions' in hs.extensions: + if 'supported_versions' not in info: + info['supported_versions'] = {} + info['supported_versions']['client'] = hs.extensions['supported_versions'] if ja3_available: info['ja3'] = hs.ja3() info['ja3_digest'] = hs.ja3_digest() @@ -915,6 +977,12 @@ def connection_handler(self, conn): elif hs.HandshakeType == SSL3_MT_SERVER_HELLO: server_cipher = hs.cipher_suite + if blob.direction != 'sc': + inverted_ssl = True + if 'supported_versions' in hs.extensions: + if 'supported_versions' not in info: + info['supported_versions'] = {} + info['supported_versions']['server'] = hs.extensions['supported_versions'] # # Certificate. Looking for first server cert. @@ -957,8 +1025,29 @@ def connection_handler(self, conn): info['server_certs'][0]['subjectAltName'])) except KeyError: pass + + # Max Intersecting Versions of Client and Server + if 'supported_versions' in info: + if 'client' in info['supported_versions']: + if 'server' in info['supported_versions']: + overlap = [v for v in info['supported_versions']['client'] if v in info['supported_versions']['server']] + else: + # Only have client. Ignore values, not confirmed by server. + overlap = [0] + else: + overlap = info['supported_versions']['server'] + if max(overlap) > tls_version: + tls_version = max(overlap) + + # TLS Version + if tls_version in tls_version_text: + info['tls_version'] = tls_version_text[tls_version] + else: + info['tls_version'] = 'TLS: ' + hex(tls_version) + info['client_names'] = list(client_names) info['server_names'] = list(server_names) + # Cipher Lists if server_cipher in client_cipher_list: cipher_index = client_cipher_list.index(server_cipher) @@ -978,10 +1067,14 @@ def connection_handler(self, conn): return conn client_name = ','.join(info['client_names']) server_name = ','.join(info['server_names']) - if len(client_name) and client_name != server_name: + if len(client_name)>0 and len(server_name)<1: + msg = "%s" % (client_name) + elif len(client_name) and client_name != server_name: msg = "%s / %s" % (client_name, server_name) else: msg = server_name + if tls_version > 0: + msg += " ({tls_version})".format(**info) self.write(msg, **info) return conn From 3111384f53c49b6dfbc2f39e7fba7fea77df1bf0 Mon Sep 17 00:00:00 2001 From: Adam Maddock Date: Tue, 15 Mar 2022 20:31:32 +0000 Subject: [PATCH 2/2] Prior method set ja3_available flag to false, which effects this and all subsequent streams processed. New approach reports with debug statement. The ja3 array (and subsequent hash) still has usefulness even without the TLS extensions. --- dshell/plugins/ssl/tls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dshell/plugins/ssl/tls.py b/dshell/plugins/ssl/tls.py index cb74a0d..e16a5f9 100644 --- a/dshell/plugins/ssl/tls.py +++ b/dshell/plugins/ssl/tls.py @@ -663,7 +663,7 @@ def __init__(self, HandshakeType, HandshakeLength, data): self.ja3_data.extend(ja3.ja3.process_extensions( ja3.ja3.dpkt.ssl.TLSClientHello(data))) except ja3.ja3.dpkt.ssl.SSL3Exception: - ja3_available = False + self.debug("Error enumerating extensions for ja3") self.extensions = {} self.raw_extensions = [] # ordered list of tuples (ex_type, ex_data) # self.extensions_length