diff --git a/application.fam b/application.fam index f40f950..9482b7e 100644 --- a/application.fam +++ b/application.fam @@ -5,7 +5,7 @@ App( targets=["f7"], entry_point="protopirate_app", requires=["gui"], - stack_size=2 * 1024, + stack_size=4 * 1024, fap_description="Decode car key fob signals from Sub-GHz", fap_version="1.8", fap_icon="images/protopirate_10px.png", diff --git a/keystore/vw b/keystore/vw new file mode 100644 index 0000000..745067a --- /dev/null +++ b/keystore/vw @@ -0,0 +1,6 @@ +Filetype: Flipper SubGhz Keystore RAW File +Version: 0 +Encryption: 1 +IV: B9 A8 C0 F6 9A 78 B1 CB 3E A7 69 0E 86 53 9F A7 +Encrypt_data: RAW +BA2A8B783B6DB3E17DEF07EA2AE0104FAF722A07775E96338D734FFD0EA8BB29BF1A5A6451B1784AE2B51D967EFB45EAFAE2C20D5721553727C026D4009BBCB0 \ No newline at end of file diff --git a/protocols/aut64.c b/protocols/aut64.c new file mode 100644 index 0000000..a946d67 --- /dev/null +++ b/protocols/aut64.c @@ -0,0 +1,485 @@ +#include +#include "aut64.h" + +// https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_garcia.pdf + +/* + * AUT64 algorithm, 12 rounds + * 8 bytes block size, 8 bytes key size + * 8 bytes pbox size, 16 bytes sbox size + * + * Based on: Reference AUT64 implementation in JavaScript (aut64.js) + * Vencislav Atanasov, 2025-09-13 + * + * Based on: Reference AUT64 implementation in python + * C Hicks, hkscy.org, 03-01-19 + */ + +static const uint8_t table_ln[AUT64_NUM_ROUNDS][8] = { + { + 0x4, + 0x5, + 0x6, + 0x7, + 0x0, + 0x1, + 0x2, + 0x3, + }, // Round 0 + { + 0x5, + 0x4, + 0x7, + 0x6, + 0x1, + 0x0, + 0x3, + 0x2, + }, // Round 1 + { + 0x6, + 0x7, + 0x4, + 0x5, + 0x2, + 0x3, + 0x0, + 0x1, + }, // Round 2 + { + 0x7, + 0x6, + 0x5, + 0x4, + 0x3, + 0x2, + 0x1, + 0x0, + }, // Round 3 + { + 0x0, + 0x1, + 0x2, + 0x3, + 0x4, + 0x5, + 0x6, + 0x7, + }, // Round 4 + { + 0x1, + 0x0, + 0x3, + 0x2, + 0x5, + 0x4, + 0x7, + 0x6, + }, // Round 5 + { + 0x2, + 0x3, + 0x0, + 0x1, + 0x6, + 0x7, + 0x4, + 0x5, + }, // Round 6 + { + 0x3, + 0x2, + 0x1, + 0x0, + 0x7, + 0x6, + 0x5, + 0x4, + }, // Round 7 + { + 0x5, + 0x4, + 0x7, + 0x6, + 0x1, + 0x0, + 0x3, + 0x2, + }, // Round 8 + { + 0x4, + 0x5, + 0x6, + 0x7, + 0x0, + 0x1, + 0x2, + 0x3, + }, // Round 9 + { + 0x7, + 0x6, + 0x5, + 0x4, + 0x3, + 0x2, + 0x1, + 0x0, + }, // Round 10 + { + 0x6, + 0x7, + 0x4, + 0x5, + 0x2, + 0x3, + 0x0, + 0x1, + }, // Round 11 +}; + +static const uint8_t table_un[AUT64_NUM_ROUNDS][8] = { + { + 0x1, + 0x0, + 0x3, + 0x2, + 0x5, + 0x4, + 0x7, + 0x6, + }, // Round 0 + { + 0x0, + 0x1, + 0x2, + 0x3, + 0x4, + 0x5, + 0x6, + 0x7, + }, // Round 1 + { + 0x3, + 0x2, + 0x1, + 0x0, + 0x7, + 0x6, + 0x5, + 0x4, + }, // Round 2 + { + 0x2, + 0x3, + 0x0, + 0x1, + 0x6, + 0x7, + 0x4, + 0x5, + }, // Round 3 + { + 0x5, + 0x4, + 0x7, + 0x6, + 0x1, + 0x0, + 0x3, + 0x2, + }, // Round 4 + { + 0x4, + 0x5, + 0x6, + 0x7, + 0x0, + 0x1, + 0x2, + 0x3, + }, // Round 5 + { + 0x7, + 0x6, + 0x5, + 0x4, + 0x3, + 0x2, + 0x1, + 0x0, + }, // Round 6 + { + 0x6, + 0x7, + 0x4, + 0x5, + 0x2, + 0x3, + 0x0, + 0x1, + }, // Round 7 + { + 0x3, + 0x2, + 0x1, + 0x0, + 0x7, + 0x6, + 0x5, + 0x4, + }, // Round 8 + { + 0x2, + 0x3, + 0x0, + 0x1, + 0x6, + 0x7, + 0x4, + 0x5, + }, // Round 9 + { + 0x1, + 0x0, + 0x3, + 0x2, + 0x5, + 0x4, + 0x7, + 0x6, + }, // Round 10 + { + 0x0, + 0x1, + 0x2, + 0x3, + 0x4, + 0x5, + 0x6, + 0x7, + }, // Round 11 +}; + +static const uint8_t table_offset[256] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // 0 + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, // 1 + 0x0, 0x2, 0x4, 0x6, 0x8, 0xA, 0xC, 0xE, 0x3, 0x1, 0x7, 0x5, 0xB, 0x9, 0xF, 0xD, // 2 + 0x0, 0x3, 0x6, 0x5, 0xC, 0xF, 0xA, 0x9, 0xB, 0x8, 0xD, 0xE, 0x7, 0x4, 0x1, 0x2, // 3 + 0x0, 0x4, 0x8, 0xC, 0x3, 0x7, 0xB, 0xF, 0x6, 0x2, 0xE, 0xA, 0x5, 0x1, 0xD, 0x9, // 4 + 0x0, 0x5, 0xA, 0xF, 0x7, 0x2, 0xD, 0x8, 0xE, 0xB, 0x4, 0x1, 0x9, 0xC, 0x3, 0x6, // 5 + 0x0, 0x6, 0xC, 0xA, 0xB, 0xD, 0x7, 0x1, 0x5, 0x3, 0x9, 0xF, 0xE, 0x8, 0x2, 0x4, // 6 + 0x0, 0x7, 0xE, 0x9, 0xF, 0x8, 0x1, 0x6, 0xD, 0xA, 0x3, 0x4, 0x2, 0x5, 0xC, 0xB, // 7 + 0x0, 0x8, 0x3, 0xB, 0x6, 0xE, 0x5, 0xD, 0xC, 0x4, 0xF, 0x7, 0xA, 0x2, 0x9, 0x1, // 8 + 0x0, 0x9, 0x1, 0x8, 0x2, 0xB, 0x3, 0xA, 0x4, 0xD, 0x5, 0xC, 0x6, 0xF, 0x7, 0xE, // 9 + 0x0, 0xA, 0x7, 0xD, 0xE, 0x4, 0x9, 0x3, 0xF, 0x5, 0x8, 0x2, 0x1, 0xB, 0x6, 0xC, // A + 0x0, 0xB, 0x5, 0xE, 0xA, 0x1, 0xF, 0x4, 0x7, 0xC, 0x2, 0x9, 0xD, 0x6, 0x8, 0x3, // B + 0x0, 0xC, 0xB, 0x7, 0x5, 0x9, 0xE, 0x2, 0xA, 0x6, 0x1, 0xD, 0xF, 0x3, 0x4, 0x8, // C + 0x0, 0xD, 0x9, 0x4, 0x1, 0xC, 0x8, 0x5, 0x2, 0xF, 0xB, 0x6, 0x3, 0xE, 0xA, 0x7, // D + 0x0, 0xE, 0xF, 0x1, 0xD, 0x3, 0x2, 0xC, 0x9, 0x7, 0x6, 0x8, 0x4, 0xA, 0xB, 0x5, // E + 0x0, 0xF, 0xD, 0x2, 0x9, 0x6, 0x4, 0xB, 0x1, 0xE, 0xC, 0x3, 0x8, 0x7, 0x5, 0xA // F +}; + +static const uint8_t table_sub[16] = { + 0x0, + 0x1, + 0x9, + 0xE, + 0xD, + 0xB, + 0x7, + 0x6, + 0xF, + 0x2, + 0xC, + 0x5, + 0xA, + 0x4, + 0x3, + 0x8, +}; + +static uint8_t key_nibble( + const struct aut64_key key, + const uint8_t nibble, + const uint8_t table[], + const uint8_t iteration) { + const uint8_t keyValue = key.key[table[iteration]]; + const uint8_t offset = (keyValue << 4) | nibble; + return table_offset[offset]; +} + +static uint8_t round_key(const struct aut64_key key, const uint8_t state[], const uint8_t roundN) { + uint8_t result_hi = 0, result_lo = 0; + + for(int i = 0; i < AUT64_BLOCK_SIZE - 1; i++) { + result_hi ^= key_nibble(key, state[i] >> 4, table_un[roundN], i); + result_lo ^= key_nibble(key, state[i] & 0x0F, table_ln[roundN], i); + } + + return (result_hi << 4) | result_lo; +} + +static uint8_t final_byte_nibble(const struct aut64_key key, const uint8_t table[]) { + const uint8_t keyValue = key.key[table[AUT64_BLOCK_SIZE - 1]]; + return table_sub[keyValue] << 4; +} + +static uint8_t encrypt_final_byte_nibble( + const struct aut64_key key, + const uint8_t nibble, + const uint8_t table[]) { + const uint8_t offset = final_byte_nibble(key, table); + + int i; + + for(i = 0; i < 16; i++) { + if(table_offset[offset + i] == nibble) { + break; + } + } + + return i; +} + +static uint8_t + encrypt_compress(const struct aut64_key key, const uint8_t state[], const uint8_t roundN) { + const uint8_t roundKey = round_key(key, state, roundN); + uint8_t result_hi = roundKey >> 4, result_lo = roundKey & 0x0F; + + result_hi ^= + encrypt_final_byte_nibble(key, state[AUT64_BLOCK_SIZE - 1] >> 4, table_un[roundN]); + result_lo ^= + encrypt_final_byte_nibble(key, state[AUT64_BLOCK_SIZE - 1] & 0x0F, table_ln[roundN]); + + return (result_hi << 4) | result_lo; +} + +static uint8_t decrypt_final_byte_nibble( + const struct aut64_key key, + const uint8_t nibble, + const uint8_t table[], + const uint8_t result) { + const uint8_t offset = final_byte_nibble(key, table); + + return table_offset[(result ^ nibble) + offset]; +} + +static uint8_t + decrypt_compress(const struct aut64_key key, const uint8_t state[], const uint8_t roundN) { + const uint8_t roundKey = round_key(key, state, roundN); + uint8_t result_hi = roundKey >> 4, result_lo = roundKey & 0x0F; + + result_hi = decrypt_final_byte_nibble( + key, state[AUT64_BLOCK_SIZE - 1] >> 4, table_un[roundN], result_hi); + result_lo = decrypt_final_byte_nibble( + key, state[AUT64_BLOCK_SIZE - 1] & 0x0F, table_ln[roundN], result_lo); + + return (result_hi << 4) | result_lo; +} + +static uint8_t substitute(const struct aut64_key key, const uint8_t byte) { + return (key.sbox[byte >> 4] << 4) | key.sbox[byte & 0x0F]; +} + +static void permute_bytes(const struct aut64_key key, uint8_t state[]) { + uint8_t result[AUT64_PBOX_SIZE] = {0}; + + for(int i = 0; i < AUT64_PBOX_SIZE; i++) { + result[key.pbox[i]] = state[i]; + } + + memcpy(state, result, AUT64_PBOX_SIZE); +} + +static uint8_t permute_bits(const struct aut64_key key, const uint8_t byte) { + uint8_t result = 0; + + for(int i = 0; i < 8; i++) { + if(byte & (1 << i)) { + result |= (1 << key.pbox[i]); + } + } + + return result; +} + +static void reverse_box(uint8_t* reversed, const uint8_t* box, const size_t len) { + for(size_t i = 0; i < len; i++) { + for(size_t j = 0; j < len; j++) { + if(box[j] == i) { + reversed[i] = j; + break; + } + } + } +} + +void aut64_encrypt(const struct aut64_key key, uint8_t message[]) { + struct aut64_key reverse_key; + memcpy(reverse_key.key, key.key, AUT64_KEY_SIZE); + reverse_box(reverse_key.pbox, key.pbox, AUT64_PBOX_SIZE); + reverse_box(reverse_key.sbox, key.sbox, AUT64_SBOX_SIZE); + + for(int i = 0; i < AUT64_NUM_ROUNDS; i++) { + permute_bytes(reverse_key, message); + message[7] = encrypt_compress(reverse_key, message, i); + message[7] = substitute(reverse_key, message[7]); + message[7] = permute_bits(reverse_key, message[7]); + message[7] = substitute(reverse_key, message[7]); + } +} + +void aut64_decrypt(const struct aut64_key key, uint8_t message[]) { + for(int i = AUT64_NUM_ROUNDS - 1; i >= 0; i--) { + message[7] = substitute(key, message[7]); + message[7] = permute_bits(key, message[7]); + message[7] = substitute(key, message[7]); + message[7] = decrypt_compress(key, message, i); + permute_bytes(key, message); + } +} + +void aut64_pack(uint8_t dest[], const struct aut64_key src) { + dest[0] = src.index; + + for(uint8_t i = 0; i < sizeof(src.key) / 2; i++) { + dest[i + 1] = (src.key[i * 2] << 4) | src.key[i * 2 + 1]; + } + + uint32_t pbox = 0; + + for(uint8_t i = 0; i < sizeof(src.pbox); i++) { + pbox = (pbox << 3) | src.pbox[i]; + } + + dest[5] = pbox >> 16; + dest[6] = (pbox >> 8) & 0xFF; + dest[7] = pbox & 0xFF; + + for(uint8_t i = 0; i < sizeof(src.sbox) / 2; i++) { + dest[i + 8] = (src.sbox[i * 2] << 4) | src.sbox[i * 2 + 1]; + } +} + +void aut64_unpack(struct aut64_key* dest, const uint8_t src[]) { + dest->index = src[0]; + + for(uint8_t i = 0; i < sizeof(dest->key) / 2; i++) { + dest->key[i * 2] = src[i + 1] >> 4; + dest->key[i * 2 + 1] = src[i + 1] & 0xF; + } + + uint32_t pbox = (src[5] << 16) | (src[6] << 8) | src[7]; + + for(int8_t i = sizeof(dest->pbox) - 1; i >= 0; i--) { + dest->pbox[i] = pbox & 0x7; + pbox >>= 3; + } + + for(uint8_t i = 0; i < sizeof(dest->sbox) / 2; i++) { + dest->sbox[i * 2] = src[i + 8] >> 4; + dest->sbox[i * 2 + 1] = src[i + 8] & 0xF; + } +} diff --git a/protocols/aut64.h b/protocols/aut64.h new file mode 100644 index 0000000..b78db65 --- /dev/null +++ b/protocols/aut64.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#define AUT64_NUM_ROUNDS 12 +#define AUT64_BLOCK_SIZE 8 +#define AUT64_KEY_SIZE 8 +#define AUT64_PBOX_SIZE 8 +#define AUT64_SBOX_SIZE 16 + +struct aut64_key { + uint8_t index; + uint8_t key[AUT64_KEY_SIZE]; + uint8_t pbox[AUT64_PBOX_SIZE]; + uint8_t sbox[AUT64_SBOX_SIZE]; +}; + +#define AUT64_KEY_STRUCT_PACKED_SIZE 16 + +void aut64_encrypt(const struct aut64_key key, uint8_t message[]); +void aut64_decrypt(const struct aut64_key key, uint8_t message[]); +void aut64_pack(uint8_t dest[], const struct aut64_key src); +void aut64_unpack(struct aut64_key* dest, const uint8_t src[]); diff --git a/protocols/vw.c b/protocols/vw.c index 8738bb8..c8b9ee9 100644 --- a/protocols/vw.c +++ b/protocols/vw.c @@ -1,4 +1,5 @@ #include "vw.h" +#include "aut64.h" #define TAG "VWProtocol" @@ -9,13 +10,28 @@ static const SubGhzBlockConst subghz_protocol_vw_const = { .min_count_bit_for_found = 80, }; +/* +// Slightly different timings for some newer remotes? +static const SubGhzBlockConst subghz_protocol_vw3_const = { + .te_short = 300, + .te_long = 600, + .te_delta = 120, // ??? + .min_count_bit_for_found = 80, // ???? +}; +*/ + typedef struct SubGhzProtocolDecoderVw { SubGhzProtocolDecoderBase base; SubGhzBlockDecoder decoder; SubGhzBlockGeneric generic; ManchesterState manchester_state; - uint64_t data_2; // Additional 16 bits (type byte + check byte) + uint8_t data[10]; + uint8_t type; + uint32_t key1_low; + uint32_t key1_high; + uint16_t key2; + uint8_t crc; } SubGhzProtocolDecoderVw; typedef struct SubGhzProtocolEncoderVw { @@ -55,12 +71,98 @@ const SubGhzProtocolEncoder subghz_protocol_vw_encoder = { const SubGhzProtocol vw_protocol = { .name = VW_PROTOCOL_NAME, .type = SubGhzProtocolTypeDynamic, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save, .decoder = &subghz_protocol_vw_decoder, .encoder = &subghz_protocol_vw_encoder, }; -// Fixed manchester_advance for VW protocol +#define VW_KEYS_COUNT 3 + +#define VW_TEA_DELTA 0x9E3779B9U +#define VW_TEA_DELTA_INC 0x61C88647U +#define VW_TEA_ROUNDS 32 + +static const uint32_t vw_tea_key_schedule[] = {0x2BF93A19, 0x622C1206, 0x6A55B5DA, 0xD5AAAAAA}; +static const uint32_t vw_tea_key_schedule_rom[] = {0x46280509, 0xBDF0B005, 0x23084924, 0x4638466A}; + +/// + +static int8_t protocol_vw_keys_loaded = -1; +static struct aut64_key protocol_vw_keys[VW_KEYS_COUNT]; + +static void protocol_vw_load_keys(const char* file_name) { + if(protocol_vw_keys_loaded >= 0) { + FURI_LOG_I( + TAG, + "Already loaded %u keys from %s, skipping load", + protocol_vw_keys_loaded, + file_name); + return; + } + + FURI_LOG_I(TAG, "Loading keys from %s", file_name); + + protocol_vw_keys_loaded = 0; + + for(uint8_t i = 0; i < VW_KEYS_COUNT; i++) { + uint8_t key_packed[AUT64_KEY_STRUCT_PACKED_SIZE]; + + if(subghz_keystore_raw_get_data( + file_name, + i * AUT64_KEY_STRUCT_PACKED_SIZE, + key_packed, + AUT64_KEY_STRUCT_PACKED_SIZE)) { + aut64_unpack(&protocol_vw_keys[i], key_packed); + protocol_vw_keys_loaded++; + } else { + FURI_LOG_E(TAG, "Unable to load key %u", i); + break; + } + } + + FURI_LOG_I(TAG, "Loaded %u keys", protocol_vw_keys_loaded); +} + +static struct aut64_key* protocol_vw_get_key(uint8_t index) { + for(uint8_t i = 0; i < MIN(protocol_vw_keys_loaded, VW_KEYS_COUNT); i++) { + if(protocol_vw_keys[i].index == index) { + return &protocol_vw_keys[i]; + } + } + + return NULL; +} +/// + +static void vw_build_decoder_block(const uint8_t* key1_8, uint8_t* block_8) { + uint32_t low = (uint32_t)key1_8[0] | ((uint32_t)key1_8[1] << 8) | ((uint32_t)key1_8[2] << 16) | + ((uint32_t)key1_8[3] << 24); + uint32_t high = (uint32_t)key1_8[4] | ((uint32_t)key1_8[5] << 8) | + ((uint32_t)key1_8[6] << 16) | ((uint32_t)key1_8[7] << 24); + block_8[0] = (uint8_t)(high >> 24); + block_8[1] = (uint8_t)(high >> 16); + block_8[2] = (uint8_t)(high >> 8); + block_8[3] = (uint8_t)(high); + block_8[4] = (uint8_t)(low >> 24); + block_8[5] = (uint8_t)(low >> 16); + block_8[6] = (uint8_t)(low >> 8); + block_8[7] = (uint8_t)(low); +} + +static void vw_tea_decrypt(uint32_t* v0, uint32_t* v1, const uint32_t* key_schedule) { + uint32_t sum = VW_TEA_DELTA * VW_TEA_ROUNDS; + for(int i = 0; i < VW_TEA_ROUNDS; i++) { + uint32_t k_idx2 = (sum >> 11) & 3; + uint32_t temp = key_schedule[k_idx2] + sum; + *v1 -= temp ^ (((*v0 >> 5) ^ (*v0 << 4)) + *v0); + sum -= VW_TEA_DELTA_INC; + uint32_t k_idx1 = sum & 3; + temp = key_schedule[k_idx1] + sum; + *v0 -= temp ^ (((*v1 >> 5) ^ (*v1 << 4)) + *v1); + } +} + static bool vw_manchester_advance( ManchesterState state, ManchesterEvent event, @@ -109,50 +211,18 @@ static bool vw_manchester_advance( return result; } -static uint8_t vw_get_bit_index(uint8_t bit) { - uint8_t bit_index = 0; - - if(bit < 72 && bit >= 8) { - // use generic.data (bytes 1-8) - bit_index = bit - 8; - } else { - // use data_2 - if(bit >= 72) { - bit_index = bit - 64; // byte 0 = type - } - if(bit < 8) { - bit_index = bit; // byte 9 = check digit - } - bit_index |= 0x80; // mark for data_2 - } - - return bit_index; -} - static void vw_add_bit(SubGhzProtocolDecoderVw* instance, bool level) { + furi_assert(instance); + if(instance->generic.data_count_bit >= subghz_protocol_vw_const.min_count_bit_for_found) { return; } - uint8_t bit_index_full = - subghz_protocol_vw_const.min_count_bit_for_found - 1 - instance->generic.data_count_bit; - uint8_t bit_index_masked = vw_get_bit_index(bit_index_full); - uint8_t bit_index = bit_index_masked & 0x7F; + if(level) { + uint8_t byte_index = instance->generic.data_count_bit / 8; + uint8_t bit_index = instance->generic.data_count_bit % 8; - if(bit_index_masked & 0x80) { - // use data_2 - if(level) { - instance->data_2 |= (1ULL << bit_index); - } else { - instance->data_2 &= ~(1ULL << bit_index); - } - } else { - // use data - if(level) { - instance->generic.data |= (1ULL << bit_index); - } else { - instance->generic.data &= ~(1ULL << bit_index); - } + instance->data[byte_index] |= 1 << (7 - bit_index); } instance->generic.data_count_bit++; @@ -160,6 +230,131 @@ static void vw_add_bit(SubGhzProtocolDecoderVw* instance, bool level) { if(instance->generic.data_count_bit >= subghz_protocol_vw_const.min_count_bit_for_found) { if(instance->base.callback) { instance->base.callback(&instance->base, instance->base.context); + } else { + subghz_protocol_decoder_vw_reset(instance); + } + } +} + +static void vw_fill_from_decrypted( + SubGhzProtocolDecoderVw* instance, + const uint8_t* dec, + uint8_t check_byte) { + uint64_t key1 = ((uint64_t)instance->data[0] << 56) | ((uint64_t)instance->data[1] << 48) | + ((uint64_t)instance->data[2] << 40) | ((uint64_t)instance->data[3] << 32) | + ((uint64_t)instance->data[4] << 24) | ((uint64_t)instance->data[5] << 16) | + ((uint64_t)instance->data[6] << 8) | (uint64_t)instance->data[7]; + instance->key1_low = (uint32_t)(key1 & 0xFFFFFFFFU); + instance->key1_high = (uint32_t)((key1 >> 32) & 0xFFFFFFFFU); + instance->key2 = ((uint16_t)instance->data[8] << 8) | instance->data[9]; + uint32_t serial_le = (uint32_t)dec[0] | ((uint32_t)dec[1] << 8) | ((uint32_t)dec[2] << 16) | + ((uint32_t)dec[3] << 24); + instance->generic.serial = ((serial_le & 0xFFU) << 24) | ((serial_le & 0xFF00U) << 8) | + ((serial_le & 0xFF0000U) >> 8) | ((serial_le & 0xFF000000U) >> 24); + instance->generic.cnt = (uint32_t)dec[4] | ((uint32_t)dec[5] << 8) | ((uint32_t)dec[6] << 16); + instance->generic.btn = (dec[7] >> 4) & 0xFU; + instance->crc = check_byte; + instance->type = 0xC0; +} + +static bool vw_try_aut64_block( + SubGhzProtocolDecoderVw* instance, + const uint8_t* block_8, + uint8_t check_byte, + uint8_t button_from_check, + size_t key_start, + size_t key_end) { + uint8_t dec[8]; + for(size_t i = key_start; i < key_end; i++) { + memcpy(dec, block_8, 8); + const struct aut64_key* key = protocol_vw_get_key(i + 1); + if(!key) { + FURI_LOG_E(TAG, "Key not found: %zu", i + 1); + continue; + } + aut64_decrypt(*key, dec); + uint8_t btn = (dec[7] >> 4) & 0xFU; + if(btn == button_from_check) { + vw_fill_from_decrypted(instance, dec, check_byte); + return true; + } + } + return false; +} + +static bool vw_dispatch_type_path_3_4(uint8_t t) { + return (t == 0x2B || t == 0x1D || t == 0x47); +} + +static void vw_parse_data(SubGhzProtocolDecoderVw* instance) { + furi_assert(instance); + + instance->type = instance->data[0]; + uint8_t check_byte = instance->data[9]; + uint8_t dispatch_type = instance->data[9]; + uint8_t button_from_check = (check_byte >> 4) & 0xFU; + + uint8_t encrypted_raw[8]; + uint8_t decoder_block[8]; + memcpy(encrypted_raw, instance->data + 1, 8); + vw_build_decoder_block(instance->data, decoder_block); + + if(vw_dispatch_type_path_3_4(dispatch_type)) { + if(vw_try_aut64_block(instance, encrypted_raw, check_byte, button_from_check, 2, 3)) { + return; + } + if(vw_try_aut64_block(instance, encrypted_raw, check_byte, button_from_check, 1, 2)) { + return; + } + uint8_t dec[8]; + memcpy(dec, encrypted_raw, 8); + const struct aut64_key* key = protocol_vw_get_key(3); + + if(!key) { + FURI_LOG_E(TAG, "Key not found: 3"); + return; + } + aut64_decrypt(*key, dec); + vw_fill_from_decrypted(instance, dec, check_byte); + return; + } + + if(vw_try_aut64_block( + instance, encrypted_raw, check_byte, button_from_check, 0, VW_KEYS_COUNT)) { + return; + } + if(vw_try_aut64_block( + instance, decoder_block, check_byte, button_from_check, 0, VW_KEYS_COUNT)) { + return; + } + if(instance->type == 0x00 && + vw_try_aut64_block( + instance, instance->data, check_byte, button_from_check, 0, VW_KEYS_COUNT)) { + return; + } + + uint32_t v0 = (uint32_t)encrypted_raw[0] | ((uint32_t)encrypted_raw[1] << 8) | + ((uint32_t)encrypted_raw[2] << 16) | ((uint32_t)encrypted_raw[3] << 24); + uint32_t v1 = (uint32_t)encrypted_raw[4] | ((uint32_t)encrypted_raw[5] << 8) | + ((uint32_t)encrypted_raw[6] << 16) | ((uint32_t)encrypted_raw[7] << 24); + for(int tea_key = 0; tea_key < 2; tea_key++) { + uint32_t d0 = v0; + uint32_t d1 = v1; + const uint32_t* key_sched = tea_key ? vw_tea_key_schedule_rom : vw_tea_key_schedule; + vw_tea_decrypt(&d0, &d1, key_sched); + uint8_t dec[8]; + dec[0] = (uint8_t)(d0); + dec[1] = (uint8_t)(d0 >> 8); + dec[2] = (uint8_t)(d0 >> 16); + dec[3] = (uint8_t)(d0 >> 24); + dec[4] = (uint8_t)(d1); + dec[5] = (uint8_t)(d1 >> 8); + dec[6] = (uint8_t)(d1 >> 16); + dec[7] = (uint8_t)(d1 >> 24); + uint8_t btn = (dec[7] >> 4) & 0xFU; + if(btn == button_from_check) { + vw_fill_from_decrypted(instance, dec, check_byte); + return; } } } @@ -169,6 +364,14 @@ void* subghz_protocol_decoder_vw_alloc(SubGhzEnvironment* environment) { SubGhzProtocolDecoderVw* instance = malloc(sizeof(SubGhzProtocolDecoderVw)); instance->base.protocol = &vw_protocol; instance->generic.protocol_name = instance->base.protocol->name; + instance->type = 0; + instance->key1_low = 0; + instance->key1_high = 0; + instance->key2 = 0; + instance->crc = 0; + + protocol_vw_load_keys(APP_ASSETS_PATH("vw")); + return instance; } @@ -182,9 +385,13 @@ void subghz_protocol_decoder_vw_reset(void* context) { furi_assert(context); SubGhzProtocolDecoderVw* instance = context; instance->decoder.parser_step = VwDecoderStepReset; + memset(instance->data, 0, 10); instance->generic.data_count_bit = 0; - instance->generic.data = 0; - instance->data_2 = 0; + instance->type = 0; + instance->key1_low = 0; + instance->key1_high = 0; + instance->key2 = 0; + instance->crc = 0; instance->manchester_state = ManchesterStateMid1; } @@ -196,7 +403,8 @@ void subghz_protocol_decoder_vw_feed(void* context, bool level, uint32_t duratio uint32_t te_long = subghz_protocol_vw_const.te_long; uint32_t te_delta = subghz_protocol_vw_const.te_delta; uint32_t te_med = (te_long + te_short) / 2; - uint32_t te_end = te_long * 5; + uint32_t te_end = + te_long * 5; // Gap to signal end of transmission (5300us on new) (none on older) ManchesterEvent event = ManchesterEventReset; @@ -209,7 +417,6 @@ void subghz_protocol_decoder_vw_feed(void* context, bool level, uint32_t duratio case VwDecoderStepFoundSync: if(DURATION_DIFF(duration, te_short) < te_delta) { - // Stay - sync pattern repeats ~43 times break; } @@ -241,12 +448,10 @@ void subghz_protocol_decoder_vw_feed(void* context, bool level, uint32_t duratio case VwDecoderStepFoundStart3: if(DURATION_DIFF(duration, te_med) < te_delta) { - // Stay - med pattern repeats break; } if(level && DURATION_DIFF(duration, te_short) < te_delta) { - // Start data collection vw_manchester_advance( instance->manchester_state, ManchesterEventReset, @@ -258,8 +463,7 @@ void subghz_protocol_decoder_vw_feed(void* context, bool level, uint32_t duratio &instance->manchester_state, NULL); instance->generic.data_count_bit = 0; - instance->generic.data = 0; - instance->data_2 = 0; + memset(instance->data, 0, 10); instance->decoder.parser_step = VwDecoderStepFoundData; break; } @@ -276,7 +480,6 @@ void subghz_protocol_decoder_vw_feed(void* context, bool level, uint32_t duratio event = level ? ManchesterEventLongHigh : ManchesterEventLongLow; } - // Last bit can be arbitrarily long if(instance->generic.data_count_bit == subghz_protocol_vw_const.min_count_bit_for_found - 1 && !level && duration > te_end) { @@ -299,8 +502,15 @@ void subghz_protocol_decoder_vw_feed(void* context, bool level, uint32_t duratio uint8_t subghz_protocol_decoder_vw_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderVw* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); + + uint8_t hash = 0; + size_t key_length = instance->generic.data_count_bit / 8; + + for(size_t i = 0; i < key_length; i++) { + hash ^= instance->data[i]; + } + + return hash; } SubGhzProtocolStatus subghz_protocol_decoder_vw_serialize( @@ -308,80 +518,150 @@ SubGhzProtocolStatus subghz_protocol_decoder_vw_serialize( FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_assert(context); + SubGhzProtocolDecoderVw* instance = context; + SubGhzProtocolStatus res = SubGhzProtocolStatusError; - SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + res = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(res != SubGhzProtocolStatusOk) { + break; + } - ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } - if(ret == SubGhzProtocolStatusOk) { - // Add VW-specific data - uint32_t type = (instance->data_2 >> 8) & 0xFF; - uint32_t check = instance->data_2 & 0xFF; - uint32_t btn = (check >> 4) & 0xF; + uint16_t key_length = instance->generic.data_count_bit / 8; - flipper_format_write_uint32(flipper_format, "Type", &type, 1); - flipper_format_write_uint32(flipper_format, "Check", &check, 1); - flipper_format_write_uint32(flipper_format, "Btn", &btn, 1); - } + if(!flipper_format_update_hex(flipper_format, "Key", instance->data, key_length)) { + FURI_LOG_E(TAG, "Unable to update Key"); + res = SubGhzProtocolStatusErrorParserKey; + break; + } + } while(false); - return ret; + return res; } SubGhzProtocolStatus subghz_protocol_decoder_vw_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderVw* instance = context; - return subghz_block_generic_deserialize_check_count_bit( - &instance->generic, flipper_format, subghz_protocol_vw_const.min_count_bit_for_found); + + SubGhzProtocolStatus ret = + subghz_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { + return ret; + } + + instance->generic.data = 0; + + if(instance->generic.data_count_bit != subghz_protocol_vw_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + return SubGhzProtocolStatusErrorValueBitCount; + } + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + return SubGhzProtocolStatusErrorParserOthers; + } + + size_t key_length = instance->generic.data_count_bit / 8; + + if(!flipper_format_read_hex(flipper_format, "Key", instance->data, key_length)) { + FURI_LOG_E(TAG, "Unable to read Key in decoder"); + return SubGhzProtocolStatusErrorParserKey; + } + + vw_parse_data(instance); + + return SubGhzProtocolStatusOk; } -static const char* vw_get_button_name(uint8_t btn) { - switch(btn) { - case 0x1: - return "UNLOCK"; - case 0x2: - return "LOCK"; - case 0x3: - return "Un+Lk"; - case 0x4: - return "TRUNK"; - case 0x5: - return "Un+Tr"; - case 0x6: - return "Lk+Tr"; - case 0x7: - return "Un+Lk+Tr"; - case 0x8: - return "PANIC"; - default: - return "Unknown"; - } -} +const char* vw_buttons[] = { + "None", + "Unlock", + "Lock", + "Un+Lk", + "Trunk", + "Un+Tr", + "Lk+Tr", + "Un+Lk+Tr", + "Panic!", + "Unlock!", + "Lock!", + "Un+Lk!", + "Trunk!", + "Un+Tr!", + "Lk+Tr!", + "Un+Lk+Tr!", +}; void subghz_protocol_decoder_vw_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderVw* instance = context; - uint8_t type = (instance->data_2 >> 8) & 0xFF; - uint8_t check = instance->data_2 & 0xFF; - uint8_t btn = (check >> 4) & 0xF; + if(instance->generic.data_count_bit >= subghz_protocol_vw_const.min_count_bit_for_found) { + vw_parse_data(instance); + } - uint32_t key_high = (instance->generic.data >> 32) & 0xFFFFFFFF; - uint32_t key_low = instance->generic.data & 0xFFFFFFFF; + if(instance->type != 0xC0) { + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Type:%02X Unknown\r\n" + "%016llX%04X\r\n", + instance->generic.protocol_name, + (int)instance->generic.data_count_bit, + instance->type, + (unsigned long long)((uint64_t)instance->data[0] << 56 | + (uint64_t)instance->data[1] << 48 | + (uint64_t)instance->data[2] << 40 | + (uint64_t)instance->data[3] << 32 | + (uint64_t)instance->data[4] << 24 | + (uint64_t)instance->data[5] << 16 | + (uint64_t)instance->data[6] << 8 | (uint64_t)instance->data[7]), + (unsigned)((uint16_t)instance->data[8] << 8 | instance->data[9])); + return; + } + uint64_t key1_full = ((uint64_t)instance->key1_high << 32) | instance->key1_low; + uint8_t btn_byte = (uint8_t)(instance->generic.btn << 4); furi_string_cat_printf( output, "%s %dbit\r\n" - "Key:%02X%08lX%08lX%02X\r\n" - "Type:%02X Btn:%X %s\r\n", + "Key1:%016llX\r\n" + "Key2:%04X Btn:%02X:%s\r\n" + "Ser:%08lX Cnt:%06lX\r\n" + "CRC:%02X\r\n", instance->generic.protocol_name, - instance->generic.data_count_bit, - type, - key_high, - key_low, - check, - type, - btn, - vw_get_button_name(btn)); + (int)instance->generic.data_count_bit, + (unsigned long long)key1_full, + (unsigned)instance->key2, + (unsigned)btn_byte, + vw_buttons[instance->generic.btn], + (unsigned long)instance->generic.serial, + (unsigned long)instance->generic.cnt, + (unsigned)instance->crc); +} + +void subghz_protocol_decoder_vw_get_string_brief(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderVw* instance = context; + if(instance->generic.data_count_bit >= subghz_protocol_vw_const.min_count_bit_for_found) { + vw_parse_data(instance); + } + if(instance->type != 0xC0) { + furi_string_cat_printf(output, "%s Unknown", instance->generic.protocol_name); + return; + } + furi_string_cat_printf( + output, + "%s %08lX %s", + instance->generic.protocol_name, + (unsigned long)instance->generic.serial, + vw_buttons[instance->generic.btn]); }