add vw crypto

by zero-mega and slackware
This commit is contained in:
MX
2026-01-29 06:02:24 +03:00
parent 833910e833
commit b422766a05
5 changed files with 898 additions and 104 deletions

View File

@@ -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",

6
keystore/vw Normal file
View File

@@ -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

485
protocols/aut64.c Normal file
View File

@@ -0,0 +1,485 @@
#include <string.h>
#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;
}
}

23
protocols/aut64.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <stdint.h>
#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[]);

View File

@@ -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]);
}