1#!/usr/bin/env python3 2# BlueKitchen GmbH (c) 2019 3 4# primitive dump for PacketLogger format 5 6# APPLE PacketLogger 7# typedef struct { 8# uint32_t len; 9# uint32_t ts_sec; 10# uint32_t ts_usec; 11# uint8_t type; // 0xfc for note 12# } 13 14#define BLUETOOTH_DATA_TYPE_PB_ADV 0x29 // PB-ADV 15#define BLUETOOTH_DATA_TYPE_MESH_MESSAGE 0x2A // Mesh Message 16#define BLUETOOTH_DATA_TYPE_MESH_BEACON 0x2B // Mesh Beacon 17 18import re 19import sys 20import time 21import datetime 22import struct 23from mesh_crypto import * 24 25# state 26netkeys = {} 27appkeys = {} 28devkey = b'' 29ivi = b'\x00' 30segmented_messages = {} 31 32 33# helpers 34def read_net_32_from_file(f): 35 data = f.read(4) 36 if len(data) < 4: 37 return -1 38 return struct.unpack('>I', data)[0] 39 40def as_hex(data): 41 return ''.join(["{0:02x} ".format(byte) for byte in data]) 42 43def as_big_endian32(value): 44 return struct.pack('>I', value) 45 46def read_net_16(data): 47 return struct.unpack('>H', data)[0] 48 49def read_net_24(data): 50 return data[0] << 16 | struct.unpack('>H', data[1:3])[0] 51 52# log engine - simple pretty printer 53max_indent = 0 54def log_pdu(pdu, indent = 0, hide_properties = []): 55 spaces = ' ' * indent 56 print(spaces + pdu.type) 57 if len(pdu.status) > 0: 58 print (spaces + '| status: ' + pdu.status) 59 for property in pdu.properties: 60 if property.key in hide_properties: 61 continue 62 if isinstance( property.value, int): 63 print (spaces + "|%15s: 0x%x (%u)" % (property.key, property.value, property.value)) 64 elif isinstance( property.value, bytes): 65 print (spaces + "|%15s: %s" % (property.key, as_hex(property.value))) 66 else: 67 print (spaces + "|%15s: %s" % (property.key, str(property.value))) 68 hide_properties.append(property.key) 69 print (spaces + '| data: ' + as_hex(pdu.data)) 70 if indent >= max_indent: 71 return 72 print (spaces + '----') 73 for origin in pdu.origins: 74 log_pdu(origin, indent + 1, hide_properties) 75 76# classes 77 78class network_key(object): 79 def __init__(self, index, netkey): 80 self.index = index 81 self.netkey = netkey 82 (self.nid, self.encryption_key, self.privacy_key) = k2(netkey, b'\x00') 83 84 def __repr__(self): 85 return ("NetKey-%04x %s: NID %02x Encryption %s Privacy %s" % (self.index, self.netkey.hex(), self.nid, self.encryption_key.hex(), self.privacy_key.hex())) 86 87class application_key(object): 88 def __init__(self, index, appkey): 89 self.index = index 90 self.appkey = appkey 91 self.aid = k4(self.appkey) 92 93 def __repr__(self): 94 return ("AppKey-%04x %s: AID %02x" % (self.index, self.appkey.hex(), self.aid)) 95 96class property(object): 97 def __init__(self, key, value): 98 self.key = key 99 self.value = value 100 101class layer_pdu(object): 102 def __init__(self, pdu_type, pdu_data): 103 self.status = '' 104 self.src = None 105 self.dst = None 106 self.type = pdu_type 107 self.data = pdu_data 108 self.origins = [] 109 self.properties = [] 110 111 def add_property(self, key, value): 112 self.properties.append(property(key, value)) 113 114class network_pdu(layer_pdu): 115 def __init__(self, pdu_data): 116 super().__init__("Network(unencrpyted)", pdu_data) 117 118 # parse pdu 119 self.ivi = (self.data[1] & 0x80) >> 7 120 self.nid = self.data[0] & 0x7f 121 self.ctl = (self.data[1] & 0x80) == 0x80 122 self.ttl = self.data[1] & 0x7f 123 self.seq = read_net_24(self.data[2:5]) 124 self.src = read_net_16(self.data[5:7]) 125 self.dst = read_net_16(self.data[7:9]) 126 self.lower_transport = self.data[9:] 127 128 # set properties 129 self.add_property('ivi', self.ivi) 130 self.add_property('nid', self.nid) 131 self.add_property('ctl', self.ctl) 132 self.add_property('ttl', self.ttl) 133 self.add_property('seq', self.seq) 134 self.add_property('src', self.src) 135 self.add_property('dst', self.dst) 136 self.add_property('lower_transport', self.lower_transport) 137 138class lower_transport_pdu(layer_pdu): 139 def __init__(self, network_pdu): 140 super().__init__('Lower Transport', network_pdu.lower_transport) 141 142 # inherit properties 143 self.ctl = network_pdu.ctl 144 self.seq = network_pdu.seq 145 self.src = network_pdu.src 146 self.dst = network_pdu.dst 147 self.add_property('ctl', self.ctl) 148 self.add_property('seq', self.seq) 149 self.add_property('src', self.src) 150 self.add_property('dst', self.dst) 151 152 # parse pdu and set propoerties 153 self.seg = (self.data[0] & 0x80) == 0x80 154 self.add_property('seg', self.seg) 155 self.szmic = False 156 if self.ctl: 157 self.opcode = self.data[0] & 0x7f 158 self.add_property('opcode', self.opcode) 159 else: 160 self.aid = self.data[0] & 0x3f 161 self.add_property('aid', self.aid) 162 self.akf = self.data[0] & 0x40 == 0x040 163 self.add_property('akf', self.akf) 164 if self.seg: 165 if self.ctl: 166 self.szmic = self.data[1] & 0x80 == 0x80 167 self.add_property('szmic', self.szmic) 168 temp_12 = struct.unpack('>H', self.data[1:3])[0] 169 self.seq_zero = (temp_12 >> 2) & 0x1fff 170 self.add_property('seq_zero', self.seq_zero) 171 temp_23 = struct.unpack('>H', self.data[2:4])[0] 172 self.seg_o = (temp_23 >> 5) & 0x1f 173 self.add_property('seg_o', self.seg_o) 174 self.seg_n = temp_23 & 0x1f 175 self.add_property('seg_n', self.seg_n) 176 self.segment = self.data[4:] 177 self.add_property('segment', self.segment) 178 else: 179 self.upper_transport = self.data[1:] 180 self.add_property('upper_transport', self.upper_transport) 181 182class uppert_transport_pdu(layer_pdu): 183 def __init__(self, segment): 184 if segment.ctl: 185 super().__init__('Segmented Control', b'') 186 else: 187 super().__init__('Segmented Transport', b'') 188 self.ctl = segment.ctl 189 self.src = segment.src 190 self.dst = segment.dst 191 self.seq = segment.seq 192 self.akf = segment.akf 193 self.aid = segment.aid 194 self.szmic = segment.szmic 195 self.seg_n = segment.seg_n 196 self.seq_zero = segment.seq_zero 197 # TODO handle seq_zero overrun 198 self.seq_auth = segment.seq & 0xffffe000 | segment.seq_zero 199 self.add_property('seq_auth', self.seq_auth) 200 self.missing = (1 << (segment.seg_n+1)) - 1 201 self.data = b'' 202 self.processed = False 203 self.origins = [] 204 if self.ctl: 205 self.segment_len = 8 206 else: 207 self.segment_len = 12 208 209 self.add_property('src', self.src) 210 self.add_property('dst', self.dst) 211 self.add_property('segment_len', self.segment_len) 212 213 def add_segment(self, network_pdu): 214 self.origins.append(network_pdu) 215 self.missing &= ~ (1 << network_pdu.seg_o) 216 if network_pdu.seg_o == self.seg_n: 217 # last segment, set len 218 self.len = (self.seg_n * self.segment_len) + len(network_pdu.segment) 219 if len(self.data) == 0 and self.complete(): 220 self.reassemble() 221 222 def complete(self): 223 return self.missing == 0 224 225 def reassemble(self): 226 self.data = bytearray(self.len) 227 missing = (1 << (self.seg_n+1)) - 1 228 for pdu in self.origins: 229 # copy data 230 pos = pdu.seg_o * self.segment_len 231 self.data[pos:pos+len(pdu.segment)] = pdu.segment 232 # done? 233 missing &= ~ (1 << pdu.seg_o) 234 if missing == 0: 235 break 236 237class access_pdu(layer_pdu): 238 def __init__(self, lower_pdu, data): 239 super().__init__('Access', b'') 240 self.src = lower_pdu.src 241 self.dst = lower_pdu.dst 242 self.akf = lower_pdu.akf 243 self.aid = lower_pdu.aid 244 self.data = data 245 self.add_property('src', self.src) 246 self.add_property('dst', self.dst) 247 self.add_property('akf', self.akf) 248 self.add_property('aid', self.aid) 249 250def segmented_message_for_pdu(pdu): 251 if pdu.src in segmented_messages: 252 seg_message = segmented_messages[pdu.src] 253 # check seq zero 254 else: 255 seg_message = uppert_transport_pdu(pdu) 256 segmented_messages[pdu.src] = seg_message 257 return seg_message 258 259def mesh_set_iv_index(iv_index): 260 global ivi 261 ivi = iv_index 262 print ("IV-Index: " + as_big_endian32(ivi).hex()) 263 264# key management 265def mesh_add_netkey(index, netkey): 266 key = network_key(index, netkey) 267 print (key) 268 netkeys[index] = key 269 270def mesh_network_keys_for_nid(nid): 271 for (index, key) in netkeys.items(): 272 if key.nid == nid: 273 yield key 274 275def mesh_set_device_key(key): 276 global devkey 277 print ("DevKey: " + key.hex()) 278 devkey = key 279 280def mesh_add_application_key(index, appkey): 281 key = application_key(index, appkey) 282 print (key) 283 appkeys[index] = key 284 285def mesh_application_keys_for_aid(aid): 286 for (index, key) in appkeys.items(): 287 if key.aid == aid: 288 yield key 289 290def mesh_transport_nonce(pdu, nonce_type): 291 if pdu.szmic: 292 aszmic = 0x80 293 else: 294 aszmic = 0x00 295 return bytes( [nonce_type, aszmic, pdu.seq_auth >> 16, (pdu.seq_auth >> 8) & 0xff, pdu.seq_auth & 0xff, pdu.src >> 8, pdu.src & 0xff, pdu.dst >> 8, pdu.dst & 0xff]) + as_big_endian32(ivi) 296 297def mesh_application_nonce(pdu): 298 return mesh_transport_nonce(pdu, 0x01) 299 300def mesh_device_nonce(pdu): 301 return mesh_transport_nonce(pdu, 0x02) 302 303def mesh_upper_transport_decrypt(message, data): 304 if message.szmic: 305 trans_mic_len = 8 306 else: 307 trans_mic_len = 4 308 ciphertext = data[:-trans_mic_len] 309 trans_mic = data[-trans_mic_len:] 310 nonce = mesh_device_nonce(message) 311 decrypted = None 312 if message.akf: 313 for key in mesh_application_keys_for_aid(message.aid): 314 decrypted = aes_ccm_decrypt(key.appkey, nonce, ciphertext, b'', trans_mic_len, trans_mic) 315 if decrypted != None: 316 break 317 else: 318 decrypted = aes_ccm_decrypt(devkey, nonce, ciphertext, b'', trans_mic_len, trans_mic) 319 return decrypted 320 321def mesh_process_control(control_pdu): 322 # TODO decode control message 323 # TODO add Seg Ack to sender access message origins 324 log_pdu(control_pdu, 0, []) 325 326def mesh_proess_access(access_pdu): 327 log_pdu(access_pdu, 0, []) 328 329def mesh_process_network_pdu_tx(network_pdu_encrypted): 330 331 # network layer - decrypt pdu 332 nid = network_pdu_encrypted.data[0] & 0x7f 333 for key in mesh_network_keys_for_nid(nid): 334 network_pdu_decrypted_data = network_decrypt(network_pdu_encrypted.data, as_big_endian32(ivi), key.encryption_key, key.privacy_key) 335 if network_pdu_decrypted_data != None: 336 break 337 if network_pdu_decrypted_data == None: 338 network_pdu_encrypted.status = 'No encryption key found' 339 log_pdu(network_pdu_encrypted, 0, []) 340 return 341 342 # decrypted network pdu 343 network_pdu_decrypted = network_pdu(network_pdu_decrypted_data) 344 network_pdu_decrypted.origins.append(network_pdu_encrypted) 345 346 # lower transport - reassemble 347 lower_transport = lower_transport_pdu(network_pdu_decrypted) 348 lower_transport.origins.append(network_pdu_decrypted) 349 350 if lower_transport.seg: 351 message = segmented_message_for_pdu(lower_transport) 352 message.add_segment(lower_transport) 353 if not message.complete(): 354 return 355 if message.processed: 356 return 357 358 message.processed = True 359 if message.ctl: 360 mesh_process_control(message) 361 else: 362 access_payload = mesh_upper_transport_decrypt(message, message.data) 363 if access_payload == None: 364 message.status = 'No encryption key found' 365 log_pdu(message, 0, []) 366 else: 367 access = access_pdu(message, access_payload) 368 access.origins.append(message) 369 mesh_proess_access(access) 370 371 else: 372 if lower_transport.ctl: 373 control = layer_pdu('Unsegmented Control', lower_transport.data) 374 control.origins.add(lower_transport) 375 mesh_process_control(control) 376 else: 377 access_payload = mesh_upper_transport_decrypt(lower_transport, lower_transport.upper_transport) 378 if access_payload == None: 379 lower_transport.status = 'No encryption key found' 380 log_pdu(lower_transport, 0, []) 381 else: 382 access = access_pdu(lower_transport, access_payload) 383 access.add_property('seq_auth', lower_transport.seq) 384 access.origins.append(lower_transport) 385 mesh_proess_access(access) 386 387 388def mesh_process_beacon_pdu(adv_pdu): 389 log_pdu(adv_pdu, 0, []) 390 391def mesh_process_adv(adv_pdu): 392 ad_type = adv_pdu.data[1] 393 if ad_type == 0x2A: 394 network_pdu_encrypted = layer_pdu("Network(encrypted)", adv_data[2:]) 395 network_pdu_encrypted.add_property('ivi', adv_data[2] >> 7) 396 network_pdu_encrypted.add_property('nid', adv_data[2] & 0x7f) 397 network_pdu_encrypted.origins.append(adv_pdu) 398 mesh_process_network_pdu_tx(network_pdu_encrypted) 399 if ad_type == 0x2b: 400 beacon_pdu = layer_pdu("Beacon", adv_data[2:]) 401 beacon_pdu.origins.append(adv_pdu) 402 mesh_process_beacon_pdu(beacon_pdu) 403 404 405if len(sys.argv) == 1: 406 print ('Dump Mesh PacketLogger file') 407 print ('Copyright 2019, BlueKitchen GmbH') 408 print ('') 409 print ('Usage: ' + sys.argv[0] + 'hci_dump.pklg') 410 exit(0) 411 412infile = sys.argv[1] 413 414with open (infile, 'rb') as fin: 415 pos = 0 416 while True: 417 payload_length = read_net_32_from_file(fin) 418 if payload_length < 0: 419 break 420 ts_sec = read_net_32_from_file(fin) 421 ts_usec = read_net_32_from_file(fin) 422 type = ord(fin.read(1)) 423 packet_len = payload_length - 9; 424 if (packet_len > 66000): 425 print ("Error parsing pklg at offset %u (%x)." % (pos, pos)) 426 break 427 428 packet = fin.read(packet_len) 429 pos = pos + 4 + payload_length 430 # time = "[%s.%03u] " % (datetime.datetime.fromtimestamp(ts_sec).strftime("%Y-%m-%d %H:%M:%S"), ts_usec / 1000) 431 432 if type == 0: 433 # CMD 434 if packet[0] != 0x08: 435 continue 436 if packet[1] != 0x20: 437 continue 438 adv_data = packet[4:] 439 adv_pdu = layer_pdu("ADV", adv_data) 440 mesh_process_adv(adv_pdu) 441 442 elif type == 1: 443 # EVT 444 event = packet[0] 445 if event != 0x3e: 446 continue 447 event_len = packet[1] 448 if event_len != 0x2b: 449 continue 450 adv_data = packet[13:-1] 451 adv_pdu = layer_pdu("ADV", adv_data) 452 mesh_process_adv(adv_pdu) 453 454 elif type == 0xfc: 455 # LOG 456 log = packet.decode("utf-8") 457 parts = re.match('mesh-iv-index: (.*)', log) 458 if parts and len(parts.groups()) == 1: 459 mesh_set_iv_index(int(parts.groups()[0], 16)) 460 continue 461 parts = re.match('mesh-devkey: (.*)', log) 462 if parts and len(parts.groups()) == 1: 463 mesh_set_device_key(bytes.fromhex(parts.groups()[0])) 464 continue 465 parts = re.match('mesh-appkey-(.*): (.*)', log) 466 if parts and len(parts.groups()) == 2: 467 mesh_add_application_key(int(parts.groups()[0], 16), bytes.fromhex(parts.groups()[1])) 468 continue 469 parts = re.match('mesh-netkey-(.*): (.*)', log) 470 if parts and len(parts.groups()) == 2: 471 mesh_add_netkey(int(parts.groups()[0], 16), bytes.fromhex(parts.groups()[1])) 472 continue 473 474