1#!/usr/bin/env python3 2# BlueKitchen GmbH (c) 2022 3 4# parse PacketLogger and dump encryption keys 5 6import sys 7import datetime 8import struct 9 10 11def as_hex(data): 12 str_list = [] 13 for byte in data: 14 str_list.append("{0:02x} ".format(byte)) 15 return ''.join(str_list) 16 17 18def as_key(data): 19 str_list = [] 20 for byte in data: 21 str_list.append("{0:02x}".format(byte)) 22 return ''.join(str_list) 23 24 25def as_bd_addr(data): 26 str_list = [] 27 for byte in data: 28 str_list.append("{0:02x}".format(byte)) 29 return ':'.join(str_list) 30 31 32def read_header(f): 33 bytes_read = f.read(13) 34 if bytes_read: 35 return struct.unpack(">IIIB", bytes_read) 36 else: 37 return (-1, 0, 0, 0) 38 39 40def handle_at_offset(data, offset): 41 return struct.unpack_from("<H", data, offset)[0]; 42 43 44def bd_addr_at_offset(data, offset): 45 peer_addr = reversed(data[offset:offset + 6]) 46 return as_bd_addr(peer_addr) 47 48 49class hci_connection: 50 51 def __init__(self, bd_addr, con_handle): 52 self.bd_addr = bd_addr 53 self.con_handle = con_handle; 54 55 56def connection_for_handle(con_handle): 57 if con_handle in connections: 58 return connections[con_handle] 59 else: 60 return None 61 62 63def handle_cmd(packet): 64 opcode = struct.unpack_from("<H", packet, 0)[0]; 65 if opcode == 0x201a: 66 # LE Long Term Key Request Reply 67 con_handle = handle_at_offset(packet, 3) 68 conn = connection_for_handle(con_handle) 69 print("LE LTK for %s - %s" % (conn.bd_addr, as_key(reversed(packet[5:5+16])))) 70 elif opcode == 0x2019: 71 # LE Enable Encryption command 72 con_handle = handle_at_offset(packet, 3) 73 conn = connection_for_handle(con_handle) 74 print("LE LTK for %s - %s" % (conn.bd_addr, as_key(reversed(packet[15:15+16])))) 75 elif opcode == 0x040b: 76 # Link Key Request Reply 77 bd_addr = bd_addr_at_offset(packet, 3) 78 print("Link Key for %s - %s" % (bd_addr, as_key(reversed(packet[9:9+16])))) 79 80def handle_evt(event): 81 if event[0] == 0x05: 82 # Disconnection Complete 83 con_handle = handle_at_offset(event, 3) 84 print("Disconnection Complete: handle 0x%04x" % con_handle) 85 connections.pop(con_handle, None) 86 elif event[0] == 0x18: 87 # Link Key Notification 88 bd_addr = bd_addr_at_offset(packet, 2) 89 print("Link Key for %s - %s" % (bd_addr, as_key(reversed(packet[8:8+16])))) 90 elif event[0] == 0x3e: 91 if event[2] == 0x01: 92 # LE Connection Complete 93 con_handle = handle_at_offset(event, 4); 94 peer_addr = bd_addr_at_offset(event, 8) 95 connection = hci_connection(peer_addr, con_handle) 96 connections[con_handle] = connection 97 print("LE Connection Complete: %s handle 0x%04x" % (peer_addr, con_handle)) 98 99 100# globals 101connections = {} 102 103if len(sys.argv) == 1: 104 print ('Dump encryptiong keys from PacketLogger trace file') 105 print ('Copyright 2023, BlueKitchen GmbH') 106 print ('') 107 print ('Usage: ', sys.argv[0], 'hci_dump.pklg') 108 exit(0) 109 110infile = sys.argv[1] 111 112with open (infile, 'rb') as fin: 113 pos = 0 114 try: 115 while True: 116 (entry_len, ts_sec, ts_usec, type) = read_header(fin) 117 if entry_len < 0: 118 break 119 packet_len = entry_len - 9 120 if packet_len > 66000: 121 print ("Error parsing pklg at offset %u (%x)." % (pos, pos)) 122 break 123 packet = fin.read(packet_len) 124 pos = pos + 4 + entry_len 125 if type == 0x00: 126 handle_cmd(packet) 127 elif type == 0x01: 128 handle_evt(packet) 129 elif type == 0x02: 130 pass 131 elif type == 0x03: 132 pass 133 134 except TypeError as e: 135 print(e) 136 print ("Error parsing pklg at offset %u (%x)." % (pos, pos)) 137