From 47abdb9cfb7b6d0c78a43ee142209f4884dc794e Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Sat, 28 Sep 2024 15:41:39 +0200 Subject: [PATCH 1/2] Invert http check and cleanup uri extraction --- devtools/parse_pcap_klap.py | 125 ++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 64 deletions(-) diff --git a/devtools/parse_pcap_klap.py b/devtools/parse_pcap_klap.py index 36384631b..95b9dbdde 100755 --- a/devtools/parse_pcap_klap.py +++ b/devtools/parse_pcap_klap.py @@ -224,71 +224,68 @@ def main( packet = capture.next() # packet_number = capture._current_packet # we only care about http packets - if hasattr( - packet, "http" - ): # this is redundant, as pyshark is set to only load http packets - if hasattr(packet.http, "request_uri_path"): - uri = packet.http.get("request_uri_path") - elif hasattr(packet.http, "request_uri"): - uri = packet.http.get("request_uri") - else: - uri = None - if hasattr(packet.http, "request_uri_query"): - query = packet.http.get("request_uri_query") - # use regex to get: seq=(\d+) - seq = re.search(r"seq=(\d+)", query) - if seq is not None: - operator.seq = int( - seq.group(1) - ) # grab the sequence number from the query - data = ( - # Windows and linux file_data attribute returns different - # pretty format so get the raw field value. - packet.http.get_field_value("file_data", raw=True) - if hasattr(packet.http, "file_data") - else None - ) - match uri: - case "/app/request": - if packet.ip.dst != device_ip: - continue - message = bytes.fromhex(data) - try: - plaintext = operator.decrypt(message) - payload = json.loads(plaintext) - print(json.dumps(payload, indent=2)) - packets.append(payload) - except ValueError: - print("Insufficient data to decrypt thus far") - - case "/app/handshake1": - if packet.ip.dst != device_ip: - continue - message = bytes.fromhex(data) - operator.local_seed = message - response = None - while ( - True - ): # we are going to now look for the response to this request - response = capture.next() - if ( - hasattr(response, "http") - and hasattr(response.http, "response_for_uri") - and ( - response.http.response_for_uri - == packet.http.request_full_uri - ) - ): - break - data = response.http.get_field_value("file_data", raw=True) - message = bytes.fromhex(data) - operator.remote_seed = message[0:16] - operator.remote_auth_hash = message[16:] - - case "/app/handshake2": - continue # we don't care about this - case _: + # this is redundant, as pyshark is set to only load http packets + if not hasattr(packet, "http"): + continue + + uri = packet.http.get("request_uri_path", packet.http.get("request_uri")) + if uri is None: + continue + + if hasattr(packet.http, "request_uri_query"): + query = packet.http.get("request_uri_query") + # use regex to get: seq=(\d+) + seq = re.search(r"seq=(\d+)", query) + if seq is not None: + operator.seq = int( + seq.group(1) + ) # grab the sequence number from the query + + # Windows and linux file_data attribute returns different + # pretty format so get the raw field value. + data = packet.http.get_field_value("file_data", raw=True) + + match uri: + case "/app/request": + if packet.ip.dst != device_ip: + continue + message = bytes.fromhex(data) + try: + plaintext = operator.decrypt(message) + payload = json.loads(plaintext) + print(json.dumps(payload, indent=2)) + packets.append(payload) + except ValueError: + print("Insufficient data to decrypt thus far") + + case "/app/handshake1": + if packet.ip.dst != device_ip: continue + message = bytes.fromhex(data) + operator.local_seed = message + response = None + while ( + True + ): # we are going to now look for the response to this request + response = capture.next() + if ( + hasattr(response, "http") + and hasattr(response.http, "response_for_uri") + and ( + response.http.response_for_uri + == packet.http.request_full_uri + ) + ): + break + data = response.http.get_field_value("file_data", raw=True) + message = bytes.fromhex(data) + operator.remote_seed = message[0:16] + operator.remote_auth_hash = message[16:] + + case "/app/handshake2": + continue # we don't care about this + case _: + continue except StopIteration: break From 64268cda65fd9236ae604f1a4d400d6f1cf4e725 Mon Sep 17 00:00:00 2001 From: Teemu Rytilahti Date: Sat, 28 Sep 2024 15:47:16 +0200 Subject: [PATCH 2/2] Extract sequence extraction into its own function --- devtools/parse_pcap_klap.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/devtools/parse_pcap_klap.py b/devtools/parse_pcap_klap.py index 95b9dbdde..40799cc54 100755 --- a/devtools/parse_pcap_klap.py +++ b/devtools/parse_pcap_klap.py @@ -29,6 +29,18 @@ from kasa.protocol import DEFAULT_CREDENTIALS, get_default_credentials +def _get_seq_from_query(packet): + """Return sequence number for the query.""" + query = packet.http.get("request_uri_query") + if query is None: + raise Exception("No request_uri_query found") + # use regex to get: seq=(\d+) + seq = re.search(r"seq=(\d+)", query) + if seq is not None: + return int(seq.group(1)) + raise Exception("Unable to find sequence number") + + class MyEncryptionSession(KlapEncryptionSession): """A custom KlapEncryptionSession class that allows for decryption.""" @@ -232,14 +244,7 @@ def main( if uri is None: continue - if hasattr(packet.http, "request_uri_query"): - query = packet.http.get("request_uri_query") - # use regex to get: seq=(\d+) - seq = re.search(r"seq=(\d+)", query) - if seq is not None: - operator.seq = int( - seq.group(1) - ) # grab the sequence number from the query + operator.seq = _get_seq_from_query(packet) # Windows and linux file_data attribute returns different # pretty format so get the raw field value.