From d06d3684e56732a33c68e519d2dea2bfe4f87fdf Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 6 Sep 2025 04:41:15 +0200 Subject: [PATCH 01/26] Sub-GHz: Actually fix Linear display for real now --- lib/subghz/protocols/linear.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index 4b6114b98..7671dc428 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -10,10 +10,10 @@ #define DIP_PATTERN "%c%c%c%c%c%c%c%c%c%c" #define DATA_TO_DIP(dip) \ - (dip & 0x0200 ? '0' : '1'), (dip & 0x0100 ? '0' : '1'), (dip & 0x0080 ? '0' : '1'), \ - (dip & 0x0040 ? '0' : '1'), (dip & 0x0020 ? '0' : '1'), (dip & 0x0010 ? '0' : '1'), \ - (dip & 0x0008 ? '0' : '1'), (dip & 0x0004 ? '0' : '1'), (dip & 0x0002 ? '0' : '1'), \ - (dip & 0x0001 ? '0' : '1') + (dip & 0x0200 ? '1' : '0'), (dip & 0x0100 ? '1' : '0'), (dip & 0x0080 ? '1' : '0'), \ + (dip & 0x0040 ? '1' : '0'), (dip & 0x0020 ? '1' : '0'), (dip & 0x0010 ? '1' : '0'), \ + (dip & 0x0008 ? '1' : '0'), (dip & 0x0004 ? '1' : '0'), (dip & 0x0002 ? '1' : '0'), \ + (dip & 0x0001 ? '1' : '0') static const SubGhzBlockConst subghz_protocol_linear_const = { .te_short = 500, @@ -323,13 +323,13 @@ void subghz_protocol_decoder_linear_get_string(void* context, FuriString* output // Protocol is actually implemented wrong way around, bits are inverted. // Instead of fixing it and breaking old saved remotes, - // only the display here is inverted to show correct values. - uint32_t code_found_reverse_lo = instance->generic.data & 0x00000000ffffffff; + // only the display here is inverted (~) to show correct values. + uint32_t code_found_lo = ~instance->generic.data & 0x00000000000003ff; - uint64_t code_found = subghz_protocol_blocks_reverse_key( - instance->generic.data, instance->generic.data_count_bit); + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + ~instance->generic.data, instance->generic.data_count_bit); - uint32_t code_found_lo = code_found & 0x00000000ffffffff; + uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000000003ff; furi_string_cat_printf( output, @@ -341,5 +341,5 @@ void subghz_protocol_decoder_linear_get_string(void* context, FuriString* output instance->generic.data_count_bit, code_found_lo, code_found_reverse_lo, - DATA_TO_DIP(code_found_reverse_lo)); + DATA_TO_DIP(code_found_lo)); } From ffb8eb7cff4345826ac83ecc9dda816db693ae2d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 9 Sep 2025 15:31:43 +0300 Subject: [PATCH 02/26] upd mfkey --- applications/system/mfkey/application.fam | 2 +- applications/system/mfkey/crypto1.h | 405 ++-- applications/system/mfkey/mfkey.c | 2058 +++++++++++++-------- applications/system/mfkey/mfkey.h | 173 +- 4 files changed, 1563 insertions(+), 1075 deletions(-) diff --git a/applications/system/mfkey/application.fam b/applications/system/mfkey/application.fam index dfd513847..0d81c1050 100644 --- a/applications/system/mfkey/application.fam +++ b/applications/system/mfkey/application.fam @@ -15,7 +15,7 @@ App( fap_icon_assets="images", fap_weburl="https://github.com/noproto/FlipperMfkey", fap_description="MIFARE Classic key recovery tool", - fap_version="3.0", + fap_version="3.1", ) App( diff --git a/applications/system/mfkey/crypto1.h b/applications/system/mfkey/crypto1.h index 9caf5b35e..b9f7c7725 100644 --- a/applications/system/mfkey/crypto1.h +++ b/applications/system/mfkey/crypto1.h @@ -6,251 +6,260 @@ #include #include -#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) -#define BIT(x, n) ((x) >> (n) & 1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) #define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) static inline uint32_t prng_successor(uint32_t x, uint32_t n); static inline int filter(uint32_t const x); static inline uint8_t evenparity32(uint32_t x); static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2); -void crypto1_get_lfsr(struct Crypto1State* state, MfClassicKey* lfsr); -static inline uint32_t crypt_word(struct Crypto1State* s); -static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x); -static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x); +void crypto1_get_lfsr(struct Crypto1State *state, MfClassicKey *lfsr); +static inline uint32_t crypt_word(struct Crypto1State *s); +static inline void crypt_word_noret(struct Crypto1State *s, uint32_t in, int x); +static inline uint32_t crypt_word_ret(struct Crypto1State *s, uint32_t in, int x); static uint32_t crypt_word_par( - struct Crypto1State* s, - uint32_t in, - int is_encrypted, - uint32_t nt_plain, - uint8_t* parity_keystream_bits); -static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x); -static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb); -static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb); + struct Crypto1State *s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t *parity_keystream_bits); +static inline void rollback_word_noret(struct Crypto1State *s, uint32_t in, int x); +static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State *s, uint32_t in, int fb); +static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State *s, uint32_t in, int fb); static const uint8_t lookup1[256] = { - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, - 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, + 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; static const uint8_t lookup2[256] = { - 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, - 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, - 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, - 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, - 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, - 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, - 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, - 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, - 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; + 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, + 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, + 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, + 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, + 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, + 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, + 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, + 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, + 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; -static inline int filter(uint32_t const x) { - uint32_t f; - f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; - f |= 0x0d938 >> (x >> 16 & 0xf) & 1; - return BIT(0xEC57E80A, f); +static inline int filter(uint32_t const x) +{ + uint32_t f; + f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; + f |= 0x0d938 >> (x >> 16 & 0xf) & 1; + return BIT(0xEC57E80A, f); } -#ifndef __ARM_ARCH_7EM__ -static inline uint8_t evenparity32(uint32_t x) { - return __builtin_parity(x); -} -#endif - #ifdef __ARM_ARCH_7EM__ -static inline uint8_t evenparity32(uint32_t x) { - uint32_t result; - __asm__ volatile("eor r1, %[x], %[x], lsr #16 \n\t" // r1 = x ^ (x >> 16) - "eor r1, r1, r1, lsr #8 \n\t" // r1 = r1 ^ (r1 >> 8) - "eor r1, r1, r1, lsr #4 \n\t" // r1 = r1 ^ (r1 >> 4) - "eor r1, r1, r1, lsr #2 \n\t" // r1 = r1 ^ (r1 >> 2) - "eor r1, r1, r1, lsr #1 \n\t" // r1 = r1 ^ (r1 >> 1) - "and %[result], r1, #1 \n\t" // result = r1 & 1 - : [result] "=r"(result) - : [x] "r"(x) - : "r1"); - return result; +static inline uint8_t evenparity32(uint32_t x) +{ + // fold 32 bits -> 16 -> 8 -> 4 + x ^= x >> 16; + x ^= x >> 8; + x ^= x >> 4; + // magic 0x6996: bit i tells you parity of i (0 ≤ i < 16) + return (uint8_t)((0x6996u >> (x & 0xF)) & 1); } #endif -static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) { - int p = data[item] >> 25; - p = p << 1 | evenparity32(data[item] & mask1); - p = p << 1 | evenparity32(data[item] & mask2); - data[item] = p << 24 | (data[item] & 0xffffff); +static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) +{ + int p = data[item] >> 25; + p = p << 1 | evenparity32(data[item] & mask1); + p = p << 1 | evenparity32(data[item] & mask2); + data[item] = p << 24 | (data[item] & 0xffffff); } -static inline uint32_t crypt_word(struct Crypto1State* s) { - // "in" and "x" are always 0 (last iteration) - uint32_t res_ret = 0; - uint32_t feedin, t; - for(int i = 0; i <= 31; i++) { - res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 - feedin = LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - } - return res_ret; +static inline uint32_t crypt_word(struct Crypto1State *s) +{ + // "in" and "x" are always 0 (last iteration) + uint32_t res_ret = 0; + uint32_t feedin, t; + for (int i = 0; i <= 31; i++) + { + res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 + feedin = LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return res_ret; } -static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x) { - uint8_t ret; - uint32_t feedin, t, next_in; - for(int i = 0; i <= 31; i++) { - next_in = BEBIT(in, i); - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - } - return; +static inline void crypt_word_noret(struct Crypto1State *s, uint32_t in, int x) +{ + uint8_t ret; + uint32_t feedin, t, next_in; + for (int i = 0; i <= 31; i++) + { + next_in = BEBIT(in, i); + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return; } -static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x) { - uint32_t ret = 0; - uint32_t feedin, t, next_in; - uint8_t next_ret; - for(int i = 0; i <= 31; i++) { - next_in = BEBIT(in, i); - next_ret = filter(s->odd); - feedin = next_ret & (!!x); - feedin ^= LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - ret |= next_ret << (24 ^ i); - } - return ret; +static inline uint32_t crypt_word_ret(struct Crypto1State *s, uint32_t in, int x) +{ + uint32_t ret = 0; + uint32_t feedin, t, next_in; + uint8_t next_ret; + for (int i = 0; i <= 31; i++) + { + next_in = BEBIT(in, i); + next_ret = filter(s->odd); + feedin = next_ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + ret |= next_ret << (24 ^ i); + } + return ret; } -static uint8_t get_nth_byte(uint32_t value, int n) { - if(n < 0 || n > 3) { - // Handle invalid input - return 0; - } - return (value >> (8 * (3 - n))) & 0xFF; +static uint8_t get_nth_byte(uint32_t value, int n) +{ + if (n < 0 || n > 3) + { + // Handle invalid input + return 0; + } + return (value >> (8 * (3 - n))) & 0xFF; } -static uint8_t crypt_bit(struct Crypto1State* s, uint8_t in, int is_encrypted) { - uint32_t feedin, t; - uint8_t ret = filter(s->odd); - feedin = ret & !!is_encrypted; - feedin ^= !!in; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= LF_POLY_EVEN & s->even; - s->even = s->even << 1 | evenparity32(feedin); - t = s->odd, s->odd = s->even, s->even = t; - return ret; +static uint8_t crypt_bit(struct Crypto1State *s, uint8_t in, int is_encrypted) +{ + uint32_t feedin, t; + uint8_t ret = filter(s->odd); + feedin = ret & !!is_encrypted; + feedin ^= !!in; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= LF_POLY_EVEN & s->even; + s->even = s->even << 1 | evenparity32(feedin); + t = s->odd, s->odd = s->even, s->even = t; + return ret; } static inline uint32_t crypt_word_par( - struct Crypto1State* s, - uint32_t in, - int is_encrypted, - uint32_t nt_plain, - uint8_t* parity_keystream_bits) { - uint32_t ret = 0; - *parity_keystream_bits = 0; // Reset parity keystream bits + struct Crypto1State *s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t *parity_keystream_bits) +{ + uint32_t ret = 0; + *parity_keystream_bits = 0; // Reset parity keystream bits - for(int i = 0; i < 32; i++) { - uint8_t bit = crypt_bit(s, BEBIT(in, i), is_encrypted); - ret |= bit << (24 ^ i); - // Save keystream parity bit - if((i + 1) % 8 == 0) { - *parity_keystream_bits |= - (filter(s->odd) ^ nfc_util_even_parity8(get_nth_byte(nt_plain, i / 8))) - << (3 - (i / 8)); - } - } - return ret; + for (int i = 0; i < 32; i++) + { + uint8_t bit = crypt_bit(s, BEBIT(in, i), is_encrypted); + ret |= bit << (24 ^ i); + // Save keystream parity bit + if ((i + 1) % 8 == 0) + { + *parity_keystream_bits |= + (filter(s->odd) ^ nfc_util_even_parity8(get_nth_byte(nt_plain, i / 8))) + << (3 - (i / 8)); + } + } + return ret; } -static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x) { - uint8_t ret; - uint32_t feedin, t, next_in; - for(int i = 31; i >= 0; i--) { - next_in = BEBIT(in, i); - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= s->even & 1; - feedin ^= LF_POLY_EVEN & (s->even >>= 1); - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even |= (evenparity32(feedin)) << 23; - } - return; +static inline void rollback_word_noret(struct Crypto1State *s, uint32_t in, int x) +{ + uint8_t ret; + uint32_t feedin, t, next_in; + for (int i = 31; i >= 0; i--) + { + next_in = BEBIT(in, i); + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= s->even & 1; + feedin ^= LF_POLY_EVEN & (s->even >>= 1); + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even |= (evenparity32(feedin)) << 23; + } + return; } // TODO: /* uint32_t rollback_word(struct Crypto1State *s, uint32_t in, int x) { - uint32_t res_ret = 0; - uint8_t ret; - uint32_t feedin, t, next_in; - for (int i = 31; i >= 0; i--) { - next_in = BEBIT(in, i); - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= s->even & 1; - feedin ^= LF_POLY_EVEN & (s->even >>= 1); - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even |= (evenparity32(feedin)) << 23; - res_ret |= (ret << (24 ^ i)); - } - return res_ret; + uint32_t res_ret = 0; + uint8_t ret; + uint32_t feedin, t, next_in; + for (int i = 31; i >= 0; i--) { + next_in = BEBIT(in, i); + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= s->even & 1; + feedin ^= LF_POLY_EVEN & (s->even >>= 1); + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even |= (evenparity32(feedin)) << 23; + res_ret |= (ret << (24 ^ i)); + } + return res_ret; } */ -uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb) { - int out; - uint8_t ret; - uint32_t t; - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; +uint8_t napi_lfsr_rollback_bit(struct Crypto1State *s, uint32_t in, int fb) +{ + int out; + uint8_t ret; + uint32_t t; + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; - out = s->even & 1; - out ^= LF_POLY_EVEN & (s->even >>= 1); - out ^= LF_POLY_ODD & s->odd; - out ^= !!in; - out ^= (ret = filter(s->odd)) & !!fb; + out = s->even & 1; + out ^= LF_POLY_EVEN & (s->even >>= 1); + out ^= LF_POLY_ODD & s->odd; + out ^= !!in; + out ^= (ret = filter(s->odd)) & !!fb; - s->even |= evenparity32(out) << 23; - return ret; + s->even |= evenparity32(out) << 23; + return ret; } -uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb) { - int i; - uint32_t ret = 0; - for(i = 31; i >= 0; --i) - ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); - return ret; +uint32_t napi_lfsr_rollback_word(struct Crypto1State *s, uint32_t in, int fb) +{ + int i; + uint32_t ret = 0; + for (i = 31; i >= 0; --i) + ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); + return ret; } -static inline uint32_t prng_successor(uint32_t x, uint32_t n) { - SWAPENDIAN(x); - while(n--) - x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; - return SWAPENDIAN(x); +static inline uint32_t prng_successor(uint32_t x, uint32_t n) +{ + SWAPENDIAN(x); + while (n--) + x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; + return SWAPENDIAN(x); } -#endif // CRYPTO1_H +#endif // CRYPTO1_H \ No newline at end of file diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index ae5cc90f2..e49b96263 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -11,8 +11,6 @@ // TODO: Find ~1 KB memory leak // TODO: Use seednt16 to reduce static encrypted key candidates: https://gist.github.com/noproto/8102f8f32546564cd674256e62ff76ea // https://eprint.iacr.org/2024/1275.pdf section X -// TODO: Static Encrypted: Minimum RAM for adding to keys dict (avoid crashes) -// TODO: Static Encrypted: Optimize KeysDict or buffer keys to write in chunks #include #include @@ -37,879 +35,1345 @@ // TODO: Remove defines that are not needed #define KEYS_DICT_SYSTEM_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") -#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") -#define MAX_NAME_LEN 32 -#define MAX_PATH_LEN 64 +#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") +#define MAX_NAME_LEN 32 +#define MAX_PATH_LEN 64 +#define STATIC_ENCRYPTED_RAM_THRESHOLD 4096 -#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) -#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) -#define CONST_M2_1 (LF_POLY_ODD << 1) -#define CONST_M1_2 (LF_POLY_ODD) -#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) -#define BIT(x, n) ((x) >> (n) & 1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define SWAP(a, b) \ + do \ + { \ + unsigned int t = a; \ + a = b; \ + b = t; \ + } while (0) #define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) -//#define SIZEOF(arr) sizeof(arr) / sizeof(*arr) + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) +// #define SIZEOF(arr) sizeof(arr) / sizeof(*arr) -static int eta_round_time = 44; -static int eta_total_time = 705; -// MSB_LIMIT: Chunk size (out of 256) -static int MSB_LIMIT = 16; +// Reduced to 16-bit as these values are small and don't need 32-bit +static int16_t eta_round_time = 44; +static int16_t eta_total_time = 705; +// MSB_LIMIT: Chunk size (out of 256) - can be 8-bit as it's a small value +static uint8_t MSB_LIMIT = 16; + +static inline void flush_key_buffer(ProgramState *program_state) +{ + if (program_state->key_buffer && program_state->key_buffer_count > 0 && program_state->cuid_dict) + { + // Pre-allocate exact size needed: 12 hex chars + 1 newline per key + size_t total_size = program_state->key_buffer_count * 13; + //FURI_LOG_I(TAG, "Flushing key buffer: %d keys", program_state->key_buffer_count); + //FURI_LOG_I(TAG, "Total size: %d bytes", total_size); + char* batch_buffer = malloc(total_size + 1); // +1 for null terminator + + char* ptr = batch_buffer; + const char hex_chars[] = "0123456789ABCDEF"; + + for (size_t i = 0; i < program_state->key_buffer_count; i++) + { + // Convert key to hex string directly into buffer + for (size_t j = 0; j < sizeof(MfClassicKey); j++) + { + uint8_t byte = program_state->key_buffer[i].data[j]; + *ptr++ = hex_chars[byte >> 4]; + *ptr++ = hex_chars[byte & 0x0F]; + } + *ptr++ = '\n'; + } + *ptr = '\0'; + + // Write all keys at once by directly accessing the stream + Stream* stream = program_state->cuid_dict->stream; + uint32_t actual_pos = stream_tell(stream); + + if (stream_seek(stream, 0, StreamOffsetFromEnd) && + stream_write(stream, (uint8_t*)batch_buffer, total_size) == total_size) + { + // Update total key count + program_state->cuid_dict->total_keys += program_state->key_buffer_count; + } + + // May not be needed + stream_seek(stream, actual_pos, StreamOffsetFromStart); + free(batch_buffer); + program_state->key_buffer_count = 0; + } +} static inline int - check_state(struct Crypto1State* t, MfClassicNonce* n, ProgramState* program_state) { - if(!(t->odd | t->even)) return 0; - if(n->attack == mfkey32) { - uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); - if(rb != n->ar0_enc) { - return 0; - } - rollback_word_noret(t, n->nr0_enc, 1); - rollback_word_noret(t, n->uid_xor_nt0, 0); - struct Crypto1State temp = {t->odd, t->even}; - crypt_word_noret(t, n->uid_xor_nt1, 0); - crypt_word_noret(t, n->nr1_enc, 1); - if(n->ar1_enc == (crypt_word(t) ^ n->p64b)) { - crypto1_get_lfsr(&temp, &(n->key)); - return 1; - } - } else if(n->attack == static_nested) { - struct Crypto1State temp = {t->odd, t->even}; - rollback_word_noret(t, n->uid_xor_nt1, 0); - if(n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) { - rollback_word_noret(&temp, n->uid_xor_nt1, 0); - crypto1_get_lfsr(&temp, &(n->key)); - return 1; - } - } else if(n->attack == static_encrypted) { - // TODO: Parity bits from rollback_word? - if(n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) { - // Reduce with parity - uint8_t local_parity_keystream_bits; - struct Crypto1State temp = {t->odd, t->even}; - if((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == - n->ks1_1_enc) && - (local_parity_keystream_bits == n->par_1)) { - // Found key candidate - crypto1_get_lfsr(t, &(n->key)); - program_state->num_candidates++; - keys_dict_add_key(program_state->cuid_dict, n->key.data, sizeof(MfClassicKey)); - } - } - } - return 0; +check_state(struct Crypto1State *t, MfClassicNonce *n, ProgramState *program_state) +{ + if (!(t->odd | t->even)) + return 0; + if (n->attack == mfkey32) + { + uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); + if (rb != n->ar0_enc) + { + return 0; + } + rollback_word_noret(t, n->nr0_enc, 1); + rollback_word_noret(t, n->uid_xor_nt0, 0); + struct Crypto1State temp = {t->odd, t->even}; + crypt_word_noret(t, n->uid_xor_nt1, 0); + crypt_word_noret(t, n->nr1_enc, 1); + if (n->ar1_enc == (crypt_word(t) ^ n->p64b)) + { + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + } + else if (n->attack == static_nested) + { + struct Crypto1State temp = {t->odd, t->even}; + rollback_word_noret(t, n->uid_xor_nt1, 0); + if (n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) + { + rollback_word_noret(&temp, n->uid_xor_nt1, 0); + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + } + else if (n->attack == static_encrypted) + { + // TODO: Parity bits from rollback_word? + if (n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) + { + // Reduce with parity + uint8_t local_parity_keystream_bits; + struct Crypto1State temp = {t->odd, t->even}; + if ((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == + n->ks1_1_enc) && + (local_parity_keystream_bits == n->par_1)) + { + // Found key candidate + crypto1_get_lfsr(t, &(n->key)); + program_state->num_candidates++; + + // Use key buffer - buffer is guaranteed to be available for static_encrypted + program_state->key_buffer[program_state->key_buffer_count] = n->key; + program_state->key_buffer_count++; + + // Flush buffer when full + if (program_state->key_buffer_count >= program_state->key_buffer_size) + { + flush_key_buffer(program_state); + } + } + } + } + return 0; } -static inline int state_loop( - unsigned int* states_buffer, - int xks, - int m1, - int m2, - unsigned int in, - uint8_t and_val) { - int states_tail = 0; - int round = 0, s = 0, xks_bit = 0, round_in = 0; +static inline __attribute__((hot)) int state_loop( + unsigned int *states_buffer, + int xks, + int m1, + int m2, + unsigned int in, + int and_val) +{ + int states_tail = 0; + int xks_bit = 0, round_in = 0; - for(round = 1; round <= 12; round++) { - xks_bit = BIT(xks, round); - if(round > 4) { - round_in = ((in >> (2 * (round - 4))) & and_val) << 24; - } + // Unroll first 4 rounds (no round_in calculations needed) + // Hoist the filter() calls to just one iteration and reuse the results + // This avoids redundant calculations and improves performance and gives us 2000b of extra ram (11496b free on run) + // V.28/04. Aprox 3s speedup per round. Total 3 keys 7mins 17s!! + for (int round = 1; round <= 4; ++round) + { + xks_bit = BIT(xks, round); + for (int s = 0; s <= states_tail; ++s) + { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v); + int f1 = filter(v | 1); - for(s = 0; s <= states_tail; s++) { - states_buffer[s] <<= 1; + if (__builtin_expect((f0 ^ f1) != 0, 0)) + { + states_buffer[s] |= f0 ^ xks_bit; + } + else if (__builtin_expect(f0 == xks_bit, 1)) + { + states_buffer[++states_tail] = states_buffer[++s]; + states_buffer[s] = states_buffer[s - 1] | 1; + } + else + { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } - if((filter(states_buffer[s]) ^ filter(states_buffer[s] | 1)) != 0) { - states_buffer[s] |= filter(states_buffer[s]) ^ xks_bit; - if(round > 4) { - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= round_in; - } - } else if(filter(states_buffer[s]) == xks_bit) { - // TODO: Refactor - if(round > 4) { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = states_buffer[s] | 1; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s++] ^= round_in; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= round_in; - } else { - states_buffer[++states_tail] = states_buffer[++s]; - states_buffer[s] = states_buffer[s - 1] | 1; - } - } else { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } + // Round 5 (unrolled) + { + xks_bit = BIT(xks, 5); + int r5_in = ((in >> 2) & and_val) << 24; // 2*(5-4)=2 + for (int s = 0; s <= states_tail; ++s) + { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if (__builtin_expect((f0 ^ f1) != 0, 0)) + { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r5_in; + } + else if (__builtin_expect(f0 == xks_bit, 1)) + { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= r5_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r5_in; + } + else + { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } - return states_tail; + // Round 6 (unrolled) + { + xks_bit = BIT(xks, 6); + int r6_in = ((in >> 4) & and_val) << 24; // 2*(6-4)=4 + for (int s = 0; s <= states_tail; ++s) + { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if (__builtin_expect((f0 ^ f1) != 0, 0)) + { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r6_in; + } + else if (__builtin_expect(f0 == xks_bit, 1)) + { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= r6_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r6_in; + } + else + { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } + + // Loop rounds 7–12 + for (int round = 7; round <= 12; ++round) + { + xks_bit = BIT(xks, round); + round_in = ((in >> (2 * (round - 4))) & and_val) << 24; + for (int s = 0; s <= states_tail; ++s) + { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if (__builtin_expect((f0 ^ f1) != 0, 0)) + { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } + else if (__builtin_expect(f0 == xks_bit, 1)) + { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= round_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } + else + { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } + + return states_tail; } -int binsearch(unsigned int data[], int start, int stop) { - int mid, val = data[stop] & 0xff000000; - while(start != stop) { - mid = (stop - start) >> 1; - if((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) - stop = start + mid; - else - start += mid + 1; - } - return start; +int binsearch(unsigned int data[], int start, int stop) +{ + int mid, val = data[stop] & 0xff000000; + while (start != stop) + { + mid = (stop - start) >> 1; + if ((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) + stop = start + mid; + else + start += mid + 1; + } + return start; } -void quicksort(unsigned int array[], int low, int high) { - //if (SIZEOF(array) == 0) - // return; - if(low >= high) return; - int middle = low + (high - low) / 2; - unsigned int pivot = array[middle]; - int i = low, j = high; - while(i <= j) { - while(array[i] < pivot) { - i++; - } - while(array[j] > pivot) { - j--; - } - if(i <= j) { // swap - int temp = array[i]; - array[i] = array[j]; - array[j] = temp; - i++; - j--; - } - } - if(low < j) { - quicksort(array, low, j); - } - if(high > i) { - quicksort(array, i, high); - } + +void quicksort(unsigned int array[], int low, int high) +{ + // Use insertion sort for small arrays (threshold determined by testing) + if (high - low < 16) + { + // Insertion sort + for (int i = low + 1; i <= high; i++) + { + unsigned int key = array[i]; + int j = i - 1; + while (j >= low && array[j] > key) + { + array[j + 1] = array[j]; + j--; + } + array[j + 1] = key; + } + return; + } + + if (low >= high) + return; + + // Median-of-three pivot selection + int middle = low + (high - low) / 2; + if (array[middle] < array[low]) + SWAP(array[middle], array[low]); + if (array[high] < array[low]) + SWAP(array[high], array[low]); + if (array[high] < array[middle]) + SWAP(array[high], array[middle]); + + unsigned int pivot = array[middle]; + + // Rest of quicksort with improved partitioning + int i = low, j = high; + while (i <= j) + { + while (array[i] < pivot) + i++; + while (array[j] > pivot) + j--; + if (i <= j) + { + // swap + unsigned int temp = array[i]; + array[i] = array[j]; + array[j] = temp; + i++; + j--; + } + } + + if (low < j) + quicksort(array, low, j); + if (high > i) + quicksort(array, i, high); } -int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) { - in <<= 24; - for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { - if((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) { - data[tbl] |= filter(data[tbl]) ^ bit; - update_contribution(data, tbl, m1, m2); - data[tbl] ^= in; - } else if(filter(data[tbl]) == bit) { - data[++end] = data[tbl + 1]; - data[tbl + 1] = data[tbl] | 1; - update_contribution(data, tbl, m1, m2); - data[tbl++] ^= in; - update_contribution(data, tbl, m1, m2); - data[tbl] ^= in; - } else { - data[tbl--] = data[end--]; - } - } - return end; + +int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) +{ + in <<= 24; + for (data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) + { + if ((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) + { + data[tbl] |= filter(data[tbl]) ^ bit; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } + else if (filter(data[tbl]) == bit) + { + data[++end] = data[tbl + 1]; + data[tbl + 1] = data[tbl] | 1; + update_contribution(data, tbl, m1, m2); + data[tbl++] ^= in; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } + else + { + data[tbl--] = data[end--]; + } + } + return end; } int old_recover( - unsigned int odd[], - int o_head, - int o_tail, - int oks, - unsigned int even[], - int e_head, - int e_tail, - int eks, - int rem, - int s, - MfClassicNonce* n, - unsigned int in, - int first_run, - ProgramState* program_state) { - int o, e, i; - if(rem == -1) { - for(e = e_head; e <= e_tail; ++e) { - even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); - for(o = o_head; o <= o_tail; ++o, ++s) { - struct Crypto1State temp = {0, 0}; - temp.even = odd[o]; - temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); - if(check_state(&temp, n, program_state)) { - return -1; - } - } - } - return s; - } - if(first_run == 0) { - for(i = 0; (i < 4) && (rem-- != 0); i++) { - oks >>= 1; - eks >>= 1; - in >>= 2; - o_tail = extend_table( - odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); - if(o_head > o_tail) return s; - e_tail = extend_table( - even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); - if(e_head > e_tail) return s; - } - } - first_run = 0; - quicksort(odd, o_head, o_tail); - quicksort(even, e_head, e_tail); - while(o_tail >= o_head && e_tail >= e_head) { - if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) { - o_tail = binsearch(odd, o_head, o = o_tail); - e_tail = binsearch(even, e_head, e = e_tail); - s = old_recover( - odd, - o_tail--, - o, - oks, - even, - e_tail--, - e, - eks, - rem, - s, - n, - in, - first_run, - program_state); - if(s == -1) { - break; - } - } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) { - o_tail = binsearch(odd, o_head, o_tail) - 1; - } else { - e_tail = binsearch(even, e_head, e_tail) - 1; - } - } - return s; + unsigned int odd[], + int o_head, + int o_tail, + int oks, + unsigned int even[], + int e_head, + int e_tail, + int eks, + int rem, + int s, + MfClassicNonce *n, + unsigned int in, + int first_run, + ProgramState *program_state) +{ + int o, e, i; + if (rem == -1) + { + for (e = e_head; e <= e_tail; ++e) + { + even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); + for (o = o_head; o <= o_tail; ++o, ++s) + { + struct Crypto1State temp = {0, 0}; + temp.even = odd[o]; + temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); + if (check_state(&temp, n, program_state)) + { + return -1; + } + } + } + return s; + } + if (first_run == 0) + { + for (i = 0; (i < 4) && (rem-- != 0); i++) + { + oks >>= 1; + eks >>= 1; + in >>= 2; + o_tail = extend_table( + odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); + if (o_head > o_tail) + return s; + e_tail = extend_table( + even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); + if (e_head > e_tail) + return s; + } + } + first_run = 0; + quicksort(odd, o_head, o_tail); + quicksort(even, e_head, e_tail); + while (o_tail >= o_head && e_tail >= e_head) + { + if (((odd[o_tail] ^ even[e_tail]) >> 24) == 0) + { + o_tail = binsearch(odd, o_head, o = o_tail); + e_tail = binsearch(even, e_head, e = e_tail); + s = old_recover( + odd, + o_tail--, + o, + oks, + even, + e_tail--, + e, + eks, + rem, + s, + n, + in, + first_run, + program_state); + if (s == -1) + { + break; + } + } + else if ((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) + { + o_tail = binsearch(odd, o_head, o_tail) - 1; + } + else + { + e_tail = binsearch(even, e_head, e_tail) - 1; + } + } + return s; } -static inline int sync_state(ProgramState* program_state) { - int ts = furi_hal_rtc_get_timestamp(); - int elapsed_time = ts - program_state->eta_timestamp; - if(elapsed_time < program_state->eta_round) { - program_state->eta_round -= elapsed_time; - } else { - program_state->eta_round = 0; - } - if(elapsed_time < program_state->eta_total) { - program_state->eta_total -= elapsed_time; - } else { - program_state->eta_total = 0; - } - program_state->eta_timestamp = ts; - if(program_state->close_thread_please) { - return 1; - } - return 0; +static inline int sync_state(ProgramState *program_state) +{ + int ts = furi_hal_rtc_get_timestamp(); + int elapsed_time = ts - program_state->eta_timestamp; + if (elapsed_time < program_state->eta_round) + { + program_state->eta_round -= elapsed_time; + } + else + { + program_state->eta_round = 0; + } + if (elapsed_time < program_state->eta_total) + { + program_state->eta_total -= elapsed_time; + } + else + { + program_state->eta_total = 0; + } + program_state->eta_timestamp = ts; + if (program_state->close_thread_please) + { + return 1; + } + return 0; } int calculate_msb_tables( - int oks, - int eks, - int msb_round, - MfClassicNonce* n, - unsigned int* states_buffer, - struct Msb* odd_msbs, - struct Msb* even_msbs, - unsigned int* temp_states_odd, - unsigned int* temp_states_even, - unsigned int in, - ProgramState* program_state) { - //FURI_LOG_I(TAG, "MSB GO %i", msb_iter); // DEBUG - unsigned int msb_head = (MSB_LIMIT * msb_round); // msb_iter ranges from 0 to (256/MSB_LIMIT)-1 - unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); - int states_tail = 0, tail = 0; - int i = 0, j = 0, semi_state = 0, found = 0; - unsigned int msb = 0; - in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; - // TODO: Why is this necessary? - memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + int oks, + int eks, + int msb_round, + MfClassicNonce *n, + unsigned int *states_buffer, + struct Msb *odd_msbs, + struct Msb *even_msbs, + unsigned int *temp_states_odd, + unsigned int *temp_states_even, + unsigned int in, + ProgramState *program_state) +{ + unsigned int msb_head = (MSB_LIMIT * msb_round); + unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); + int states_tail = 0; + int semi_state = 0; + unsigned int msb = 0; - for(semi_state = 1 << 20; semi_state >= 0; semi_state--) { - if(semi_state % 32768 == 0) { - if(sync_state(program_state) == 1) { - return 0; - } - } + // Preprocessed in value + in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; - if(filter(semi_state) == (oks & 1)) { //-V547 - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); + // Clear MSB arrays once before loop instead of inside loop + memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - for(i = states_tail; i >= 0; i--) { - msb = states_buffer[i] >> 24; - if((msb >= msb_head) && (msb < msb_tail)) { - found = 0; - for(j = 0; j < odd_msbs[msb - msb_head].tail - 1; j++) { - if(odd_msbs[msb - msb_head].states[j] == states_buffer[i]) { - found = 1; - break; - } - } + // Bit values to check - calculate once outside the loop + int oks_bit = oks & 1; + int eks_bit = eks & 1; - if(!found) { - tail = odd_msbs[msb - msb_head].tail++; - odd_msbs[msb - msb_head].states[tail] = states_buffer[i]; - } - } - } - } + // Check for stop request less frequently + int sync_check_interval = 32768 * 2; // Doubled the interval - if(filter(semi_state) == (eks & 1)) { //-V547 - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); + for (semi_state = 1 << 20; semi_state >= 0; semi_state--) + { + if (semi_state % sync_check_interval == 0) + { + if (sync_state(program_state) == 1) + { + return 0; + } + } - for(i = 0; i <= states_tail; i++) { - msb = states_buffer[i] >> 24; - if((msb >= msb_head) && (msb < msb_tail)) { - found = 0; + // Process both filter conditions in one pass when possible + int filter_semi_state = filter(semi_state); - for(j = 0; j < even_msbs[msb - msb_head].tail; j++) { - if(even_msbs[msb - msb_head].states[j] == states_buffer[i]) { - found = 1; - break; - } - } + // Check oks condition + if (filter_semi_state == oks_bit) + { + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); - if(!found) { - tail = even_msbs[msb - msb_head].tail++; - even_msbs[msb - msb_head].states[tail] = states_buffer[i]; - } - } - } - } - } + for (int i = states_tail; i >= 0; i--) + { + msb = states_buffer[i] >> 24; + if ((msb >= msb_head) && (msb < msb_tail)) + { + // Calculate index once + int msb_idx = msb - msb_head; - oks >>= 12; - eks >>= 12; + // Avoid sequential scan by using a direct flag + int found = 0; + for (int j = 0; j < odd_msbs[msb_idx].tail; j++) + { + if (odd_msbs[msb_idx].states[j] == states_buffer[i]) + { + found = 1; + break; + } + } - for(i = 0; i < MSB_LIMIT; i++) { - if(sync_state(program_state) == 1) { - return 0; - } - // TODO: Why is this necessary? - memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); - memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); - memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); - memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); - int res = old_recover( - temp_states_odd, - 0, - odd_msbs[i].tail, - oks, - temp_states_even, - 0, - even_msbs[i].tail, - eks, - 3, - 0, - n, - in >> 16, - 1, - program_state); - if(res == -1) { - return 1; - } - //odd_msbs[i].tail = 0; - //even_msbs[i].tail = 0; - } + if (!found) + { + int tail = odd_msbs[msb_idx].tail++; + odd_msbs[msb_idx].states[tail] = states_buffer[i]; + } + } + } + } - return 0; + // Check eks condition + if (filter_semi_state == eks_bit) + { + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); + + for (int i = 0; i <= states_tail; i++) + { + msb = states_buffer[i] >> 24; + if ((msb >= msb_head) && (msb < msb_tail)) + { + // Calculate index once + int msb_idx = msb - msb_head; + + // Avoid sequential scan + int found = 0; + for (int j = 0; j < even_msbs[msb_idx].tail; j++) + { + if (even_msbs[msb_idx].states[j] == states_buffer[i]) + { + found = 1; + break; + } + } + + if (!found) + { + int tail = even_msbs[msb_idx].tail++; + even_msbs[msb_idx].states[tail] = states_buffer[i]; + } + } + } + } + } + + // Shift once outside the loop + oks >>= 12; + eks >>= 12; + + // Process results + for (int i = 0; i < MSB_LIMIT; i++) + { + if ((i % 4) == 0 && sync_state(program_state) == 1) + { + return 0; + } + + // Only clear buffers if they're going to be used + if (odd_msbs[i].tail > 0 || even_msbs[i].tail > 0) + { + memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); + memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); + memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); + memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); + + int res = old_recover( + temp_states_odd, + 0, + odd_msbs[i].tail, + oks, + temp_states_even, + 0, + even_msbs[i].tail, + eks, + 3, + 0, + n, + in >> 16, + 1, + program_state); + + if (res == -1) + { + return 1; + } + } + } + + return 0; } -void** allocate_blocks(const size_t* block_sizes, int num_blocks) { - void** block_pointers = malloc(num_blocks * sizeof(void*)); +void **allocate_blocks(const size_t *block_sizes, int num_blocks) +{ + void **block_pointers = malloc(num_blocks * sizeof(void *)); + if (!block_pointers) + { + return NULL; + } - for(int i = 0; i < num_blocks; i++) { - if(memmgr_heap_get_max_free_block() < block_sizes[i]) { - // Not enough memory, free previously allocated blocks - for(int j = 0; j < i; j++) { - free(block_pointers[j]); - } - free(block_pointers); - return NULL; - } + for (int i = 0; i < num_blocks; i++) + { + if (memmgr_heap_get_max_free_block() < block_sizes[i]) + { + // Not enough memory, free previously allocated blocks + for (int j = 0; j < i; j++) + { + free(block_pointers[j]); + } + free(block_pointers); + return NULL; + } - block_pointers[i] = malloc(block_sizes[i]); - } + block_pointers[i] = malloc(block_sizes[i]); + if (!block_pointers[i]) + { + // Allocation failed + for (int j = 0; j < i; j++) + { + free(block_pointers[j]); + } + free(block_pointers); + return NULL; + } + } - return block_pointers; + return block_pointers; } -bool is_full_speed() { - return MSB_LIMIT == 16; -} +bool recover(MfClassicNonce *n, int ks2, unsigned int in, ProgramState *program_state) +{ + bool found = false; + const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; + const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; + const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); + // Reset globals each nonce + eta_round_time = 44; + eta_total_time = 705; + MSB_LIMIT = 16; + + // Use half speed (reduced block sizes) for static encrypted nonces so we can buffer keys + bool use_half_speed = (n->attack == static_encrypted); + if (use_half_speed) + { + //eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + } -bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_state) { - bool found = false; - const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; - const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; - const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); - void** block_pointers = allocate_blocks(block_sizes, num_blocks); - if(block_pointers == NULL) { - // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed - if(is_full_speed()) { - //eta_round_time *= 2; - eta_total_time *= 2; - MSB_LIMIT /= 2; - } - block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); - if(block_pointers == NULL) { - // System has less than 70 KB of RAM - should never happen so we don't reduce speed further - program_state->err = InsufficientRAM; - program_state->mfkey_state = Error; - return false; - } - } - // Adjust estimates for static encrypted attacks - if(n->attack == static_encrypted) { - eta_round_time *= 4; - eta_total_time *= 4; - if(is_full_speed()) { - eta_round_time *= 4; - eta_total_time *= 4; - } - } - struct Msb* odd_msbs = block_pointers[0]; - struct Msb* even_msbs = block_pointers[1]; - unsigned int* temp_states_odd = block_pointers[2]; - unsigned int* temp_states_even = block_pointers[3]; - unsigned int* states_buffer = block_pointers[4]; - int oks = 0, eks = 0; - int i = 0, msb = 0; - for(i = 31; i >= 0; i -= 2) { - oks = oks << 1 | BEBIT(ks2, i); - } - for(i = 30; i >= 0; i -= 2) { - eks = eks << 1 | BEBIT(ks2, i); - } - int bench_start = furi_hal_rtc_get_timestamp(); - program_state->eta_total = eta_total_time; - program_state->eta_timestamp = bench_start; - for(msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) { - program_state->search = msb; - program_state->eta_round = eta_round_time; - program_state->eta_total = eta_total_time - (eta_round_time * msb); - if(calculate_msb_tables( - oks, - eks, - msb, - n, - states_buffer, - odd_msbs, - even_msbs, - temp_states_odd, - temp_states_even, - in, - program_state)) { - //int bench_stop = furi_hal_rtc_get_timestamp(); - //FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); - found = true; - break; - } - if(program_state->close_thread_please) { - break; - } - } - // Free the allocated blocks - for(int i = 0; i < num_blocks; i++) { - free(block_pointers[i]); - } - free(block_pointers); - return found; + void **block_pointers = allocate_blocks(use_half_speed ? reduced_block_sizes : block_sizes, num_blocks); + if (block_pointers == NULL) { + if (n->attack != static_encrypted) + { + // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed + // eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); + if (block_pointers == NULL) + { + // System has less than 70 KB of RAM - should never happen so we don't reduce speed further + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } else + { + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + struct Msb *odd_msbs = block_pointers[0]; + struct Msb *even_msbs = block_pointers[1]; + unsigned int *temp_states_odd = block_pointers[2]; + unsigned int *temp_states_even = block_pointers[3]; + unsigned int *states_buffer = block_pointers[4]; + + // Allocate key buffer for static encrypted nonces + if (n->attack == static_encrypted) + { + size_t available_ram = memmgr_heap_get_max_free_block(); + // Each key becomes 12 hex chars + 1 newline = 13 bytes in the batch string + // Plus original 6 bytes in buffer = 19 bytes total per key + // Add extra safety margin for string overhead and other allocations + const size_t safety_threshold = STATIC_ENCRYPTED_RAM_THRESHOLD; + const size_t bytes_per_key = sizeof(MfClassicKey) + 13; // buffer + string representation + if (available_ram > safety_threshold) + { + program_state->key_buffer_size = (available_ram - safety_threshold) / bytes_per_key; + program_state->key_buffer = malloc(program_state->key_buffer_size * sizeof(MfClassicKey)); + program_state->key_buffer_count = 0; + if (!program_state->key_buffer) + { + // Free the allocated blocks before returning + for (int i = 0; i < num_blocks; i++) + { + free(block_pointers[i]); + } + free(block_pointers); + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + else + { + // Free the allocated blocks before returning + for (int i = 0; i < num_blocks; i++) + { + free(block_pointers[i]); + } + free(block_pointers); + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + else + { + program_state->key_buffer = NULL; + program_state->key_buffer_size = 0; + program_state->key_buffer_count = 0; + } + + int oks = 0, eks = 0; + int i = 0, msb = 0; + for (i = 31; i >= 0; i -= 2) + { + oks = oks << 1 | BEBIT(ks2, i); + } + for (i = 30; i >= 0; i -= 2) + { + eks = eks << 1 | BEBIT(ks2, i); + } + int bench_start = furi_hal_rtc_get_timestamp(); + program_state->eta_total = eta_total_time; + program_state->eta_timestamp = bench_start; + for (msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) + { + program_state->search = msb; + program_state->eta_round = eta_round_time; + program_state->eta_total = eta_total_time - (eta_round_time * msb); + if (calculate_msb_tables( + oks, + eks, + msb, + n, + states_buffer, + odd_msbs, + even_msbs, + temp_states_odd, + temp_states_even, + in, + program_state)) + { + // int bench_stop = furi_hal_rtc_get_timestamp(); + // FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); + found = true; + break; + } + if (program_state->close_thread_please) + { + break; + } + } + + // Final flush and cleanup for key buffer + if (n->attack == static_encrypted && program_state->key_buffer) + { + flush_key_buffer(program_state); + free(program_state->key_buffer); + program_state->key_buffer = NULL; + program_state->key_buffer_size = 0; + program_state->key_buffer_count = 0; + } + + // Free the allocated blocks + for (int i = 0; i < num_blocks; i++) + { + free(block_pointers[i]); + } + free(block_pointers); + return found; } bool key_already_found_for_nonce_in_solved( - MfClassicKey* keyarray, - int keyarray_size, - MfClassicNonce* nonce) { - for(int k = 0; k < keyarray_size; k++) { - uint64_t key_as_int = bit_lib_bytes_to_num_be(keyarray[k].data, sizeof(MfClassicKey)); - struct Crypto1State temp = {0, 0}; - for(int i = 0; i < 24; i++) { - (&temp)->odd |= (BIT(key_as_int, 2 * i + 1) << (i ^ 3)); - (&temp)->even |= (BIT(key_as_int, 2 * i) << (i ^ 3)); - } - if(nonce->attack == mfkey32) { - crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); - crypt_word_noret(&temp, nonce->nr1_enc, 1); - if(nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) { - return true; - } - } else if(nonce->attack == static_nested) { - uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); - if(nonce->ks1_1_enc == expected_ks1) { - return true; - } - } - } - return false; + MfClassicKey *keyarray, + int keyarray_size, + MfClassicNonce *nonce) +{ + for (int k = 0; k < keyarray_size; k++) + { + uint64_t key_as_int = bit_lib_bytes_to_num_be(keyarray[k].data, sizeof(MfClassicKey)); + struct Crypto1State temp = {0, 0}; + for (int i = 0; i < 24; i++) + { + (&temp)->odd |= (BIT(key_as_int, 2 * i + 1) << (i ^ 3)); + (&temp)->even |= (BIT(key_as_int, 2 * i) << (i ^ 3)); + } + if (nonce->attack == mfkey32) + { + crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); + crypt_word_noret(&temp, nonce->nr1_enc, 1); + if (nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) + { + return true; + } + } + else if (nonce->attack == static_nested) + { + uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); + if (nonce->ks1_1_enc == expected_ks1) + { + return true; + } + } + } + return false; } #pragma GCC push_options #pragma GCC optimize("Os") -static void finished_beep() { - // Beep to indicate completion - NotificationApp* notification = furi_record_open("notification"); - notification_message(notification, &sequence_audiovisual_alert); - notification_message(notification, &sequence_display_backlight_on); - furi_record_close("notification"); +static void finished_beep() +{ + // Beep to indicate completion + NotificationApp *notification = furi_record_open("notification"); + notification_message(notification, &sequence_audiovisual_alert); + notification_message(notification, &sequence_display_backlight_on); + furi_record_close("notification"); } -void mfkey(ProgramState* program_state) { - uint32_t ks_enc = 0, nt_xor_uid = 0; - MfClassicKey found_key; // Recovered key - size_t keyarray_size = 0; - MfClassicKey* keyarray = malloc(sizeof(MfClassicKey) * 1); - uint32_t i = 0, j = 0; - //FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); - Storage* storage = furi_record_open(RECORD_STORAGE); - FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); - flipper_application_preload(app, APP_ASSETS_PATH("plugins/mfkey_init_plugin.fal")); - flipper_application_map_to_memory(app); - const FlipperAppPluginDescriptor* app_descriptor = - flipper_application_plugin_get_descriptor(app); - const MfkeyPlugin* init_plugin = app_descriptor->entry_point; - // Check for nonces - program_state->mfkey32_present = init_plugin->napi_mf_classic_mfkey32_nonces_check_presence(); - program_state->nested_present = init_plugin->napi_mf_classic_nested_nonces_check_presence(); - if(!(program_state->mfkey32_present) && !(program_state->nested_present)) { - program_state->err = MissingNonces; - program_state->mfkey_state = Error; - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - free(keyarray); - return; - } - // Read dictionaries (optional) - KeysDict* system_dict = {0}; - bool system_dict_exists = keys_dict_check_presence(KEYS_DICT_SYSTEM_PATH); - KeysDict* user_dict = {0}; - bool user_dict_exists = keys_dict_check_presence(KEYS_DICT_USER_PATH); - uint32_t total_dict_keys = 0; - if(system_dict_exists) { - system_dict = - keys_dict_alloc(KEYS_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); - total_dict_keys += keys_dict_get_total_keys(system_dict); - } - user_dict = keys_dict_alloc(KEYS_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); - if(user_dict_exists) { - total_dict_keys += keys_dict_get_total_keys(user_dict); - } - user_dict_exists = true; - program_state->dict_count = total_dict_keys; - program_state->mfkey_state = DictionaryAttack; - // Read nonces - MfClassicNonceArray* nonce_arr; - nonce_arr = init_plugin->napi_mf_classic_nonce_array_alloc( - system_dict, system_dict_exists, user_dict, program_state); - if(system_dict_exists) { - keys_dict_free(system_dict); - } - if(nonce_arr->total_nonces == 0) { - // Nothing to crack - program_state->err = ZeroNonces; - program_state->mfkey_state = Error; - init_plugin->napi_mf_classic_nonce_array_free(nonce_arr); - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - keys_dict_free(user_dict); - free(keyarray); - return; - } - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - // TODO: Track free state at the time this is called to ensure double free does not happen - furi_assert(nonce_arr); - furi_assert(nonce_arr->stream); - // TODO: Already closed? - buffered_file_stream_close(nonce_arr->stream); - stream_free(nonce_arr->stream); - //FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); - program_state->mfkey_state = MFKeyAttack; - // TODO: Work backwards on this array and free memory - for(i = 0; i < nonce_arr->total_nonces; i++) { - MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; - if(key_already_found_for_nonce_in_solved(keyarray, keyarray_size, &next_nonce)) { - nonce_arr->remaining_nonces--; - (program_state->cracked)++; - (program_state->num_completed)++; - continue; - } - //FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); - FuriString* cuid_dict_path; - switch(next_nonce.attack) { - case mfkey32: - ks_enc = next_nonce.ar0_enc ^ next_nonce.p64; - nt_xor_uid = 0; - break; - case static_nested: - ks_enc = next_nonce.ks1_2_enc; - nt_xor_uid = next_nonce.uid_xor_nt1; - break; - case static_encrypted: - ks_enc = next_nonce.ks1_1_enc; - nt_xor_uid = next_nonce.uid_xor_nt0; - cuid_dict_path = furi_string_alloc_printf( - "%s/mf_classic_dict_%08lx.nfc", EXT_PATH("nfc/assets"), next_nonce.uid); - // May need RECORD_STORAGE? - program_state->cuid_dict = keys_dict_alloc( - furi_string_get_cstr(cuid_dict_path), - KeysDictModeOpenAlways, - sizeof(MfClassicKey)); - break; - } +void mfkey(ProgramState *program_state) +{ + uint32_t ks_enc = 0, nt_xor_uid = 0; + MfClassicKey found_key; // Recovered key + size_t keyarray_size = 0; + MfClassicKey *keyarray = malloc(sizeof(MfClassicKey) * 1); + if (!keyarray) + { + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return; + } - if(!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) { - if((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) { - keys_dict_free(program_state->cuid_dict); - } - if(program_state->close_thread_please) { - break; - } - // No key found in recover() or static encrypted - (program_state->num_completed)++; - continue; - } - (program_state->cracked)++; - (program_state->num_completed)++; - found_key = next_nonce.key; - bool already_found = false; - for(j = 0; j < keyarray_size; j++) { - if(memcmp(keyarray[j].data, found_key.data, MF_CLASSIC_KEY_SIZE) == 0) { - already_found = true; - break; - } - } - if(already_found == false) { - // New key - keyarray = realloc(keyarray, sizeof(MfClassicKey) * (keyarray_size + 1)); //-V701 - keyarray_size += 1; - keyarray[keyarray_size - 1] = found_key; - (program_state->unique_cracked)++; - } - } - // TODO: Update display to show all keys were found - // TODO: Prepend found key(s) to user dictionary file - //FURI_LOG_I(TAG, "Unique keys found:"); - for(i = 0; i < keyarray_size; i++) { - //FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); - keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); - } - if(keyarray_size > 0) { - dolphin_deed(DolphinDeedNfcMfcAdd); - } - free(nonce_arr); - keys_dict_free(user_dict); - free(keyarray); - if(program_state->mfkey_state == Error) { - return; - } - //FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG - program_state->mfkey_state = Complete; - // No need to alert the user if they asked it to stop - if(!(program_state->close_thread_please)) { - finished_beep(); - } - return; + uint32_t i = 0, j = 0; + // FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); + Storage *storage = furi_record_open(RECORD_STORAGE); + FlipperApplication *app = flipper_application_alloc(storage, firmware_api_interface); + flipper_application_preload(app, APP_ASSETS_PATH("plugins/mfkey_init_plugin.fal")); + flipper_application_map_to_memory(app); + const FlipperAppPluginDescriptor *app_descriptor = + flipper_application_plugin_get_descriptor(app); + const MfkeyPlugin *init_plugin = app_descriptor->entry_point; + // Check for nonces + program_state->mfkey32_present = init_plugin->napi_mf_classic_mfkey32_nonces_check_presence(); + program_state->nested_present = init_plugin->napi_mf_classic_nested_nonces_check_presence(); + if (!(program_state->mfkey32_present) && !(program_state->nested_present)) + { + program_state->err = MissingNonces; + program_state->mfkey_state = Error; + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + free(keyarray); + return; + } + // Read dictionaries (optional) + KeysDict *system_dict = {0}; + bool system_dict_exists = keys_dict_check_presence(KEYS_DICT_SYSTEM_PATH); + KeysDict *user_dict = {0}; + bool user_dict_exists = keys_dict_check_presence(KEYS_DICT_USER_PATH); + uint32_t total_dict_keys = 0; + if (system_dict_exists) + { + system_dict = + keys_dict_alloc(KEYS_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); + total_dict_keys += keys_dict_get_total_keys(system_dict); + } + user_dict = keys_dict_alloc(KEYS_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); + if (user_dict_exists) + { + total_dict_keys += keys_dict_get_total_keys(user_dict); + } + user_dict_exists = true; + program_state->dict_count = total_dict_keys; + program_state->mfkey_state = DictionaryAttack; + // Read nonces + MfClassicNonceArray *nonce_arr; + nonce_arr = init_plugin->napi_mf_classic_nonce_array_alloc( + system_dict, system_dict_exists, user_dict, program_state); + if (system_dict_exists) + { + keys_dict_free(system_dict); + } + if (nonce_arr->total_nonces == 0) + { + // Nothing to crack + program_state->err = ZeroNonces; + program_state->mfkey_state = Error; + init_plugin->napi_mf_classic_nonce_array_free(nonce_arr); + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + keys_dict_free(user_dict); + free(keyarray); + return; + } + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + // TODO: Track free state at the time this is called to ensure double free does not happen + furi_assert(nonce_arr); + furi_assert(nonce_arr->stream); + // TODO: Already closed? + buffered_file_stream_close(nonce_arr->stream); + stream_free(nonce_arr->stream); + // FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); + program_state->mfkey_state = MFKeyAttack; + // TODO: Work backwards on this array and free memory + for (i = 0; i < nonce_arr->total_nonces; i++) + { + MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; + if (key_already_found_for_nonce_in_solved(keyarray, keyarray_size, &next_nonce)) + { + nonce_arr->remaining_nonces--; + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + // FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); + FuriString *cuid_dict_path; + switch (next_nonce.attack) + { + case mfkey32: + ks_enc = next_nonce.ar0_enc ^ next_nonce.p64; + nt_xor_uid = 0; + break; + case static_nested: + ks_enc = next_nonce.ks1_2_enc; + nt_xor_uid = next_nonce.uid_xor_nt1; + break; + case static_encrypted: + ks_enc = next_nonce.ks1_1_enc; + nt_xor_uid = next_nonce.uid_xor_nt0; + cuid_dict_path = furi_string_alloc_printf( + "%s/mf_classic_dict_%08lx.nfc", EXT_PATH("nfc/assets"), next_nonce.uid); + // May need RECORD_STORAGE? + program_state->cuid_dict = keys_dict_alloc( + furi_string_get_cstr(cuid_dict_path), + KeysDictModeOpenAlways, + sizeof(MfClassicKey)); + furi_string_free(cuid_dict_path); + break; + } + + if (!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) + { + // Check for non-recoverable errors and break the loop + if (program_state->mfkey_state == Error) + { + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } + break; + } + if (program_state->close_thread_please) + { + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } + break; + } + // No key found in recover() or static encrypted + (program_state->num_completed)++; + // Free CUID dictionary after each static_encrypted nonce processing + if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) + { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } + continue; + } + (program_state->cracked)++; + (program_state->num_completed)++; + found_key = next_nonce.key; + bool already_found = false; + for (j = 0; j < keyarray_size; j++) + { + if (memcmp(keyarray[j].data, found_key.data, MF_CLASSIC_KEY_SIZE) == 0) + { + already_found = true; + break; + } + } + if (already_found == false) + { + // New key + MfClassicKey *new_keyarray = realloc(keyarray, sizeof(MfClassicKey) * (keyarray_size + 1)); + if (!new_keyarray) + { + // Realloc failed - continue with existing keyarray + FURI_LOG_E(TAG, "Failed to realloc keyarray"); + } + else + { + keyarray = new_keyarray; + keyarray_size += 1; + keyarray[keyarray_size - 1] = found_key; + (program_state->unique_cracked)++; + } + } + } + // TODO: Update display to show all keys were found + // TODO: Prepend found key(s) to user dictionary file + // FURI_LOG_I(TAG, "Unique keys found:"); + for (i = 0; i < keyarray_size; i++) + { + // FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); + keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); + } + if (keyarray_size > 0) + { + dolphin_deed(DolphinDeedNfcKeyAdd); + } + free(nonce_arr); + keys_dict_free(user_dict); + free(keyarray); + if (program_state->mfkey_state == Error) + { + return; + } + // FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG + program_state->mfkey_state = Complete; + // No need to alert the user if they asked it to stop + if (!(program_state->close_thread_please)) + { + finished_beep(); + } + return; } // Screen is 128x64 px -static void render_callback(Canvas* const canvas, void* ctx) { - furi_assert(ctx); - ProgramState* program_state = ctx; - furi_mutex_acquire(program_state->mutex, FuriWaitForever); - char draw_str[44] = {}; +static void render_callback(Canvas *const canvas, void *ctx) +{ + furi_assert(ctx); + ProgramState *program_state = ctx; + furi_mutex_acquire(program_state->mutex, FuriWaitForever); + char draw_str[44] = {}; - canvas_draw_frame(canvas, 0, 0, 128, 64); - canvas_draw_frame(canvas, 0, 15, 128, 64); + canvas_draw_frame(canvas, 0, 0, 128, 64); + canvas_draw_frame(canvas, 0, 15, 128, 64); - // FontSecondary by default, title is drawn at the end - snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap()); - canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str); - canvas_draw_icon(canvas, 114, 4, &I_mfkey); - if(program_state->mfkey_state == MFKeyAttack) { - float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); - float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); - float progress = (float)program_state->num_completed / (float)program_state->total; - if(eta_round < 0 || eta_round > 1) { - // Round ETA miscalculated - eta_round = 1; - program_state->eta_round = 0; - } - if(eta_total < 0 || eta_round > 1) { - // Total ETA miscalculated - eta_total = 1; - program_state->eta_total = 0; - } - snprintf( - draw_str, - sizeof(draw_str), - "Cracking: %d/%d - in prog.", - program_state->num_completed, - program_state->total); - elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); - snprintf( - draw_str, - sizeof(draw_str), - "Round: %d/%d - ETA %02d Sec", - (program_state->search) + 1, // Zero indexed - 256 / MSB_LIMIT, - program_state->eta_round); - elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); - snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); - elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); - } else if(program_state->mfkey_state == DictionaryAttack) { - snprintf( - draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); - canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); - snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); - canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); - } else if(program_state->mfkey_state == Complete) { - // TODO: Scrollable list view to see cracked keys if user presses down - elements_progress_bar(canvas, 5, 18, 118, 1); - canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, "Complete"); - snprintf( - draw_str, - sizeof(draw_str), - "Keys added to user dict: %d", - program_state->unique_cracked); - canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, draw_str); - if(program_state->num_candidates > 0) { - snprintf( - draw_str, - sizeof(draw_str), - "SEN key candidates: %d", - program_state->num_candidates); - canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignTop, draw_str); - } - } else if(program_state->mfkey_state == Ready) { - canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); - elements_button_center(canvas, "Start"); - elements_button_right(canvas, "Help"); - } else if(program_state->mfkey_state == Help) { - canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces by reading"); - canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "tag or reader in NFC app:"); - canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "https://docs.flipper.net/"); - canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "nfc/mfkey32"); - } else if(program_state->mfkey_state == Error) { - canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); - if(program_state->err == MissingNonces) { - canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); - } else if(program_state->err == ZeroNonces) { - canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); - } else if(program_state->err == InsufficientRAM) { - canvas_draw_str_aligned(canvas, 35, 36, AlignLeft, AlignTop, "No free RAM"); - } else { - // Unhandled error - } - } else { - // Unhandled program state - } - // Title - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); - furi_mutex_release(program_state->mutex); + // FontSecondary by default, title is drawn at the end + snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap()); + canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str); + canvas_draw_icon(canvas, 114, 4, &I_mfkey); + if (program_state->mfkey_state == MFKeyAttack) + { + float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); + float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); + float progress = (float)program_state->num_completed / (float)program_state->total; + if (eta_round < 0 || eta_round > 1) + { + // Round ETA miscalculated + eta_round = 1; + program_state->eta_round = 0; + } + if (eta_total < 0 || eta_round > 1) + { + // Total ETA miscalculated + eta_total = 1; + program_state->eta_total = 0; + } + snprintf( + draw_str, + sizeof(draw_str), + "Cracking: %d/%d - in prog.", + program_state->num_completed, + program_state->total); + elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); + snprintf( + draw_str, + sizeof(draw_str), + "Round: %d/%d - ETA %02d Sec", + (program_state->search) + 1, // Zero indexed + 256 / MSB_LIMIT, + program_state->eta_round); + elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); + snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); + elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); + } + else if (program_state->mfkey_state == DictionaryAttack) + { + snprintf( + draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); + canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); + snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); + canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); + } + else if (program_state->mfkey_state == Complete) + { + // TODO: Scrollable list view to see cracked keys if user presses down + elements_progress_bar(canvas, 5, 18, 118, 1); + canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, "Complete"); + snprintf( + draw_str, + sizeof(draw_str), + "Keys added to user dict: %d", + program_state->unique_cracked); + canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, draw_str); + if (program_state->num_candidates > 0) + { + snprintf( + draw_str, + sizeof(draw_str), + "SEN key candidates: %d", + program_state->num_candidates); + canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignTop, draw_str); + } + } + else if (program_state->mfkey_state == Ready) + { + canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); + elements_button_center(canvas, "Start"); + elements_button_right(canvas, "Help"); + } + else if (program_state->mfkey_state == Help) + { + canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces by reading"); + canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "tag or reader in NFC app:"); + canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "https://docs.flipper.net/"); + canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "nfc/mfkey32"); + } + else if (program_state->mfkey_state == Error) + { + canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); + if (program_state->err == MissingNonces) + { + canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); + } + else if (program_state->err == ZeroNonces) + { + canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); + } + else if (program_state->err == InsufficientRAM) + { + canvas_draw_str_aligned(canvas, 35, 36, AlignLeft, AlignTop, "No free RAM"); + } + else + { + // Unhandled error + } + } + else + { + // Unhandled program state + } + // Title + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); + furi_mutex_release(program_state->mutex); } -static void input_callback(InputEvent* input_event, void* event_queue) { - furi_assert(event_queue); - furi_message_queue_put((FuriMessageQueue*)event_queue, input_event, FuriWaitForever); +static void input_callback(InputEvent *input_event, void *event_queue) +{ + furi_assert(event_queue); + furi_message_queue_put((FuriMessageQueue *)event_queue, input_event, FuriWaitForever); } -static void mfkey_state_init(ProgramState* program_state) { - program_state->mfkey_state = Ready; - program_state->cracked = 0; - program_state->unique_cracked = 0; - program_state->num_completed = 0; - program_state->num_candidates = 0; - program_state->total = 0; - program_state->dict_count = 0; +static void mfkey_state_init(ProgramState *program_state) +{ + program_state->mfkey_state = Ready; + program_state->cracked = 0; + program_state->unique_cracked = 0; + program_state->num_completed = 0; + program_state->num_candidates = 0; + program_state->total = 0; + program_state->dict_count = 0; } // Entrypoint for worker thread -static int32_t mfkey_worker_thread(void* ctx) { - ProgramState* program_state = ctx; - program_state->mfkey_state = Initializing; - mfkey(program_state); - return 0; +static int32_t mfkey_worker_thread(void *ctx) +{ + ProgramState *program_state = ctx; + program_state->mfkey_state = Initializing; + mfkey(program_state); + return 0; } -int32_t mfkey_main() { - FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); +int32_t mfkey_main() +{ + FuriMessageQueue *event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); - ProgramState* program_state = malloc(sizeof(ProgramState)); + ProgramState *program_state = malloc(sizeof(ProgramState)); - mfkey_state_init(program_state); + mfkey_state_init(program_state); - program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - // Set system callbacks - ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, render_callback, program_state); - view_port_input_callback_set(view_port, input_callback, event_queue); + // Set system callbacks + ViewPort *view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, program_state); + view_port_input_callback_set(view_port, input_callback, event_queue); - // Open GUI and register view_port - Gui* gui = furi_record_open(RECORD_GUI); - gui_add_view_port(gui, view_port, GuiLayerFullscreen); + // Open GUI and register view_port + Gui *gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); - program_state->mfkeythread = - furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); + program_state->mfkeythread = + furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); - InputEvent input_event; - for(bool main_loop = true; main_loop;) { - FuriStatus event_status = furi_message_queue_get(event_queue, &input_event, 100); + InputEvent input_event; + for (bool main_loop = true; main_loop;) + { + FuriStatus event_status = furi_message_queue_get(event_queue, &input_event, 100); - furi_mutex_acquire(program_state->mutex, FuriWaitForever); + furi_mutex_acquire(program_state->mutex, FuriWaitForever); - if(event_status == FuriStatusOk) { - if(input_event.type == InputTypePress) { - switch(input_event.key) { - case InputKeyRight: - if(program_state->mfkey_state == Ready) { - program_state->mfkey_state = Help; - } - break; - case InputKeyOk: - if(program_state->mfkey_state == Ready) { - furi_thread_start(program_state->mfkeythread); - } - break; - case InputKeyBack: - if(program_state->mfkey_state == Help) { - program_state->mfkey_state = Ready; - } else { - program_state->close_thread_please = true; - // Wait until thread is finished - furi_thread_join(program_state->mfkeythread); - main_loop = false; - } - break; - default: - break; - } - } - } + if (event_status == FuriStatusOk) + { + if (input_event.type == InputTypePress) + { + switch (input_event.key) + { + case InputKeyRight: + if (program_state->mfkey_state == Ready) + { + program_state->mfkey_state = Help; + } + break; + case InputKeyOk: + if (program_state->mfkey_state == Ready) + { + furi_thread_start(program_state->mfkeythread); + } + break; + case InputKeyBack: + if (program_state->mfkey_state == Help) + { + program_state->mfkey_state = Ready; + } + else + { + program_state->close_thread_please = true; + // Wait until thread is finished + furi_thread_join(program_state->mfkeythread); + main_loop = false; + } + break; + default: + break; + } + } + } - furi_mutex_release(program_state->mutex); - view_port_update(view_port); - } + furi_mutex_release(program_state->mutex); + view_port_update(view_port); + } - // Thread joined in back event handler - furi_thread_free(program_state->mfkeythread); - view_port_enabled_set(view_port, false); - gui_remove_view_port(gui, view_port); - furi_record_close(RECORD_GUI); - view_port_free(view_port); - furi_message_queue_free(event_queue); - furi_mutex_free(program_state->mutex); - free(program_state); + // Thread joined in back event handler + furi_thread_free(program_state->mfkeythread); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_mutex_free(program_state->mutex); + free(program_state); - return 0; + return 0; } #pragma GCC pop_options diff --git a/applications/system/mfkey/mfkey.h b/applications/system/mfkey/mfkey.h index 4a7ab3423..7b5be5c5a 100644 --- a/applications/system/mfkey/mfkey.h +++ b/applications/system/mfkey/mfkey.h @@ -9,100 +9,115 @@ #include #include -struct Crypto1State { - uint32_t odd, even; +struct Crypto1State +{ + uint32_t odd, even; }; -struct Msb { - int tail; - uint32_t states[768]; +struct Msb +{ + int tail; + uint32_t states[768]; }; -typedef enum { - MissingNonces, - ZeroNonces, - InsufficientRAM, +typedef enum +{ + MissingNonces, + ZeroNonces, + InsufficientRAM, } MFKeyError; -typedef enum { - Ready, - Initializing, - DictionaryAttack, - MFKeyAttack, - Complete, - Error, - Help, +typedef enum +{ + Ready, + Initializing, + DictionaryAttack, + MFKeyAttack, + Complete, + Error, + Help, } MFKeyState; // TODO: Can we eliminate any of the members of this struct? -typedef struct { - FuriMutex* mutex; - MFKeyError err; - MFKeyState mfkey_state; - int cracked; - int unique_cracked; - int num_completed; - int num_candidates; - int total; - int dict_count; - int search; - int eta_timestamp; - int eta_total; - int eta_round; - bool mfkey32_present; - bool nested_present; - bool close_thread_please; - FuriThread* mfkeythread; - KeysDict* cuid_dict; +typedef struct +{ + FuriMutex *mutex; + MFKeyError err; + MFKeyState mfkey_state; + int cracked; + int unique_cracked; + int num_completed; + int num_candidates; + int total; + int dict_count; + int search; + int eta_timestamp; + int eta_total; + int eta_round; + bool mfkey32_present; + bool nested_present; + bool close_thread_please; + FuriThread *mfkeythread; + KeysDict *cuid_dict; + MfClassicKey *key_buffer; + size_t key_buffer_size; + size_t key_buffer_count; } ProgramState; -typedef enum { - mfkey32, - static_nested, - static_encrypted +typedef enum +{ + mfkey32, + static_nested, + static_encrypted } AttackType; -typedef struct { - AttackType attack; - MfClassicKey key; // key - uint32_t uid; // serial number - uint32_t nt0; // tag challenge first - uint32_t nt1; // tag challenge second - uint32_t uid_xor_nt0; // uid ^ nt0 - uint32_t uid_xor_nt1; // uid ^ nt1 - union { - // Mfkey32 - struct { - uint32_t p64; // 64th successor of nt0 - uint32_t p64b; // 64th successor of nt1 - uint32_t nr0_enc; // first encrypted reader challenge - uint32_t ar0_enc; // first encrypted reader response - uint32_t nr1_enc; // second encrypted reader challenge - uint32_t ar1_enc; // second encrypted reader response - }; - // Nested - struct { - uint32_t ks1_1_enc; // first encrypted keystream - uint32_t ks1_2_enc; // second encrypted keystream - char par_1_str[5]; // first parity bits (string representation) - char par_2_str[5]; // second parity bits (string representation) - uint8_t par_1; // first parity bits - uint8_t par_2; // second parity bits - }; - }; +typedef struct +{ + AttackType attack; + MfClassicKey key; // key + uint32_t uid; // serial number + uint32_t nt0; // tag challenge first + uint32_t nt1; // tag challenge second + uint32_t uid_xor_nt0; // uid ^ nt0 + uint32_t uid_xor_nt1; // uid ^ nt1 + union + { + // Mfkey32 + struct + { + uint32_t p64; // 64th successor of nt0 + uint32_t p64b; // 64th successor of nt1 + uint32_t nr0_enc; // first encrypted reader challenge + uint32_t ar0_enc; // first encrypted reader response + uint32_t nr1_enc; // second encrypted reader challenge + uint32_t ar1_enc; // second encrypted reader response + }; + // Nested + struct + { + uint32_t ks1_1_enc; // first encrypted keystream + uint32_t ks1_2_enc; // second encrypted keystream + char par_1_str[5]; // first parity bits (string representation) + char par_2_str[5]; // second parity bits (string representation) + uint8_t par_1; // first parity bits + uint8_t par_2; // second parity bits + }; + }; } MfClassicNonce; -typedef struct { - Stream* stream; - uint32_t total_nonces; - MfClassicNonce* remaining_nonce_array; - size_t remaining_nonces; +typedef struct +{ + Stream *stream; + uint32_t total_nonces; + MfClassicNonce *remaining_nonce_array; + size_t remaining_nonces; } MfClassicNonceArray; -struct KeysDict { - Stream* stream; - size_t key_size; - size_t key_size_symbols; - size_t total_keys; +struct KeysDict +{ + Stream *stream; + size_t key_size; + size_t key_size_symbols; + size_t total_keys; }; -#endif // MFKEY_H +#endif // MFKEY_H \ No newline at end of file From 40c6c8b59c3a13d28e441093dcd7f8ad493e67b8 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 9 Sep 2025 15:34:19 +0300 Subject: [PATCH 03/26] format --- applications/system/mfkey/crypto1.h | 360 ++--- applications/system/mfkey/mfkey.c | 2245 ++++++++++++--------------- applications/system/mfkey/mfkey.h | 176 +-- 3 files changed, 1269 insertions(+), 1512 deletions(-) diff --git a/applications/system/mfkey/crypto1.h b/applications/system/mfkey/crypto1.h index b9f7c7725..25205ed70 100644 --- a/applications/system/mfkey/crypto1.h +++ b/applications/system/mfkey/crypto1.h @@ -6,202 +6,185 @@ #include #include -#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) -#define BIT(x, n) ((x) >> (n) & 1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) #define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) static inline uint32_t prng_successor(uint32_t x, uint32_t n); static inline int filter(uint32_t const x); static inline uint8_t evenparity32(uint32_t x); static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2); -void crypto1_get_lfsr(struct Crypto1State *state, MfClassicKey *lfsr); -static inline uint32_t crypt_word(struct Crypto1State *s); -static inline void crypt_word_noret(struct Crypto1State *s, uint32_t in, int x); -static inline uint32_t crypt_word_ret(struct Crypto1State *s, uint32_t in, int x); +void crypto1_get_lfsr(struct Crypto1State* state, MfClassicKey* lfsr); +static inline uint32_t crypt_word(struct Crypto1State* s); +static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x); +static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x); static uint32_t crypt_word_par( - struct Crypto1State *s, - uint32_t in, - int is_encrypted, - uint32_t nt_plain, - uint8_t *parity_keystream_bits); -static inline void rollback_word_noret(struct Crypto1State *s, uint32_t in, int x); -static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State *s, uint32_t in, int fb); -static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State *s, uint32_t in, int fb); + struct Crypto1State* s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t* parity_keystream_bits); +static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x); +static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb); +static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb); static const uint8_t lookup1[256] = { - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, - 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, + 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; static const uint8_t lookup2[256] = { - 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, - 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, - 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, - 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, - 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, - 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, - 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, - 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, - 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; + 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, + 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, + 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, + 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, + 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, + 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, + 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, + 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, + 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; -static inline int filter(uint32_t const x) -{ - uint32_t f; - f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; - f |= 0x0d938 >> (x >> 16 & 0xf) & 1; - return BIT(0xEC57E80A, f); +static inline int filter(uint32_t const x) { + uint32_t f; + f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; + f |= 0x0d938 >> (x >> 16 & 0xf) & 1; + return BIT(0xEC57E80A, f); } #ifdef __ARM_ARCH_7EM__ -static inline uint8_t evenparity32(uint32_t x) -{ - // fold 32 bits -> 16 -> 8 -> 4 - x ^= x >> 16; - x ^= x >> 8; - x ^= x >> 4; - // magic 0x6996: bit i tells you parity of i (0 ≤ i < 16) - return (uint8_t)((0x6996u >> (x & 0xF)) & 1); +static inline uint8_t evenparity32(uint32_t x) { + // fold 32 bits -> 16 -> 8 -> 4 + x ^= x >> 16; + x ^= x >> 8; + x ^= x >> 4; + // magic 0x6996: bit i tells you parity of i (0 ≤ i < 16) + return (uint8_t)((0x6996u >> (x & 0xF)) & 1); } #endif -static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) -{ - int p = data[item] >> 25; - p = p << 1 | evenparity32(data[item] & mask1); - p = p << 1 | evenparity32(data[item] & mask2); - data[item] = p << 24 | (data[item] & 0xffffff); +static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) { + int p = data[item] >> 25; + p = p << 1 | evenparity32(data[item] & mask1); + p = p << 1 | evenparity32(data[item] & mask2); + data[item] = p << 24 | (data[item] & 0xffffff); } -static inline uint32_t crypt_word(struct Crypto1State *s) -{ - // "in" and "x" are always 0 (last iteration) - uint32_t res_ret = 0; - uint32_t feedin, t; - for (int i = 0; i <= 31; i++) - { - res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 - feedin = LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - } - return res_ret; +static inline uint32_t crypt_word(struct Crypto1State* s) { + // "in" and "x" are always 0 (last iteration) + uint32_t res_ret = 0; + uint32_t feedin, t; + for(int i = 0; i <= 31; i++) { + res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 + feedin = LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return res_ret; } -static inline void crypt_word_noret(struct Crypto1State *s, uint32_t in, int x) -{ - uint8_t ret; - uint32_t feedin, t, next_in; - for (int i = 0; i <= 31; i++) - { - next_in = BEBIT(in, i); - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - } - return; +static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x) { + uint8_t ret; + uint32_t feedin, t, next_in; + for(int i = 0; i <= 31; i++) { + next_in = BEBIT(in, i); + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return; } -static inline uint32_t crypt_word_ret(struct Crypto1State *s, uint32_t in, int x) -{ - uint32_t ret = 0; - uint32_t feedin, t, next_in; - uint8_t next_ret; - for (int i = 0; i <= 31; i++) - { - next_in = BEBIT(in, i); - next_ret = filter(s->odd); - feedin = next_ret & (!!x); - feedin ^= LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - ret |= next_ret << (24 ^ i); - } - return ret; +static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x) { + uint32_t ret = 0; + uint32_t feedin, t, next_in; + uint8_t next_ret; + for(int i = 0; i <= 31; i++) { + next_in = BEBIT(in, i); + next_ret = filter(s->odd); + feedin = next_ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + ret |= next_ret << (24 ^ i); + } + return ret; } -static uint8_t get_nth_byte(uint32_t value, int n) -{ - if (n < 0 || n > 3) - { - // Handle invalid input - return 0; - } - return (value >> (8 * (3 - n))) & 0xFF; +static uint8_t get_nth_byte(uint32_t value, int n) { + if(n < 0 || n > 3) { + // Handle invalid input + return 0; + } + return (value >> (8 * (3 - n))) & 0xFF; } -static uint8_t crypt_bit(struct Crypto1State *s, uint8_t in, int is_encrypted) -{ - uint32_t feedin, t; - uint8_t ret = filter(s->odd); - feedin = ret & !!is_encrypted; - feedin ^= !!in; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= LF_POLY_EVEN & s->even; - s->even = s->even << 1 | evenparity32(feedin); - t = s->odd, s->odd = s->even, s->even = t; - return ret; +static uint8_t crypt_bit(struct Crypto1State* s, uint8_t in, int is_encrypted) { + uint32_t feedin, t; + uint8_t ret = filter(s->odd); + feedin = ret & !!is_encrypted; + feedin ^= !!in; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= LF_POLY_EVEN & s->even; + s->even = s->even << 1 | evenparity32(feedin); + t = s->odd, s->odd = s->even, s->even = t; + return ret; } static inline uint32_t crypt_word_par( - struct Crypto1State *s, - uint32_t in, - int is_encrypted, - uint32_t nt_plain, - uint8_t *parity_keystream_bits) -{ - uint32_t ret = 0; - *parity_keystream_bits = 0; // Reset parity keystream bits + struct Crypto1State* s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t* parity_keystream_bits) { + uint32_t ret = 0; + *parity_keystream_bits = 0; // Reset parity keystream bits - for (int i = 0; i < 32; i++) - { - uint8_t bit = crypt_bit(s, BEBIT(in, i), is_encrypted); - ret |= bit << (24 ^ i); - // Save keystream parity bit - if ((i + 1) % 8 == 0) - { - *parity_keystream_bits |= - (filter(s->odd) ^ nfc_util_even_parity8(get_nth_byte(nt_plain, i / 8))) - << (3 - (i / 8)); - } - } - return ret; + for(int i = 0; i < 32; i++) { + uint8_t bit = crypt_bit(s, BEBIT(in, i), is_encrypted); + ret |= bit << (24 ^ i); + // Save keystream parity bit + if((i + 1) % 8 == 0) { + *parity_keystream_bits |= + (filter(s->odd) ^ nfc_util_even_parity8(get_nth_byte(nt_plain, i / 8))) + << (3 - (i / 8)); + } + } + return ret; } -static inline void rollback_word_noret(struct Crypto1State *s, uint32_t in, int x) -{ - uint8_t ret; - uint32_t feedin, t, next_in; - for (int i = 31; i >= 0; i--) - { - next_in = BEBIT(in, i); - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= s->even & 1; - feedin ^= LF_POLY_EVEN & (s->even >>= 1); - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even |= (evenparity32(feedin)) << 23; - } - return; +static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x) { + uint8_t ret; + uint32_t feedin, t, next_in; + for(int i = 31; i >= 0; i--) { + next_in = BEBIT(in, i); + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= s->even & 1; + feedin ^= LF_POLY_EVEN & (s->even >>= 1); + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even |= (evenparity32(feedin)) << 23; + } + return; } // TODO: @@ -227,39 +210,36 @@ uint32_t rollback_word(struct Crypto1State *s, uint32_t in, int x) { } */ -uint8_t napi_lfsr_rollback_bit(struct Crypto1State *s, uint32_t in, int fb) -{ - int out; - uint8_t ret; - uint32_t t; - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; +uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb) { + int out; + uint8_t ret; + uint32_t t; + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; - out = s->even & 1; - out ^= LF_POLY_EVEN & (s->even >>= 1); - out ^= LF_POLY_ODD & s->odd; - out ^= !!in; - out ^= (ret = filter(s->odd)) & !!fb; + out = s->even & 1; + out ^= LF_POLY_EVEN & (s->even >>= 1); + out ^= LF_POLY_ODD & s->odd; + out ^= !!in; + out ^= (ret = filter(s->odd)) & !!fb; - s->even |= evenparity32(out) << 23; - return ret; + s->even |= evenparity32(out) << 23; + return ret; } -uint32_t napi_lfsr_rollback_word(struct Crypto1State *s, uint32_t in, int fb) -{ - int i; - uint32_t ret = 0; - for (i = 31; i >= 0; --i) - ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); - return ret; +uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb) { + int i; + uint32_t ret = 0; + for(i = 31; i >= 0; --i) + ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); + return ret; } -static inline uint32_t prng_successor(uint32_t x, uint32_t n) -{ - SWAPENDIAN(x); - while (n--) - x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; - return SWAPENDIAN(x); +static inline uint32_t prng_successor(uint32_t x, uint32_t n) { + SWAPENDIAN(x); + while(n--) + x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; + return SWAPENDIAN(x); } -#endif // CRYPTO1_H \ No newline at end of file +#endif // CRYPTO1_H diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index e49b96263..9e3e71847 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -34,29 +34,28 @@ #define TAG "MFKey" // TODO: Remove defines that are not needed -#define KEYS_DICT_SYSTEM_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") -#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") -#define MAX_NAME_LEN 32 -#define MAX_PATH_LEN 64 +#define KEYS_DICT_SYSTEM_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") +#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") +#define MAX_NAME_LEN 32 +#define MAX_PATH_LEN 64 #define STATIC_ENCRYPTED_RAM_THRESHOLD 4096 -#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) -#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) -#define CONST_M2_1 (LF_POLY_ODD << 1) -#define CONST_M1_2 (LF_POLY_ODD) -#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) -#define BIT(x, n) ((x) >> (n) & 1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) #define SWAP(a, b) \ - do \ - { \ - unsigned int t = a; \ - a = b; \ - b = t; \ - } while (0) + do { \ + unsigned int t = a; \ + a = b; \ + b = t; \ + } while(0) #define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) // #define SIZEOF(arr) sizeof(arr) / sizeof(*arr) // Reduced to 16-bit as these values are small and don't need 32-bit @@ -65,1315 +64,1105 @@ static int16_t eta_total_time = 705; // MSB_LIMIT: Chunk size (out of 256) - can be 8-bit as it's a small value static uint8_t MSB_LIMIT = 16; -static inline void flush_key_buffer(ProgramState *program_state) -{ - if (program_state->key_buffer && program_state->key_buffer_count > 0 && program_state->cuid_dict) - { - // Pre-allocate exact size needed: 12 hex chars + 1 newline per key - size_t total_size = program_state->key_buffer_count * 13; - //FURI_LOG_I(TAG, "Flushing key buffer: %d keys", program_state->key_buffer_count); - //FURI_LOG_I(TAG, "Total size: %d bytes", total_size); - char* batch_buffer = malloc(total_size + 1); // +1 for null terminator - - char* ptr = batch_buffer; - const char hex_chars[] = "0123456789ABCDEF"; - - for (size_t i = 0; i < program_state->key_buffer_count; i++) - { - // Convert key to hex string directly into buffer - for (size_t j = 0; j < sizeof(MfClassicKey); j++) - { - uint8_t byte = program_state->key_buffer[i].data[j]; - *ptr++ = hex_chars[byte >> 4]; - *ptr++ = hex_chars[byte & 0x0F]; - } - *ptr++ = '\n'; - } - *ptr = '\0'; - - // Write all keys at once by directly accessing the stream - Stream* stream = program_state->cuid_dict->stream; - uint32_t actual_pos = stream_tell(stream); - - if (stream_seek(stream, 0, StreamOffsetFromEnd) && - stream_write(stream, (uint8_t*)batch_buffer, total_size) == total_size) - { - // Update total key count - program_state->cuid_dict->total_keys += program_state->key_buffer_count; - } - - // May not be needed - stream_seek(stream, actual_pos, StreamOffsetFromStart); - free(batch_buffer); - program_state->key_buffer_count = 0; - } +static inline void flush_key_buffer(ProgramState* program_state) { + if(program_state->key_buffer && program_state->key_buffer_count > 0 && + program_state->cuid_dict) { + // Pre-allocate exact size needed: 12 hex chars + 1 newline per key + size_t total_size = program_state->key_buffer_count * 13; + //FURI_LOG_I(TAG, "Flushing key buffer: %d keys", program_state->key_buffer_count); + //FURI_LOG_I(TAG, "Total size: %d bytes", total_size); + char* batch_buffer = malloc(total_size + 1); // +1 for null terminator + + char* ptr = batch_buffer; + const char hex_chars[] = "0123456789ABCDEF"; + + for(size_t i = 0; i < program_state->key_buffer_count; i++) { + // Convert key to hex string directly into buffer + for(size_t j = 0; j < sizeof(MfClassicKey); j++) { + uint8_t byte = program_state->key_buffer[i].data[j]; + *ptr++ = hex_chars[byte >> 4]; + *ptr++ = hex_chars[byte & 0x0F]; + } + *ptr++ = '\n'; + } + *ptr = '\0'; + + // Write all keys at once by directly accessing the stream + Stream* stream = program_state->cuid_dict->stream; + uint32_t actual_pos = stream_tell(stream); + + if(stream_seek(stream, 0, StreamOffsetFromEnd) && + stream_write(stream, (uint8_t*)batch_buffer, total_size) == total_size) { + // Update total key count + program_state->cuid_dict->total_keys += program_state->key_buffer_count; + } + + // May not be needed + stream_seek(stream, actual_pos, StreamOffsetFromStart); + free(batch_buffer); + program_state->key_buffer_count = 0; + } } static inline int -check_state(struct Crypto1State *t, MfClassicNonce *n, ProgramState *program_state) -{ - if (!(t->odd | t->even)) - return 0; - if (n->attack == mfkey32) - { - uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); - if (rb != n->ar0_enc) - { - return 0; - } - rollback_word_noret(t, n->nr0_enc, 1); - rollback_word_noret(t, n->uid_xor_nt0, 0); - struct Crypto1State temp = {t->odd, t->even}; - crypt_word_noret(t, n->uid_xor_nt1, 0); - crypt_word_noret(t, n->nr1_enc, 1); - if (n->ar1_enc == (crypt_word(t) ^ n->p64b)) - { - crypto1_get_lfsr(&temp, &(n->key)); - return 1; - } - } - else if (n->attack == static_nested) - { - struct Crypto1State temp = {t->odd, t->even}; - rollback_word_noret(t, n->uid_xor_nt1, 0); - if (n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) - { - rollback_word_noret(&temp, n->uid_xor_nt1, 0); - crypto1_get_lfsr(&temp, &(n->key)); - return 1; - } - } - else if (n->attack == static_encrypted) - { - // TODO: Parity bits from rollback_word? - if (n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) - { - // Reduce with parity - uint8_t local_parity_keystream_bits; - struct Crypto1State temp = {t->odd, t->even}; - if ((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == - n->ks1_1_enc) && - (local_parity_keystream_bits == n->par_1)) - { - // Found key candidate - crypto1_get_lfsr(t, &(n->key)); - program_state->num_candidates++; - - // Use key buffer - buffer is guaranteed to be available for static_encrypted - program_state->key_buffer[program_state->key_buffer_count] = n->key; - program_state->key_buffer_count++; - - // Flush buffer when full - if (program_state->key_buffer_count >= program_state->key_buffer_size) - { - flush_key_buffer(program_state); - } - } - } - } - return 0; + check_state(struct Crypto1State* t, MfClassicNonce* n, ProgramState* program_state) { + if(!(t->odd | t->even)) return 0; + if(n->attack == mfkey32) { + uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); + if(rb != n->ar0_enc) { + return 0; + } + rollback_word_noret(t, n->nr0_enc, 1); + rollback_word_noret(t, n->uid_xor_nt0, 0); + struct Crypto1State temp = {t->odd, t->even}; + crypt_word_noret(t, n->uid_xor_nt1, 0); + crypt_word_noret(t, n->nr1_enc, 1); + if(n->ar1_enc == (crypt_word(t) ^ n->p64b)) { + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + } else if(n->attack == static_nested) { + struct Crypto1State temp = {t->odd, t->even}; + rollback_word_noret(t, n->uid_xor_nt1, 0); + if(n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) { + rollback_word_noret(&temp, n->uid_xor_nt1, 0); + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + } else if(n->attack == static_encrypted) { + // TODO: Parity bits from rollback_word? + if(n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) { + // Reduce with parity + uint8_t local_parity_keystream_bits; + struct Crypto1State temp = {t->odd, t->even}; + if((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == + n->ks1_1_enc) && + (local_parity_keystream_bits == n->par_1)) { + // Found key candidate + crypto1_get_lfsr(t, &(n->key)); + program_state->num_candidates++; + + // Use key buffer - buffer is guaranteed to be available for static_encrypted + program_state->key_buffer[program_state->key_buffer_count] = n->key; + program_state->key_buffer_count++; + + // Flush buffer when full + if(program_state->key_buffer_count >= program_state->key_buffer_size) { + flush_key_buffer(program_state); + } + } + } + } + return 0; } -static inline __attribute__((hot)) int state_loop( - unsigned int *states_buffer, - int xks, - int m1, - int m2, - unsigned int in, - int and_val) -{ - int states_tail = 0; - int xks_bit = 0, round_in = 0; +static inline __attribute__((hot)) int + state_loop(unsigned int* states_buffer, int xks, int m1, int m2, unsigned int in, int and_val) { + int states_tail = 0; + int xks_bit = 0, round_in = 0; - // Unroll first 4 rounds (no round_in calculations needed) - // Hoist the filter() calls to just one iteration and reuse the results - // This avoids redundant calculations and improves performance and gives us 2000b of extra ram (11496b free on run) - // V.28/04. Aprox 3s speedup per round. Total 3 keys 7mins 17s!! - for (int round = 1; round <= 4; ++round) - { - xks_bit = BIT(xks, round); - for (int s = 0; s <= states_tail; ++s) - { - unsigned int v = states_buffer[s] << 1; - states_buffer[s] = v; - int f0 = filter(v); - int f1 = filter(v | 1); + // Unroll first 4 rounds (no round_in calculations needed) + // Hoist the filter() calls to just one iteration and reuse the results + // This avoids redundant calculations and improves performance and gives us 2000b of extra ram (11496b free on run) + // V.28/04. Aprox 3s speedup per round. Total 3 keys 7mins 17s!! + for(int round = 1; round <= 4; ++round) { + xks_bit = BIT(xks, round); + for(int s = 0; s <= states_tail; ++s) { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v); + int f1 = filter(v | 1); - if (__builtin_expect((f0 ^ f1) != 0, 0)) - { - states_buffer[s] |= f0 ^ xks_bit; - } - else if (__builtin_expect(f0 == xks_bit, 1)) - { - states_buffer[++states_tail] = states_buffer[++s]; - states_buffer[s] = states_buffer[s - 1] | 1; - } - else - { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } + if(__builtin_expect((f0 ^ f1) != 0, 0)) { + states_buffer[s] |= f0 ^ xks_bit; + } else if(__builtin_expect(f0 == xks_bit, 1)) { + states_buffer[++states_tail] = states_buffer[++s]; + states_buffer[s] = states_buffer[s - 1] | 1; + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } - // Round 5 (unrolled) - { - xks_bit = BIT(xks, 5); - int r5_in = ((in >> 2) & and_val) << 24; // 2*(5-4)=2 - for (int s = 0; s <= states_tail; ++s) - { - unsigned int v = states_buffer[s] << 1; - states_buffer[s] = v; - int f0 = filter(v), f1 = filter(v | 1); - if (__builtin_expect((f0 ^ f1) != 0, 0)) - { - states_buffer[s] |= f0 ^ xks_bit; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= r5_in; - } - else if (__builtin_expect(f0 == xks_bit, 1)) - { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = v | 1; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s++] ^= r5_in; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= r5_in; - } - else - { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } + // Round 5 (unrolled) + { + xks_bit = BIT(xks, 5); + int r5_in = ((in >> 2) & and_val) << 24; // 2*(5-4)=2 + for(int s = 0; s <= states_tail; ++s) { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if(__builtin_expect((f0 ^ f1) != 0, 0)) { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r5_in; + } else if(__builtin_expect(f0 == xks_bit, 1)) { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= r5_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r5_in; + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } - // Round 6 (unrolled) - { - xks_bit = BIT(xks, 6); - int r6_in = ((in >> 4) & and_val) << 24; // 2*(6-4)=4 - for (int s = 0; s <= states_tail; ++s) - { - unsigned int v = states_buffer[s] << 1; - states_buffer[s] = v; - int f0 = filter(v), f1 = filter(v | 1); - if (__builtin_expect((f0 ^ f1) != 0, 0)) - { - states_buffer[s] |= f0 ^ xks_bit; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= r6_in; - } - else if (__builtin_expect(f0 == xks_bit, 1)) - { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = v | 1; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s++] ^= r6_in; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= r6_in; - } - else - { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } + // Round 6 (unrolled) + { + xks_bit = BIT(xks, 6); + int r6_in = ((in >> 4) & and_val) << 24; // 2*(6-4)=4 + for(int s = 0; s <= states_tail; ++s) { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if(__builtin_expect((f0 ^ f1) != 0, 0)) { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r6_in; + } else if(__builtin_expect(f0 == xks_bit, 1)) { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= r6_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= r6_in; + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } - // Loop rounds 7–12 - for (int round = 7; round <= 12; ++round) - { - xks_bit = BIT(xks, round); - round_in = ((in >> (2 * (round - 4))) & and_val) << 24; - for (int s = 0; s <= states_tail; ++s) - { - unsigned int v = states_buffer[s] << 1; - states_buffer[s] = v; - int f0 = filter(v), f1 = filter(v | 1); - if (__builtin_expect((f0 ^ f1) != 0, 0)) - { - states_buffer[s] |= f0 ^ xks_bit; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= round_in; - } - else if (__builtin_expect(f0 == xks_bit, 1)) - { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = v | 1; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s++] ^= round_in; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= round_in; - } - else - { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } + // Loop rounds 7–12 + for(int round = 7; round <= 12; ++round) { + xks_bit = BIT(xks, round); + round_in = ((in >> (2 * (round - 4))) & and_val) << 24; + for(int s = 0; s <= states_tail; ++s) { + unsigned int v = states_buffer[s] << 1; + states_buffer[s] = v; + int f0 = filter(v), f1 = filter(v | 1); + if(__builtin_expect((f0 ^ f1) != 0, 0)) { + states_buffer[s] |= f0 ^ xks_bit; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } else if(__builtin_expect(f0 == xks_bit, 1)) { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = v | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= round_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } - return states_tail; + return states_tail; } -int binsearch(unsigned int data[], int start, int stop) -{ - int mid, val = data[stop] & 0xff000000; - while (start != stop) - { - mid = (stop - start) >> 1; - if ((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) - stop = start + mid; - else - start += mid + 1; - } - return start; +int binsearch(unsigned int data[], int start, int stop) { + int mid, val = data[stop] & 0xff000000; + while(start != stop) { + mid = (stop - start) >> 1; + if((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) + stop = start + mid; + else + start += mid + 1; + } + return start; } -void quicksort(unsigned int array[], int low, int high) -{ - // Use insertion sort for small arrays (threshold determined by testing) - if (high - low < 16) - { - // Insertion sort - for (int i = low + 1; i <= high; i++) - { - unsigned int key = array[i]; - int j = i - 1; - while (j >= low && array[j] > key) - { - array[j + 1] = array[j]; - j--; - } - array[j + 1] = key; - } - return; - } +void quicksort(unsigned int array[], int low, int high) { + // Use insertion sort for small arrays (threshold determined by testing) + if(high - low < 16) { + // Insertion sort + for(int i = low + 1; i <= high; i++) { + unsigned int key = array[i]; + int j = i - 1; + while(j >= low && array[j] > key) { + array[j + 1] = array[j]; + j--; + } + array[j + 1] = key; + } + return; + } - if (low >= high) - return; + if(low >= high) return; - // Median-of-three pivot selection - int middle = low + (high - low) / 2; - if (array[middle] < array[low]) - SWAP(array[middle], array[low]); - if (array[high] < array[low]) - SWAP(array[high], array[low]); - if (array[high] < array[middle]) - SWAP(array[high], array[middle]); + // Median-of-three pivot selection + int middle = low + (high - low) / 2; + if(array[middle] < array[low]) SWAP(array[middle], array[low]); + if(array[high] < array[low]) SWAP(array[high], array[low]); + if(array[high] < array[middle]) SWAP(array[high], array[middle]); - unsigned int pivot = array[middle]; + unsigned int pivot = array[middle]; - // Rest of quicksort with improved partitioning - int i = low, j = high; - while (i <= j) - { - while (array[i] < pivot) - i++; - while (array[j] > pivot) - j--; - if (i <= j) - { - // swap - unsigned int temp = array[i]; - array[i] = array[j]; - array[j] = temp; - i++; - j--; - } - } + // Rest of quicksort with improved partitioning + int i = low, j = high; + while(i <= j) { + while(array[i] < pivot) + i++; + while(array[j] > pivot) + j--; + if(i <= j) { + // swap + unsigned int temp = array[i]; + array[i] = array[j]; + array[j] = temp; + i++; + j--; + } + } - if (low < j) - quicksort(array, low, j); - if (high > i) - quicksort(array, i, high); + if(low < j) quicksort(array, low, j); + if(high > i) quicksort(array, i, high); } -int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) -{ - in <<= 24; - for (data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) - { - if ((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) - { - data[tbl] |= filter(data[tbl]) ^ bit; - update_contribution(data, tbl, m1, m2); - data[tbl] ^= in; - } - else if (filter(data[tbl]) == bit) - { - data[++end] = data[tbl + 1]; - data[tbl + 1] = data[tbl] | 1; - update_contribution(data, tbl, m1, m2); - data[tbl++] ^= in; - update_contribution(data, tbl, m1, m2); - data[tbl] ^= in; - } - else - { - data[tbl--] = data[end--]; - } - } - return end; +int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) { + in <<= 24; + for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { + if((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) { + data[tbl] |= filter(data[tbl]) ^ bit; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } else if(filter(data[tbl]) == bit) { + data[++end] = data[tbl + 1]; + data[tbl + 1] = data[tbl] | 1; + update_contribution(data, tbl, m1, m2); + data[tbl++] ^= in; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } else { + data[tbl--] = data[end--]; + } + } + return end; } int old_recover( - unsigned int odd[], - int o_head, - int o_tail, - int oks, - unsigned int even[], - int e_head, - int e_tail, - int eks, - int rem, - int s, - MfClassicNonce *n, - unsigned int in, - int first_run, - ProgramState *program_state) -{ - int o, e, i; - if (rem == -1) - { - for (e = e_head; e <= e_tail; ++e) - { - even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); - for (o = o_head; o <= o_tail; ++o, ++s) - { - struct Crypto1State temp = {0, 0}; - temp.even = odd[o]; - temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); - if (check_state(&temp, n, program_state)) - { - return -1; - } - } - } - return s; - } - if (first_run == 0) - { - for (i = 0; (i < 4) && (rem-- != 0); i++) - { - oks >>= 1; - eks >>= 1; - in >>= 2; - o_tail = extend_table( - odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); - if (o_head > o_tail) - return s; - e_tail = extend_table( - even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); - if (e_head > e_tail) - return s; - } - } - first_run = 0; - quicksort(odd, o_head, o_tail); - quicksort(even, e_head, e_tail); - while (o_tail >= o_head && e_tail >= e_head) - { - if (((odd[o_tail] ^ even[e_tail]) >> 24) == 0) - { - o_tail = binsearch(odd, o_head, o = o_tail); - e_tail = binsearch(even, e_head, e = e_tail); - s = old_recover( - odd, - o_tail--, - o, - oks, - even, - e_tail--, - e, - eks, - rem, - s, - n, - in, - first_run, - program_state); - if (s == -1) - { - break; - } - } - else if ((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) - { - o_tail = binsearch(odd, o_head, o_tail) - 1; - } - else - { - e_tail = binsearch(even, e_head, e_tail) - 1; - } - } - return s; + unsigned int odd[], + int o_head, + int o_tail, + int oks, + unsigned int even[], + int e_head, + int e_tail, + int eks, + int rem, + int s, + MfClassicNonce* n, + unsigned int in, + int first_run, + ProgramState* program_state) { + int o, e, i; + if(rem == -1) { + for(e = e_head; e <= e_tail; ++e) { + even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); + for(o = o_head; o <= o_tail; ++o, ++s) { + struct Crypto1State temp = {0, 0}; + temp.even = odd[o]; + temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); + if(check_state(&temp, n, program_state)) { + return -1; + } + } + } + return s; + } + if(first_run == 0) { + for(i = 0; (i < 4) && (rem-- != 0); i++) { + oks >>= 1; + eks >>= 1; + in >>= 2; + o_tail = extend_table( + odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); + if(o_head > o_tail) return s; + e_tail = extend_table( + even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); + if(e_head > e_tail) return s; + } + } + first_run = 0; + quicksort(odd, o_head, o_tail); + quicksort(even, e_head, e_tail); + while(o_tail >= o_head && e_tail >= e_head) { + if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) { + o_tail = binsearch(odd, o_head, o = o_tail); + e_tail = binsearch(even, e_head, e = e_tail); + s = old_recover( + odd, + o_tail--, + o, + oks, + even, + e_tail--, + e, + eks, + rem, + s, + n, + in, + first_run, + program_state); + if(s == -1) { + break; + } + } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) { + o_tail = binsearch(odd, o_head, o_tail) - 1; + } else { + e_tail = binsearch(even, e_head, e_tail) - 1; + } + } + return s; } -static inline int sync_state(ProgramState *program_state) -{ - int ts = furi_hal_rtc_get_timestamp(); - int elapsed_time = ts - program_state->eta_timestamp; - if (elapsed_time < program_state->eta_round) - { - program_state->eta_round -= elapsed_time; - } - else - { - program_state->eta_round = 0; - } - if (elapsed_time < program_state->eta_total) - { - program_state->eta_total -= elapsed_time; - } - else - { - program_state->eta_total = 0; - } - program_state->eta_timestamp = ts; - if (program_state->close_thread_please) - { - return 1; - } - return 0; +static inline int sync_state(ProgramState* program_state) { + int ts = furi_hal_rtc_get_timestamp(); + int elapsed_time = ts - program_state->eta_timestamp; + if(elapsed_time < program_state->eta_round) { + program_state->eta_round -= elapsed_time; + } else { + program_state->eta_round = 0; + } + if(elapsed_time < program_state->eta_total) { + program_state->eta_total -= elapsed_time; + } else { + program_state->eta_total = 0; + } + program_state->eta_timestamp = ts; + if(program_state->close_thread_please) { + return 1; + } + return 0; } int calculate_msb_tables( - int oks, - int eks, - int msb_round, - MfClassicNonce *n, - unsigned int *states_buffer, - struct Msb *odd_msbs, - struct Msb *even_msbs, - unsigned int *temp_states_odd, - unsigned int *temp_states_even, - unsigned int in, - ProgramState *program_state) -{ - unsigned int msb_head = (MSB_LIMIT * msb_round); - unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); - int states_tail = 0; - int semi_state = 0; - unsigned int msb = 0; + int oks, + int eks, + int msb_round, + MfClassicNonce* n, + unsigned int* states_buffer, + struct Msb* odd_msbs, + struct Msb* even_msbs, + unsigned int* temp_states_odd, + unsigned int* temp_states_even, + unsigned int in, + ProgramState* program_state) { + unsigned int msb_head = (MSB_LIMIT * msb_round); + unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); + int states_tail = 0; + int semi_state = 0; + unsigned int msb = 0; - // Preprocessed in value - in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; + // Preprocessed in value + in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; - // Clear MSB arrays once before loop instead of inside loop - memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + // Clear MSB arrays once before loop instead of inside loop + memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - // Bit values to check - calculate once outside the loop - int oks_bit = oks & 1; - int eks_bit = eks & 1; + // Bit values to check - calculate once outside the loop + int oks_bit = oks & 1; + int eks_bit = eks & 1; - // Check for stop request less frequently - int sync_check_interval = 32768 * 2; // Doubled the interval + // Check for stop request less frequently + int sync_check_interval = 32768 * 2; // Doubled the interval - for (semi_state = 1 << 20; semi_state >= 0; semi_state--) - { - if (semi_state % sync_check_interval == 0) - { - if (sync_state(program_state) == 1) - { - return 0; - } - } + for(semi_state = 1 << 20; semi_state >= 0; semi_state--) { + if(semi_state % sync_check_interval == 0) { + if(sync_state(program_state) == 1) { + return 0; + } + } - // Process both filter conditions in one pass when possible - int filter_semi_state = filter(semi_state); + // Process both filter conditions in one pass when possible + int filter_semi_state = filter(semi_state); - // Check oks condition - if (filter_semi_state == oks_bit) - { - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); + // Check oks condition + if(filter_semi_state == oks_bit) { + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); - for (int i = states_tail; i >= 0; i--) - { - msb = states_buffer[i] >> 24; - if ((msb >= msb_head) && (msb < msb_tail)) - { - // Calculate index once - int msb_idx = msb - msb_head; + for(int i = states_tail; i >= 0; i--) { + msb = states_buffer[i] >> 24; + if((msb >= msb_head) && (msb < msb_tail)) { + // Calculate index once + int msb_idx = msb - msb_head; - // Avoid sequential scan by using a direct flag - int found = 0; - for (int j = 0; j < odd_msbs[msb_idx].tail; j++) - { - if (odd_msbs[msb_idx].states[j] == states_buffer[i]) - { - found = 1; - break; - } - } + // Avoid sequential scan by using a direct flag + int found = 0; + for(int j = 0; j < odd_msbs[msb_idx].tail; j++) { + if(odd_msbs[msb_idx].states[j] == states_buffer[i]) { + found = 1; + break; + } + } - if (!found) - { - int tail = odd_msbs[msb_idx].tail++; - odd_msbs[msb_idx].states[tail] = states_buffer[i]; - } - } - } - } + if(!found) { + int tail = odd_msbs[msb_idx].tail++; + odd_msbs[msb_idx].states[tail] = states_buffer[i]; + } + } + } + } - // Check eks condition - if (filter_semi_state == eks_bit) - { - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); + // Check eks condition + if(filter_semi_state == eks_bit) { + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); - for (int i = 0; i <= states_tail; i++) - { - msb = states_buffer[i] >> 24; - if ((msb >= msb_head) && (msb < msb_tail)) - { - // Calculate index once - int msb_idx = msb - msb_head; + for(int i = 0; i <= states_tail; i++) { + msb = states_buffer[i] >> 24; + if((msb >= msb_head) && (msb < msb_tail)) { + // Calculate index once + int msb_idx = msb - msb_head; - // Avoid sequential scan - int found = 0; - for (int j = 0; j < even_msbs[msb_idx].tail; j++) - { - if (even_msbs[msb_idx].states[j] == states_buffer[i]) - { - found = 1; - break; - } - } + // Avoid sequential scan + int found = 0; + for(int j = 0; j < even_msbs[msb_idx].tail; j++) { + if(even_msbs[msb_idx].states[j] == states_buffer[i]) { + found = 1; + break; + } + } - if (!found) - { - int tail = even_msbs[msb_idx].tail++; - even_msbs[msb_idx].states[tail] = states_buffer[i]; - } - } - } - } - } + if(!found) { + int tail = even_msbs[msb_idx].tail++; + even_msbs[msb_idx].states[tail] = states_buffer[i]; + } + } + } + } + } - // Shift once outside the loop - oks >>= 12; - eks >>= 12; + // Shift once outside the loop + oks >>= 12; + eks >>= 12; - // Process results - for (int i = 0; i < MSB_LIMIT; i++) - { - if ((i % 4) == 0 && sync_state(program_state) == 1) - { - return 0; - } + // Process results + for(int i = 0; i < MSB_LIMIT; i++) { + if((i % 4) == 0 && sync_state(program_state) == 1) { + return 0; + } - // Only clear buffers if they're going to be used - if (odd_msbs[i].tail > 0 || even_msbs[i].tail > 0) - { - memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); - memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); - memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); - memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); + // Only clear buffers if they're going to be used + if(odd_msbs[i].tail > 0 || even_msbs[i].tail > 0) { + memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); + memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); + memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); + memcpy( + temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); - int res = old_recover( - temp_states_odd, - 0, - odd_msbs[i].tail, - oks, - temp_states_even, - 0, - even_msbs[i].tail, - eks, - 3, - 0, - n, - in >> 16, - 1, - program_state); + int res = old_recover( + temp_states_odd, + 0, + odd_msbs[i].tail, + oks, + temp_states_even, + 0, + even_msbs[i].tail, + eks, + 3, + 0, + n, + in >> 16, + 1, + program_state); - if (res == -1) - { - return 1; - } - } - } + if(res == -1) { + return 1; + } + } + } - return 0; + return 0; } -void **allocate_blocks(const size_t *block_sizes, int num_blocks) -{ - void **block_pointers = malloc(num_blocks * sizeof(void *)); - if (!block_pointers) - { - return NULL; - } +void** allocate_blocks(const size_t* block_sizes, int num_blocks) { + void** block_pointers = malloc(num_blocks * sizeof(void*)); + if(!block_pointers) { + return NULL; + } - for (int i = 0; i < num_blocks; i++) - { - if (memmgr_heap_get_max_free_block() < block_sizes[i]) - { - // Not enough memory, free previously allocated blocks - for (int j = 0; j < i; j++) - { - free(block_pointers[j]); - } - free(block_pointers); - return NULL; - } + for(int i = 0; i < num_blocks; i++) { + if(memmgr_heap_get_max_free_block() < block_sizes[i]) { + // Not enough memory, free previously allocated blocks + for(int j = 0; j < i; j++) { + free(block_pointers[j]); + } + free(block_pointers); + return NULL; + } - block_pointers[i] = malloc(block_sizes[i]); - if (!block_pointers[i]) - { - // Allocation failed - for (int j = 0; j < i; j++) - { - free(block_pointers[j]); - } - free(block_pointers); - return NULL; - } - } + block_pointers[i] = malloc(block_sizes[i]); + if(!block_pointers[i]) { + // Allocation failed + for(int j = 0; j < i; j++) { + free(block_pointers[j]); + } + free(block_pointers); + return NULL; + } + } - return block_pointers; + return block_pointers; } -bool recover(MfClassicNonce *n, int ks2, unsigned int in, ProgramState *program_state) -{ - bool found = false; - const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; - const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; - const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); - // Reset globals each nonce - eta_round_time = 44; +bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_state) { + bool found = false; + const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; + const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; + const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); + // Reset globals each nonce + eta_round_time = 44; eta_total_time = 705; - MSB_LIMIT = 16; - - // Use half speed (reduced block sizes) for static encrypted nonces so we can buffer keys - bool use_half_speed = (n->attack == static_encrypted); - if (use_half_speed) - { - //eta_round_time *= 2; - eta_total_time *= 2; - MSB_LIMIT /= 2; - } + MSB_LIMIT = 16; - void **block_pointers = allocate_blocks(use_half_speed ? reduced_block_sizes : block_sizes, num_blocks); - if (block_pointers == NULL) { - if (n->attack != static_encrypted) - { - // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed - // eta_round_time *= 2; - eta_total_time *= 2; - MSB_LIMIT /= 2; - block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); - if (block_pointers == NULL) - { - // System has less than 70 KB of RAM - should never happen so we don't reduce speed further - program_state->err = InsufficientRAM; - program_state->mfkey_state = Error; - return false; - } - } else - { - program_state->err = InsufficientRAM; - program_state->mfkey_state = Error; - return false; - } - } - struct Msb *odd_msbs = block_pointers[0]; - struct Msb *even_msbs = block_pointers[1]; - unsigned int *temp_states_odd = block_pointers[2]; - unsigned int *temp_states_even = block_pointers[3]; - unsigned int *states_buffer = block_pointers[4]; + // Use half speed (reduced block sizes) for static encrypted nonces so we can buffer keys + bool use_half_speed = (n->attack == static_encrypted); + if(use_half_speed) { + //eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + } - // Allocate key buffer for static encrypted nonces - if (n->attack == static_encrypted) - { - size_t available_ram = memmgr_heap_get_max_free_block(); - // Each key becomes 12 hex chars + 1 newline = 13 bytes in the batch string - // Plus original 6 bytes in buffer = 19 bytes total per key - // Add extra safety margin for string overhead and other allocations - const size_t safety_threshold = STATIC_ENCRYPTED_RAM_THRESHOLD; - const size_t bytes_per_key = sizeof(MfClassicKey) + 13; // buffer + string representation - if (available_ram > safety_threshold) - { - program_state->key_buffer_size = (available_ram - safety_threshold) / bytes_per_key; - program_state->key_buffer = malloc(program_state->key_buffer_size * sizeof(MfClassicKey)); - program_state->key_buffer_count = 0; - if (!program_state->key_buffer) - { - // Free the allocated blocks before returning - for (int i = 0; i < num_blocks; i++) - { - free(block_pointers[i]); - } - free(block_pointers); - program_state->err = InsufficientRAM; - program_state->mfkey_state = Error; - return false; - } - } - else - { - // Free the allocated blocks before returning - for (int i = 0; i < num_blocks; i++) - { - free(block_pointers[i]); - } - free(block_pointers); - program_state->err = InsufficientRAM; - program_state->mfkey_state = Error; - return false; - } - } - else - { - program_state->key_buffer = NULL; - program_state->key_buffer_size = 0; - program_state->key_buffer_count = 0; - } + void** block_pointers = + allocate_blocks(use_half_speed ? reduced_block_sizes : block_sizes, num_blocks); + if(block_pointers == NULL) { + if(n->attack != static_encrypted) { + // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed + // eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); + if(block_pointers == NULL) { + // System has less than 70 KB of RAM - should never happen so we don't reduce speed further + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } else { + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + struct Msb* odd_msbs = block_pointers[0]; + struct Msb* even_msbs = block_pointers[1]; + unsigned int* temp_states_odd = block_pointers[2]; + unsigned int* temp_states_even = block_pointers[3]; + unsigned int* states_buffer = block_pointers[4]; - int oks = 0, eks = 0; - int i = 0, msb = 0; - for (i = 31; i >= 0; i -= 2) - { - oks = oks << 1 | BEBIT(ks2, i); - } - for (i = 30; i >= 0; i -= 2) - { - eks = eks << 1 | BEBIT(ks2, i); - } - int bench_start = furi_hal_rtc_get_timestamp(); - program_state->eta_total = eta_total_time; - program_state->eta_timestamp = bench_start; - for (msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) - { - program_state->search = msb; - program_state->eta_round = eta_round_time; - program_state->eta_total = eta_total_time - (eta_round_time * msb); - if (calculate_msb_tables( - oks, - eks, - msb, - n, - states_buffer, - odd_msbs, - even_msbs, - temp_states_odd, - temp_states_even, - in, - program_state)) - { - // int bench_stop = furi_hal_rtc_get_timestamp(); - // FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); - found = true; - break; - } - if (program_state->close_thread_please) - { - break; - } - } + // Allocate key buffer for static encrypted nonces + if(n->attack == static_encrypted) { + size_t available_ram = memmgr_heap_get_max_free_block(); + // Each key becomes 12 hex chars + 1 newline = 13 bytes in the batch string + // Plus original 6 bytes in buffer = 19 bytes total per key + // Add extra safety margin for string overhead and other allocations + const size_t safety_threshold = STATIC_ENCRYPTED_RAM_THRESHOLD; + const size_t bytes_per_key = sizeof(MfClassicKey) + 13; // buffer + string representation + if(available_ram > safety_threshold) { + program_state->key_buffer_size = (available_ram - safety_threshold) / bytes_per_key; + program_state->key_buffer = + malloc(program_state->key_buffer_size * sizeof(MfClassicKey)); + program_state->key_buffer_count = 0; + if(!program_state->key_buffer) { + // Free the allocated blocks before returning + for(int i = 0; i < num_blocks; i++) { + free(block_pointers[i]); + } + free(block_pointers); + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } else { + // Free the allocated blocks before returning + for(int i = 0; i < num_blocks; i++) { + free(block_pointers[i]); + } + free(block_pointers); + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } else { + program_state->key_buffer = NULL; + program_state->key_buffer_size = 0; + program_state->key_buffer_count = 0; + } - // Final flush and cleanup for key buffer - if (n->attack == static_encrypted && program_state->key_buffer) - { - flush_key_buffer(program_state); - free(program_state->key_buffer); - program_state->key_buffer = NULL; - program_state->key_buffer_size = 0; - program_state->key_buffer_count = 0; - } + int oks = 0, eks = 0; + int i = 0, msb = 0; + for(i = 31; i >= 0; i -= 2) { + oks = oks << 1 | BEBIT(ks2, i); + } + for(i = 30; i >= 0; i -= 2) { + eks = eks << 1 | BEBIT(ks2, i); + } + int bench_start = furi_hal_rtc_get_timestamp(); + program_state->eta_total = eta_total_time; + program_state->eta_timestamp = bench_start; + for(msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) { + program_state->search = msb; + program_state->eta_round = eta_round_time; + program_state->eta_total = eta_total_time - (eta_round_time * msb); + if(calculate_msb_tables( + oks, + eks, + msb, + n, + states_buffer, + odd_msbs, + even_msbs, + temp_states_odd, + temp_states_even, + in, + program_state)) { + // int bench_stop = furi_hal_rtc_get_timestamp(); + // FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); + found = true; + break; + } + if(program_state->close_thread_please) { + break; + } + } - // Free the allocated blocks - for (int i = 0; i < num_blocks; i++) - { - free(block_pointers[i]); - } - free(block_pointers); - return found; + // Final flush and cleanup for key buffer + if(n->attack == static_encrypted && program_state->key_buffer) { + flush_key_buffer(program_state); + free(program_state->key_buffer); + program_state->key_buffer = NULL; + program_state->key_buffer_size = 0; + program_state->key_buffer_count = 0; + } + + // Free the allocated blocks + for(int i = 0; i < num_blocks; i++) { + free(block_pointers[i]); + } + free(block_pointers); + return found; } bool key_already_found_for_nonce_in_solved( - MfClassicKey *keyarray, - int keyarray_size, - MfClassicNonce *nonce) -{ - for (int k = 0; k < keyarray_size; k++) - { - uint64_t key_as_int = bit_lib_bytes_to_num_be(keyarray[k].data, sizeof(MfClassicKey)); - struct Crypto1State temp = {0, 0}; - for (int i = 0; i < 24; i++) - { - (&temp)->odd |= (BIT(key_as_int, 2 * i + 1) << (i ^ 3)); - (&temp)->even |= (BIT(key_as_int, 2 * i) << (i ^ 3)); - } - if (nonce->attack == mfkey32) - { - crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); - crypt_word_noret(&temp, nonce->nr1_enc, 1); - if (nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) - { - return true; - } - } - else if (nonce->attack == static_nested) - { - uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); - if (nonce->ks1_1_enc == expected_ks1) - { - return true; - } - } - } - return false; + MfClassicKey* keyarray, + int keyarray_size, + MfClassicNonce* nonce) { + for(int k = 0; k < keyarray_size; k++) { + uint64_t key_as_int = bit_lib_bytes_to_num_be(keyarray[k].data, sizeof(MfClassicKey)); + struct Crypto1State temp = {0, 0}; + for(int i = 0; i < 24; i++) { + (&temp)->odd |= (BIT(key_as_int, 2 * i + 1) << (i ^ 3)); + (&temp)->even |= (BIT(key_as_int, 2 * i) << (i ^ 3)); + } + if(nonce->attack == mfkey32) { + crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); + crypt_word_noret(&temp, nonce->nr1_enc, 1); + if(nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) { + return true; + } + } else if(nonce->attack == static_nested) { + uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); + if(nonce->ks1_1_enc == expected_ks1) { + return true; + } + } + } + return false; } #pragma GCC push_options #pragma GCC optimize("Os") -static void finished_beep() -{ - // Beep to indicate completion - NotificationApp *notification = furi_record_open("notification"); - notification_message(notification, &sequence_audiovisual_alert); - notification_message(notification, &sequence_display_backlight_on); - furi_record_close("notification"); +static void finished_beep() { + // Beep to indicate completion + NotificationApp* notification = furi_record_open("notification"); + notification_message(notification, &sequence_audiovisual_alert); + notification_message(notification, &sequence_display_backlight_on); + furi_record_close("notification"); } -void mfkey(ProgramState *program_state) -{ - uint32_t ks_enc = 0, nt_xor_uid = 0; - MfClassicKey found_key; // Recovered key - size_t keyarray_size = 0; - MfClassicKey *keyarray = malloc(sizeof(MfClassicKey) * 1); - if (!keyarray) - { - program_state->err = InsufficientRAM; - program_state->mfkey_state = Error; - return; - } +void mfkey(ProgramState* program_state) { + uint32_t ks_enc = 0, nt_xor_uid = 0; + MfClassicKey found_key; // Recovered key + size_t keyarray_size = 0; + MfClassicKey* keyarray = malloc(sizeof(MfClassicKey) * 1); + if(!keyarray) { + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return; + } - uint32_t i = 0, j = 0; - // FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); - Storage *storage = furi_record_open(RECORD_STORAGE); - FlipperApplication *app = flipper_application_alloc(storage, firmware_api_interface); - flipper_application_preload(app, APP_ASSETS_PATH("plugins/mfkey_init_plugin.fal")); - flipper_application_map_to_memory(app); - const FlipperAppPluginDescriptor *app_descriptor = - flipper_application_plugin_get_descriptor(app); - const MfkeyPlugin *init_plugin = app_descriptor->entry_point; - // Check for nonces - program_state->mfkey32_present = init_plugin->napi_mf_classic_mfkey32_nonces_check_presence(); - program_state->nested_present = init_plugin->napi_mf_classic_nested_nonces_check_presence(); - if (!(program_state->mfkey32_present) && !(program_state->nested_present)) - { - program_state->err = MissingNonces; - program_state->mfkey_state = Error; - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - free(keyarray); - return; - } - // Read dictionaries (optional) - KeysDict *system_dict = {0}; - bool system_dict_exists = keys_dict_check_presence(KEYS_DICT_SYSTEM_PATH); - KeysDict *user_dict = {0}; - bool user_dict_exists = keys_dict_check_presence(KEYS_DICT_USER_PATH); - uint32_t total_dict_keys = 0; - if (system_dict_exists) - { - system_dict = - keys_dict_alloc(KEYS_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); - total_dict_keys += keys_dict_get_total_keys(system_dict); - } - user_dict = keys_dict_alloc(KEYS_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); - if (user_dict_exists) - { - total_dict_keys += keys_dict_get_total_keys(user_dict); - } - user_dict_exists = true; - program_state->dict_count = total_dict_keys; - program_state->mfkey_state = DictionaryAttack; - // Read nonces - MfClassicNonceArray *nonce_arr; - nonce_arr = init_plugin->napi_mf_classic_nonce_array_alloc( - system_dict, system_dict_exists, user_dict, program_state); - if (system_dict_exists) - { - keys_dict_free(system_dict); - } - if (nonce_arr->total_nonces == 0) - { - // Nothing to crack - program_state->err = ZeroNonces; - program_state->mfkey_state = Error; - init_plugin->napi_mf_classic_nonce_array_free(nonce_arr); - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - keys_dict_free(user_dict); - free(keyarray); - return; - } - flipper_application_free(app); - furi_record_close(RECORD_STORAGE); - // TODO: Track free state at the time this is called to ensure double free does not happen - furi_assert(nonce_arr); - furi_assert(nonce_arr->stream); - // TODO: Already closed? - buffered_file_stream_close(nonce_arr->stream); - stream_free(nonce_arr->stream); - // FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); - program_state->mfkey_state = MFKeyAttack; - // TODO: Work backwards on this array and free memory - for (i = 0; i < nonce_arr->total_nonces; i++) - { - MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; - if (key_already_found_for_nonce_in_solved(keyarray, keyarray_size, &next_nonce)) - { - nonce_arr->remaining_nonces--; - (program_state->cracked)++; - (program_state->num_completed)++; - continue; - } - // FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); - FuriString *cuid_dict_path; - switch (next_nonce.attack) - { - case mfkey32: - ks_enc = next_nonce.ar0_enc ^ next_nonce.p64; - nt_xor_uid = 0; - break; - case static_nested: - ks_enc = next_nonce.ks1_2_enc; - nt_xor_uid = next_nonce.uid_xor_nt1; - break; - case static_encrypted: - ks_enc = next_nonce.ks1_1_enc; - nt_xor_uid = next_nonce.uid_xor_nt0; - cuid_dict_path = furi_string_alloc_printf( - "%s/mf_classic_dict_%08lx.nfc", EXT_PATH("nfc/assets"), next_nonce.uid); - // May need RECORD_STORAGE? - program_state->cuid_dict = keys_dict_alloc( - furi_string_get_cstr(cuid_dict_path), - KeysDictModeOpenAlways, - sizeof(MfClassicKey)); - furi_string_free(cuid_dict_path); - break; - } + uint32_t i = 0, j = 0; + // FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); + flipper_application_preload(app, APP_ASSETS_PATH("plugins/mfkey_init_plugin.fal")); + flipper_application_map_to_memory(app); + const FlipperAppPluginDescriptor* app_descriptor = + flipper_application_plugin_get_descriptor(app); + const MfkeyPlugin* init_plugin = app_descriptor->entry_point; + // Check for nonces + program_state->mfkey32_present = init_plugin->napi_mf_classic_mfkey32_nonces_check_presence(); + program_state->nested_present = init_plugin->napi_mf_classic_nested_nonces_check_presence(); + if(!(program_state->mfkey32_present) && !(program_state->nested_present)) { + program_state->err = MissingNonces; + program_state->mfkey_state = Error; + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + free(keyarray); + return; + } + // Read dictionaries (optional) + KeysDict* system_dict = {0}; + bool system_dict_exists = keys_dict_check_presence(KEYS_DICT_SYSTEM_PATH); + KeysDict* user_dict = {0}; + bool user_dict_exists = keys_dict_check_presence(KEYS_DICT_USER_PATH); + uint32_t total_dict_keys = 0; + if(system_dict_exists) { + system_dict = + keys_dict_alloc(KEYS_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); + total_dict_keys += keys_dict_get_total_keys(system_dict); + } + user_dict = keys_dict_alloc(KEYS_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); + if(user_dict_exists) { + total_dict_keys += keys_dict_get_total_keys(user_dict); + } + user_dict_exists = true; + program_state->dict_count = total_dict_keys; + program_state->mfkey_state = DictionaryAttack; + // Read nonces + MfClassicNonceArray* nonce_arr; + nonce_arr = init_plugin->napi_mf_classic_nonce_array_alloc( + system_dict, system_dict_exists, user_dict, program_state); + if(system_dict_exists) { + keys_dict_free(system_dict); + } + if(nonce_arr->total_nonces == 0) { + // Nothing to crack + program_state->err = ZeroNonces; + program_state->mfkey_state = Error; + init_plugin->napi_mf_classic_nonce_array_free(nonce_arr); + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + keys_dict_free(user_dict); + free(keyarray); + return; + } + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + // TODO: Track free state at the time this is called to ensure double free does not happen + furi_assert(nonce_arr); + furi_assert(nonce_arr->stream); + // TODO: Already closed? + buffered_file_stream_close(nonce_arr->stream); + stream_free(nonce_arr->stream); + // FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); + program_state->mfkey_state = MFKeyAttack; + // TODO: Work backwards on this array and free memory + for(i = 0; i < nonce_arr->total_nonces; i++) { + MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; + if(key_already_found_for_nonce_in_solved(keyarray, keyarray_size, &next_nonce)) { + nonce_arr->remaining_nonces--; + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + // FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); + FuriString* cuid_dict_path; + switch(next_nonce.attack) { + case mfkey32: + ks_enc = next_nonce.ar0_enc ^ next_nonce.p64; + nt_xor_uid = 0; + break; + case static_nested: + ks_enc = next_nonce.ks1_2_enc; + nt_xor_uid = next_nonce.uid_xor_nt1; + break; + case static_encrypted: + ks_enc = next_nonce.ks1_1_enc; + nt_xor_uid = next_nonce.uid_xor_nt0; + cuid_dict_path = furi_string_alloc_printf( + "%s/mf_classic_dict_%08lx.nfc", EXT_PATH("nfc/assets"), next_nonce.uid); + // May need RECORD_STORAGE? + program_state->cuid_dict = keys_dict_alloc( + furi_string_get_cstr(cuid_dict_path), + KeysDictModeOpenAlways, + sizeof(MfClassicKey)); + furi_string_free(cuid_dict_path); + break; + } - if (!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) - { - // Check for non-recoverable errors and break the loop - if (program_state->mfkey_state == Error) - { - if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) - { - keys_dict_free(program_state->cuid_dict); - program_state->cuid_dict = NULL; - } - break; - } - if (program_state->close_thread_please) - { - if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) - { - keys_dict_free(program_state->cuid_dict); - program_state->cuid_dict = NULL; - } - break; - } - // No key found in recover() or static encrypted - (program_state->num_completed)++; - // Free CUID dictionary after each static_encrypted nonce processing - if ((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) - { - keys_dict_free(program_state->cuid_dict); - program_state->cuid_dict = NULL; - } - continue; - } - (program_state->cracked)++; - (program_state->num_completed)++; - found_key = next_nonce.key; - bool already_found = false; - for (j = 0; j < keyarray_size; j++) - { - if (memcmp(keyarray[j].data, found_key.data, MF_CLASSIC_KEY_SIZE) == 0) - { - already_found = true; - break; - } - } - if (already_found == false) - { - // New key - MfClassicKey *new_keyarray = realloc(keyarray, sizeof(MfClassicKey) * (keyarray_size + 1)); - if (!new_keyarray) - { - // Realloc failed - continue with existing keyarray - FURI_LOG_E(TAG, "Failed to realloc keyarray"); - } - else - { - keyarray = new_keyarray; - keyarray_size += 1; - keyarray[keyarray_size - 1] = found_key; - (program_state->unique_cracked)++; - } - } - } - // TODO: Update display to show all keys were found - // TODO: Prepend found key(s) to user dictionary file - // FURI_LOG_I(TAG, "Unique keys found:"); - for (i = 0; i < keyarray_size; i++) - { - // FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); - keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); - } - if (keyarray_size > 0) - { - dolphin_deed(DolphinDeedNfcKeyAdd); - } - free(nonce_arr); - keys_dict_free(user_dict); - free(keyarray); - if (program_state->mfkey_state == Error) - { - return; - } - // FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG - program_state->mfkey_state = Complete; - // No need to alert the user if they asked it to stop - if (!(program_state->close_thread_please)) - { - finished_beep(); - } - return; + if(!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) { + // Check for non-recoverable errors and break the loop + if(program_state->mfkey_state == Error) { + if((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } + break; + } + if(program_state->close_thread_please) { + if((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } + break; + } + // No key found in recover() or static encrypted + (program_state->num_completed)++; + // Free CUID dictionary after each static_encrypted nonce processing + if((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) { + keys_dict_free(program_state->cuid_dict); + program_state->cuid_dict = NULL; + } + continue; + } + (program_state->cracked)++; + (program_state->num_completed)++; + found_key = next_nonce.key; + bool already_found = false; + for(j = 0; j < keyarray_size; j++) { + if(memcmp(keyarray[j].data, found_key.data, MF_CLASSIC_KEY_SIZE) == 0) { + already_found = true; + break; + } + } + if(already_found == false) { + // New key + MfClassicKey* new_keyarray = + realloc(keyarray, sizeof(MfClassicKey) * (keyarray_size + 1)); + if(!new_keyarray) { + // Realloc failed - continue with existing keyarray + FURI_LOG_E(TAG, "Failed to realloc keyarray"); + } else { + keyarray = new_keyarray; + keyarray_size += 1; + keyarray[keyarray_size - 1] = found_key; + (program_state->unique_cracked)++; + } + } + } + // TODO: Update display to show all keys were found + // TODO: Prepend found key(s) to user dictionary file + // FURI_LOG_I(TAG, "Unique keys found:"); + for(i = 0; i < keyarray_size; i++) { + // FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); + keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); + } + if(keyarray_size > 0) { + dolphin_deed(DolphinDeedNfcKeyAdd); + } + free(nonce_arr); + keys_dict_free(user_dict); + free(keyarray); + if(program_state->mfkey_state == Error) { + return; + } + // FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG + program_state->mfkey_state = Complete; + // No need to alert the user if they asked it to stop + if(!(program_state->close_thread_please)) { + finished_beep(); + } + return; } // Screen is 128x64 px -static void render_callback(Canvas *const canvas, void *ctx) -{ - furi_assert(ctx); - ProgramState *program_state = ctx; - furi_mutex_acquire(program_state->mutex, FuriWaitForever); - char draw_str[44] = {}; +static void render_callback(Canvas* const canvas, void* ctx) { + furi_assert(ctx); + ProgramState* program_state = ctx; + furi_mutex_acquire(program_state->mutex, FuriWaitForever); + char draw_str[44] = {}; - canvas_draw_frame(canvas, 0, 0, 128, 64); - canvas_draw_frame(canvas, 0, 15, 128, 64); + canvas_draw_frame(canvas, 0, 0, 128, 64); + canvas_draw_frame(canvas, 0, 15, 128, 64); - // FontSecondary by default, title is drawn at the end - snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap()); - canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str); - canvas_draw_icon(canvas, 114, 4, &I_mfkey); - if (program_state->mfkey_state == MFKeyAttack) - { - float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); - float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); - float progress = (float)program_state->num_completed / (float)program_state->total; - if (eta_round < 0 || eta_round > 1) - { - // Round ETA miscalculated - eta_round = 1; - program_state->eta_round = 0; - } - if (eta_total < 0 || eta_round > 1) - { - // Total ETA miscalculated - eta_total = 1; - program_state->eta_total = 0; - } - snprintf( - draw_str, - sizeof(draw_str), - "Cracking: %d/%d - in prog.", - program_state->num_completed, - program_state->total); - elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); - snprintf( - draw_str, - sizeof(draw_str), - "Round: %d/%d - ETA %02d Sec", - (program_state->search) + 1, // Zero indexed - 256 / MSB_LIMIT, - program_state->eta_round); - elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); - snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); - elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); - } - else if (program_state->mfkey_state == DictionaryAttack) - { - snprintf( - draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); - canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); - snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); - canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); - } - else if (program_state->mfkey_state == Complete) - { - // TODO: Scrollable list view to see cracked keys if user presses down - elements_progress_bar(canvas, 5, 18, 118, 1); - canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, "Complete"); - snprintf( - draw_str, - sizeof(draw_str), - "Keys added to user dict: %d", - program_state->unique_cracked); - canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, draw_str); - if (program_state->num_candidates > 0) - { - snprintf( - draw_str, - sizeof(draw_str), - "SEN key candidates: %d", - program_state->num_candidates); - canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignTop, draw_str); - } - } - else if (program_state->mfkey_state == Ready) - { - canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); - elements_button_center(canvas, "Start"); - elements_button_right(canvas, "Help"); - } - else if (program_state->mfkey_state == Help) - { - canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces by reading"); - canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "tag or reader in NFC app:"); - canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "https://docs.flipper.net/"); - canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "nfc/mfkey32"); - } - else if (program_state->mfkey_state == Error) - { - canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); - if (program_state->err == MissingNonces) - { - canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); - } - else if (program_state->err == ZeroNonces) - { - canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); - } - else if (program_state->err == InsufficientRAM) - { - canvas_draw_str_aligned(canvas, 35, 36, AlignLeft, AlignTop, "No free RAM"); - } - else - { - // Unhandled error - } - } - else - { - // Unhandled program state - } - // Title - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); - furi_mutex_release(program_state->mutex); + // FontSecondary by default, title is drawn at the end + snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap()); + canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str); + canvas_draw_icon(canvas, 114, 4, &I_mfkey); + if(program_state->mfkey_state == MFKeyAttack) { + float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); + float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); + float progress = (float)program_state->num_completed / (float)program_state->total; + if(eta_round < 0 || eta_round > 1) { + // Round ETA miscalculated + eta_round = 1; + program_state->eta_round = 0; + } + if(eta_total < 0 || eta_round > 1) { + // Total ETA miscalculated + eta_total = 1; + program_state->eta_total = 0; + } + snprintf( + draw_str, + sizeof(draw_str), + "Cracking: %d/%d - in prog.", + program_state->num_completed, + program_state->total); + elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); + snprintf( + draw_str, + sizeof(draw_str), + "Round: %d/%d - ETA %02d Sec", + (program_state->search) + 1, // Zero indexed + 256 / MSB_LIMIT, + program_state->eta_round); + elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); + snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); + elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); + } else if(program_state->mfkey_state == DictionaryAttack) { + snprintf( + draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); + canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); + snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); + canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); + } else if(program_state->mfkey_state == Complete) { + // TODO: Scrollable list view to see cracked keys if user presses down + elements_progress_bar(canvas, 5, 18, 118, 1); + canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, "Complete"); + snprintf( + draw_str, + sizeof(draw_str), + "Keys added to user dict: %d", + program_state->unique_cracked); + canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, draw_str); + if(program_state->num_candidates > 0) { + snprintf( + draw_str, + sizeof(draw_str), + "SEN key candidates: %d", + program_state->num_candidates); + canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignTop, draw_str); + } + } else if(program_state->mfkey_state == Ready) { + canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); + elements_button_center(canvas, "Start"); + elements_button_right(canvas, "Help"); + } else if(program_state->mfkey_state == Help) { + canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces by reading"); + canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "tag or reader in NFC app:"); + canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "https://docs.flipper.net/"); + canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "nfc/mfkey32"); + } else if(program_state->mfkey_state == Error) { + canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); + if(program_state->err == MissingNonces) { + canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); + } else if(program_state->err == ZeroNonces) { + canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); + } else if(program_state->err == InsufficientRAM) { + canvas_draw_str_aligned(canvas, 35, 36, AlignLeft, AlignTop, "No free RAM"); + } else { + // Unhandled error + } + } else { + // Unhandled program state + } + // Title + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); + furi_mutex_release(program_state->mutex); } -static void input_callback(InputEvent *input_event, void *event_queue) -{ - furi_assert(event_queue); - furi_message_queue_put((FuriMessageQueue *)event_queue, input_event, FuriWaitForever); +static void input_callback(InputEvent* input_event, void* event_queue) { + furi_assert(event_queue); + furi_message_queue_put((FuriMessageQueue*)event_queue, input_event, FuriWaitForever); } -static void mfkey_state_init(ProgramState *program_state) -{ - program_state->mfkey_state = Ready; - program_state->cracked = 0; - program_state->unique_cracked = 0; - program_state->num_completed = 0; - program_state->num_candidates = 0; - program_state->total = 0; - program_state->dict_count = 0; +static void mfkey_state_init(ProgramState* program_state) { + program_state->mfkey_state = Ready; + program_state->cracked = 0; + program_state->unique_cracked = 0; + program_state->num_completed = 0; + program_state->num_candidates = 0; + program_state->total = 0; + program_state->dict_count = 0; } // Entrypoint for worker thread -static int32_t mfkey_worker_thread(void *ctx) -{ - ProgramState *program_state = ctx; - program_state->mfkey_state = Initializing; - mfkey(program_state); - return 0; +static int32_t mfkey_worker_thread(void* ctx) { + ProgramState* program_state = ctx; + program_state->mfkey_state = Initializing; + mfkey(program_state); + return 0; } -int32_t mfkey_main() -{ - FuriMessageQueue *event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); +int32_t mfkey_main() { + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); - ProgramState *program_state = malloc(sizeof(ProgramState)); + ProgramState* program_state = malloc(sizeof(ProgramState)); - mfkey_state_init(program_state); + mfkey_state_init(program_state); - program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - // Set system callbacks - ViewPort *view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, render_callback, program_state); - view_port_input_callback_set(view_port, input_callback, event_queue); + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, program_state); + view_port_input_callback_set(view_port, input_callback, event_queue); - // Open GUI and register view_port - Gui *gui = furi_record_open(RECORD_GUI); - gui_add_view_port(gui, view_port, GuiLayerFullscreen); + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); - program_state->mfkeythread = - furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); + program_state->mfkeythread = + furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); - InputEvent input_event; - for (bool main_loop = true; main_loop;) - { - FuriStatus event_status = furi_message_queue_get(event_queue, &input_event, 100); + InputEvent input_event; + for(bool main_loop = true; main_loop;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &input_event, 100); - furi_mutex_acquire(program_state->mutex, FuriWaitForever); + furi_mutex_acquire(program_state->mutex, FuriWaitForever); - if (event_status == FuriStatusOk) - { - if (input_event.type == InputTypePress) - { - switch (input_event.key) - { - case InputKeyRight: - if (program_state->mfkey_state == Ready) - { - program_state->mfkey_state = Help; - } - break; - case InputKeyOk: - if (program_state->mfkey_state == Ready) - { - furi_thread_start(program_state->mfkeythread); - } - break; - case InputKeyBack: - if (program_state->mfkey_state == Help) - { - program_state->mfkey_state = Ready; - } - else - { - program_state->close_thread_please = true; - // Wait until thread is finished - furi_thread_join(program_state->mfkeythread); - main_loop = false; - } - break; - default: - break; - } - } - } + if(event_status == FuriStatusOk) { + if(input_event.type == InputTypePress) { + switch(input_event.key) { + case InputKeyRight: + if(program_state->mfkey_state == Ready) { + program_state->mfkey_state = Help; + } + break; + case InputKeyOk: + if(program_state->mfkey_state == Ready) { + furi_thread_start(program_state->mfkeythread); + } + break; + case InputKeyBack: + if(program_state->mfkey_state == Help) { + program_state->mfkey_state = Ready; + } else { + program_state->close_thread_please = true; + // Wait until thread is finished + furi_thread_join(program_state->mfkeythread); + main_loop = false; + } + break; + default: + break; + } + } + } - furi_mutex_release(program_state->mutex); - view_port_update(view_port); - } + furi_mutex_release(program_state->mutex); + view_port_update(view_port); + } - // Thread joined in back event handler - furi_thread_free(program_state->mfkeythread); - view_port_enabled_set(view_port, false); - gui_remove_view_port(gui, view_port); - furi_record_close(RECORD_GUI); - view_port_free(view_port); - furi_message_queue_free(event_queue); - furi_mutex_free(program_state->mutex); - free(program_state); + // Thread joined in back event handler + furi_thread_free(program_state->mfkeythread); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_mutex_free(program_state->mutex); + free(program_state); - return 0; + return 0; } #pragma GCC pop_options diff --git a/applications/system/mfkey/mfkey.h b/applications/system/mfkey/mfkey.h index 7b5be5c5a..43e558874 100644 --- a/applications/system/mfkey/mfkey.h +++ b/applications/system/mfkey/mfkey.h @@ -9,115 +9,103 @@ #include #include -struct Crypto1State -{ - uint32_t odd, even; +struct Crypto1State { + uint32_t odd, even; }; -struct Msb -{ - int tail; - uint32_t states[768]; +struct Msb { + int tail; + uint32_t states[768]; }; -typedef enum -{ - MissingNonces, - ZeroNonces, - InsufficientRAM, +typedef enum { + MissingNonces, + ZeroNonces, + InsufficientRAM, } MFKeyError; -typedef enum -{ - Ready, - Initializing, - DictionaryAttack, - MFKeyAttack, - Complete, - Error, - Help, +typedef enum { + Ready, + Initializing, + DictionaryAttack, + MFKeyAttack, + Complete, + Error, + Help, } MFKeyState; // TODO: Can we eliminate any of the members of this struct? -typedef struct -{ - FuriMutex *mutex; - MFKeyError err; - MFKeyState mfkey_state; - int cracked; - int unique_cracked; - int num_completed; - int num_candidates; - int total; - int dict_count; - int search; - int eta_timestamp; - int eta_total; - int eta_round; - bool mfkey32_present; - bool nested_present; - bool close_thread_please; - FuriThread *mfkeythread; - KeysDict *cuid_dict; - MfClassicKey *key_buffer; - size_t key_buffer_size; - size_t key_buffer_count; +typedef struct { + FuriMutex* mutex; + MFKeyError err; + MFKeyState mfkey_state; + int cracked; + int unique_cracked; + int num_completed; + int num_candidates; + int total; + int dict_count; + int search; + int eta_timestamp; + int eta_total; + int eta_round; + bool mfkey32_present; + bool nested_present; + bool close_thread_please; + FuriThread* mfkeythread; + KeysDict* cuid_dict; + MfClassicKey* key_buffer; + size_t key_buffer_size; + size_t key_buffer_count; } ProgramState; -typedef enum -{ - mfkey32, - static_nested, - static_encrypted +typedef enum { + mfkey32, + static_nested, + static_encrypted } AttackType; -typedef struct -{ - AttackType attack; - MfClassicKey key; // key - uint32_t uid; // serial number - uint32_t nt0; // tag challenge first - uint32_t nt1; // tag challenge second - uint32_t uid_xor_nt0; // uid ^ nt0 - uint32_t uid_xor_nt1; // uid ^ nt1 - union - { - // Mfkey32 - struct - { - uint32_t p64; // 64th successor of nt0 - uint32_t p64b; // 64th successor of nt1 - uint32_t nr0_enc; // first encrypted reader challenge - uint32_t ar0_enc; // first encrypted reader response - uint32_t nr1_enc; // second encrypted reader challenge - uint32_t ar1_enc; // second encrypted reader response - }; - // Nested - struct - { - uint32_t ks1_1_enc; // first encrypted keystream - uint32_t ks1_2_enc; // second encrypted keystream - char par_1_str[5]; // first parity bits (string representation) - char par_2_str[5]; // second parity bits (string representation) - uint8_t par_1; // first parity bits - uint8_t par_2; // second parity bits - }; - }; +typedef struct { + AttackType attack; + MfClassicKey key; // key + uint32_t uid; // serial number + uint32_t nt0; // tag challenge first + uint32_t nt1; // tag challenge second + uint32_t uid_xor_nt0; // uid ^ nt0 + uint32_t uid_xor_nt1; // uid ^ nt1 + union { + // Mfkey32 + struct { + uint32_t p64; // 64th successor of nt0 + uint32_t p64b; // 64th successor of nt1 + uint32_t nr0_enc; // first encrypted reader challenge + uint32_t ar0_enc; // first encrypted reader response + uint32_t nr1_enc; // second encrypted reader challenge + uint32_t ar1_enc; // second encrypted reader response + }; + // Nested + struct { + uint32_t ks1_1_enc; // first encrypted keystream + uint32_t ks1_2_enc; // second encrypted keystream + char par_1_str[5]; // first parity bits (string representation) + char par_2_str[5]; // second parity bits (string representation) + uint8_t par_1; // first parity bits + uint8_t par_2; // second parity bits + }; + }; } MfClassicNonce; -typedef struct -{ - Stream *stream; - uint32_t total_nonces; - MfClassicNonce *remaining_nonce_array; - size_t remaining_nonces; +typedef struct { + Stream* stream; + uint32_t total_nonces; + MfClassicNonce* remaining_nonce_array; + size_t remaining_nonces; } MfClassicNonceArray; -struct KeysDict -{ - Stream *stream; - size_t key_size; - size_t key_size_symbols; - size_t total_keys; +struct KeysDict { + Stream* stream; + size_t key_size; + size_t key_size_symbols; + size_t total_keys; }; -#endif // MFKEY_H \ No newline at end of file +#endif // MFKEY_H From b07cb9401f1bd4a6a8442e03e88dff75731a6c2a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:48:08 +0300 Subject: [PATCH 04/26] subghz raw protocol fixes by WillyJL --- lib/subghz/protocols/raw.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index 96908130e..326576bde 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -291,9 +291,11 @@ void subghz_protocol_encoder_raw_stop(void* context) { furi_check(context); SubGhzProtocolEncoderRAW* instance = context; instance->is_running = false; - if(subghz_file_encoder_worker_is_running(instance->file_worker_encoder)) { + if(instance->file_worker_encoder && + subghz_file_encoder_worker_is_running(instance->file_worker_encoder)) { subghz_file_encoder_worker_stop(instance->file_worker_encoder); subghz_file_encoder_worker_free(instance->file_worker_encoder); + instance->file_worker_encoder = NULL; } } @@ -318,6 +320,7 @@ void subghz_protocol_raw_file_encoder_worker_set_callback_end( static bool subghz_protocol_encoder_raw_worker_init(SubGhzProtocolEncoderRAW* instance) { furi_assert(instance); + furi_check(!instance->file_worker_encoder); instance->file_worker_encoder = subghz_file_encoder_worker_alloc(); if(subghz_file_encoder_worker_start( From 19bb5cb0d92db87310ce20adc8e6abe75cea0ffe Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 9 Sep 2025 17:22:35 +0300 Subject: [PATCH 05/26] upd subghz remote --- applications/main/subghz_remote | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/subghz_remote b/applications/main/subghz_remote index 25ff072cc..0cec9db02 160000 --- a/applications/main/subghz_remote +++ b/applications/main/subghz_remote @@ -1 +1 @@ -Subproject commit 25ff072cc7f90a4af3a8b43bb418bb74dcf9da41 +Subproject commit 0cec9db022cdca2f5ead27115bf54c715abe1ca7 From efc608cf328c59d9457b7139aa01261be05716fb Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 9 Sep 2025 18:59:02 +0300 Subject: [PATCH 06/26] upd changelog [ci skip] --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06a3728aa..714670fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## Main changes - Current API: 86.0 -* SubGHz: Tune Linear (edited by @WillyJL in PR #919) (add better EZCode support), Dickert MAHS decoders +* SubGHz: Tune Linear (edited by @WillyJL in PR #919 #) (add better EZCode support), Dickert MAHS decoders * SubGHz: Add ZKTeco 430.5 MHz add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) * OFW PR 4265: NFC: Fix read crash with unexpectedly large MFC AUTH(0) response (by @WillyJL) From 55d2348171462e73fcce6ac3aaded3bf5c9b1a5e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 9 Sep 2025 22:07:40 +0300 Subject: [PATCH 07/26] fix mfkey [ci skip] --- applications/system/mfkey/mfkey.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index 9e3e71847..4d55a5b03 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -955,7 +955,7 @@ void mfkey(ProgramState* program_state) { keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); } if(keyarray_size > 0) { - dolphin_deed(DolphinDeedNfcKeyAdd); + dolphin_deed(DolphinDeedNfcMfcAdd); } free(nonce_arr); keys_dict_free(user_dict); From 3efa40b079eff5290f3e50f15069696d33476138 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 10 Sep 2025 00:10:55 +0300 Subject: [PATCH 08/26] upd changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 714670fb2..31ea11bf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Main changes - Current API: 86.0 -* SubGHz: Tune Linear (edited by @WillyJL in PR #919 #) (add better EZCode support), Dickert MAHS decoders +* SubGHz: Tune Linear (edited by @WillyJL in PR #919 #920) (add better EZCode support), Dickert MAHS decoders +* SubGHz: RAW protocol fixes (by @WillyJL) * SubGHz: Add ZKTeco 430.5 MHz add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) * OFW PR 4265: NFC: Fix read crash with unexpectedly large MFC AUTH(0) response (by @WillyJL) From 2c2228a4b91af0b88360b8e3ac7d55144ccb77e6 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 21 Sep 2025 16:45:31 +0300 Subject: [PATCH 09/26] merge ofw pr 4271 also thanks WillyJL link to PR https://github.com/flipperdevices/flipperzero-firmware/pull/4271/files --- .../main/nfc/helpers/mf_ultralight_auth.c | 6 +- .../mf_ultralight/mf_ultralight.c | 51 ++- .../protocol_support/nfc_protocol_support.c | 1 + applications/main/nfc/nfc_app_i.h | 16 +- .../nfc/assets/mf_ultralight_c_dict.nfc | 55 ++++ .../main/nfc/scenes/nfc_scene_config.h | 7 + .../nfc/scenes/nfc_scene_delete_success.c | 4 + .../main/nfc/scenes/nfc_scene_extra_actions.c | 10 + .../nfc/scenes/nfc_scene_mf_classic_keys.c | 2 - .../nfc_scene_mf_ultralight_c_dict_attack.c | 238 ++++++++++++++ .../scenes/nfc_scene_mf_ultralight_c_keys.c | 96 ++++++ .../nfc_scene_mf_ultralight_c_keys_add.c | 63 ++++ .../nfc_scene_mf_ultralight_c_keys_delete.c | 108 +++++++ .../nfc_scene_mf_ultralight_c_keys_list.c | 66 ++++ ...cene_mf_ultralight_c_keys_warn_duplicate.c | 49 +++ .../main/nfc/scenes/nfc_scene_save_success.c | 4 + applications/main/nfc/views/dict_attack.c | 299 +++++++++++------- applications/main/nfc/views/dict_attack.h | 13 + .../protocols/mf_ultralight/mf_ultralight.h | 6 - .../mf_ultralight/mf_ultralight_poller.c | 170 ++++++---- .../mf_ultralight/mf_ultralight_poller.h | 34 +- .../mf_ultralight/mf_ultralight_poller_i.c | 33 -- .../mf_ultralight/mf_ultralight_poller_i.h | 3 +- lib/toolbox/keys_dict.c | 19 +- targets/f7/api_symbols.csv | 1 - 25 files changed, 1091 insertions(+), 263 deletions(-) create mode 100644 applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c create mode 100644 applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c diff --git a/applications/main/nfc/helpers/mf_ultralight_auth.c b/applications/main/nfc/helpers/mf_ultralight_auth.c index fccf50cdd..e97649cb3 100644 --- a/applications/main/nfc/helpers/mf_ultralight_auth.c +++ b/applications/main/nfc/helpers/mf_ultralight_auth.c @@ -18,11 +18,9 @@ void mf_ultralight_auth_free(MfUltralightAuth* instance) { void mf_ultralight_auth_reset(MfUltralightAuth* instance) { furi_assert(instance); - uint32_t default_password = MF_ULTRALIGHT_DEFAULT_PASSWORD; - instance->type = MfUltralightAuthTypeNone; - memcpy(&instance->password, &default_password, sizeof(MfUltralightAuthPassword)); - memcpy(&instance->tdes_key, MF_ULTRALIGHT_C_DEFAULT_KEY, sizeof(MfUltralightC3DesAuthKey)); + memset(&instance->password, 0, sizeof(MfUltralightAuthPassword)); + memset(&instance->tdes_key, 0, sizeof(MfUltralightC3DesAuthKey)); memset(&instance->pack, 0, sizeof(MfUltralightAuthPack)); } diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 8eb42b89b..aa3cc2e75 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -14,6 +14,7 @@ enum { SubmenuIndexUnlock = SubmenuIndexCommonMax, SubmenuIndexUnlockByReader, SubmenuIndexUnlockByPassword, + SubmenuIndexDictAttack }; enum { @@ -149,7 +150,15 @@ static NfcCommand } if(!mf_ultralight_event->data->auth_context.skip_auth) { mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password; - mf_ultralight_event->data->auth_context.tdes_key = instance->mf_ul_auth->tdes_key; + + // Only set tdes_key for Manual/Reader auth types, not for dictionary attacks + if(instance->mf_ul_auth->type == MfUltralightAuthTypeManual || + instance->mf_ul_auth->type == MfUltralightAuthTypeReader) { + mf_ultralight_event->data->key_request_data.key = instance->mf_ul_auth->tdes_key; + mf_ultralight_event->data->key_request_data.key_provided = true; + } else { + mf_ultralight_event->data->key_request_data.key_provided = false; + } } } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) { instance->mf_ul_auth->pack = mf_ultralight_event->data->auth_context.pack; @@ -165,15 +174,31 @@ static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) { bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { - if(event.event == NfcCustomEventCardDetected) { - nfc_unlock_helper_card_detected_handler(instance); - } else if(event.event == NfcCustomEventPollerIncomplete) { - notification_message(instance->notifications, &sequence_semi_success); + if(event.event == NfcCustomEventPollerSuccess) { + notification_message(instance->notifications, &sequence_success); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); dolphin_deed(DolphinDeedNfcReadSuccess); + return true; + } else if(event.event == NfcCustomEventPollerIncomplete) { + const MfUltralightData* data = + nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight); + if(data->type == MfUltralightTypeMfulC && + instance->mf_ul_auth->type == MfUltralightAuthTypeNone) { + // Start dict attack for MFUL C cards only if no specific auth was attempted + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + } else { + if(data->pages_read == data->pages_total) { + notification_message(instance->notifications, &sequence_success); + } else { + notification_message(instance->notifications, &sequence_semi_success); + } + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + return true; } } - return true; + return false; } static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instance) { @@ -197,6 +222,14 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc SubmenuIndexUnlock, nfc_protocol_support_common_submenu_callback, instance); + if(data->type == MfUltralightTypeMfulC) { + submenu_add_item( + submenu, + "Unlock with Dictionary", + SubmenuIndexDictAttack, + nfc_protocol_support_common_submenu_callback, + instance); + } } } @@ -249,6 +282,12 @@ static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight( NfcSceneMfUltralightUnlockMenu; scene_manager_next_scene(instance->scene_manager, next_scene); consumed = true; + } else if(event.event == SubmenuIndexDictAttack) { + if(!scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneMfUltralightCDictAttack)) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + } + consumed = true; } } return consumed; diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 008f9c317..2ee29668c 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -680,6 +680,7 @@ static void nfc_protocol_support_scene_save_name_on_enter(NfcApp* instance) { furi_string_replace(prefix, " Plus", "+"); // NTAG I2C+ furi_string_replace(prefix, " (Unknown)", ""); furi_string_replace_all(prefix, " ", "_"); + furi_string_replace_all(prefix, "/", "_"); name_generator_make_auto( instance->text_store, NFC_TEXT_STORE_SIZE, furi_string_get_cstr(prefix)); furi_string_free(prefix); diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 4336d25e0..a142e47da 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -50,6 +50,7 @@ #include #include #include +#include #include #include @@ -67,7 +68,7 @@ #define NFC_NAME_SIZE 22 #define NFC_TEXT_STORE_SIZE 128 -#define NFC_BYTE_INPUT_STORE_SIZE 10 +#define NFC_BYTE_INPUT_STORE_SIZE 16 #define NFC_LOG_SIZE_MAX (1024) #define NFC_APP_FOLDER EXT_PATH("nfc") #define NFC_APP_EXTENSION ".nfc" @@ -83,6 +84,10 @@ #define NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict.nfc") #define NFC_APP_MF_CLASSIC_DICT_SYSTEM_NESTED_PATH \ (NFC_APP_FOLDER "/assets/mf_classic_dict_nested.nfc") +#define NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH \ + (NFC_APP_FOLDER "/assets/mf_ultralight_c_dict_user.nfc") +#define NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH \ + (NFC_APP_FOLDER "/assets/mf_ultralight_c_dict.nfc") #define NFC_MFKEY32_APP_PATH (EXT_PATH("apps/NFC/mfkey.fap")) @@ -110,6 +115,14 @@ typedef struct { bool enhanced_dict; } NfcMfClassicDictAttackContext; +typedef struct { + KeysDict* dict; + bool auth_success; + bool is_card_present; + size_t dict_keys_total; + size_t dict_keys_current; +} NfcMfUltralightCDictContext; + struct NfcApp { DialogsApp* dialogs; Storage* storage; @@ -148,6 +161,7 @@ struct NfcApp { MfUltralightAuth* mf_ul_auth; SlixUnlock* slix_unlock; NfcMfClassicDictAttackContext nfc_dict_context; + NfcMfUltralightCDictContext mf_ultralight_c_dict_context; Mfkey32Logger* mfkey32_logger; MfUserDict* mf_user_dict; MfClassicKeyCache* mfc_key_cache; diff --git a/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc b/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc new file mode 100644 index 000000000..fa5dbb1fb --- /dev/null +++ b/applications/main/nfc/resources/nfc/assets/mf_ultralight_c_dict.nfc @@ -0,0 +1,55 @@ +# Sample Key (BREAKMEIFYOUCAN!) +425245414B4D454946594F5543414E21 +# Hexadecimal-Reversed Sample Key +12E4143455F495649454D4B414542524 +# Byte-Reversed Sample Key (!NACUOYFIEMKAERB) +214E4143554F594649454D4B41455242 +# Semnox Key (IEMKAERB!NACUOY ) +49454D4B41455242214E4143554F5900 +# Modified Semnox Key (IEMKAERB!NACUOYF) +49454D4B41455242214E4143554F5946 + +# Mix of Proxmark and ChameleonMiniLiveDebugger +00000000000000000000000000000000 +000102030405060708090A0B0C0D0E0F +01010101010101010101010101010101 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +00112233445566778899AABBCCDDEEFF +47454D5850524553534F53414D504C45 +79702553797025537970255379702553 +4E617468616E2E4C6920546564647920 +43464F494D48504E4C4359454E528841 +6AC292FAA1315B4D858AB3A3D7D5933A +404142434445464748494A4B4C4D4E4F +2B7E151628AED2A6ABF7158809CF4F3C +FBEED618357133667C85E08F7236A8DE +F7DDAC306AE266CCF90BC11EE46D513B +54686973206973206D79206B65792020 +A0A1A2A3A4A5A6A7A0A1A2A3A4A5A6A7 +B0B1B2B3B4B5B6B7B0B1B2B3B4B5B6B7 +B0B1B2B3B4B5B6B7B8B9BABBBCBDBEBF +D3F7D3F7D3F7D3F7D3F7D3F7D3F7D3F7 +11111111111111111111111111111111 +22222222222222222222222222222222 +33333333333333333333333333333333 +44444444444444444444444444444444 +55555555555555555555555555555555 +66666666666666666666666666666666 +77777777777777777777777777777777 +88888888888888888888888888888888 +99999999999999999999999999999999 +AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC +DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +0102030405060708090A0B0C0D0E0F10 +00010203040506070809101112131415 +01020304050607080910111213141516 +16151413121110090807060504030201 +15141312111009080706050403020100 +0F0E0D0C0B0A09080706050403020100 +100F0E0D0C0B0A090807060504030201 +303132333435363738393A3B3C3D3E3F +9CABF398358405AE2F0E2B3D31C99A8A +605F5E5D5C5B5A59605F5E5D5C5B5A59 diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index b902bc940..d498eb90c 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -26,6 +26,7 @@ ADD_SCENE(nfc, retry_confirm, RetryConfirm) ADD_SCENE(nfc, exit_confirm, ExitConfirm) ADD_SCENE(nfc, save_confirm, SaveConfirm) +ADD_SCENE(nfc, mf_ultralight_c_dict_attack, MfUltralightCDictAttack) ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) @@ -52,6 +53,12 @@ ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete) ADD_SCENE(nfc, mf_classic_keys_add, MfClassicKeysAdd) ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate) +ADD_SCENE(nfc, mf_ultralight_c_keys, MfUltralightCKeys) +ADD_SCENE(nfc, mf_ultralight_c_keys_list, MfUltralightCKeysList) +ADD_SCENE(nfc, mf_ultralight_c_keys_delete, MfUltralightCKeysDelete) +ADD_SCENE(nfc, mf_ultralight_c_keys_add, MfUltralightCKeysAdd) +ADD_SCENE(nfc, mf_ultralight_c_keys_warn_duplicate, MfUltralightCKeysWarnDuplicate) + ADD_SCENE(nfc, set_type, SetType) ADD_SCENE(nfc, set_sak, SetSak) ADD_SCENE(nfc, set_atqa, SetAtqa) diff --git a/applications/main/nfc/scenes/nfc_scene_delete_success.c b/applications/main/nfc/scenes/nfc_scene_delete_success.c index d41e52549..d8308addd 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete_success.c +++ b/applications/main/nfc/scenes/nfc_scene_delete_success.c @@ -28,6 +28,10 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicKeys); + } else if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys); } else { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneFileSelect); diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index 2943c0c55..6720b2d7b 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -3,6 +3,7 @@ enum SubmenuIndex { SubmenuIndexReadCardType, SubmenuIndexMfClassicKeys, + SubmenuIndexMfUltralightCKeys, SubmenuIndexMfUltralightUnlock, SubmenuIndexSlixUnlock, }; @@ -29,6 +30,12 @@ void nfc_scene_extra_actions_on_enter(void* context) { SubmenuIndexMfClassicKeys, nfc_scene_extra_actions_submenu_callback, instance); + submenu_add_item( + submenu, + "MIFARE Ultralight C Keys", + SubmenuIndexMfUltralightCKeys, + nfc_scene_extra_actions_submenu_callback, + instance); submenu_add_item( submenu, "Unlock NTAG/Ultralight", @@ -54,6 +61,9 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { if(event.event == SubmenuIndexMfClassicKeys) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicKeys); consumed = true; + } else if(event.event == SubmenuIndexMfUltralightCKeys) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeys); + consumed = true; } else if(event.event == SubmenuIndexMfUltralightUnlock) { mf_ultralight_auth_reset(instance->mf_ul_auth); scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); diff --git a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c index eaa054149..7ee203285 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_classic_keys.c @@ -1,7 +1,5 @@ #include "../nfc_app_i.h" -#define NFC_SCENE_MF_CLASSIC_KEYS_MAX (100) - void nfc_scene_mf_classic_keys_widget_callback(GuiButtonType result, InputType type, void* context) { NfcApp* instance = context; if(type == InputTypeShort) { diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c new file mode 100644 index 000000000..b1e24def2 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -0,0 +1,238 @@ +#include "../nfc_app_i.h" +#include + +#define TAG "NfcMfUlCDictAttack" + +// TODO: Support card_detected properly + +enum { + DictAttackStateUserDictInProgress, + DictAttackStateSystemDictInProgress, +}; + +NfcCommand nfc_mf_ultralight_c_dict_attack_worker_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.protocol == NfcProtocolMfUltralight); + NfcCommand command = NfcCommandContinue; + NfcApp* instance = context; + MfUltralightPollerEvent* poller_event = event.event_data; + + if(poller_event->type == MfUltralightPollerEventTypeRequestMode) { + poller_event->data->poller_mode = MfUltralightPollerModeDictAttack; + command = NfcCommandContinue; + } else if(poller_event->type == MfUltralightPollerEventTypeRequestKey) { + MfUltralightC3DesAuthKey key = {}; + if(keys_dict_get_next_key( + instance->mf_ultralight_c_dict_context.dict, + key.data, + sizeof(MfUltralightC3DesAuthKey))) { + poller_event->data->key_request_data.key = key; + poller_event->data->key_request_data.key_provided = true; + instance->mf_ultralight_c_dict_context.dict_keys_current++; + + if(instance->mf_ultralight_c_dict_context.dict_keys_current % 10 == 0) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackDataUpdate); + } + } else { + poller_event->data->key_request_data.key_provided = false; + } + } else if(poller_event->type == MfUltralightPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller)); + // Check if this is a successful authentication by looking at the poller's auth context + const MfUltralightData* data = nfc_poller_get_data(instance->poller); + + // Update page information + dict_attack_set_pages_read(instance->dict_attack, data->pages_read); + dict_attack_set_pages_total(instance->dict_attack, data->pages_total); + + if(data->pages_read == data->pages_total) { + // Full read indicates successful authentication in dict attack mode + instance->mf_ultralight_c_dict_context.auth_success = true; + dict_attack_set_key_found(instance->dict_attack, true); + } + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventDictAttackComplete); + command = NfcCommandStop; + } + return command; +} + +void nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback( + DictAttackEvent event, + void* context) { + furi_assert(context); + NfcApp* instance = context; + if(event == DictAttackEventSkipPressed) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventDictAttackSkip); + } +} + +void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) { + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + + // Set attack type to Ultralight C + dict_attack_set_type(instance->dict_attack, DictAttackTypeMfUltralightC); + + if(state == DictAttackStateUserDictInProgress) { + do { + if(!keys_dict_check_presence(NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH)) { + state = DictAttackStateSystemDictInProgress; + break; + } + instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + if(keys_dict_get_total_keys(instance->mf_ultralight_c_dict_context.dict) == 0) { + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + state = DictAttackStateSystemDictInProgress; + break; + } + dict_attack_set_header(instance->dict_attack, "MFUL C User Dictionary"); + } while(false); + } + if(state == DictAttackStateSystemDictInProgress) { + instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH, + KeysDictModeOpenExisting, + sizeof(MfUltralightC3DesAuthKey)); + dict_attack_set_header(instance->dict_attack, "MFUL C System Dictionary"); + } + + instance->mf_ultralight_c_dict_context.dict_keys_total = + keys_dict_get_total_keys(instance->mf_ultralight_c_dict_context.dict); + dict_attack_set_total_dict_keys( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_total); + instance->mf_ultralight_c_dict_context.dict_keys_current = 0; + dict_attack_set_current_dict_key( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); + + // Set initial Ultralight C specific values + dict_attack_set_key_found(instance->dict_attack, false); + dict_attack_set_pages_total(instance->dict_attack, 48); // Ultralight C page count + dict_attack_set_pages_read(instance->dict_attack, 0); + + dict_attack_set_callback( + instance->dict_attack, + nfc_scene_mf_ultralight_c_dict_attack_dict_attack_result_callback, + instance); + scene_manager_set_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack, state); +} + +void nfc_scene_mf_ultralight_c_dict_attack_on_enter(void* context) { + NfcApp* instance = context; + + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateUserDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + + // Setup and start worker + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start(instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); + + dict_attack_set_card_state(instance->dict_attack, true); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewDictAttack); + nfc_blink_read_start(instance); +} + +void nfc_scene_mf_ul_c_dict_attack_update_view(NfcApp* instance) { + dict_attack_set_card_state( + instance->dict_attack, instance->mf_ultralight_c_dict_context.is_card_present); + dict_attack_set_current_dict_key( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); +} + +bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCDictAttack); + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventDictAttackComplete) { + if(state == DictAttackStateUserDictInProgress) { + if(instance->mf_ultralight_c_dict_context.auth_success) { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } else { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start( + instance->poller, + nfc_mf_ultralight_c_dict_attack_worker_callback, + instance); + consumed = true; + } + } else { + // Could check if card is fully read here like MFC dict attack, but found key means fully read + if(instance->mf_ultralight_c_dict_context.auth_success) { + notification_message(instance->notifications, &sequence_success); + } else { + notification_message(instance->notifications, &sequence_semi_success); + } + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } + } else if(event.event == NfcCustomEventDictAttackDataUpdate) { + dict_attack_set_current_dict_key( + instance->dict_attack, instance->mf_ultralight_c_dict_context.dict_keys_current); + consumed = true; + } else if(event.event == NfcCustomEventDictAttackSkip) { + if(state == DictAttackStateUserDictInProgress) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateSystemDictInProgress); + nfc_scene_mf_ultralight_c_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight); + nfc_poller_start( + instance->poller, nfc_mf_ultralight_c_dict_attack_worker_callback, instance); + } else { + notification_message(instance->notifications, &sequence_semi_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm); + consumed = true; + } + return consumed; +} + +void nfc_scene_mf_ultralight_c_dict_attack_on_exit(void* context) { + NfcApp* instance = context; + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + scene_manager_set_scene_state( + instance->scene_manager, + NfcSceneMfUltralightCDictAttack, + DictAttackStateUserDictInProgress); + keys_dict_free(instance->mf_ultralight_c_dict_context.dict); + instance->mf_ultralight_c_dict_context.dict_keys_total = 0; + instance->mf_ultralight_c_dict_context.dict_keys_current = 0; + instance->mf_ultralight_c_dict_context.auth_success = false; + instance->mf_ultralight_c_dict_context.is_card_present = false; + nfc_blink_stop(instance); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c new file mode 100644 index 000000000..9bf96f0b4 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys.c @@ -0,0 +1,96 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_ultralight_c_keys_on_enter(void* context) { + NfcApp* instance = context; + + // Load flipper dict keys total + uint32_t flipper_dict_keys_total = 0; + KeysDict* dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH, + KeysDictModeOpenExisting, + sizeof(MfUltralightC3DesAuthKey)); + flipper_dict_keys_total = keys_dict_get_total_keys(dict); + keys_dict_free(dict); + + // Load user dict keys total + uint32_t user_dict_keys_total = 0; + dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + user_dict_keys_total = keys_dict_get_total_keys(dict); + keys_dict_free(dict); + + FuriString* temp_str = furi_string_alloc(); + widget_add_string_element( + instance->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "MIFARE Ultralight C Keys"); + furi_string_printf(temp_str, "System dict: %lu", flipper_dict_keys_total); + widget_add_string_element( + instance->widget, + 0, + 20, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + furi_string_printf(temp_str, "User dict: %lu", user_dict_keys_total); + widget_add_string_element( + instance->widget, + 0, + 32, + AlignLeft, + AlignTop, + FontSecondary, + furi_string_get_cstr(temp_str)); + widget_add_icon_element(instance->widget, 87, 13, &I_Keychain_39x36); + widget_add_button_element( + instance->widget, + GuiButtonTypeCenter, + "Add", + nfc_scene_mf_ultralight_c_keys_widget_callback, + instance); + if(user_dict_keys_total > 0) { + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "List", + nfc_scene_mf_ultralight_c_keys_widget_callback, + instance); + } + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_ultralight_c_keys_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeCenter) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysAdd); + consumed = true; + } else if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysList); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c new file mode 100644 index 000000000..aa4c2fe1c --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c @@ -0,0 +1,63 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_add_byte_input_callback(void* context) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_scene_mf_ultralight_c_keys_add_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + ByteInput* byte_input = instance->byte_input; + byte_input_set_header_text(byte_input, "Enter the key in hex"); + byte_input_set_result_callback( + byte_input, + nfc_scene_mf_ultralight_c_keys_add_byte_input_callback, + NULL, + instance, + instance->byte_input_store, + sizeof(MfUltralightC3DesAuthKey)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput); +} + +bool nfc_scene_mf_ultralight_c_keys_add_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventByteInputDone) { + // Add key to dict + KeysDict* dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + + MfUltralightC3DesAuthKey key = {}; + memcpy(key.data, instance->byte_input_store, sizeof(MfUltralightC3DesAuthKey)); + if(keys_dict_is_key_present(dict, key.data, sizeof(MfUltralightC3DesAuthKey))) { + scene_manager_next_scene( + instance->scene_manager, NfcSceneMfUltralightCKeysWarnDuplicate); + } else if(keys_dict_add_key(dict, key.data, sizeof(MfUltralightC3DesAuthKey))) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); + dolphin_deed(DolphinDeedNfcMfcAdd); + } else { + scene_manager_previous_scene(instance->scene_manager); + } + + keys_dict_free(dict); + consumed = true; + } + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_add_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(instance->byte_input, ""); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c new file mode 100644 index 000000000..db2903939 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_delete.c @@ -0,0 +1,108 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_delete_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_mf_ultralight_c_keys_delete_on_enter(void* context) { + NfcApp* instance = context; + + uint32_t key_index = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMfUltralightCKeysDelete); + FuriString* key_str = furi_string_alloc(); + + widget_add_string_element( + instance->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Delete this key?"); + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "Cancel", + nfc_scene_mf_ultralight_c_keys_delete_widget_callback, + instance); + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "Delete", + nfc_scene_mf_ultralight_c_keys_delete_widget_callback, + instance); + + KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict); + furi_assert(key_index < dict_keys_num); + MfUltralightC3DesAuthKey stack_key; + for(size_t i = 0; i < (key_index + 1); i++) { + bool key_loaded = keys_dict_get_next_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + furi_assert(key_loaded); + } + furi_string_reset(key_str); + for(size_t i = 0; i < sizeof(MfUltralightC3DesAuthKey); i++) { + furi_string_cat_printf(key_str, "%02X", stack_key.data[i]); + } + + widget_add_string_element( + instance->widget, + 64, + 32, + AlignCenter, + AlignCenter, + FontSecondary, + furi_string_get_cstr(key_str)); + + keys_dict_free(mf_ultralight_c_user_dict); + furi_string_free(key_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_mf_ultralight_c_keys_delete_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeRight) { + uint32_t key_index = scene_manager_get_scene_state( + instance->scene_manager, NfcSceneMfUltralightCKeysDelete); + KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict); + furi_assert(key_index < dict_keys_num); + MfUltralightC3DesAuthKey stack_key; + for(size_t i = 0; i < (key_index + 1); i++) { + bool key_loaded = keys_dict_get_next_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + furi_assert(key_loaded); + } + bool key_delete_success = keys_dict_delete_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + keys_dict_free(mf_ultralight_c_user_dict); + if(key_delete_success) { + scene_manager_next_scene(instance->scene_manager, NfcSceneDeleteSuccess); + } else { + scene_manager_previous_scene(instance->scene_manager); + } + } else if(event.event == GuiButtonTypeLeft) { + scene_manager_previous_scene(instance->scene_manager); + } + consumed = true; + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_delete_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c new file mode 100644 index 000000000..e2fda3aea --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_list.c @@ -0,0 +1,66 @@ +#include "../nfc_app_i.h" + +#define NFC_SCENE_MF_ULTRALIGHT_C_KEYS_LIST_MAX (100) + +void nfc_scene_mf_ultralight_c_keys_list_submenu_callback(void* context, uint32_t index) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, index); +} + +void nfc_scene_mf_ultralight_c_keys_list_on_enter(void* context) { + NfcApp* instance = context; + + KeysDict* mf_ultralight_c_user_dict = keys_dict_alloc( + NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH, + KeysDictModeOpenAlways, + sizeof(MfUltralightC3DesAuthKey)); + + submenu_set_header(instance->submenu, "Select key to delete:"); + FuriString* temp_str = furi_string_alloc(); + + size_t dict_keys_num = keys_dict_get_total_keys(mf_ultralight_c_user_dict); + size_t keys_num = MIN((size_t)NFC_SCENE_MF_ULTRALIGHT_C_KEYS_LIST_MAX, dict_keys_num); + MfUltralightC3DesAuthKey stack_key; + + if(keys_num > 0) { + for(size_t i = 0; i < keys_num; i++) { + bool key_loaded = keys_dict_get_next_key( + mf_ultralight_c_user_dict, stack_key.data, sizeof(MfUltralightC3DesAuthKey)); + furi_assert(key_loaded); + furi_string_reset(temp_str); + for(size_t i = 0; i < sizeof(MfUltralightC3DesAuthKey); i++) { + furi_string_cat_printf(temp_str, "%02X", stack_key.data[i]); + } + submenu_add_item( + instance->submenu, + furi_string_get_cstr(temp_str), + i, + nfc_scene_mf_ultralight_c_keys_list_submenu_callback, + instance); + } + } + keys_dict_free(mf_ultralight_c_user_dict); + furi_string_free(temp_str); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_mf_ultralight_c_keys_list_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMfUltralightCKeysDelete, event.event); + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCKeysDelete); + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_list_on_exit(void* context) { + NfcApp* instance = context; + + submenu_reset(instance->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c new file mode 100644 index 000000000..c8881e5d4 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_warn_duplicate.c @@ -0,0 +1,49 @@ +#include "../nfc_app_i.h" + +void nfc_scene_mf_ultralight_c_keys_warn_duplicate_popup_callback(void* context) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventViewExit); +} + +void nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + Popup* popup = instance->popup; + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); + popup_set_header(popup, "Key Already Exists!", 64, 3, AlignCenter, AlignTop); + popup_set_text( + popup, + "Please enter a\n" + "different key.", + 4, + 24, + AlignLeft, + AlignTop); + popup_set_timeout(popup, 1500); + popup_set_context(popup, instance); + popup_set_callback(popup, nfc_scene_mf_ultralight_c_keys_warn_duplicate_popup_callback); + popup_enable_timeout(popup); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +bool nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventViewExit) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneMfUltralightCKeysAdd); + } + } + + return consumed; +} + +void nfc_scene_mf_ultralight_c_keys_warn_duplicate_on_exit(void* context) { + NfcApp* instance = context; + + popup_reset(instance->popup); +} diff --git a/applications/main/nfc/scenes/nfc_scene_save_success.c b/applications/main/nfc/scenes/nfc_scene_save_success.c index 5f812ba9c..06999fe9f 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_success.c +++ b/applications/main/nfc/scenes/nfc_scene_save_success.c @@ -28,6 +28,10 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicKeys); + } else if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightCKeys); } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSaveConfirm)) { NfcSceneSaveConfirmState scene_state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneSaveConfirm); diff --git a/applications/main/nfc/views/dict_attack.c b/applications/main/nfc/views/dict_attack.c index 726076972..a71e466f8 100644 --- a/applications/main/nfc/views/dict_attack.c +++ b/applications/main/nfc/views/dict_attack.c @@ -13,12 +13,13 @@ struct DictAttack { typedef struct { FuriString* header; bool card_detected; + DictAttackType attack_type; + + // MIFARE Classic specific uint8_t sectors_total; uint8_t sectors_read; uint8_t current_sector; uint8_t keys_found; - size_t dict_keys_total; - size_t dict_keys_current; bool is_key_attack; uint8_t key_attack_current_sector; MfClassicNestedPhase nested_phase; @@ -26,8 +27,150 @@ typedef struct { MfClassicBackdoor backdoor; uint16_t nested_target_key; uint16_t msb_count; + + // Ultralight C specific + uint8_t pages_total; + uint8_t pages_read; + bool key_found; + + // Common + size_t dict_keys_total; + size_t dict_keys_current; } DictAttackViewModel; +static void dict_attack_draw_mf_classic(Canvas* canvas, DictAttackViewModel* m) { + char draw_str[32] = {}; + canvas_set_font(canvas, FontSecondary); + + switch(m->nested_phase) { + case MfClassicNestedPhaseAnalyzePRNG: + furi_string_set(m->header, "PRNG Analysis"); + break; + case MfClassicNestedPhaseDictAttack: + case MfClassicNestedPhaseDictAttackVerify: + case MfClassicNestedPhaseDictAttackResume: + furi_string_set(m->header, "Nested Dictionary"); + break; + case MfClassicNestedPhaseCalibrate: + case MfClassicNestedPhaseRecalibrate: + furi_string_set(m->header, "Calibration"); + break; + case MfClassicNestedPhaseCollectNtEnc: + furi_string_set(m->header, "Nonce Collection"); + break; + default: + break; + } + + if(m->prng_type == MfClassicPrngTypeHard) { + furi_string_cat(m->header, " (Hard)"); + } + + if(m->backdoor != MfClassicBackdoorNone && m->backdoor != MfClassicBackdoorUnknown) { + if(m->nested_phase != MfClassicNestedPhaseNone) { + furi_string_cat(m->header, " (Backdoor)"); + } else { + furi_string_set(m->header, "Backdoor Read"); + } + } + + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); + if(m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + uint8_t nonce_sector = + m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 4 : 2); + snprintf(draw_str, sizeof(draw_str), "Collecting from sector: %d", nonce_sector); + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + } else if(m->is_key_attack) { + snprintf( + draw_str, + sizeof(draw_str), + "Reuse key check for sector: %d", + m->key_attack_current_sector); + } else { + snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->current_sector); + } + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + float dict_progress = 0; + if(m->nested_phase == MfClassicNestedPhaseAnalyzePRNG || + m->nested_phase == MfClassicNestedPhaseDictAttack || + m->nested_phase == MfClassicNestedPhaseDictAttackVerify || + m->nested_phase == MfClassicNestedPhaseDictAttackResume) { + // Phase: Nested dictionary attack + uint8_t target_sector = + m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 2 : 16); + dict_progress = (float)(target_sector) / (float)(m->sectors_total); + snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); + } else if( + m->nested_phase == MfClassicNestedPhaseCalibrate || + m->nested_phase == MfClassicNestedPhaseRecalibrate || + m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { + // Phase: Nonce collection + if(m->prng_type == MfClassicPrngTypeWeak) { + uint8_t target_sector = m->nested_target_key / 4; + dict_progress = (float)(target_sector) / (float)(m->sectors_total); + snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); + } else { + uint16_t max_msb = UINT8_MAX + 1; + dict_progress = (float)(m->msb_count) / (float)(max_msb); + snprintf(draw_str, sizeof(draw_str), "%d/%d", m->msb_count, max_msb); + } + } else { + dict_progress = m->dict_keys_total == 0 ? + 0 : + (float)(m->dict_keys_current) / (float)(m->dict_keys_total); + if(m->dict_keys_current == 0) { + // Cause when people see 0 they think it's broken + snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); + } else { + snprintf( + draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total); + } + } + if(dict_progress > 1.0f) { + dict_progress = 1.0f; + } + elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); + canvas_set_font(canvas, FontSecondary); + snprintf( + draw_str, + sizeof(draw_str), + "Keys found: %d/%d", + m->keys_found, + m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR); + canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); + snprintf(draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); + canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); +} + +static void dict_attack_draw_mf_ultralight_c(Canvas* canvas, DictAttackViewModel* m) { + char draw_str[32] = {}; + canvas_set_font(canvas, FontSecondary); + + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); + + snprintf(draw_str, sizeof(draw_str), "Trying keys"); + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + + float dict_progress = + m->dict_keys_total == 0 ? 0 : (float)(m->dict_keys_current) / (float)(m->dict_keys_total); + if(m->dict_keys_current == 0) { + snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); + } else { + snprintf(draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total); + } + if(dict_progress > 1.0f) { + dict_progress = 1.0f; + } + elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); + + canvas_set_font(canvas, FontSecondary); + snprintf(draw_str, sizeof(draw_str), "Key found: %s", m->key_found ? "Yes" : "No"); + canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); + + snprintf(draw_str, sizeof(draw_str), "Pages read: %d/%d", m->pages_read, m->pages_total); + canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); +} + static void dict_attack_draw_callback(Canvas* canvas, void* model) { DictAttackViewModel* m = model; if(!m->card_detected) { @@ -37,113 +180,11 @@ static void dict_attack_draw_callback(Canvas* canvas, void* model) { elements_multiline_text_aligned( canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly."); } else { - char draw_str[32] = {}; - canvas_set_font(canvas, FontSecondary); - - switch(m->nested_phase) { - case MfClassicNestedPhaseAnalyzePRNG: - furi_string_set(m->header, "PRNG Analysis"); - break; - case MfClassicNestedPhaseDictAttack: - case MfClassicNestedPhaseDictAttackVerify: - case MfClassicNestedPhaseDictAttackResume: - furi_string_set(m->header, "Nested Dictionary"); - break; - case MfClassicNestedPhaseCalibrate: - case MfClassicNestedPhaseRecalibrate: - furi_string_set(m->header, "Calibration"); - break; - case MfClassicNestedPhaseCollectNtEnc: - furi_string_set(m->header, "Nonce Collection"); - break; - default: - break; + if(m->attack_type == DictAttackTypeMfClassic) { + dict_attack_draw_mf_classic(canvas, m); + } else if(m->attack_type == DictAttackTypeMfUltralightC) { + dict_attack_draw_mf_ultralight_c(canvas, m); } - - if(m->prng_type == MfClassicPrngTypeHard) { - furi_string_cat(m->header, " (Hard)"); - } - - if(m->backdoor != MfClassicBackdoorNone && m->backdoor != MfClassicBackdoorUnknown) { - if(m->nested_phase != MfClassicNestedPhaseNone) { - furi_string_cat(m->header, " (Backdoor)"); - } else { - furi_string_set(m->header, "Backdoor Read"); - } - } - - canvas_draw_str_aligned( - canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(m->header)); - if(m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { - uint8_t nonce_sector = - m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 4 : 2); - snprintf(draw_str, sizeof(draw_str), "Collecting from sector: %d", nonce_sector); - canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); - } else if(m->is_key_attack) { - snprintf( - draw_str, - sizeof(draw_str), - "Reuse key check for sector: %d", - m->key_attack_current_sector); - } else { - snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->current_sector); - } - canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); - float dict_progress = 0; - if(m->nested_phase == MfClassicNestedPhaseAnalyzePRNG || - m->nested_phase == MfClassicNestedPhaseDictAttack || - m->nested_phase == MfClassicNestedPhaseDictAttackVerify || - m->nested_phase == MfClassicNestedPhaseDictAttackResume) { - // Phase: Nested dictionary attack - uint8_t target_sector = - m->nested_target_key / (m->prng_type == MfClassicPrngTypeWeak ? 2 : 16); - dict_progress = (float)(target_sector) / (float)(m->sectors_total); - snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); - } else if( - m->nested_phase == MfClassicNestedPhaseCalibrate || - m->nested_phase == MfClassicNestedPhaseRecalibrate || - m->nested_phase == MfClassicNestedPhaseCollectNtEnc) { - // Phase: Nonce collection - if(m->prng_type == MfClassicPrngTypeWeak) { - uint8_t target_sector = m->nested_target_key / 4; - dict_progress = (float)(target_sector) / (float)(m->sectors_total); - snprintf(draw_str, sizeof(draw_str), "%d/%d", target_sector, m->sectors_total); - } else { - uint16_t max_msb = UINT8_MAX + 1; - dict_progress = (float)(m->msb_count) / (float)(max_msb); - snprintf(draw_str, sizeof(draw_str), "%d/%d", m->msb_count, max_msb); - } - } else { - dict_progress = m->dict_keys_total == 0 ? - 0 : - (float)(m->dict_keys_current) / (float)(m->dict_keys_total); - if(m->dict_keys_current == 0) { - // Cause when people see 0 they think it's broken - snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); - } else { - snprintf( - draw_str, - sizeof(draw_str), - "%zu/%zu", - m->dict_keys_current, - m->dict_keys_total); - } - } - if(dict_progress > 1.0f) { - dict_progress = 1.0f; - } - elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); - canvas_set_font(canvas, FontSecondary); - snprintf( - draw_str, - sizeof(draw_str), - "Keys found: %d/%d", - m->keys_found, - m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR); - canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); - snprintf( - draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); - canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); } elements_button_center(canvas, "Skip"); } @@ -195,18 +236,28 @@ void dict_attack_reset(DictAttack* instance) { instance->view, DictAttackViewModel * model, { + model->attack_type = DictAttackTypeMfClassic; + + // MIFARE Classic fields model->sectors_total = 0; model->sectors_read = 0; model->current_sector = 0; model->keys_found = 0; - model->dict_keys_total = 0; - model->dict_keys_current = 0; model->is_key_attack = false; model->nested_phase = MfClassicNestedPhaseNone; model->prng_type = MfClassicPrngTypeUnknown; model->backdoor = MfClassicBackdoorUnknown; model->nested_target_key = 0; model->msb_count = 0; + + // Ultralight C fields + model->pages_total = 0; + model->pages_read = 0; + model->key_found = false; + + // Common fields + model->dict_keys_total = 0; + model->dict_keys_current = 0; furi_string_reset(model->header); }, false); @@ -355,3 +406,31 @@ void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count) { with_view_model( instance->view, DictAttackViewModel * model, { model->msb_count = msb_count; }, true); } + +void dict_attack_set_type(DictAttack* instance, DictAttackType type) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->attack_type = type; }, true); +} + +void dict_attack_set_pages_total(DictAttack* instance, uint8_t pages_total) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->pages_total = pages_total; }, true); +} + +void dict_attack_set_pages_read(DictAttack* instance, uint8_t pages_read) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->pages_read = pages_read; }, true); +} + +void dict_attack_set_key_found(DictAttack* instance, bool key_found) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->key_found = key_found; }, true); +} diff --git a/applications/main/nfc/views/dict_attack.h b/applications/main/nfc/views/dict_attack.h index b6c6fdbdc..70709f86e 100644 --- a/applications/main/nfc/views/dict_attack.h +++ b/applications/main/nfc/views/dict_attack.h @@ -8,6 +8,11 @@ extern "C" { #endif +typedef enum { + DictAttackTypeMfClassic, + DictAttackTypeMfUltralightC, +} DictAttackType; + typedef struct DictAttack DictAttack; typedef enum { @@ -56,6 +61,14 @@ void dict_attack_set_nested_target_key(DictAttack* instance, uint16_t target_key void dict_attack_set_msb_count(DictAttack* instance, uint16_t msb_count); +void dict_attack_set_type(DictAttack* instance, DictAttackType type); + +void dict_attack_set_pages_total(DictAttack* instance, uint8_t pages_total); + +void dict_attack_set_pages_read(DictAttack* instance, uint8_t pages_read); + +void dict_attack_set_key_found(DictAttack* instance, bool key_found); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight.h index 191deeda6..caf25ceee 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.h @@ -38,7 +38,6 @@ extern "C" { #define MF_ULTRALIGHT_TEARING_FLAG_NUM (3) #define MF_ULTRALIGHT_AUTH_PASSWORD_SIZE (4) #define MF_ULTRALIGHT_AUTH_PACK_SIZE (2) -#define MF_ULTRALIGHT_DEFAULT_PASSWORD (0xffffffffUL) #define MF_ULTRALIGHT_C_AUTH_RESPONSE_SIZE (9) #define MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE (16) @@ -48,11 +47,6 @@ extern "C" { #define MF_ULTRALIGHT_C_AUTH_RND_A_BLOCK_OFFSET (0) #define MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET (8) #define MF_ULTRALIGHT_C_ENCRYPTED_PACK_SIZE (MF_ULTRALIGHT_C_AUTH_DATA_SIZE + 1) -#define MF_ULTRALIGHT_C_DEFAULT_KEY \ - (uint8_t[]) { \ - 0x49, 0x45, 0x4D, 0x4B, 0x41, 0x45, 0x52, 0x42, 0x21, 0x4E, 0x41, 0x43, 0x55, 0x4F, 0x59, \ - 0x46 \ - } typedef enum { MfUltralightErrorNone, diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 6c6e230f0..cf942172b 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -251,7 +251,7 @@ static NfcCommand mf_ultralight_poller_handler_read_version(MfUltralightPoller* instance->data->type = mf_ultralight_get_type_by_version(&instance->data->version); instance->state = MfUltralightPollerStateGetFeatureSet; } else { - FURI_LOG_D(TAG, "Didn't response. Check Ultralight C"); + FURI_LOG_D(TAG, "Didn't respond. Check Ultralight C"); iso14443_3a_poller_halt(instance->iso14443_3a_poller); instance->state = MfUltralightPollerStateDetectMfulC; } @@ -266,7 +266,7 @@ static NfcCommand mf_ultralight_poller_handler_check_ultralight_c(MfUltralightPo instance->data->type = MfUltralightTypeMfulC; instance->state = MfUltralightPollerStateGetFeatureSet; } else { - FURI_LOG_D(TAG, "Didn't response. Check NTAG 203"); + FURI_LOG_D(TAG, "Didn't respond. Check NTAG 203"); instance->state = MfUltralightPollerStateDetectNtag203; } iso14443_3a_poller_halt(instance->iso14443_3a_poller); @@ -445,36 +445,105 @@ static NfcCommand mf_ultralight_poller_handler_auth(MfUltralightPoller* instance static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPoller* instance) { NfcCommand command = NfcCommandContinue; FURI_LOG_D(TAG, "MfulC auth"); + if(mf_ultralight_support_feature( + instance->feature_set, MfUltralightFeatureSupportAuthenticate)) { + instance->mfu_event.type = MfUltralightPollerEventTypeAuthRequest; - do { - if(mf_ultralight_support_feature( - instance->feature_set, MfUltralightFeatureSupportAuthenticate)) { - instance->mfu_event.type = MfUltralightPollerEventTypeAuthRequest; - - command = instance->callback(instance->general_event, instance->context); - if(!instance->mfu_event.data->auth_context.skip_auth) { - FURI_LOG_D(TAG, "Trying to authenticate with 3des key"); - instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key; - instance->error = - mf_ultralight_poller_auth_tdes(instance, &instance->auth_context); - - if(instance->error == MfUltralightErrorNone && - instance->auth_context.auth_success) { - FURI_LOG_D(TAG, "Auth success"); + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->auth_context.skip_auth) { + FURI_LOG_D(TAG, "Trying to authenticate with 3des key"); + // Only use the key if it was actually provided + if(instance->mfu_event.data->key_request_data.key_provided) { + instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; + } else if(instance->mode == MfUltralightPollerModeDictAttack) { + // TODO: Can logic be rearranged to request this key before reaching mf_ultralight_poller_handler_auth_ultralight_c in poller? + FURI_LOG_D(TAG, "No initial key provided, requesting key from dictionary"); + // Trigger dictionary key request + instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->key_request_data.key_provided) { + instance->state = MfUltralightPollerStateReadPages; + return command; } else { - FURI_LOG_D(TAG, "Auth failed"); - iso14443_3a_poller_halt(instance->iso14443_3a_poller); + instance->auth_context.tdes_key = + instance->mfu_event.data->key_request_data.key; } } else { - // We assume here that it is card read without explicitly provided key - // So we try to auth with default one - instance->state = MfUltralightPollerStateTryDefaultMfulCKey; - break; + FURI_LOG_D(TAG, "No key provided, skipping auth"); + instance->state = MfUltralightPollerStateReadPages; + return command; + } + instance->auth_context.auth_success = false; + // For debugging + FURI_LOG_D( + "TAG", + "Key data: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", + instance->auth_context.tdes_key.data[0], + instance->auth_context.tdes_key.data[1], + instance->auth_context.tdes_key.data[2], + instance->auth_context.tdes_key.data[3], + instance->auth_context.tdes_key.data[4], + instance->auth_context.tdes_key.data[5], + instance->auth_context.tdes_key.data[6], + instance->auth_context.tdes_key.data[7], + instance->auth_context.tdes_key.data[8], + instance->auth_context.tdes_key.data[9], + instance->auth_context.tdes_key.data[10], + instance->auth_context.tdes_key.data[11], + instance->auth_context.tdes_key.data[12], + instance->auth_context.tdes_key.data[13], + instance->auth_context.tdes_key.data[14], + instance->auth_context.tdes_key.data[15]); + do { + uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE]; + uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; + furi_hal_random_fill_buf(RndA, sizeof(RndA)); + instance->error = mf_ultralight_poller_authenticate_start(instance, RndA, output); + if(instance->error != MfUltralightErrorNone) break; + + uint8_t decoded_shifted_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; + const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET; + instance->error = mf_ultralight_poller_authenticate_end( + instance, RndB, output, decoded_shifted_RndA); + if(instance->error != MfUltralightErrorNone) break; + + mf_ultralight_3des_shift_data(RndA); + instance->auth_context.auth_success = + (memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0); + if(instance->auth_context.auth_success) { + FURI_LOG_E(TAG, "Auth success"); + if(instance->mode == MfUltralightPollerModeDictAttack) { + memcpy( + &instance->data->page[44], + instance->auth_context.tdes_key.data, + MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE); + // Continue to read pages after successful authentication + instance->state = MfUltralightPollerStateReadPages; + } + } + } while(false); + if(instance->error != MfUltralightErrorNone || !instance->auth_context.auth_success) { + FURI_LOG_E(TAG, "Auth failed"); + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + if(instance->mode == MfUltralightPollerModeDictAttack) { + // Not needed? We already do a callback earlier? + instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; + command = instance->callback(instance->general_event, instance->context); + if(!instance->mfu_event.data->key_request_data.key_provided) { + instance->state = MfUltralightPollerStateReadPages; + } else { + instance->auth_context.tdes_key = + instance->mfu_event.data->key_request_data.key; + instance->state = MfUltralightPollerStateAuthMfulC; + } + } } } + } + // Regression review + if(instance->mode != MfUltralightPollerModeDictAttack) { instance->state = MfUltralightPollerStateReadPages; - } while(false); - + } return command; } @@ -497,12 +566,16 @@ static NfcCommand mf_ultralight_poller_handler_read_pages(MfUltralightPoller* in instance->error = mf_ultralight_poller_read_page(instance, start_page, &data); } + // Regression review + const uint8_t read_cnt = instance->data->type == MfUltralightTypeMfulC ? 1 : 4; if(instance->error == MfUltralightErrorNone) { - if(start_page < instance->pages_total) { - FURI_LOG_D(TAG, "Read page %d success", start_page); - instance->data->page[start_page] = data.page[0]; - instance->pages_read++; - instance->data->pages_read = instance->pages_read; + for(size_t i = 0; i < read_cnt; i++) { + if(start_page + i < instance->pages_total) { + FURI_LOG_D(TAG, "Read page %d success", start_page + i); + instance->data->page[start_page + i] = data.page[i]; + instance->pages_read++; + instance->data->pages_read = instance->pages_read; + } } if(instance->pages_read == instance->pages_total) { @@ -570,40 +643,6 @@ static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoll return NfcCommandContinue; } -static NfcCommand - mf_ultralight_poller_handler_try_default_ultralight_c_key(MfUltralightPoller* instance) { - do { - if(!mf_ultralight_support_feature( - instance->feature_set, MfUltralightFeatureSupportAuthenticate)) { - break; - } - - if(instance->auth_context.auth_success) { - break; - } - - FURI_LOG_D(TAG, "Trying authentication with default 3DES key"); - - memcpy( - &instance->auth_context.tdes_key.data, - MF_ULTRALIGHT_C_DEFAULT_KEY, - MF_ULTRALIGHT_C_AUTH_DES_KEY_SIZE); - - instance->error = mf_ultralight_poller_auth_tdes(instance, &instance->auth_context); - - if(instance->error == MfUltralightErrorNone && instance->auth_context.auth_success) { - FURI_LOG_D(TAG, "Default 3DES key detected"); - } else { - FURI_LOG_D(TAG, "Authentication attempt with default 3DES key failed"); - iso14443_3a_poller_halt(instance->iso14443_3a_poller); - } - - } while(false); - - instance->state = MfUltralightPollerStateReadPages; - return NfcCommandContinue; -} - static NfcCommand mf_ultralight_poller_handler_check_mfuc_auth_status(MfUltralightPoller* instance) { instance->state = MfUltralightPollerStateReadSuccess; @@ -768,8 +807,6 @@ static const MfUltralightPollerReadHandler mf_ultralight_poller_handler_read_tearing_flags, [MfUltralightPollerStateAuth] = mf_ultralight_poller_handler_auth, [MfUltralightPollerStateTryDefaultPass] = mf_ultralight_poller_handler_try_default_pass, - [MfUltralightPollerStateTryDefaultMfulCKey] = - mf_ultralight_poller_handler_try_default_ultralight_c_key, [MfUltralightPollerStateCheckMfulCAuthStatus] = mf_ultralight_poller_handler_check_mfuc_auth_status, [MfUltralightPollerStateAuthMfulC] = mf_ultralight_poller_handler_auth_ultralight_c, @@ -781,7 +818,6 @@ static const MfUltralightPollerReadHandler [MfUltralightPollerStateWritePages] = mf_ultralight_poller_handler_write_pages, [MfUltralightPollerStateWriteFail] = mf_ultralight_poller_handler_write_fail, [MfUltralightPollerStateWriteSuccess] = mf_ultralight_poller_handler_write_success, - }; static NfcCommand mf_ultralight_poller_run(NfcGenericEvent event, void* context) { diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h index 661c6db74..2552abeb5 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h @@ -27,6 +27,7 @@ typedef enum { MfUltralightPollerEventTypeCardLocked, /**< Presented card is locked by password, AUTH0 or lock bytes. */ MfUltralightPollerEventTypeWriteSuccess, /**< Poller wrote card successfully. */ MfUltralightPollerEventTypeWriteFail, /**< Poller failed to write card. */ + MfUltralightPollerEventTypeRequestKey, /**< Poller requests key for dict attack. */ } MfUltralightPollerEventType; /** @@ -35,6 +36,7 @@ typedef enum { typedef enum { MfUltralightPollerModeRead, /**< Poller will only read card. It's a default mode. */ MfUltralightPollerModeWrite, /**< Poller will write already saved card to another presented card. */ + MfUltralightPollerModeDictAttack, /**< Poller will perform dictionary attack against card. */ } MfUltralightPollerMode; /** @@ -42,20 +44,29 @@ typedef enum { */ typedef struct { MfUltralightAuthPassword password; /**< Password to be used for authentication. */ - MfUltralightC3DesAuthKey tdes_key; - MfUltralightAuthPack pack; /**< Pack received on successfull authentication. */ + MfUltralightC3DesAuthKey tdes_key; /**< 3DES key to be used for authentication. */ + MfUltralightAuthPack pack; /**< Pack received on successful authentication. */ bool auth_success; /**< Set to true if authentication succeeded, false otherwise. */ bool skip_auth; /**< Set to true if authentication should be skipped, false otherwise. */ } MfUltralightPollerAuthContext; +/** + * @brief MfUltralight poller key request data. + */ +typedef struct { + MfUltralightC3DesAuthKey key; /**< Key to try. */ + bool key_provided; /**< Set to true if key was provided, false to stop attack. */ +} MfUltralightPollerKeyRequestData; + /** * @brief MfUltralight poller event data. */ typedef union { MfUltralightPollerAuthContext auth_context; /**< Authentication context. */ MfUltralightError error; /**< Error code indicating reading fail reason. */ - const MfUltralightData* write_data; - MfUltralightPollerMode poller_mode; + const MfUltralightData* write_data; /**< Data to be written to card. */ + MfUltralightPollerMode poller_mode; /**< Mode to operate in. */ + MfUltralightPollerKeyRequestData key_request_data; /**< Key request data. */ } MfUltralightPollerEventData; /** @@ -64,7 +75,7 @@ typedef union { * Upon emission of an event, an instance of this struct will be passed to the callback. */ typedef struct { - MfUltralightPollerEventType type; /**< Type of emmitted event. */ + MfUltralightPollerEventType type; /**< Type of emitted event. */ MfUltralightPollerEventData* data; /**< Pointer to event specific data. */ } MfUltralightPollerEvent; @@ -81,19 +92,6 @@ MfUltralightError mf_ultralight_poller_auth_pwd( MfUltralightPoller* instance, MfUltralightPollerAuthContext* data); -/** - * @brief Perform 3DES authentication with key. - * - * Must ONLY be used inside the callback function. - * - * @param[in, out] instance pointer to the instance to be used in the transaction. - * @param[in, out] data pointer to the authentication context. - * @return MfUltralightErrorNone on success, an error code on failure. - */ -MfUltralightError mf_ultralight_poller_auth_tdes( - MfUltralightPoller* instance, - MfUltralightPollerAuthContext* data); - /** * @brief Start authentication procedure. * diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c index fdafaf37d..82e647da8 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.c @@ -1,7 +1,6 @@ #include "mf_ultralight_poller_i.h" #include -#include #define TAG "MfUltralightPoller" @@ -63,38 +62,6 @@ MfUltralightError mf_ultralight_poller_auth_pwd( return ret; } -MfUltralightError mf_ultralight_poller_auth_tdes( - MfUltralightPoller* instance, - MfUltralightPollerAuthContext* data) { - furi_check(instance); - furi_check(data); - - MfUltralightError ret = MfUltralightErrorNone; - - uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE]; - uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; - furi_hal_random_fill_buf(RndA, sizeof(RndA)); - - ret = mf_ultralight_poller_authenticate_start(instance, RndA, output); - - if(ret != MfUltralightErrorNone) { - return ret; - } - - uint8_t decoded_shifted_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; - const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET; - ret = mf_ultralight_poller_authenticate_end(instance, RndB, output, decoded_shifted_RndA); - - if(ret != MfUltralightErrorNone) { - return ret; - } - - mf_ultralight_3des_shift_data(RndA); - data->auth_success = (memcmp(RndA, decoded_shifted_RndA, sizeof(decoded_shifted_RndA)) == 0); - - return ret; -} - static MfUltralightError mf_ultralight_poller_send_authenticate_cmd( MfUltralightPoller* instance, const uint8_t* cmd, diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h index 7db9a77d9..b35c49aea 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -11,6 +11,8 @@ extern "C" { #define MF_ULTRALIGHT_POLLER_STANDARD_FWT_FC (60000) #define MF_ULTRALIGHT_MAX_BUFF_SIZE (64) +#define MF_ULTRALIGHT_DEFAULT_PASSWORD (0xffffffffUL) + #define MF_ULTRALIGHT_IS_NTAG_I2C(type) \ (((type) == MfUltralightTypeNTAGI2C1K) || ((type) == MfUltralightTypeNTAGI2C2K) || \ ((type) == MfUltralightTypeNTAGI2CPlus1K) || ((type) == MfUltralightTypeNTAGI2CPlus2K)) @@ -59,7 +61,6 @@ typedef enum { MfUltralightPollerStateAuthMfulC, MfUltralightPollerStateReadPages, MfUltralightPollerStateTryDefaultPass, - MfUltralightPollerStateTryDefaultMfulCKey, MfUltralightPollerStateCheckMfulCAuthStatus, MfUltralightPollerStateReadFailed, MfUltralightPollerStateReadSuccess, diff --git a/lib/toolbox/keys_dict.c b/lib/toolbox/keys_dict.c index 602653e8f..c26e9c1e7 100644 --- a/lib/toolbox/keys_dict.c +++ b/lib/toolbox/keys_dict.c @@ -134,22 +134,21 @@ static void keys_dict_int_to_str(KeysDict* instance, const uint8_t* key_int, Fur furi_string_cat_printf(key_str, "%02X", key_int[i]); } -static void keys_dict_str_to_int(KeysDict* instance, FuriString* key_str, uint64_t* key_int) { +static void keys_dict_str_to_int(KeysDict* instance, FuriString* key_str, uint8_t* key_out) { furi_assert(instance); furi_assert(key_str); - furi_assert(key_int); + furi_assert(key_out); uint8_t key_byte_tmp; char h, l; - *key_int = 0ULL; - + // Process two hex characters at a time to create each byte for(size_t i = 0; i < instance->key_size_symbols - 1; i += 2) { h = furi_string_get_char(key_str, i); l = furi_string_get_char(key_str, i + 1); args_char_to_hex(h, l, &key_byte_tmp); - *key_int |= (uint64_t)key_byte_tmp << (8 * (instance->key_size - 1 - i / 2)); + key_out[i / 2] = key_byte_tmp; } } @@ -193,15 +192,7 @@ bool keys_dict_get_next_key(KeysDict* instance, uint8_t* key, size_t key_size) { bool key_read = keys_dict_get_next_key_str(instance, temp_key); if(key_read) { - size_t tmp_len = key_size; - uint64_t key_int = 0; - - keys_dict_str_to_int(instance, temp_key, &key_int); - - while(tmp_len--) { - key[tmp_len] = (uint8_t)key_int; - key_int >>= 8; - } + keys_dict_str_to_int(instance, temp_key, key); } furi_string_free(temp_key); diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 7696a05bc..2f1b0646e 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2765,7 +2765,6 @@ Function,+,mf_ultralight_is_equal,_Bool,"const MfUltralightData*, const MfUltral Function,+,mf_ultralight_is_page_pwd_or_pack,_Bool,"MfUltralightType, uint16_t" Function,+,mf_ultralight_load,_Bool,"MfUltralightData*, FlipperFormat*, uint32_t" Function,+,mf_ultralight_poller_auth_pwd,MfUltralightError,"MfUltralightPoller*, MfUltralightPollerAuthContext*" -Function,+,mf_ultralight_poller_auth_tdes,MfUltralightError,"MfUltralightPoller*, MfUltralightPollerAuthContext*" Function,+,mf_ultralight_poller_authenticate_end,MfUltralightError,"MfUltralightPoller*, const uint8_t*, const uint8_t*, uint8_t*" Function,+,mf_ultralight_poller_authenticate_start,MfUltralightError,"MfUltralightPoller*, const uint8_t*, uint8_t*" Function,+,mf_ultralight_poller_read_counter,MfUltralightError,"MfUltralightPoller*, uint8_t, MfUltralightCounter*" From d45048fc5d3b4c4902f172bd374011b6f465bf5d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 21 Sep 2025 16:46:14 +0300 Subject: [PATCH 10/26] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31ea11bf8..d7258caf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * SubGHz: RAW protocol fixes (by @WillyJL) * SubGHz: Add ZKTeco 430.5 MHz add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) +* OFW PR 4271: NFC: Ultralight C NFC App Key Management, Dictionary Attack (by @noproto) * OFW PR 4265: NFC: Fix read crash with unexpectedly large MFC AUTH(0) response (by @WillyJL) * OFW PR 4251: CLI: Fix long delay with quick connect/disconnect (by @WillyJL) * LFRFID: Add additional procotols supported by EM4305 chipset (by @jamisonderek) From d791711b09712470ef275c544e06024942563d33 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 24 Sep 2025 00:44:19 +0300 Subject: [PATCH 11/26] Fix FaacSLH 868 add manually crash --- applications/main/subghz/helpers/subghz_gen_info.c | 4 ++-- applications/main/subghz/scenes/subghz_scene_set_type.c | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_gen_info.c b/applications/main/subghz/helpers/subghz_gen_info.c index 90853cec5..69fa37187 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.c +++ b/applications/main/subghz/helpers/subghz_gen_info.c @@ -233,7 +233,7 @@ void subghz_scene_set_type_fill_generation_infos(GenInfo* infos_dest, SetType ty .faac_slh.serial = ((key & 0x00FFFFF0) | 0xA0000006) >> 4, .faac_slh.btn = 0x06, .faac_slh.cnt = 0x02, - .faac_slh.seed = key, + .faac_slh.seed = (uint32_t)key, .faac_slh.manuf = "FAAC_SLH"}; break; case SetTypeFaacSLH_868: @@ -244,7 +244,7 @@ void subghz_scene_set_type_fill_generation_infos(GenInfo* infos_dest, SetType ty .faac_slh.serial = ((key & 0x00FFFFF0) | 0xA0000006) >> 4, .faac_slh.btn = 0x06, .faac_slh.cnt = 0x02, - .faac_slh.seed = (key & 0x0FFFFFFF), + .faac_slh.seed = (uint32_t)key, .faac_slh.manuf = "FAAC_SLH"}; break; case SetTypeBeninca433: diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 93419a974..d3bb2482e 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -223,6 +223,8 @@ bool subghz_scene_set_type_generate_protocol_from_infos(SubGhz* subghz) { if(generated_protocol) { subghz_file_name_clear(subghz); + scene_manager_set_scene_state( + subghz->scene_manager, SubGhzSceneSetType, SubGhzCustomEventManagerSet); scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName); } else { furi_string_set(subghz->error_str, "Function requires\nan SD card with\nfresh databases."); From a0c85ed7374ca6a7c79ceb7332c3e32abfdf641c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 24 Sep 2025 00:46:35 +0300 Subject: [PATCH 12/26] upd changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7258caf6..1ac4e782c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ * LFRFID: Add additional procotols supported by EM4305 chipset (by @jamisonderek) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* SubGHz: Fix crash in add manually menu +* OFW: NFC: MFC 1k Banapass Parser +* OFW: GUI Bug Fix: Number Input Save Icon * Add possibility to use custom buttons when using the SubGHz remote app (by @MrLego8-9) * Input Settings: Add Vibro Trigger option (by @956MB & @WillyJL) * BT Remote: Add Rename Option (by @aaronjamt & @WillyJL) From 46c335ad255354e0f22a0f63a440988843d24879 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 28 Sep 2025 01:53:21 +0300 Subject: [PATCH 13/26] upd nfc OFW PR 4271 [ci skip] --- lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index cf942172b..c3dbe88d6 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -469,9 +469,8 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol instance->mfu_event.data->key_request_data.key; } } else { - FURI_LOG_D(TAG, "No key provided, skipping auth"); - instance->state = MfUltralightPollerStateReadPages; - return command; + // Fallback: use key from auth context (for sync poller compatibility) + instance->auth_context.tdes_key = instance->mfu_event.data->auth_context.tdes_key; } instance->auth_context.auth_success = false; // For debugging From 372681553a05f734d9f505365c8f3ec3728a513e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 28 Sep 2025 01:55:35 +0300 Subject: [PATCH 14/26] remove honeywellsec for now [ci skip] TODO: make proper decoder, current works very unstable, same goes for encoder, current doesnt act as original signal, having no "gap" between packets --- lib/subghz/protocols/honeywell.c | 371 -------------------------- lib/subghz/protocols/honeywell.h | 61 ----- lib/subghz/protocols/protocol_items.c | 1 - lib/subghz/protocols/protocol_items.h | 1 - 4 files changed, 434 deletions(-) delete mode 100644 lib/subghz/protocols/honeywell.c delete mode 100644 lib/subghz/protocols/honeywell.h diff --git a/lib/subghz/protocols/honeywell.c b/lib/subghz/protocols/honeywell.c deleted file mode 100644 index 8d8dc22d4..000000000 --- a/lib/subghz/protocols/honeywell.c +++ /dev/null @@ -1,371 +0,0 @@ -#include "honeywell.h" -#include - -//Created by HTotoo 2023-10-30 -//Got a lot of help from LiQuiDz. -//Protocol decoding help from: https://github.com/merbanan/rtl_433/blob/master/src/devices/honeywell.c - -/* -64 bit packets, repeated multiple times per open/close event. - -Protocol whitepaper: "DEFCON 22: Home Insecurity" by Logan Lamb. - -Data layout: - - PP PP C IIIII EE SS SS - -- P: 16bit Preamble and sync bit (always ff fe) -- C: 4bit Channel -- I: 20bit Device serial number / or counter value -- E: 8bit Event, where 0x80 = Open/Close, 0x04 = Heartbeat / or id -- S: 16bit CRC -*/ - -#define TAG "SubGhzProtocolHoneywell" - -uint16_t subghz_protocol_honeywell_crc16( - uint8_t const message[], - unsigned nBytes, - uint16_t polynomial, - uint16_t init) { - uint16_t remainder = init; - unsigned byte, bit; - - for(byte = 0; byte < nBytes; ++byte) { - remainder ^= message[byte] << 8; - for(bit = 0; bit < 8; ++bit) { - if(remainder & 0x8000) { - remainder = (remainder << 1) ^ polynomial; - } else { - remainder = (remainder << 1); - } - } - } - return remainder; -} - -void subghz_protocol_decoder_honeywell_free(void* context) { - furi_assert(context); - SubGhzProtocolDecoderHoneywell* instance = context; - free(instance); -} - -void subghz_protocol_decoder_honeywell_reset(void* context) { - furi_assert(context); - SubGhzProtocolDecoderHoneywell* instance = context; - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; -} - -void subghz_protocol_decoder_honeywell_addbit(void* context, bool data) { - SubGhzProtocolDecoderHoneywell* instance = context; - instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data; - instance->decoder.decode_count_bit++; - - if(instance->decoder.decode_count_bit < 62) return; - - uint16_t preamble = (instance->decoder.decode_data >> 48) & 0xFFFF; - //can be multiple, since flipper can't read it well.. - if(preamble == 0b0011111111111110 || preamble == 0b0111111111111110 || - preamble == 0b1111111111111110) { - uint8_t datatocrc[4]; - datatocrc[0] = (instance->decoder.decode_data >> 40) & 0xFFFF; - datatocrc[1] = (instance->decoder.decode_data >> 32) & 0xFFFF; - datatocrc[2] = (instance->decoder.decode_data >> 24) & 0xFFFF; - datatocrc[3] = (instance->decoder.decode_data >> 16) & 0xFFFF; - uint8_t channel = (instance->decoder.decode_data >> 44) & 0xF; - uint16_t crc_calc = 0; - if(channel == 0x2 || channel == 0x4 || channel == 0xA) { - // 2GIG brand - crc_calc = subghz_protocol_honeywell_crc16(datatocrc, 4, 0x8050, 0); - } else if(channel == 0x8) { - crc_calc = subghz_protocol_honeywell_crc16(datatocrc, 4, 0x8005, 0); - } else { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - return; - } - uint16_t crc = instance->decoder.decode_data & 0xFFFF; - if(crc == crc_calc) { - //the data is good. process it. - instance->generic.data = instance->decoder.decode_data; - instance->generic.data_count_bit = - instance->decoder - .decode_count_bit; //maybe set it to 64, and hack the first 2 bits to 1! will see if replay needs it - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - } else { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - return; - } - } else if(instance->decoder.decode_count_bit >= 64) { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - return; - } -} - -void subghz_protocol_decoder_honeywell_feed(void* context, bool level, uint32_t duration) { - furi_assert(context); - SubGhzProtocolDecoderHoneywell* instance = context; - - ManchesterEvent event = ManchesterEventReset; - if(!level) { - if(DURATION_DIFF(duration, subghz_protocol_honeywell_const.te_short) < - subghz_protocol_honeywell_const.te_delta) { - event = ManchesterEventShortLow; - } else if( - DURATION_DIFF(duration, subghz_protocol_honeywell_const.te_long) < - subghz_protocol_honeywell_const.te_delta * 2) { - event = ManchesterEventLongLow; - } - } else { - if(DURATION_DIFF(duration, subghz_protocol_honeywell_const.te_short) < - subghz_protocol_honeywell_const.te_delta) { - event = ManchesterEventShortHigh; - } else if( - DURATION_DIFF(duration, subghz_protocol_honeywell_const.te_long) < - subghz_protocol_honeywell_const.te_delta * 2) { - event = ManchesterEventLongHigh; - } - } - if(event != ManchesterEventReset) { - bool data; - bool data_ok = manchester_advance( - instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); - if(data_ok) { - subghz_protocol_decoder_honeywell_addbit(instance, data); - } - } else { - instance->decoder.decode_data = 0; - instance->decoder.decode_count_bit = 0; - } -} - -uint8_t subghz_protocol_decoder_honeywell_get_hash_data(void* context) { - furi_assert(context); - SubGhzProtocolDecoderHoneywell* instance = context; - return subghz_protocol_blocks_get_hash_data( - &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); -} - -SubGhzProtocolStatus subghz_protocol_decoder_honeywell_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset) { - furi_assert(context); - SubGhzProtocolDecoderHoneywell* instance = context; - return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); -} - -SubGhzProtocolStatus - subghz_protocol_decoder_honeywell_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - SubGhzProtocolDecoderHoneywell* instance = context; - return subghz_block_generic_deserialize_check_count_bit( - &instance->generic, - flipper_format, - subghz_protocol_honeywell_const.min_count_bit_for_found); -} - -void subghz_protocol_decoder_honeywell_get_string(void* context, FuriString* output) { - furi_assert(context); - SubGhzProtocolDecoderHoneywell* instance = context; - - // Parse here and not in decode to avoid visual glitches when loading from file - instance->generic.serial = (instance->generic.data >> 24) & 0xFFFFF; - instance->generic.btn = (instance->generic.data >> 16) & - 0xFF; //not exactly button, but can contain btn data too. - - uint8_t channel = (instance->generic.data >> 44) & 0xF; - uint8_t contact = (instance->generic.btn & 0x80) >> 7; - uint8_t tamper = (instance->generic.btn & 0x40) >> 6; - uint8_t reed = (instance->generic.btn & 0x20) >> 5; - uint8_t alarm = (instance->generic.btn & 0x10) >> 4; - uint8_t battery_low = (instance->generic.btn & 0x08) >> 3; - uint8_t heartbeat = (instance->generic.btn & 0x04) >> 2; - - furi_string_cat_printf( - output, - "%s\r\n%dbit " - "Sn:%07lu\r\nCh:%u Bat:%d Hb: %d\r\n" - "L1: %u, L2: %u, L3: %u, L4: %u\r\n", - instance->generic.protocol_name, - instance->generic.data_count_bit, - instance->generic.serial, - channel, - battery_low, - heartbeat, - contact, - reed, - alarm, - tamper); -} - -void* subghz_protocol_decoder_honeywell_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - SubGhzProtocolDecoderHoneywell* instance = malloc(sizeof(SubGhzProtocolDecoderHoneywell)); - instance->base.protocol = &subghz_protocol_honeywell; - instance->generic.protocol_name = instance->base.protocol->name; - return instance; -} - -void* subghz_protocol_encoder_honeywell_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - SubGhzProtocolEncoderHoneywell* instance = malloc(sizeof(SubGhzProtocolEncoderHoneywell)); - - instance->base.protocol = &subghz_protocol_honeywell; - instance->generic.protocol_name = instance->base.protocol->name; - - instance->encoder.repeat = 3; - instance->encoder.size_upload = 64 * 2 + 10; - instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); - instance->encoder.is_running = false; - return instance; -} - -void subghz_protocol_encoder_honeywell_free(void* context) { - furi_assert(context); - SubGhzProtocolEncoderHoneywell* instance = context; - free(instance->encoder.upload); - free(instance); -} -static LevelDuration - subghz_protocol_encoder_honeywell_add_duration_to_upload(ManchesterEncoderResult result) { - LevelDuration data = {.duration = 0, .level = 0}; - switch(result) { - case ManchesterEncoderResultShortLow: - data.duration = subghz_protocol_honeywell_const.te_short; - data.level = false; - break; - case ManchesterEncoderResultLongLow: - data.duration = subghz_protocol_honeywell_const.te_long; - data.level = false; - break; - case ManchesterEncoderResultLongHigh: - data.duration = subghz_protocol_honeywell_const.te_long; - data.level = true; - break; - case ManchesterEncoderResultShortHigh: - data.duration = subghz_protocol_honeywell_const.te_short; - data.level = true; - break; - - default: - furi_crash("SubGhz: ManchesterEncoderResult is incorrect."); - break; - } - return level_duration_make(data.level, data.duration); -} - -static void - subghz_protocol_encoder_honeywell_get_upload(SubGhzProtocolEncoderHoneywell* instance) { - furi_assert(instance); - size_t index = 0; - - ManchesterEncoderState enc_state; - manchester_encoder_reset(&enc_state); - ManchesterEncoderResult result; - - for(uint8_t i = 63; i > 0; i--) { - if(!manchester_encoder_advance( - &enc_state, bit_read(instance->generic.data, i - 1), &result)) { - instance->encoder.upload[index++] = - subghz_protocol_encoder_honeywell_add_duration_to_upload(result); - manchester_encoder_advance( - &enc_state, bit_read(instance->generic.data, i - 1), &result); - } - instance->encoder.upload[index++] = - subghz_protocol_encoder_honeywell_add_duration_to_upload(result); - } - instance->encoder.upload[index] = subghz_protocol_encoder_honeywell_add_duration_to_upload( - manchester_encoder_finish(&enc_state)); - if(level_duration_get_level(instance->encoder.upload[index])) { - index++; - } - instance->encoder.size_upload = index; -} - -SubGhzProtocolStatus - subghz_protocol_encoder_honeywell_deserialize(void* context, FlipperFormat* flipper_format) { - furi_assert(context); - SubGhzProtocolEncoderHoneywell* instance = context; - SubGhzProtocolStatus res = SubGhzProtocolStatusError; - do { - if(SubGhzProtocolStatusOk != - subghz_block_generic_deserialize(&instance->generic, flipper_format)) { - FURI_LOG_E(TAG, "Deserialize error"); - break; - } - - //optional parameter parameter - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - - subghz_protocol_encoder_honeywell_get_upload(instance); - - if(!flipper_format_rewind(flipper_format)) { - FURI_LOG_E(TAG, "Rewind error"); - break; - } - - instance->encoder.is_running = true; - - res = SubGhzProtocolStatusOk; - } while(false); - - return res; -} - -void subghz_protocol_encoder_honeywell_stop(void* context) { - SubGhzProtocolEncoderHoneywell* instance = context; - instance->encoder.is_running = false; -} - -LevelDuration subghz_protocol_encoder_honeywell_yield(void* context) { - SubGhzProtocolEncoderHoneywell* instance = context; - - if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { - instance->encoder.is_running = false; - return level_duration_reset(); - } - LevelDuration ret = instance->encoder.upload[instance->encoder.front]; - if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; - instance->encoder.front = 0; - } - return ret; -} - -const SubGhzProtocolDecoder subghz_protocol_honeywell_decoder = { - .alloc = subghz_protocol_decoder_honeywell_alloc, - .free = subghz_protocol_decoder_honeywell_free, - .feed = subghz_protocol_decoder_honeywell_feed, - .reset = subghz_protocol_decoder_honeywell_reset, - .get_hash_data = subghz_protocol_decoder_honeywell_get_hash_data, - .serialize = subghz_protocol_decoder_honeywell_serialize, - .deserialize = subghz_protocol_decoder_honeywell_deserialize, - .get_string = subghz_protocol_decoder_honeywell_get_string, -}; - -const SubGhzProtocolEncoder subghz_protocol_honeywell_encoder = { - .alloc = subghz_protocol_encoder_honeywell_alloc, - .free = subghz_protocol_encoder_honeywell_free, - .deserialize = subghz_protocol_encoder_honeywell_deserialize, - .stop = subghz_protocol_encoder_honeywell_stop, - .yield = subghz_protocol_encoder_honeywell_yield, -}; - -const SubGhzProtocol subghz_protocol_honeywell = { - .name = SUBGHZ_PROTOCOL_HONEYWELL_NAME, - .type = SubGhzProtocolTypeStatic, - .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | - SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | - SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send | SubGhzProtocolFlag_Sensors, - .encoder = &subghz_protocol_honeywell_encoder, - .decoder = &subghz_protocol_honeywell_decoder, - -}; diff --git a/lib/subghz/protocols/honeywell.h b/lib/subghz/protocols/honeywell.h deleted file mode 100644 index 8aa35ce6c..000000000 --- a/lib/subghz/protocols/honeywell.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -#include - -#include -#include -#include -#include "base.h" -#include "../blocks/generic.h" -#include -#include -#include - -#define SUBGHZ_PROTOCOL_HONEYWELL_NAME "Honeywell Sec" - -typedef struct SubGhzProtocolDecoderHoneywell SubGhzProtocolDecoderHoneywell; -typedef struct SubGhzProtocolEncoderHoneywell SubGhzProtocolEncoderHoneywell; - -extern const SubGhzProtocolDecoder subghz_protocol_honeywell_decoder; -extern const SubGhzProtocolEncoder subghz_protocol_honeywell_encoder; -extern const SubGhzProtocol subghz_protocol_honeywell; - -void* subghz_protocol_decoder_honeywell_alloc(SubGhzEnvironment* environment); - -void subghz_protocol_decoder_honeywell_free(void* context); - -void subghz_protocol_decoder_honeywell_reset(void* context); - -void subghz_protocol_decoder_honeywell_feed(void* context, bool level, uint32_t duration); - -uint8_t subghz_protocol_decoder_honeywell_get_hash_data(void* context); - -SubGhzProtocolStatus subghz_protocol_decoder_honeywell_serialize( - void* context, - FlipperFormat* flipper_format, - SubGhzRadioPreset* preset); - -SubGhzProtocolStatus - subghz_protocol_decoder_honeywell_deserialize(void* context, FlipperFormat* flipper_format); - -void subghz_protocol_decoder_honeywell_get_string(void* context, FuriString* output); - -static const SubGhzBlockConst subghz_protocol_honeywell_const = { - .te_long = 280, - .te_short = 143, - .te_delta = 51, - .min_count_bit_for_found = 62, -}; - -struct SubGhzProtocolDecoderHoneywell { - SubGhzProtocolDecoderBase base; - SubGhzBlockGeneric generic; - SubGhzBlockDecoder decoder; - ManchesterState manchester_saved_state; -}; - -struct SubGhzProtocolEncoderHoneywell { - SubGhzProtocolEncoderBase base; - SubGhzBlockGeneric generic; - SubGhzProtocolBlockEncoder encoder; -}; diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 465585d77..91ceaec32 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -44,7 +44,6 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { &subghz_protocol_kinggates_stylo_4k, &subghz_protocol_bin_raw, &subghz_protocol_mastercode, - &subghz_protocol_honeywell, &subghz_protocol_legrand, &subghz_protocol_dickert_mahs, &subghz_protocol_gangqi, diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 4f63b030e..1cde46ef5 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -45,7 +45,6 @@ #include "kinggates_stylo_4k.h" #include "bin_raw.h" #include "mastercode.h" -#include "honeywell.h" #include "legrand.h" #include "dickert_mahs.h" #include "gangqi.h" From 2dd6b5cdce81d5cc37c7457f7acfb96a6904489e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 28 Sep 2025 01:57:04 +0300 Subject: [PATCH 15/26] format [ci skip] --- applications/system/js_app/js_modules.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/system/js_app/js_modules.h b/applications/system/js_app/js_modules.h index c19d4805c..1a4cd8f20 100644 --- a/applications/system/js_app/js_modules.h +++ b/applications/system/js_app/js_modules.h @@ -16,8 +16,8 @@ extern "C" { #define JS_SDK_VENDOR_FIRMWARE "unleashed" #define JS_SDK_VENDOR "flipperdevices" -#define JS_SDK_MAJOR 1 -#define JS_SDK_MINOR 0 +#define JS_SDK_MAJOR 1 +#define JS_SDK_MINOR 0 /** * @brief Returns the foreign pointer in `obj["_"]` From 1e431422e7d3baed4a2dd0dbd8733e6978024d23 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 28 Sep 2025 02:11:06 +0300 Subject: [PATCH 16/26] smol fixes [ci skip] --- applications/main/subghz/helpers/subghz_gen_info.c | 1 + lib/subghz/protocols/gangqi.c | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_gen_info.c b/applications/main/subghz/helpers/subghz_gen_info.c index 69fa37187..459f600ca 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.c +++ b/applications/main/subghz/helpers/subghz_gen_info.c @@ -1,6 +1,7 @@ #include "subghz_gen_info.h" #include "../helpers/subghz_txrx_create_protocol_key.h" #include +#include void subghz_gen_info_reset(GenInfo* gen_info) { furi_assert(gen_info); diff --git a/lib/subghz/protocols/gangqi.c b/lib/subghz/protocols/gangqi.c index 905c22982..a079a41bd 100644 --- a/lib/subghz/protocols/gangqi.c +++ b/lib/subghz/protocols/gangqi.c @@ -363,19 +363,15 @@ void subghz_protocol_decoder_gangqi_feed(void* context, bool level, volatile uin instance->decoder.parser_step = GangQiDecoderStepSaveDuration; } else if( // End of the key - DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_short * 4) < - subghz_protocol_gangqi_const.te_delta) { + (DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long * 2) < + subghz_protocol_gangqi_const.te_delta * 3)) { //Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes) if((DURATION_DIFF( instance->decoder.te_last, subghz_protocol_gangqi_const.te_short) < - subghz_protocol_gangqi_const.te_delta) && - (DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_short * 4) < subghz_protocol_gangqi_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 0); } if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_gangqi_const.te_long) < - subghz_protocol_gangqi_const.te_delta) && - (DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_short * 4) < subghz_protocol_gangqi_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 1); } From a357dfd1adf9e4a5f49ce39a55d10d169f472c85 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 28 Sep 2025 04:50:50 +0300 Subject: [PATCH 17/26] upd changelog --- CHANGELOG.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ac4e782c..7f3e3e1ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,14 @@ ## Main changes -- Current API: 86.0 -* SubGHz: Tune Linear (edited by @WillyJL in PR #919 #920) (add better EZCode support), Dickert MAHS decoders +- Current API: 87.0 +* SubGHz: Tune Linear (edited by @WillyJL in PR #919 #920) (add better EZCode support) and Dickert MAHS protocol decoders * SubGHz: RAW protocol fixes (by @WillyJL) * SubGHz: Add ZKTeco 430.5 MHz add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) +* SubGHz: Temporarily remove HoneywellSec protocol due to unstable decoding and incorrect encoding +* OFW: LFRFID: Show ISO-3166 Country Names For Pet Chips +* OFW: JS views finished +* OFW: BLE: improved pairing security +* OFW: FeliCa Emulation: Handle certain Polling commands in firmware * OFW PR 4271: NFC: Ultralight C NFC App Key Management, Dictionary Attack (by @noproto) * OFW PR 4265: NFC: Fix read crash with unexpectedly large MFC AUTH(0) response (by @WillyJL) * OFW PR 4251: CLI: Fix long delay with quick connect/disconnect (by @WillyJL) @@ -11,6 +16,11 @@ * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * SubGHz: Fix crash in add manually menu +* OFW: Update demo_windows.txt +* OFW: Fix PVS warnings +* OFW: NFC: Amusement IC Card Parser (FeliCa Lite & Lite-S) +* OFW: hid_app mouse clicker: make mouse button selectable +* OFW: JS: Expose button event type in gui/widget button callback * OFW: NFC: MFC 1k Banapass Parser * OFW: GUI Bug Fix: Number Input Save Icon * Add possibility to use custom buttons when using the SubGHz remote app (by @MrLego8-9) From 3e45fce96d25c55bb4565d9a5d99a42fa771408b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 30 Sep 2025 02:07:29 +0300 Subject: [PATCH 18/26] Try to decode BFT 2 buttons remotes on the fly --- lib/subghz/protocols/keeloq.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index e1ccf8c2f..921c3fadc 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -858,6 +858,11 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( } break; case KEELOQ_LEARNING_SECURE: + if((strcmp(furi_string_get_cstr(manufacture_code->name), "BFT") == 0)) { + if(instance->seed == 0) { + instance->seed = (fix & 0xFFFFFFF); + } + } man = subghz_protocol_keeloq_common_secure_learning( fix, instance->seed, manufacture_code->key); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); From 999afe3f5b7d7a40f645173e8df87f8e6f66bde0 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 30 Sep 2025 02:16:07 +0300 Subject: [PATCH 19/26] post merge fix --- .../main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c | 2 +- applications/system/mfkey/mfkey.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c index aa4c2fe1c..63fdaed49 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_keys_add.c @@ -41,7 +41,7 @@ bool nfc_scene_mf_ultralight_c_keys_add_on_event(void* context, SceneManagerEven instance->scene_manager, NfcSceneMfUltralightCKeysWarnDuplicate); } else if(keys_dict_add_key(dict, key.data, sizeof(MfUltralightC3DesAuthKey))) { scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); - dolphin_deed(DolphinDeedNfcMfcAdd); + dolphin_deed(DolphinDeedNfcKeyAdd); } else { scene_manager_previous_scene(instance->scene_manager); } diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index 4d55a5b03..9e3e71847 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -955,7 +955,7 @@ void mfkey(ProgramState* program_state) { keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); } if(keyarray_size > 0) { - dolphin_deed(DolphinDeedNfcMfcAdd); + dolphin_deed(DolphinDeedNfcKeyAdd); } free(nonce_arr); keys_dict_free(user_dict); From a4f5f1e862c5b7adfb9ff7dfa84481e63f53d7af Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 30 Sep 2025 02:49:51 +0300 Subject: [PATCH 20/26] add elplast subghz protocol --- lib/subghz/protocols/elplast.c | 322 ++++++++++++++++++++++++++ lib/subghz/protocols/elplast.h | 109 +++++++++ lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + 4 files changed, 433 insertions(+) create mode 100644 lib/subghz/protocols/elplast.c create mode 100644 lib/subghz/protocols/elplast.h diff --git a/lib/subghz/protocols/elplast.c b/lib/subghz/protocols/elplast.c new file mode 100644 index 000000000..c95eb84ba --- /dev/null +++ b/lib/subghz/protocols/elplast.c @@ -0,0 +1,322 @@ +#include "elplast.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolElplast" + +static const SubGhzBlockConst subghz_protocol_elplast_const = { + .te_short = 230, + .te_long = 1550, + .te_delta = 160, + .min_count_bit_for_found = 18, +}; + +struct SubGhzProtocolDecoderElplast { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderElplast { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + ElplastDecoderStepReset = 0, + ElplastDecoderStepSaveDuration, + ElplastDecoderStepCheckDuration, +} ElplastDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_elplast_decoder = { + .alloc = subghz_protocol_decoder_elplast_alloc, + .free = subghz_protocol_decoder_elplast_free, + + .feed = subghz_protocol_decoder_elplast_feed, + .reset = subghz_protocol_decoder_elplast_reset, + + .get_hash_data = subghz_protocol_decoder_elplast_get_hash_data, + .serialize = subghz_protocol_decoder_elplast_serialize, + .deserialize = subghz_protocol_decoder_elplast_deserialize, + .get_string = subghz_protocol_decoder_elplast_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_elplast_encoder = { + .alloc = subghz_protocol_encoder_elplast_alloc, + .free = subghz_protocol_encoder_elplast_free, + + .deserialize = subghz_protocol_encoder_elplast_deserialize, + .stop = subghz_protocol_encoder_elplast_stop, + .yield = subghz_protocol_encoder_elplast_yield, +}; + +const SubGhzProtocol subghz_protocol_elplast = { + .name = SUBGHZ_PROTOCOL_ELPLAST_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_elplast_decoder, + .encoder = &subghz_protocol_elplast_encoder, +}; + +void* subghz_protocol_encoder_elplast_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderElplast* instance = malloc(sizeof(SubGhzProtocolEncoderElplast)); + + instance->base.protocol = &subghz_protocol_elplast; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_elplast_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderElplast* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderElplast instance + */ +static void subghz_protocol_encoder_elplast_get_upload(SubGhzProtocolEncoderElplast* instance) { + furi_assert(instance); + size_t index = 0; + + // Send key and GAP + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // Send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_elplast_const.te_long); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_elplast_const.te_long * 8); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_elplast_const.te_short); + } + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_elplast_const.te_short); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_elplast_const.te_long * 8); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_elplast_const.te_long); + } + } + } + + instance->encoder.size_upload = index; + return; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_elplast_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderElplast* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_elplast_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_elplast_get_upload(instance); + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_elplast_stop(void* context) { + SubGhzProtocolEncoderElplast* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_elplast_yield(void* context) { + SubGhzProtocolEncoderElplast* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_elplast_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderElplast* instance = malloc(sizeof(SubGhzProtocolDecoderElplast)); + instance->base.protocol = &subghz_protocol_elplast; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_elplast_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderElplast* instance = context; + free(instance); +} + +void subghz_protocol_decoder_elplast_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderElplast* instance = context; + instance->decoder.parser_step = ElplastDecoderStepReset; +} + +void subghz_protocol_decoder_elplast_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderElplast* instance = context; + + // Elplast/P-11B/3BK/E.C.A Decoder + // 2025.09 - @xMasterX (MMX) + + // Key samples + // 00110010110000001010 = 32C0A + // 00110010110010000010 = 32C82 + + switch(instance->decoder.parser_step) { + case ElplastDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_elplast_const.te_long * 8) < + subghz_protocol_elplast_const.te_delta * 13)) { + //Found GAP + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = ElplastDecoderStepSaveDuration; + } + break; + case ElplastDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = ElplastDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = ElplastDecoderStepReset; + } + break; + case ElplastDecoderStepCheckDuration: + if(!level) { + // Bit 1 is long and short timing = 1550us HIGH (te_last) and 230us LOW + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_elplast_const.te_long) < + subghz_protocol_elplast_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_elplast_const.te_short) < + subghz_protocol_elplast_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = ElplastDecoderStepSaveDuration; + // Bit 0 is short and long timing = 230us HIGH (te_last) and 1550us LOW + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_elplast_const.te_short) < + subghz_protocol_elplast_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_elplast_const.te_long) < + subghz_protocol_elplast_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = ElplastDecoderStepSaveDuration; + } else if( + // End of the key + DURATION_DIFF(duration, subghz_protocol_elplast_const.te_long * 8) < + subghz_protocol_elplast_const.te_delta * 13) { + //Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes) + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_elplast_const.te_long) < + subghz_protocol_elplast_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_elplast_const.te_short) < + subghz_protocol_elplast_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + // If got 18 bits key reading is finished + if(instance->decoder.decode_count_bit == + subghz_protocol_elplast_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = ElplastDecoderStepReset; + } else { + instance->decoder.parser_step = ElplastDecoderStepReset; + } + } else { + instance->decoder.parser_step = ElplastDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_elplast_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderElplast* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_elplast_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderElplast* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_elplast_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderElplast* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_elplast_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_elplast_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderElplast* instance = context; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + uint32_t code_found_reverse_lo = code_found_reverse & 0x000003ffffffffff; + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key: 0x%05lX\r\n" + "Yek: 0x%05lX", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFF), + code_found_reverse_lo); +} diff --git a/lib/subghz/protocols/elplast.h b/lib/subghz/protocols/elplast.h new file mode 100644 index 000000000..3f5f34002 --- /dev/null +++ b/lib/subghz/protocols/elplast.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_ELPLAST_NAME "Elplast" + +typedef struct SubGhzProtocolDecoderElplast SubGhzProtocolDecoderElplast; +typedef struct SubGhzProtocolEncoderElplast SubGhzProtocolEncoderElplast; + +extern const SubGhzProtocolDecoder subghz_protocol_elplast_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_elplast_encoder; +extern const SubGhzProtocol subghz_protocol_elplast; + +/** + * Allocate SubGhzProtocolEncoderElplast. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderElplast* pointer to a SubGhzProtocolEncoderElplast instance + */ +void* subghz_protocol_encoder_elplast_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderElplast. + * @param context Pointer to a SubGhzProtocolEncoderElplast instance + */ +void subghz_protocol_encoder_elplast_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderElplast instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_elplast_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderElplast instance + */ +void subghz_protocol_encoder_elplast_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderElplast instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_elplast_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderElplast. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderElplast* pointer to a SubGhzProtocolDecoderElplast instance + */ +void* subghz_protocol_decoder_elplast_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderElplast. + * @param context Pointer to a SubGhzProtocolDecoderElplast instance + */ +void subghz_protocol_decoder_elplast_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderElplast. + * @param context Pointer to a SubGhzProtocolDecoderElplast instance + */ +void subghz_protocol_decoder_elplast_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderElplast instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_elplast_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderElplast instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_elplast_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderElplast. + * @param context Pointer to a SubGhzProtocolDecoderElplast instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_elplast_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderElplast. + * @param context Pointer to a SubGhzProtocolDecoderElplast instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_elplast_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderElplast instance + * @param output Resulting text + */ +void subghz_protocol_decoder_elplast_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 91ceaec32..27297f293 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -53,6 +53,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { &subghz_protocol_revers_rb2, &subghz_protocol_feron, &subghz_protocol_roger, + &subghz_protocol_elplast, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 1cde46ef5..ab7fd14f8 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -54,3 +54,4 @@ #include "revers_rb2.h" #include "feron.h" #include "roger.h" +#include "elplast.h" From 5a8a0dff5e9db128c7096ae076183b69a02f6f06 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 30 Sep 2025 02:54:11 +0300 Subject: [PATCH 21/26] if got no luck with seed set it back to zero --- lib/subghz/protocols/keeloq.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 921c3fadc..e9542eb34 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -858,9 +858,11 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( } break; case KEELOQ_LEARNING_SECURE: + bool reset_seed_back = false; if((strcmp(furi_string_get_cstr(manufacture_code->name), "BFT") == 0)) { if(instance->seed == 0) { instance->seed = (fix & 0xFFFFFFF); + reset_seed_back = true; } } man = subghz_protocol_keeloq_common_secure_learning( @@ -870,6 +872,8 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; return 1; + } else { + if(reset_seed_back) instance->seed = 0; } break; case KEELOQ_LEARNING_MAGIC_XOR_TYPE_1: From fdf532066ff1d990019e34e59e0f238f5e4817d5 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 30 Sep 2025 03:03:16 +0300 Subject: [PATCH 22/26] upd changelog --- CHANGELOG.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f3e3e1ac..d6654804a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,25 @@ ## Main changes - Current API: 87.0 -* SubGHz: Tune Linear (edited by @WillyJL in PR #919 #920) (add better EZCode support) and Dickert MAHS protocol decoders +* SubGHz: Add **Elplast 18bit** static code protocol (hello Hackcat ^_^) +* SubGHz: Try to **decode BFT** (2 buttons remotes only) **on the fly** in regular Read mode (no more KL Unknown and all of that for free?!) (for 4 button remote follow docs [here](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzRemoteProg.md)) +* SubGHz: **Tune Linear** (edited by @WillyJL in PR #919 #920) (add better EZCode support) and **Dickert MAHS** protocol decoders * SubGHz: RAW protocol fixes (by @WillyJL) -* SubGHz: Add ZKTeco 430.5 MHz add manually support +* SubGHz: Add **ZKTeco 430.5 MHz** add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) * SubGHz: Temporarily remove HoneywellSec protocol due to unstable decoding and incorrect encoding -* OFW: LFRFID: Show ISO-3166 Country Names For Pet Chips -* OFW: JS views finished +* OFW: **NFC CLI commands** +* OFW: LFRFID: **Show ISO-3166 Country Names For Pet Chips** +* OFW: **JS views finished** * OFW: BLE: improved pairing security * OFW: FeliCa Emulation: Handle certain Polling commands in firmware -* OFW PR 4271: NFC: Ultralight C NFC App Key Management, Dictionary Attack (by @noproto) -* OFW PR 4265: NFC: Fix read crash with unexpectedly large MFC AUTH(0) response (by @WillyJL) -* OFW PR 4251: CLI: Fix long delay with quick connect/disconnect (by @WillyJL) -* LFRFID: Add additional procotols supported by EM4305 chipset (by @jamisonderek) +* OFW PR 4271: NFC: **Ultralight C NFC App Key Management, Dictionary Attack** (by @noproto) +* OFW PR 4265: NFC: **Fix read crash** with unexpectedly large MFC AUTH(0) response (by @WillyJL) +* OFW PR 4251: CLI: **Fix long delay** with quick connect/disconnect (by @WillyJL) +* LFRFID: Add additional procotols supported by **EM4305** chipset (by @jamisonderek) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * SubGHz: Fix crash in add manually menu +* OFW: cli: Buzzer command * OFW: Update demo_windows.txt * OFW: Fix PVS warnings * OFW: NFC: Amusement IC Card Parser (FeliCa Lite & Lite-S) @@ -32,7 +36,7 @@

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) -- NFC CLI was removed with refactoring (OFW) (will be back soon) +- While reading some EMV capable cards via NFC->Read flipper may crash due to Desfire poller issue, read those cards via Extra actions->Read specific card type->EMV ---- From b604514a2b74dbdb32e95bf301df1aceafef5589 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 30 Sep 2025 03:30:45 +0300 Subject: [PATCH 23/26] upd readme --- ReadMe.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index eaa221ea7..5e8903f9c 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -54,6 +54,7 @@ Before getting started: > Sub‑GHz Library & HAL >
> +> - Many new protocols added > - Regional TX restrictions removed > - Extra Sub-GHz frequencies added > - Frequency range can be extended in settings file _(warning: It can damage Flipper's hardware)_ @@ -80,7 +81,7 @@ Before getting started: > - FAAC SLH, BFT Mitto / Somfy Telis / Nice Flor S / CAME Atomo, etc. manual creation with programming new remote into receiver (use button 0xF for BFT Mitto, 0x8 (Prog) on Somfy Telis, (right arrow button for other protocols)) > - Debug mode counter increase settings (+1 → +5, +10, default: +1) > - Debug PIN output settings for protocol development -> - Ignore options - Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights) +> - Ignore options - Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights) > >
@@ -167,9 +168,10 @@ Thanks to Official team (to their SubGHz Developer, Skorp) for implementing supp Decoders/Encoders or emulation (+ programming mode) support made by @xMasterX
-- Roger (static 28 bit) with add manually support (by @xMasterX & @mishamyte) -- V2 Phoenix (Phox) (dynamic 52 bit) (by @xMasterX & @RocketGod-git) -- Marantec (static 49 bit) (add manually support and CRC verify) (by @xMasterX & @li0ard) +- Elplast/P-11B/3BK/E.C.A (static 18 bit) +- Roger (static 28 bit) with add manually support (thanks @mishamyte) +- V2 Phoenix (Phox) (dynamic 52 bit) (thanks @RocketGod-git) +- Marantec (static 49 bit) (add manually support and CRC verify) (thanks @li0ard) - Feron (static 32 bit) - ReversRB2 / RB2M (static 64 bit) with add manually support - Marantec24 (static 24 bit) with add manually support @@ -208,10 +210,14 @@ Thanks to Official team (to their SubGHz Developer, Skorp) for implementing supp ## ❤️ Please support development of the project The majority of this project is developed and maintained by me, @xMasterX. -Our team is small and the guys are working on this project as much as they can solely based on the enthusiasm they have for this project and the community. -- `@Leptopt1los` - NFC, RFID, Plugins, and many other things +Our team is small and the guys are working on this project as much as they can solely based on the enthusiasm they have for this project and the community. +- `@mishamyte` - NFC, RFID, SubGHz and chats moderation +- `@quen0n` - Hardware, SubGHz and chats moderation +- `@Drone1950` - Reverse Engineering, telegram bot and chats moderation +- `@HackcatDev` - Support and chats moderation +- `@Leptopt1los` - NFC, RFID, Plugins, chat moderation and many other things - `@gid9798` - SubGHz, Plugins, many other things - currently offline :( -- `@assasinfil` - SubGHz protocols, NFC parsers +- `@assasinfil` - SubGHz protocols, NFC parsers, chat moderation - `@Svaarich` - UI design and animations - `@amec0e` - Infrared assets - Community moderators in Telegram, Discord, and Reddit From a5dedec00ab132025e3be51870459433aec3c187 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 1 Oct 2025 02:34:46 +0300 Subject: [PATCH 24/26] ensure correct byte shifts --- .../main/subghz/helpers/subghz_txrx_create_protocol_key.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c index 5a7e07e0e..783273e6b 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -422,7 +422,8 @@ void subghz_txrx_gen_serial_gangqi(uint64_t* result_key) { // Add bytesum to the end // serial | const_and_button - *result_key = (serial << 18) | (const_and_button << 10) | (bytesum << 2); + *result_key = ((uint64_t)serial << 18) | ((uint64_t)const_and_button << 10) | + ((uint64_t)bytesum << 2); } void subghz_txrx_gen_key_marantec(uint64_t* result_key) { From 7db92ed1f815f4a2c9d7cd0393a93e7fea8c5ba2 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 1 Oct 2025 03:00:43 +0300 Subject: [PATCH 25/26] decoders reset step at reset func --- lib/subghz/protocols/marantec.c | 1 + lib/subghz/protocols/revers_rb2.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lib/subghz/protocols/marantec.c b/lib/subghz/protocols/marantec.c index edb176635..8ca1f8b38 100644 --- a/lib/subghz/protocols/marantec.c +++ b/lib/subghz/protocols/marantec.c @@ -265,6 +265,7 @@ void subghz_protocol_decoder_marantec_free(void* context) { void subghz_protocol_decoder_marantec_reset(void* context) { furi_assert(context); SubGhzProtocolDecoderMarantec* instance = context; + instance->decoder.parser_step = MarantecDecoderStepReset; manchester_advance( instance->manchester_saved_state, ManchesterEventReset, diff --git a/lib/subghz/protocols/revers_rb2.c b/lib/subghz/protocols/revers_rb2.c index 510e2698a..e6524174f 100644 --- a/lib/subghz/protocols/revers_rb2.c +++ b/lib/subghz/protocols/revers_rb2.c @@ -227,6 +227,8 @@ void subghz_protocol_decoder_revers_rb2_free(void* context) { void subghz_protocol_decoder_revers_rb2_reset(void* context) { furi_assert(context); SubGhzProtocolDecoderRevers_RB2* instance = context; + instance->decoder.parser_step = Revers_RB2DecoderStepReset; + instance->header_count = 0; manchester_advance( instance->manchester_saved_state, ManchesterEventReset, From d79539c75c81c3bbca10dbb037096a8adf5233ec Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 1 Oct 2025 19:48:57 +0300 Subject: [PATCH 26/26] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6654804a..705689ae0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * SubGHz: Add **ZKTeco 430.5 MHz** add manually support * SubGHz: Add variant of 'Add Manually' menu with manual editing for each value (PR #909 #911 #914 | by @MrLego8-9) * SubGHz: Temporarily remove HoneywellSec protocol due to unstable decoding and incorrect encoding +* OFW: NFC FeliCa: Service Directory Traverse + Dump All Unencrypted-Readable Services' Blocks * OFW: **NFC CLI commands** * OFW: LFRFID: **Show ISO-3166 Country Names For Pet Chips** * OFW: **JS views finished**