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 not 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.seq_auth = self.seq 180 self.upper_transport = self.data[1:] 181 self.add_property('upper_transport', self.upper_transport) 182 183class upper_transport_pdu(layer_pdu): 184 def __init__(self, segment): 185 if segment.ctl: 186 super().__init__('Segmented Control', b'') 187 else: 188 super().__init__('Segmented Transport', b'') 189 self.ctl = segment.ctl 190 self.src = segment.src 191 self.dst = segment.dst 192 self.seq = segment.seq 193 self.akf = segment.akf 194 self.aid = segment.aid 195 self.szmic = segment.szmic 196 self.seg_n = segment.seg_n 197 self.seq_zero = segment.seq_zero 198 # TODO handle seq_zero overrun 199 self.seq_auth = segment.seq & 0xffffe000 | segment.seq_zero 200 self.add_property('seq_auth', self.seq_auth) 201 self.missing = (1 << (segment.seg_n+1)) - 1 202 self.data = b'' 203 self.processed = False 204 self.origins = [] 205 if self.ctl: 206 self.segment_len = 8 207 else: 208 self.segment_len = 12 209 210 self.add_property('src', self.src) 211 self.add_property('dst', self.dst) 212 self.add_property('aid', self.aid) 213 self.add_property('akf', self.akf) 214 self.add_property('segment_len', self.segment_len) 215 216 def add_segment(self, network_pdu): 217 self.origins.append(network_pdu) 218 self.missing &= ~ (1 << network_pdu.seg_o) 219 if network_pdu.seg_o == self.seg_n: 220 # last segment, set len 221 self.len = (self.seg_n * self.segment_len) + len(network_pdu.segment) 222 if len(self.data) == 0 and self.complete(): 223 self.reassemble() 224 225 def complete(self): 226 return self.missing == 0 227 228 def reassemble(self): 229 self.data = bytearray(self.len) 230 missing = (1 << (self.seg_n+1)) - 1 231 for pdu in self.origins: 232 # copy data 233 pos = pdu.seg_o * self.segment_len 234 self.data[pos:pos+len(pdu.segment)] = pdu.segment 235 # done? 236 missing &= ~ (1 << pdu.seg_o) 237 if missing == 0: 238 break 239 240class access_pdu(layer_pdu): 241 def __init__(self, lower_pdu, data): 242 super().__init__('Access', b'') 243 self.src = lower_pdu.src 244 self.dst = lower_pdu.dst 245 self.akf = lower_pdu.akf 246 self.aid = lower_pdu.aid 247 self.seq_auth = lower_pdu.seq_auth 248 self.data = data 249 self.add_property('src', self.src) 250 self.add_property('dst', self.dst) 251 self.add_property('akf', self.akf) 252 self.add_property('aid', self.aid) 253 self.add_property('seq_auth', self.seq_auth) 254 255def segmented_message_for_pdu(pdu): 256 if pdu.src in segmented_messages: 257 seg_message = segmented_messages[pdu.src] 258 # check seq zero 259 if pdu.seq_zero == seg_message.seq_zero: 260 return seg_message 261 # print("new segmented message: src %04x, seq_zero %04x" % (pdu.src, pdu.seq_zero)) 262 seg_message = upper_transport_pdu(pdu) 263 segmented_messages[pdu.src] = seg_message 264 return seg_message 265 266def mesh_set_iv_index(iv_index): 267 global ivi 268 ivi = iv_index 269 print ("IV-Index: " + as_big_endian32(ivi).hex()) 270 271# key management 272def mesh_add_netkey(index, netkey): 273 key = network_key(index, netkey) 274 print (key) 275 netkeys[index] = key 276 277def mesh_network_keys_for_nid(nid): 278 for (index, key) in netkeys.items(): 279 if key.nid == nid: 280 yield key 281 282def mesh_set_device_key(key): 283 global devkey 284 print ("DevKey: " + key.hex()) 285 devkey = key 286 287def mesh_add_application_key(index, appkey): 288 key = application_key(index, appkey) 289 print (key) 290 appkeys[index] = key 291 292def mesh_application_keys_for_aid(aid): 293 for (index, key) in appkeys.items(): 294 if key.aid == aid: 295 yield key 296 297def mesh_transport_nonce(pdu, nonce_type): 298 if pdu.szmic: 299 aszmic = 0x80 300 else: 301 aszmic = 0x00 302 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) 303 304def mesh_application_nonce(pdu): 305 return mesh_transport_nonce(pdu, 0x01) 306 307def mesh_device_nonce(pdu): 308 return mesh_transport_nonce(pdu, 0x02) 309 310def mesh_upper_transport_decrypt(message, data): 311 if message.szmic: 312 trans_mic_len = 8 313 else: 314 trans_mic_len = 4 315 ciphertext = data[:-trans_mic_len] 316 trans_mic = data[-trans_mic_len:] 317 nonce = mesh_device_nonce(message) 318 decrypted = None 319 if message.akf: 320 for key in mesh_application_keys_for_aid(message.aid): 321 decrypted = aes_ccm_decrypt(key.appkey, nonce, ciphertext, b'', trans_mic_len, trans_mic) 322 if decrypted != None: 323 break 324 else: 325 decrypted = aes_ccm_decrypt(devkey, nonce, ciphertext, b'', trans_mic_len, trans_mic) 326 return decrypted 327 328def mesh_process_control(control_pdu): 329 # TODO decode control message 330 # TODO add Seg Ack to sender access message origins 331 log_pdu(control_pdu, 0, []) 332 333def mesh_process_access(access_pdu): 334 log_pdu(access_pdu, 0, []) 335 336def mesh_process_network_pdu_tx(network_pdu_encrypted): 337 338 # network layer - decrypt pdu 339 nid = network_pdu_encrypted.data[0] & 0x7f 340 for key in mesh_network_keys_for_nid(nid): 341 network_pdu_decrypted_data = network_decrypt(network_pdu_encrypted.data, as_big_endian32(ivi), key.encryption_key, key.privacy_key) 342 if network_pdu_decrypted_data != None: 343 break 344 if network_pdu_decrypted_data == None: 345 network_pdu_encrypted.status = 'No encryption key found' 346 log_pdu(network_pdu_encrypted, 0, []) 347 return 348 349 # decrypted network pdu 350 network_pdu_decrypted = network_pdu(network_pdu_decrypted_data) 351 network_pdu_decrypted.origins.append(network_pdu_encrypted) 352 353 # print("network pdu (enc)" + network_pdu_encrypted.data.hex()) 354 # print("network pdu (dec)" + network_pdu_decrypted_data.hex()) 355 356 # lower transport - reassemble 357 lower_transport = lower_transport_pdu(network_pdu_decrypted) 358 lower_transport.origins.append(network_pdu_decrypted) 359 360 if lower_transport.seg: 361 message = segmented_message_for_pdu(lower_transport) 362 message.add_segment(lower_transport) 363 if not message.complete(): 364 return 365 if message.processed: 366 return 367 368 message.processed = True 369 if message.ctl: 370 mesh_process_control(message) 371 else: 372 access_payload = mesh_upper_transport_decrypt(message, message.data) 373 if access_payload == None: 374 message.status = 'No encryption key found' 375 log_pdu(message, 0, []) 376 else: 377 access = access_pdu(message, access_payload) 378 access.origins.append(message) 379 mesh_process_access(access) 380 381 else: 382 # print("lower_transport.ctl = " + str(lower_transport.ctl)) 383 if lower_transport.ctl: 384 control = layer_pdu('Unsegmented Control', lower_transport.data) 385 control.origins.append(lower_transport) 386 mesh_process_control(control) 387 else: 388 access_payload = mesh_upper_transport_decrypt(lower_transport, lower_transport.upper_transport) 389 if access_payload == None: 390 lower_transport.status = 'No encryption key found' 391 log_pdu(lower_transport, 0, []) 392 else: 393 access = access_pdu(lower_transport, access_payload) 394 access.add_property('seq_auth', lower_transport.seq) 395 access.origins.append(lower_transport) 396 mesh_process_access(access) 397 398 399def mesh_process_beacon_pdu(adv_pdu): 400 log_pdu(adv_pdu, 0, []) 401 402def mesh_process_adv(adv_pdu): 403 ad_len = adv_pdu.data[0] - 1 404 ad_type = adv_pdu.data[1] 405 if ad_type == 0x2A: 406 network_pdu_encrypted = layer_pdu("Network(encrypted)", adv_data[2:2+ad_len]) 407 network_pdu_encrypted.add_property('ivi', adv_data[2] >> 7) 408 network_pdu_encrypted.add_property('nid', adv_data[2] & 0x7f) 409 network_pdu_encrypted.origins.append(adv_pdu) 410 mesh_process_network_pdu_tx(network_pdu_encrypted) 411 if ad_type == 0x2b: 412 beacon_pdu = layer_pdu("Beacon", adv_data[2:2+ad_len]) 413 beacon_pdu.origins.append(adv_pdu) 414 mesh_process_beacon_pdu(beacon_pdu) 415 416 417if len(sys.argv) == 1: 418 print ('Dump Mesh PacketLogger file') 419 print ('Copyright 2019, BlueKitchen GmbH') 420 print ('') 421 print ('Usage: ' + sys.argv[0] + 'hci_dump.pklg') 422 exit(0) 423 424infile = sys.argv[1] 425 426with open (infile, 'rb') as fin: 427 pos = 0 428 while True: 429 payload_length = read_net_32_from_file(fin) 430 if payload_length < 0: 431 break 432 ts_sec = read_net_32_from_file(fin) 433 ts_usec = read_net_32_from_file(fin) 434 type = ord(fin.read(1)) 435 packet_len = payload_length - 9; 436 if (packet_len > 66000): 437 print ("Error parsing pklg at offset %u (%x)." % (pos, pos)) 438 break 439 440 packet = fin.read(packet_len) 441 pos = pos + 4 + payload_length 442 # time = "[%s.%03u] " % (datetime.datetime.fromtimestamp(ts_sec).strftime("%Y-%m-%d %H:%M:%S"), ts_usec / 1000) 443 444 if type == 0: 445 # CMD 446 if packet[0] != 0x08: 447 continue 448 if packet[1] != 0x20: 449 continue 450 adv_data = packet[4:] 451 adv_pdu = layer_pdu("ADV", adv_data) 452 mesh_process_adv(adv_pdu) 453 454 elif type == 1: 455 # EVT 456 event = packet[0] 457 if event != 0x3e: 458 continue 459 event_len = packet[1] 460 if event_len != 0x2b: 461 continue 462 adv_data = packet[13:-1] 463 adv_pdu = layer_pdu("ADV", adv_data) 464 mesh_process_adv(adv_pdu) 465 466 elif type == 0xfc: 467 # LOG 468 log = packet.decode("utf-8") 469 parts = re.match('mesh-iv-index: (.*)', log) 470 if parts and len(parts.groups()) == 1: 471 mesh_set_iv_index(int(parts.groups()[0], 16)) 472 continue 473 parts = re.match('mesh-devkey: (.*)', log) 474 if parts and len(parts.groups()) == 1: 475 mesh_set_device_key(bytes.fromhex(parts.groups()[0])) 476 continue 477 parts = re.match('mesh-appkey-(.*): (.*)', log) 478 if parts and len(parts.groups()) == 2: 479 mesh_add_application_key(int(parts.groups()[0], 16), bytes.fromhex(parts.groups()[1])) 480 continue 481 parts = re.match('mesh-netkey-(.*): (.*)', log) 482 if parts and len(parts.groups()) == 2: 483 mesh_add_netkey(int(parts.groups()[0], 16), bytes.fromhex(parts.groups()[1])) 484 continue 485 486