From 52a30d8ccc59d0715e6e6b9d754180e7ad878a46 Mon Sep 17 00:00:00 2001 From: Vladimir Kobal Date: Mon, 10 Feb 2025 23:24:12 +0100 Subject: [PATCH] Fix JA4 calculation for TLS ClientHello without extensions (#200) * Fix missing extensions * Ignore external repos and temporary python files * Fix JA4S calculation with missing extecsions --- .gitignore | 2 ++ python/ja4.py | 45 +++++++++++++++++++++++++++++++++++---------- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 0d8806c..057d837 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ rust/target wireshark/build-scripts/build +wireshark/build-scripts/wireshark-* .DS_Store .vscode/ +__pycache__/ diff --git a/python/ja4.py b/python/ja4.py index 95fb158..b80cc36 100644 --- a/python/ja4.py +++ b/python/ja4.py @@ -171,10 +171,20 @@ def to_ja4s(x, debug_stream): print (f"computing ja4s for stream {x['stream']}") ptype = 'q' if x['quic'] else 't' + if 'extensions' not in x: + x['extensions'] = [] + + if 'ciphers' not in x: + x['ciphers'] = [] + # get extensions in hex in the order they are present (include grease values) x['extensions'] = [ '{:04x}'.format(int(k)) for k in x['extensions'] ] ext_len = '{:02d}'.format(min(len(x['extensions']), 99)) - extensions = sha_encode(x['extensions']) + + if x['extensions']: + extensions = sha_encode(x['extensions']) + else: + extensions = '000000000000' # only one cipher for ja4s x['ciphers'] = x['ciphers'][2:] @@ -208,31 +218,46 @@ def to_ja4(x, debug_stream): print (f"computing ja4 for stream {x['stream']}") ptype = 'q' if x['quic'] else 't' + if 'extensions' not in x: + x['extensions'] = [] + + if 'ciphers' not in x: + x['ciphers'] = [] + x['extensions'] = [ '0x{:04x}'.format(int(k)) for k in x['extensions'] ] ext_len = '{:02d}'.format(min(len([ x for x in x['extensions'] if x not in GREASE_TABLE]), 99)) cache_update(x, 'client_ciphers', x['ciphers'], debug_stream) if ('0x000d' in x['extensions']): - x['signature_algorithms'] = [ y[2:] for y in get_signature_algorithms(x) ] + x['signature_algorithms'] = [ y[2:] for y in get_signature_algorithms(x) ] else: - x['signature_algorithms'] = '' + x['signature_algorithms'] = '' cache_update(x, 'client_extensions', x['extensions'], debug_stream) - # Modified to include original rendering x['sorted_extensions'], _len, _ = get_hex_sorted(x, 'extensions') x['original_extensions'], _len, _ = get_hex_sorted(x, 'extensions', sort=False) if (x['signature_algorithms'] == ''): - x['sorted_extensions'] = x['sorted_extensions'] - x['original_extensions'] = x['original_extensions'] + x['sorted_extensions'] = x['sorted_extensions'] + x['original_extensions'] = x['original_extensions'] else: x['sorted_extensions'] = x['sorted_extensions'] + '_' + ','.join(x['signature_algorithms']) x['original_extensions'] = x['original_extensions'] + '_' + ','.join(x['signature_algorithms']) - sorted_extensions = sha_encode(x['sorted_extensions']) - original_extensions = sha_encode(x['original_extensions']) + + if x['extensions']: + sorted_extensions = sha_encode(x['sorted_extensions']) + original_extensions = sha_encode(x['original_extensions']) + else: + sorted_extensions = '000000000000' + original_extensions = '000000000000' + x['sorted_ciphers'], cipher_len, sorted_ciphers = get_hex_sorted(x, 'ciphers') x['original_ciphers'], cipher_len, original_ciphers = get_hex_sorted(x, 'ciphers', sort=False) + if not x['ciphers']: + sorted_ciphers = '000000000000' + original_ciphers = '000000000000' + cipher_len = '00' sni = 'd' if 'domain' in x else 'i' x['version'] = x['version'][0] if isinstance(x['version'], list) else x['version'] @@ -555,7 +580,7 @@ def main(): display(x) # Hash calculations. - if 'extensions' in x and x['type'] == '2': + if x['hl'] == 'tls' and x.get('type') == '2': to_ja4s(x, STREAM) if x['hl'] == 'x509af': @@ -567,7 +592,7 @@ def main(): to_ja4h(x, STREAM) display(x) - if 'extensions' in x and x['type'] == '1': + if x['hl'] == 'tls' and x.get('type') == '1': try: to_ja4(x, STREAM) except Exception as e: