diff --git a/decoder.py b/decoder.py new file mode 100644 index 0000000..d60213b --- /dev/null +++ b/decoder.py @@ -0,0 +1,137 @@ +from Crypto.Cipher import AES +import struct +import json + +inEnc = False +inSize = 0 +inCmd = 0 +inSet = False + +outEnc = False +outCmd = 0 +outSize = 0 +outSet = False + +encKey = 0 + +def build_iv_le(key): + iv = bytearray(16) + iv[1] = (key >> 0) & 0xFF + iv[4] = (key >> 8) & 0xFF + iv[9] = (key >> 16) & 0xFF + iv[12] = (key >> 24) & 0xFF + return bytes(iv) + +def generate_key(key): + base = "SkBRDy3gmrw1ieH0" + key_bytes = bytearray(16) + for i in range(16): + key_bytes[i] = ord(base[(key + i) % 16]) + return bytes(key_bytes) + +def decrypt_hex_cipher(ciphertext, intkey, iv_builder): + key = generate_key(intkey) + iv = iv_builder(intkey) + cipher = AES.new(key, AES.MODE_CFB, iv=iv, segment_size=128) + plaintext = cipher.decrypt(ciphertext) + return plaintext + +def bytes_to_printable_ascii(byte_data): + return ''.join((chr(b) if 32 <= b <= 126 else '.') for b in byte_data) + +def bytes_to_int_list_le(byte_data): + length = len(byte_data) + padded_length = (length + 3) // 4 * 4 + padded = byte_data.ljust(padded_length, b'\x00') + return list(struct.unpack('<' + 'I' * (padded_length // 4), padded)) + +def check_types(capdata: bytes) -> tuple[bool, bool]: + is_aa = capdata.startswith(bytes.fromhex('aa55aa55')) + is_bb = capdata.startswith(bytes.fromhex('bb55bb55')) + if is_aa or is_bb: + if len(capdata) >= 12: + size = int.from_bytes(capdata[4:8], 'little') + cmd = int.from_bytes(capdata[8:12], 'little') + return True, is_bb, size, cmd + return False, False, None, None + +def processPacket(frame, out, capdata, cmd, size, enc): + global encKey + if enc and encKey != 0 and size > 0: + capdata = decrypt_hex_cipher(capdata, encKey, build_iv_le) + if out and cmd == 240 and size == 4 and len(capdata) >= 4: + encKey = int.from_bytes(capdata[0:4], 'little') + if cmd == 170: + return + res = f"{frame:>5}{cmd:>5} {'<' if not out else '>'}{' ' if not enc else '*'} {size:<5} : " + str = "" + if len(capdata)<1000: + str = bytes_to_printable_ascii(capdata) + else: + bts = "" + for i in range(0, min(len(capdata), 20), 4): + chunk = capdata[i:i+4] + val = int.from_bytes(chunk, 'little') + bts = bts+f"{val} " + if size == 4: + str = int.from_bytes(capdata[0:4], 'little') + print(f"{'\033[92m' if not out else '\033[93m'}{res}{str}\033[0m") + print(f" {' '.join(f'{b:02x}' for b in capdata[:20])}") + + +def processInc(frame, capdata): + global inEnc, inSize, inCmd, inSet + header, enc, size, cmd = check_types(capdata) + if header: + inEnc = enc + inSize = size + inCmd = cmd + inSet = header + if inSize == 0: + processPacket(frame, False, bytearray(), inCmd, inSize, inEnc) + return + + if inSet: + processPacket(frame, False, capdata, inCmd, inSize, inEnc) + inSet = header + +def processOut(frame, capdata): + global outEnc, outSize, outCmd, outSet + header, enc, size, cmd = check_types(capdata) + if header: + outEnc = enc + outSize = size + outCmd = cmd + outSet = header + if outSize == 0: + processPacket(frame, True, bytearray(), outCmd, outSize, outEnc) + return + + if outSet: + processPacket(frame, True, capdata, outCmd, outSize, outEnc) + outSet = header + + +def processFile(file_path): + with open(file_path) as f: + data = json.load(f) + + for entry in data: + layers = entry['_source']['layers'] + usb = layers.get('usb', {}) + frame_number = layers.get('frame', {}).get('frame.number') + direction = usb.get('usb.endpoint_address_tree', {}).get('usb.endpoint_address.direction') + urb_type = usb.get('usb.urb_type') + capdata = entry['_source']['layers'].get('usb.capdata') + + if capdata: + byte_array = bytes(int(b, 16) for b in capdata.split(':')) + if (direction == "0" and urb_type == "'S'"): + processOut(frame_number, byte_array) + if(direction == "1" and urb_type == "'C'"): + processInc(frame_number, byte_array) + +print("This tool decode carlinkit protocol") +print("It requred JSON export of USB events filtered by device") +file_path = input("Path to JSON export from PCAPNG: ").strip().strip("'") +processFile(file_path) diff --git a/settings.txt b/settings.txt index 1670341..29b4063 100644 --- a/settings.txt +++ b/settings.txt @@ -52,6 +52,18 @@ # Nigh mode. 0 for day mode, 1 for night mode, 2 for automatic #night-mode = 2 +# Enable 5Ghz wifi. Otherwise will use 2,4Ghz +#wifi-5 = true + +# Enable bluetooth audio. If enables the audio will be played not through the dongle but through bluetooth connection +#bluetooth-audio = false + +# Microphone in use. Copied directly from carlinkit app. +# 1 - Car +# 2 - Box +# 3 - Phone +#mic-type = 1 + # Target DPI reported to device. Set 0 for default. Not sure if it affects anything #dpi = 0 diff --git a/src/protocol.cpp b/src/protocol.cpp index 875e484..1bd4fbd 100644 --- a/src/protocol.cpp +++ b/src/protocol.cpp @@ -171,6 +171,15 @@ void Protocol::onDevice(bool connected) sendFile("/etc/box_name", "CarPlay"); if (Settings::autoconnect) sendInt(CMD_CONTROL, 1002); + sendInt(CMD_CONTROL, Settings::bluetoothAudio ? 22 : 23); + sendInt(CMD_CONTROL, Settings::wifi5 ? 25 : 24); + int mic = 7; + if (Settings::micType == 2) + mic = 15; + if (Settings::micType == 3) + mic = 21; + sendInt(CMD_CONTROL, mic); + if (Settings::encryption) sendEncryption(); } diff --git a/src/settings.h b/src/settings.h index 3bf720d..01878c3 100644 --- a/src/settings.h +++ b/src/settings.h @@ -24,7 +24,10 @@ public: static inline Setting autoconnect{"autoconnect", true}; static inline Setting weakCharge{"weak-charge", true}; static inline Setting leftDrive{"left-hand-drive", true}; - static inline Setting nightMode{"night-mode", 2}; + static inline Setting nightMode{"night-mode", 2}; + static inline Setting wifi5{"wifi-5", true}; + static inline Setting bluetoothAudio{"bluetooth-audio", false}; + static inline Setting micType{"mic-type", 1}; static inline Setting dpi{"dpi", 0}; // Application configuration section