Kia V4 Decode working now

This commit is contained in:
RocketGod committed 2025-12-07 14:45:19 -08:00
1 parent 9722ea7f95
commit 80591cf1a7
3 files changed
+106 -110

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
# ProtoPirate
## for Flipper Zero
Work in progress by The Pirates' Plunder crew... currently only decoding Kia V0, V1, V2, and V5.
Work in progress by The Pirates' Plunder crew... currently only decoding Kia V0, V1, V2, V4, and V5.
but will eventually be a drop-in app (fap) for decoding, storing, and replicating all automotive key fobs and any other protocols which utilize rolling code.
If you contribute significantly you will be added to the app credits.
BIN
View File
Binary file not shown.
+105 -109
View File
@@ -16,7 +16,10 @@ struct SubGhzProtocolDecoderKiaV4 {
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint16_t header_count;
uint8_t short_pulse_count;
uint8_t raw_bits[32];
uint16_t raw_bit_count;
uint32_t encrypted;
uint32_t decrypted;
};
@@ -30,8 +33,7 @@ struct SubGhzProtocolEncoderKiaV4 {
typedef enum {
KiaV4DecoderStepReset = 0,
KiaV4DecoderStepCheckPreamble,
KiaV4DecoderStepSaveDuration,
KiaV4DecoderStepCheckDuration,
KiaV4DecoderStepCollectRawBits,
} KiaV4DecoderStep;
const SubGhzProtocolDecoder kia_protocol_v4_decoder = {
@@ -61,66 +63,87 @@ const SubGhzProtocol kia_protocol_v4 = {
.encoder = &kia_protocol_v4_encoder,
};
static const uint32_t keeloq_nlf = 0x3A5C742E;
static uint32_t keeloq_decrypt(uint32_t data, uint64_t key) {
// KeeLoq decrypt
static uint32_t keeloq_common_decrypt(uint32_t data, uint64_t key) {
uint32_t block = data;
uint64_t tkey = key;
for(int i = 0; i < 528; i++) {
uint8_t nlf_idx = ((data >> 1) & 1) | ((data >> 8) & 2) | ((data >> 18) & 4) |
((data >> 21) & 8) | ((data >> 27) & 16);
uint8_t nlf_bit = (keeloq_nlf >> nlf_idx) & 1;
uint8_t key_bit = (key >> (15 - (i % 64))) & 1;
uint8_t msb = (data >> 31) & 1;
uint8_t bit0 = (data >> 0) & 1;
uint8_t bit16 = (data >> 16) & 1;
uint8_t new_bit = nlf_bit ^ msb ^ bit0 ^ bit16 ^ key_bit;
data = (data >> 1) | (new_bit << 31);
int lutkey = ((block >> 0) & 1) | ((block >> 7) & 2) |
((block >> 17) & 4) | ((block >> 22) & 8) | ((block >> 26) & 16);
int lsb = ((block >> 31) ^ ((block >> 15) & 1) ^
((0x3A5C742E >> lutkey) & 1) ^ ((tkey >> 15) & 1));
block = ((block & 0x7FFFFFFF) << 1) | lsb;
tkey = ((tkey & 0x7FFFFFFFFFFFFFFFULL) << 1) | (tkey >> 63);
}
return data;
return block;
}
static uint8_t kia_v4_reverse8(uint8_t byte) {
static uint8_t reverse8(uint8_t byte) {
byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4;
byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2;
byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1;
return byte;
}
static void kia_v4_extract_fields(SubGhzProtocolDecoderKiaV4* instance) {
uint64_t inverted = ~instance->decoder.decode_data;
instance->generic.data = inverted;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
uint8_t b[8];
for(int i = 0; i < 8; i++) {
b[i] = (instance->generic.data >> (56 - i * 8)) & 0xFF;
static void kia_v4_add_raw_bit(SubGhzProtocolDecoderKiaV4* instance, bool bit) {
if(instance->raw_bit_count < 256) {
uint16_t byte_idx = instance->raw_bit_count / 8;
uint8_t bit_idx = 7 - (instance->raw_bit_count % 8);
if(bit) {
instance->raw_bits[byte_idx] |= (1 << bit_idx);
} else {
instance->raw_bits[byte_idx] &= ~(1 << bit_idx);
}
instance->raw_bit_count++;
}
}
static bool kia_v4_process_buffer(SubGhzProtocolDecoderKiaV4* instance) {
if(instance->raw_bit_count < 64) {
return false;
}
instance->generic.serial =
((uint32_t)kia_v4_reverse8(b[7] & 0xF0) << 24) |
((uint32_t)kia_v4_reverse8(b[6]) << 16) |
((uint32_t)kia_v4_reverse8(b[5]) << 8) |
(uint32_t)kia_v4_reverse8(b[4]);
uint8_t* b = instance->raw_bits;
instance->generic.btn = (kia_v4_reverse8(b[7]) & 0xF0) >> 4;
// Extract fields
uint32_t encrypted = ((uint32_t)reverse8(b[3]) << 24) |
((uint32_t)reverse8(b[2]) << 16) |
((uint32_t)reverse8(b[1]) << 8) |
(uint32_t)reverse8(b[0]);
instance->encrypted =
((uint32_t)kia_v4_reverse8(b[3]) << 24) |
((uint32_t)kia_v4_reverse8(b[2]) << 16) |
((uint32_t)kia_v4_reverse8(b[1]) << 8) |
(uint32_t)kia_v4_reverse8(b[0]);
uint32_t serial = ((uint32_t)reverse8(b[7] & 0xF0) << 24) |
((uint32_t)reverse8(b[6]) << 16) |
((uint32_t)reverse8(b[5]) << 8) |
(uint32_t)reverse8(b[4]);
instance->decrypted = keeloq_decrypt(instance->encrypted, kia_v4_mf_key);
instance->generic.cnt = instance->decrypted & 0xFFFF;
uint8_t btn = (reverse8(b[7]) & 0xF0) >> 4;
FURI_LOG_D(TAG, "Raw: %016llX Bits: %u Inv: %016llX",
instance->decoder.decode_data, instance->decoder.decode_count_bit, inverted);
FURI_LOG_D(TAG, "Sn: %07lX Btn: %X Enc: %08lX Dec: %08lX Cnt: %04lX",
instance->generic.serial, instance->generic.btn, instance->encrypted,
instance->decrypted, instance->generic.cnt);
// Decrypt with master key
uint32_t decrypted = keeloq_common_decrypt(encrypted, kia_v4_mf_key);
uint8_t dec_btn = (decrypted >> 28) & 0x0F;
uint8_t dec_serial_lsb = (decrypted >> 16) & 0xFF;
uint8_t our_serial_lsb = serial & 0xFF;
// Validate
if(dec_btn != btn || dec_serial_lsb != our_serial_lsb) {
return false;
}
// Valid decode
instance->encrypted = encrypted;
instance->decrypted = decrypted;
instance->generic.serial = serial;
instance->generic.btn = btn;
instance->generic.cnt = decrypted & 0xFFFF;
uint64_t key_data = ((uint64_t)b[0] << 56) | ((uint64_t)b[1] << 48) |
((uint64_t)b[2] << 40) | ((uint64_t)b[3] << 32) |
((uint64_t)b[4] << 24) | ((uint64_t)b[5] << 16) |
((uint64_t)b[6] << 8) | (uint64_t)b[7];
instance->generic.data = key_data;
instance->generic.data_count_bit = 64;
return true;
}
void* kia_protocol_decoder_v4_alloc(SubGhzEnvironment* environment) {
@@ -128,9 +151,6 @@ void* kia_protocol_decoder_v4_alloc(SubGhzEnvironment* environment) {
SubGhzProtocolDecoderKiaV4* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV4));
instance->base.protocol = &kia_protocol_v4;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encrypted = 0;
instance->decrypted = 0;
instance->short_pulse_count = 0;
return instance;
}
@@ -144,12 +164,9 @@ void kia_protocol_decoder_v4_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderKiaV4* instance = context;
instance->decoder.parser_step = KiaV4DecoderStepReset;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->header_count = 0;
instance->short_pulse_count = 0;
instance->encrypted = 0;
instance->decrypted = 0;
instance->raw_bit_count = 0;
memset(instance->raw_bits, 0, sizeof(instance->raw_bits));
}
void kia_protocol_decoder_v4_feed(void* context, bool level, uint32_t duration) {
@@ -158,75 +175,65 @@ void kia_protocol_decoder_v4_feed(void* context, bool level, uint32_t duration)
switch(instance->decoder.parser_step) {
case KiaV4DecoderStepReset:
if(level) {
if(DURATION_DIFF(duration, kia_protocol_v4_const.te_long) < kia_protocol_v4_const.te_delta) {
instance->decoder.parser_step = KiaV4DecoderStepCheckPreamble;
instance->header_count = 1;
instance->short_pulse_count = 0;
}
if(level && DURATION_DIFF(duration, kia_protocol_v4_const.te_short) < kia_protocol_v4_const.te_delta) {
instance->decoder.parser_step = KiaV4DecoderStepCheckPreamble;
instance->decoder.te_last = duration;
instance->header_count = 1;
}
break;
case KiaV4DecoderStepCheckPreamble:
if(level) {
if(DURATION_DIFF(duration, kia_protocol_v4_const.te_long) < kia_protocol_v4_const.te_delta) {
instance->header_count++;
instance->short_pulse_count = 0;
} else if(DURATION_DIFF(duration, kia_protocol_v4_const.te_short) < kia_protocol_v4_const.te_delta) {
instance->short_pulse_count++;
if(DURATION_DIFF(duration, kia_protocol_v4_const.te_short) < kia_protocol_v4_const.te_delta) {
instance->decoder.te_last = duration;
} else if(duration > 1000 && duration < 1500) {
if(instance->header_count >= 6 && instance->short_pulse_count >= 2) {
FURI_LOG_D(TAG, "Sync: %lu (hdr=%u, short=%u)",
duration, instance->header_count, instance->short_pulse_count);
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = KiaV4DecoderStepSaveDuration;
// Sync pulse
if(instance->header_count >= 8) {
instance->decoder.parser_step = KiaV4DecoderStepCollectRawBits;
instance->raw_bit_count = 0;
memset(instance->raw_bits, 0, sizeof(instance->raw_bits));
} else {
instance->decoder.parser_step = KiaV4DecoderStepReset;
}
} else {
instance->decoder.parser_step = KiaV4DecoderStepReset;
}
} else {
if(DURATION_DIFF(duration, kia_protocol_v4_const.te_short) < kia_protocol_v4_const.te_delta &&
DURATION_DIFF(instance->decoder.te_last, kia_protocol_v4_const.te_short) < kia_protocol_v4_const.te_delta) {
instance->header_count++;
} else if(duration > 1500) {
instance->decoder.parser_step = KiaV4DecoderStepReset;
}
}
break;
case KiaV4DecoderStepSaveDuration:
case KiaV4DecoderStepCollectRawBits:
if(level) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = KiaV4DecoderStepCheckDuration;
} else {
if(duration > 1500) {
if(instance->decoder.decode_count_bit >= kia_protocol_v4_const.min_count_bit_for_found) {
kia_v4_extract_fields(instance);
// HIGH pulse - PWM data bit (short=0, long=1)
if(duration > 1000 && duration < 1500) {
// Next sync pulse - end this packet
if(kia_v4_process_buffer(instance)) {
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.parser_step = KiaV4DecoderStepReset;
}
}
break;
case KiaV4DecoderStepCheckDuration:
if(!level) {
if(DURATION_DIFF(instance->decoder.te_last, kia_protocol_v4_const.te_short) < kia_protocol_v4_const.te_delta) {
instance->decoder.decode_data <<= 1;
instance->decoder.decode_count_bit++;
instance->decoder.parser_step = KiaV4DecoderStepSaveDuration;
} else if(DURATION_DIFF(instance->decoder.te_last, kia_protocol_v4_const.te_long) < kia_protocol_v4_const.te_delta) {
instance->decoder.decode_data <<= 1;
instance->decoder.decode_data |= 1;
instance->decoder.decode_count_bit++;
instance->decoder.parser_step = KiaV4DecoderStepSaveDuration;
} else if(DURATION_DIFF(duration, kia_protocol_v4_const.te_short) < kia_protocol_v4_const.te_delta) {
kia_v4_add_raw_bit(instance, false);
} else if(DURATION_DIFF(duration, kia_protocol_v4_const.te_long) < kia_protocol_v4_const.te_delta) {
kia_v4_add_raw_bit(instance, true);
} else {
if(instance->decoder.decode_count_bit >= kia_protocol_v4_const.min_count_bit_for_found) {
kia_v4_extract_fields(instance);
instance->decoder.parser_step = KiaV4DecoderStepReset;
}
} else {
// LOW pulse - check for end gap
if(duration > 1500) {
if(kia_v4_process_buffer(instance)) {
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.parser_step = KiaV4DecoderStepReset;
}
} else {
instance->decoder.parser_step = KiaV4DecoderStepReset;
}
break;
}
@@ -259,26 +266,15 @@ void kia_protocol_decoder_v4_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderKiaV4* instance = context;
uint8_t b[8];
for(int i = 0; i < 8; i++) {
b[i] = (instance->generic.data >> (56 - i * 8)) & 0xFF;
}
uint64_t yek = 0;
for(int i = 0; i < 8; i++) {
yek |= ((uint64_t)kia_v4_reverse8(b[7-i]) << (56 - i * 8));
}
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%016llX\r\n"
"Yek:%016llX\r\n"
"Sn:%07lX Btn:%X Cnt:%04lX\r\n"
"Enc:%08lX Dec:%08lX\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
instance->generic.data,
yek,
instance->generic.serial,
instance->generic.btn,
instance->generic.cnt,