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 32access_messages = { 330x0000: 'Config Appkey Add', 340x0001: 'Config Appkey Update', 350x0002: 'Config Composition Data Status', 360x0003: 'Config Model Publication Set', 370x0004: 'Config Health Current Status', 380x0005: 'Config Health Fault Status', 390x0006: 'Config Heartbeat Publication Status', 400x8000: 'Config Appkey Delete', 410x8001: 'Config Appkey Get', 420x8002: 'Config Appkey List', 430x8003: 'Config Appkey Status', 440x8004: 'Config Health Attention Get', 450x8005: 'Config Health Attention Set', 460x8006: 'Config Health Attention Set Unacknowledged', 470x8007: 'Config Health Attention Status', 480x8008: 'Config Composition Data Get', 490x8009: 'Config Beacon Get', 500x800a: 'Config Beacon Set', 510x800b: 'Config Beacon Status', 520x800c: 'Config Default Ttl Get', 530x800d: 'Config Default Ttl Set', 540x800e: 'Config Default Ttl Status', 550x800f: 'Config Friend Get', 560x8010: 'Config Friend Set', 570x8011: 'Config Friend Status', 580x8012: 'Config Gatt Proxy Get', 590x8013: 'Config Gatt Proxy Set', 600x8014: 'Config Gatt Proxy Status', 610x8015: 'Config Key Refresh Phase Get', 620x8016: 'Config Key Refresh Phase Set', 630x8017: 'Config Key Refresh Phase Status', 640x8018: 'Config Model Publication Get', 650x8019: 'Config Model Publication Status', 660x801a: 'Config Model Publication Virtual Address Set', 670x801b: 'Config Model Subscription Add', 680x801c: 'Config Model Subscription Delete', 690x801d: 'Config Model Subscription Delete All', 700x801e: 'Config Model Subscription Overwrite', 710x801f: 'Config Model Subscription Status', 720x8020: 'Config Model Subscription Virtual Address Add', 730x8021: 'Config Model Subscription Virtual Address Delete', 740x8022: 'Config Model Subscription Virtual Address Overwrite', 750x8023: 'Config Network Transmit Get', 760x8024: 'Config Network Transmit Set', 770x8025: 'Config Network Transmit Status', 780x8026: 'Config Relay Get', 790x8027: 'Config Relay Set', 800x8028: 'Config Relay Status', 810x8029: 'Config Sig Model Subscription Get', 820x802a: 'Config Sig Model Subscription List', 830x802b: 'Config Vendor Model Subscription Get', 840x802c: 'Config Vendor Model Subscription List', 850x802d: 'Config Low Power Node Poll Timeout Get', 860x802e: 'Config Low Power Node Poll Timeout Status', 870x802f: 'Config Health Fault Clear', 880x8030: 'Config Health Fault Clear Unacknowledged', 890x8031: 'Config Health Fault Get', 900x8032: 'Config Health Fault Test', 910x8033: 'Config Health Fault Test Unacknowledged', 920x8034: 'Config Health Period Get', 930x8035: 'Config Health Period Set', 940x8036: 'Config Health Period Set Unacknowledged', 950x8037: 'Config Health Period Status', 960x8038: 'Config Heartbeat Publication Get', 970x8039: 'Config Heartbeat Publication Set', 980x803a: 'Config Heartbeat Subscription Get', 990x803b: 'Config Heartbeat Subscription Set', 1000x803c: 'Config Heartbeat Subscription Status', 1010x803d: 'Config Model App Bind', 1020x803e: 'Config Model App Status', 1030x803f: 'Config Model App Unbind', 1040x8040: 'Config Netkey Add', 1050x8041: 'Config Netkey Delete', 1060x8042: 'Config Netkey Get', 1070x8043: 'Config Netkey List', 1080x8044: 'Config Netkey Status', 1090x8045: 'Config Netkey Update', 1100x8046: 'Config Node Identity Get', 1110x8047: 'Config Node Identity Set', 1120x8048: 'Config Node Identity Status', 1130x8049: 'Config Node Reset', 1140x804a: 'Config Node Reset Status', 1150x804b: 'Config Sig Model App Get', 1160x804c: 'Config Sig Model App List', 1170x804d: 'Config Vendor Model App Get', 1180x804e: 'Config Vendor Model App List', 119} 120 121# helpers 122def read_net_32_from_file(f): 123 data = f.read(4) 124 if len(data) < 4: 125 return -1 126 return struct.unpack('>I', data)[0] 127 128def as_hex(data): 129 return ''.join(["{0:02x} ".format(byte) for byte in data]) 130 131def as_big_endian32(value): 132 return struct.pack('>I', value) 133 134def read_net_16(data): 135 return struct.unpack('>H', data)[0] 136 137def read_net_24(data): 138 return data[0] << 16 | struct.unpack('>H', data[1:3])[0] 139 140# log engine - simple pretty printer 141max_indent = 0 142def log_pdu(pdu, indent = 0, hide_properties = []): 143 spaces = ' ' * indent 144 print(spaces + "%-20s %s" % (pdu.type, pdu.summary)) 145 if indent >= max_indent: 146 return 147 for property in pdu.properties: 148 if property.key in hide_properties: 149 continue 150 if isinstance( property.value, int): 151 print (spaces + "|%20s: 0x%x (%u)" % (property.key, property.value, property.value)) 152 elif isinstance( property.value, bytes): 153 print (spaces + "|%20s: %s" % (property.key, as_hex(property.value))) 154 else: 155 print (spaces + "|%20s: %s" % (property.key, str(property.value))) 156 hide_properties.append(property.key) 157 print (spaces + '| data: ' + as_hex(pdu.data)) 158 print (spaces + '----') 159 for origin in pdu.origins: 160 log_pdu(origin, indent + 1, hide_properties) 161 162# classes 163 164class network_key(object): 165 def __init__(self, index, netkey): 166 self.index = index 167 self.netkey = netkey 168 (self.nid, self.encryption_key, self.privacy_key) = k2(netkey, b'\x00') 169 170 def __repr__(self): 171 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())) 172 173class application_key(object): 174 def __init__(self, index, appkey): 175 self.index = index 176 self.appkey = appkey 177 self.aid = k4(self.appkey) 178 179 def __repr__(self): 180 return ("AppKey-%04x %s: AID %02x" % (self.index, self.appkey.hex(), self.aid)) 181 182class property(object): 183 def __init__(self, key, value): 184 self.key = key 185 self.value = value 186 187class layer_pdu(object): 188 def __init__(self, pdu_type, pdu_data): 189 self.summary = '' 190 self.src = None 191 self.dst = None 192 self.type = pdu_type 193 self.data = pdu_data 194 self.origins = [] 195 self.properties = [] 196 197 def add_property(self, key, value): 198 self.properties.append(property(key, value)) 199 200class network_pdu(layer_pdu): 201 def __init__(self, pdu_data): 202 super().__init__("Network(unencrpyted)", pdu_data) 203 204 # parse pdu 205 self.ivi = (self.data[1] & 0x80) >> 7 206 self.nid = self.data[0] & 0x7f 207 self.ctl = (self.data[1] & 0x80) == 0x80 208 self.ttl = self.data[1] & 0x7f 209 self.seq = read_net_24(self.data[2:5]) 210 self.src = read_net_16(self.data[5:7]) 211 self.dst = read_net_16(self.data[7:9]) 212 self.lower_transport = self.data[9:] 213 214 # set properties 215 self.add_property('ivi', self.ivi) 216 self.add_property('nid', self.nid) 217 self.add_property('ctl', self.ctl) 218 self.add_property('ttl', self.ttl) 219 self.add_property('seq', self.seq) 220 self.add_property('src', self.src) 221 self.add_property('dst', self.dst) 222 self.add_property('lower_transport', self.lower_transport) 223 224class lower_transport_pdu(layer_pdu): 225 def __init__(self, network_pdu): 226 super().__init__('Lower Transport', network_pdu.lower_transport) 227 228 # inherit properties 229 self.ctl = network_pdu.ctl 230 self.seq = network_pdu.seq 231 self.src = network_pdu.src 232 self.dst = network_pdu.dst 233 self.add_property('ctl', self.ctl) 234 self.add_property('seq', self.seq) 235 self.add_property('src', self.src) 236 self.add_property('dst', self.dst) 237 238 # parse pdu and set propoerties 239 self.seg = (self.data[0] & 0x80) == 0x80 240 self.add_property('seg', self.seg) 241 self.szmic = False 242 if self.ctl: 243 self.opcode = self.data[0] & 0x7f 244 self.add_property('opcode', self.opcode) 245 else: 246 self.aid = self.data[0] & 0x3f 247 self.add_property('aid', self.aid) 248 self.akf = self.data[0] & 0x40 == 0x040 249 self.add_property('akf', self.akf) 250 if self.seg: 251 if not self.ctl: 252 self.szmic = self.data[1] & 0x80 == 0x80 253 self.add_property('szmic', self.szmic) 254 temp_12 = struct.unpack('>H', self.data[1:3])[0] 255 self.seq_zero = (temp_12 >> 2) & 0x1fff 256 self.add_property('seq_zero', self.seq_zero) 257 temp_23 = struct.unpack('>H', self.data[2:4])[0] 258 self.seg_o = (temp_23 >> 5) & 0x1f 259 self.add_property('seg_o', self.seg_o) 260 self.seg_n = temp_23 & 0x1f 261 self.add_property('seg_n', self.seg_n) 262 self.segment = self.data[4:] 263 self.add_property('segment', self.segment) 264 else: 265 self.seq_auth = self.seq 266 self.upper_transport = self.data[1:] 267 self.add_property('upper_transport', self.upper_transport) 268 269class upper_transport_pdu(layer_pdu): 270 def __init__(self, segment): 271 if segment.ctl: 272 super().__init__('Segmented Control', b'') 273 else: 274 super().__init__('Segmented Transport', b'') 275 self.ctl = segment.ctl 276 self.src = segment.src 277 self.dst = segment.dst 278 self.seq = segment.seq 279 self.akf = segment.akf 280 self.aid = segment.aid 281 self.szmic = segment.szmic 282 self.seg_n = segment.seg_n 283 self.seq_zero = segment.seq_zero 284 # TODO handle seq_zero overrun 285 self.seq_auth = segment.seq & 0xffffe000 | segment.seq_zero 286 self.add_property('seq_auth', self.seq_auth) 287 self.missing = (1 << (segment.seg_n+1)) - 1 288 self.data = b'' 289 self.processed = False 290 self.origins = [] 291 if self.ctl: 292 self.segment_len = 8 293 else: 294 self.segment_len = 12 295 296 self.add_property('src', self.src) 297 self.add_property('dst', self.dst) 298 self.add_property('aid', self.aid) 299 self.add_property('akf', self.akf) 300 self.add_property('segment_len', self.segment_len) 301 302 def add_segment(self, network_pdu): 303 self.origins.append(network_pdu) 304 self.missing &= ~ (1 << network_pdu.seg_o) 305 if network_pdu.seg_o == self.seg_n: 306 # last segment, set len 307 self.len = (self.seg_n * self.segment_len) + len(network_pdu.segment) 308 if len(self.data) == 0 and self.complete(): 309 self.reassemble() 310 311 def complete(self): 312 return self.missing == 0 313 314 def reassemble(self): 315 self.data = bytearray(self.len) 316 missing = (1 << (self.seg_n+1)) - 1 317 for pdu in self.origins: 318 # copy data 319 pos = pdu.seg_o * self.segment_len 320 self.data[pos:pos+len(pdu.segment)] = pdu.segment 321 # done? 322 missing &= ~ (1 << pdu.seg_o) 323 if missing == 0: 324 break 325 326class access_pdu(layer_pdu): 327 def __init__(self, lower_pdu, data): 328 super().__init__('Access', b'') 329 self.src = lower_pdu.src 330 self.dst = lower_pdu.dst 331 self.akf = lower_pdu.akf 332 self.aid = lower_pdu.aid 333 self.seq_auth = lower_pdu.seq_auth 334 self.data = data 335 self.add_property('src', self.src) 336 self.add_property('dst', self.dst) 337 self.add_property('akf', self.akf) 338 self.add_property('aid', self.aid) 339 self.add_property('seq_auth', self.seq_auth) 340 341 upper_bits = data[0] >> 6 342 if upper_bits == 0 or upper_bits == 1: 343 self.opcode = data[0] 344 self.opcode_len = 1 345 elif upper_bits == 2: 346 self.opcode = read_net_16(data[0:2]) 347 self.opcode_len = 2 348 elif upper_bits == 3: 349 self.opcode = read_net_24(data[0:3]) 350 self.opcode_len = 3 351 self.add_property('opcode', self.opcode) 352 self.params = data[self.opcode_len:] 353 self.add_property('params', self.params) 354 if self.opcode in access_messages: 355 self.summary = access_messages[self.opcode] 356 357def segmented_message_for_pdu(pdu): 358 if pdu.src in segmented_messages: 359 seg_message = segmented_messages[pdu.src] 360 # check seq zero 361 if pdu.seq_zero == seg_message.seq_zero: 362 return seg_message 363 # print("new segmented message: src %04x, seq_zero %04x" % (pdu.src, pdu.seq_zero)) 364 seg_message = upper_transport_pdu(pdu) 365 segmented_messages[pdu.src] = seg_message 366 return seg_message 367 368def mesh_set_iv_index(iv_index): 369 global ivi 370 ivi = iv_index 371 print ("IV-Index: " + as_big_endian32(ivi).hex()) 372 373# key management 374def mesh_add_netkey(index, netkey): 375 key = network_key(index, netkey) 376 print (key) 377 netkeys[index] = key 378 379def mesh_network_keys_for_nid(nid): 380 for (index, key) in netkeys.items(): 381 if key.nid == nid: 382 yield key 383 384def mesh_set_device_key(key): 385 global devkey 386 print ("DevKey: " + key.hex()) 387 devkey = key 388 389def mesh_add_application_key(index, appkey): 390 key = application_key(index, appkey) 391 print (key) 392 appkeys[index] = key 393 394def mesh_application_keys_for_aid(aid): 395 for (index, key) in appkeys.items(): 396 if key.aid == aid: 397 yield key 398 399def mesh_transport_nonce(pdu, nonce_type): 400 if pdu.szmic: 401 aszmic = 0x80 402 else: 403 aszmic = 0x00 404 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) 405 406def mesh_application_nonce(pdu): 407 return mesh_transport_nonce(pdu, 0x01) 408 409def mesh_device_nonce(pdu): 410 return mesh_transport_nonce(pdu, 0x02) 411 412def mesh_upper_transport_decrypt(message, data): 413 if message.szmic: 414 trans_mic_len = 8 415 else: 416 trans_mic_len = 4 417 ciphertext = data[:-trans_mic_len] 418 trans_mic = data[-trans_mic_len:] 419 nonce = mesh_device_nonce(message) 420 decrypted = None 421 if message.akf: 422 for key in mesh_application_keys_for_aid(message.aid): 423 decrypted = aes_ccm_decrypt(key.appkey, nonce, ciphertext, b'', trans_mic_len, trans_mic) 424 if decrypted != None: 425 break 426 else: 427 decrypted = aes_ccm_decrypt(devkey, nonce, ciphertext, b'', trans_mic_len, trans_mic) 428 return decrypted 429 430def mesh_process_control(control_pdu): 431 # TODO decode control message 432 # TODO add Seg Ack to sender access message origins 433 log_pdu(control_pdu, 0, []) 434 435def mesh_process_access(access_pdu): 436 log_pdu(access_pdu, 0, []) 437 438def mesh_process_network_pdu_tx(network_pdu_encrypted): 439 440 # network layer - decrypt pdu 441 nid = network_pdu_encrypted.data[0] & 0x7f 442 for key in mesh_network_keys_for_nid(nid): 443 network_pdu_decrypted_data = network_decrypt(network_pdu_encrypted.data, as_big_endian32(ivi), key.encryption_key, key.privacy_key) 444 if network_pdu_decrypted_data != None: 445 break 446 if network_pdu_decrypted_data == None: 447 network_pdu_encrypted.summary = 'No encryption key found' 448 log_pdu(network_pdu_encrypted, 0, []) 449 return 450 451 # decrypted network pdu 452 network_pdu_decrypted = network_pdu(network_pdu_decrypted_data) 453 network_pdu_decrypted.origins.append(network_pdu_encrypted) 454 455 # print("network pdu (enc)" + network_pdu_encrypted.data.hex()) 456 # print("network pdu (dec)" + network_pdu_decrypted_data.hex()) 457 458 # lower transport - reassemble 459 lower_transport = lower_transport_pdu(network_pdu_decrypted) 460 lower_transport.origins.append(network_pdu_decrypted) 461 462 if lower_transport.seg: 463 message = segmented_message_for_pdu(lower_transport) 464 message.add_segment(lower_transport) 465 if not message.complete(): 466 return 467 if message.processed: 468 return 469 470 message.processed = True 471 if message.ctl: 472 mesh_process_control(message) 473 else: 474 access_payload = mesh_upper_transport_decrypt(message, message.data) 475 if access_payload == None: 476 message.summary = 'No encryption key found' 477 log_pdu(message, 0, []) 478 else: 479 access = access_pdu(message, access_payload) 480 access.origins.append(message) 481 mesh_process_access(access) 482 483 else: 484 # print("lower_transport.ctl = " + str(lower_transport.ctl)) 485 if lower_transport.ctl: 486 control = layer_pdu('Unsegmented Control', lower_transport.data) 487 control.origins.append(lower_transport) 488 mesh_process_control(control) 489 else: 490 access_payload = mesh_upper_transport_decrypt(lower_transport, lower_transport.upper_transport) 491 if access_payload == None: 492 lower_transport.summary = 'No encryption key found' 493 log_pdu(lower_transport, 0, []) 494 else: 495 access = access_pdu(lower_transport, access_payload) 496 access.add_property('seq_auth', lower_transport.seq) 497 access.origins.append(lower_transport) 498 mesh_process_access(access) 499 500 501def mesh_process_beacon_pdu(adv_pdu): 502 log_pdu(adv_pdu, 0, []) 503 504def mesh_process_adv(adv_pdu): 505 ad_len = adv_pdu.data[0] - 1 506 ad_type = adv_pdu.data[1] 507 if ad_type == 0x2A: 508 network_pdu_encrypted = layer_pdu("Network(encrypted)", adv_data[2:2+ad_len]) 509 network_pdu_encrypted.add_property('ivi', adv_data[2] >> 7) 510 network_pdu_encrypted.add_property('nid', adv_data[2] & 0x7f) 511 network_pdu_encrypted.origins.append(adv_pdu) 512 mesh_process_network_pdu_tx(network_pdu_encrypted) 513 if ad_type == 0x2b: 514 beacon_pdu = layer_pdu("Beacon", adv_data[2:2+ad_len]) 515 beacon_pdu.origins.append(adv_pdu) 516 mesh_process_beacon_pdu(beacon_pdu) 517 518 519if len(sys.argv) == 1: 520 print ('Dump Mesh PacketLogger file') 521 print ('Copyright 2019, BlueKitchen GmbH') 522 print ('') 523 print ('Usage: ' + sys.argv[0] + 'hci_dump.pklg') 524 exit(0) 525 526infile = sys.argv[1] 527 528with open (infile, 'rb') as fin: 529 pos = 0 530 while True: 531 payload_length = read_net_32_from_file(fin) 532 if payload_length < 0: 533 break 534 ts_sec = read_net_32_from_file(fin) 535 ts_usec = read_net_32_from_file(fin) 536 type = ord(fin.read(1)) 537 packet_len = payload_length - 9; 538 if (packet_len > 66000): 539 print ("Error parsing pklg at offset %u (%x)." % (pos, pos)) 540 break 541 542 packet = fin.read(packet_len) 543 pos = pos + 4 + payload_length 544 # time = "[%s.%03u] " % (datetime.datetime.fromtimestamp(ts_sec).strftime("%Y-%m-%d %H:%M:%S"), ts_usec / 1000) 545 546 if type == 0: 547 # CMD 548 if packet[0] != 0x08: 549 continue 550 if packet[1] != 0x20: 551 continue 552 adv_data = packet[4:] 553 adv_pdu = layer_pdu("ADV", adv_data) 554 mesh_process_adv(adv_pdu) 555 556 elif type == 1: 557 # EVT 558 event = packet[0] 559 if event != 0x3e: 560 continue 561 event_len = packet[1] 562 if event_len != 0x2b: 563 continue 564 adv_data = packet[13:-1] 565 adv_pdu = layer_pdu("ADV", adv_data) 566 mesh_process_adv(adv_pdu) 567 568 elif type == 0xfc: 569 # LOG 570 log = packet.decode("utf-8") 571 parts = re.match('mesh-iv-index: (.*)', log) 572 if parts and len(parts.groups()) == 1: 573 mesh_set_iv_index(int(parts.groups()[0], 16)) 574 continue 575 parts = re.match('mesh-devkey: (.*)', log) 576 if parts and len(parts.groups()) == 1: 577 mesh_set_device_key(bytes.fromhex(parts.groups()[0])) 578 continue 579 parts = re.match('mesh-appkey-(.*): (.*)', log) 580 if parts and len(parts.groups()) == 2: 581 mesh_add_application_key(int(parts.groups()[0], 16), bytes.fromhex(parts.groups()[1])) 582 continue 583 parts = re.match('mesh-netkey-(.*): (.*)', log) 584 if parts and len(parts.groups()) == 2: 585 mesh_add_netkey(int(parts.groups()[0], 16), bytes.fromhex(parts.groups()[1])) 586 continue 587 588