mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2025-12-12 20:49:49 +04:00
integrate mfkey to system apps
This commit is contained in:
28
applications/system/mfkey/application.fam
Normal file
28
applications/system/mfkey/application.fam
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
App(
|
||||||
|
appid="mfkey",
|
||||||
|
name="MFKey",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
targets=["f7"],
|
||||||
|
entry_point="mfkey_main",
|
||||||
|
requires=[
|
||||||
|
"gui",
|
||||||
|
"storage",
|
||||||
|
],
|
||||||
|
stack_size=1 * 1024,
|
||||||
|
fap_icon="mfkey.png",
|
||||||
|
fap_category="NFC",
|
||||||
|
fap_author="@noproto",
|
||||||
|
fap_icon_assets="images",
|
||||||
|
fap_weburl="https://github.com/noproto/FlipperMfkey",
|
||||||
|
fap_description="MIFARE Classic key recovery tool",
|
||||||
|
fap_version="3.0",
|
||||||
|
)
|
||||||
|
|
||||||
|
App(
|
||||||
|
appid="mfkey_init_plugin",
|
||||||
|
apptype=FlipperAppType.PLUGIN,
|
||||||
|
entry_point="init_plugin_ep",
|
||||||
|
requires=["mfkey"],
|
||||||
|
sources=["init_plugin.c"],
|
||||||
|
fal_embedded=True,
|
||||||
|
)
|
||||||
22
applications/system/mfkey/crypto1.c
Normal file
22
applications/system/mfkey/crypto1.c
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#pragma GCC optimize("O3")
|
||||||
|
#pragma GCC optimize("-funroll-all-loops")
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include "crypto1.h"
|
||||||
|
#include "mfkey.h"
|
||||||
|
|
||||||
|
#define BIT(x, n) ((x) >> (n) & 1)
|
||||||
|
|
||||||
|
void crypto1_get_lfsr(struct Crypto1State* state, MfClassicKey* lfsr) {
|
||||||
|
int i;
|
||||||
|
uint64_t lfsr_value = 0;
|
||||||
|
for(i = 23; i >= 0; --i) {
|
||||||
|
lfsr_value = lfsr_value << 1 | BIT(state->odd, i ^ 3);
|
||||||
|
lfsr_value = lfsr_value << 1 | BIT(state->even, i ^ 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign the key value to the MfClassicKey struct
|
||||||
|
for(i = 0; i < 6; ++i) {
|
||||||
|
lfsr->data[i] = (lfsr_value >> ((5 - i) * 8)) & 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
256
applications/system/mfkey/crypto1.h
Normal file
256
applications/system/mfkey/crypto1.h
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
#ifndef CRYPTO1_H
|
||||||
|
#define CRYPTO1_H
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include "mfkey.h"
|
||||||
|
#include <nfc/helpers/nfc_util.h>
|
||||||
|
#include <nfc/protocols/mf_classic/mf_classic.h>
|
||||||
|
|
||||||
|
#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 SWAPENDIAN(x) \
|
||||||
|
((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);
|
||||||
|
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);
|
||||||
|
|
||||||
|
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};
|
||||||
|
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};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
#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 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 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 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
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
BIN
applications/system/mfkey/images/mfkey.png
Normal file
BIN
applications/system/mfkey/images/mfkey.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 107 B |
356
applications/system/mfkey/init_plugin.c
Normal file
356
applications/system/mfkey/init_plugin.c
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
#include <furi_hal.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <toolbox/keys_dict.h>
|
||||||
|
#include <bit_lib/bit_lib.h>
|
||||||
|
#include <toolbox/stream/buffered_file_stream.h>
|
||||||
|
#include <nfc/protocols/mf_classic/mf_classic.h>
|
||||||
|
#include "mfkey.h"
|
||||||
|
#include "crypto1.h"
|
||||||
|
#include "plugin_interface.h"
|
||||||
|
#include <flipper_application/flipper_application.h>
|
||||||
|
|
||||||
|
#define TAG "MFKey"
|
||||||
|
|
||||||
|
// TODO: Remove defines that are not needed
|
||||||
|
#define MF_CLASSIC_NONCE_PATH EXT_PATH("nfc/.mfkey32.log")
|
||||||
|
#define MF_CLASSIC_NESTED_NONCE_PATH EXT_PATH("nfc/.nested.log")
|
||||||
|
#define MAX_NAME_LEN 32
|
||||||
|
#define MAX_PATH_LEN 64
|
||||||
|
|
||||||
|
#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 SWAPENDIAN(x) \
|
||||||
|
((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16)
|
||||||
|
|
||||||
|
bool key_already_found_for_nonce_in_dict(KeysDict* dict, MfClassicNonce* nonce) {
|
||||||
|
// This function must not be passed the CUID dictionary
|
||||||
|
bool found = false;
|
||||||
|
uint8_t key_bytes[sizeof(MfClassicKey)];
|
||||||
|
keys_dict_rewind(dict);
|
||||||
|
while(keys_dict_get_next_key(dict, key_bytes, sizeof(MfClassicKey))) {
|
||||||
|
uint64_t k = bit_lib_bytes_to_num_be(key_bytes, sizeof(MfClassicKey));
|
||||||
|
struct Crypto1State temp = {0, 0};
|
||||||
|
for(int i = 0; i < 24; i++) {
|
||||||
|
(&temp)->odd |= (BIT(k, 2 * i + 1) << (i ^ 3));
|
||||||
|
(&temp)->even |= (BIT(k, 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)) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if(nonce->attack == static_nested || nonce->attack == static_encrypted) {
|
||||||
|
uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0);
|
||||||
|
if(nonce->ks1_1_enc == expected_ks1) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool napi_mf_classic_mfkey32_nonces_check_presence() {
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
|
||||||
|
bool nonces_present = storage_common_stat(storage, MF_CLASSIC_NONCE_PATH, NULL) == FSE_OK;
|
||||||
|
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
|
||||||
|
return nonces_present;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool napi_mf_classic_nested_nonces_check_presence() {
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
Stream* stream = buffered_file_stream_alloc(storage);
|
||||||
|
bool nonces_present = false;
|
||||||
|
FuriString* line = furi_string_alloc();
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(!buffered_file_stream_open(
|
||||||
|
stream, MF_CLASSIC_NESTED_NONCE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(stream_read_line(stream, line)) {
|
||||||
|
if(furi_string_search_str(line, "dist 0") != FURI_STRING_FAILURE) {
|
||||||
|
nonces_present = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
furi_string_free(line);
|
||||||
|
buffered_file_stream_close(stream);
|
||||||
|
stream_free(stream);
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
|
||||||
|
return nonces_present;
|
||||||
|
}
|
||||||
|
|
||||||
|
int binaryStringToInt(const char* binStr) {
|
||||||
|
int result = 0;
|
||||||
|
while(*binStr) {
|
||||||
|
result <<= 1;
|
||||||
|
if(*binStr == '1') {
|
||||||
|
result |= 1;
|
||||||
|
}
|
||||||
|
binStr++;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool load_mfkey32_nonces(
|
||||||
|
MfClassicNonceArray* nonce_array,
|
||||||
|
ProgramState* program_state,
|
||||||
|
KeysDict* system_dict,
|
||||||
|
bool system_dict_exists,
|
||||||
|
KeysDict* user_dict) {
|
||||||
|
bool array_loaded = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
// https://github.com/flipperdevices/flipperzero-firmware/blob/5134f44c09d39344a8747655c0d59864bb574b96/applications/services/storage/filesystem_api_defines.h#L8-L22
|
||||||
|
if(!buffered_file_stream_open(
|
||||||
|
nonce_array->stream, MF_CLASSIC_NONCE_PATH, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) {
|
||||||
|
buffered_file_stream_close(nonce_array->stream);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for newline ending
|
||||||
|
if(!stream_eof(nonce_array->stream)) {
|
||||||
|
if(!stream_seek(nonce_array->stream, -1, StreamOffsetFromEnd)) break;
|
||||||
|
uint8_t last_char = 0;
|
||||||
|
if(stream_read(nonce_array->stream, &last_char, 1) != 1) break;
|
||||||
|
if(last_char != '\n') {
|
||||||
|
//FURI_LOG_D(TAG, "Adding new line ending");
|
||||||
|
if(stream_write_char(nonce_array->stream, '\n') != 1) break;
|
||||||
|
}
|
||||||
|
if(!stream_rewind(nonce_array->stream)) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read total amount of nonces
|
||||||
|
FuriString* next_line;
|
||||||
|
next_line = furi_string_alloc();
|
||||||
|
while(!(program_state->close_thread_please)) {
|
||||||
|
if(!stream_read_line(nonce_array->stream, next_line)) {
|
||||||
|
//FURI_LOG_T(TAG, "No nonces left");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
FURI_LOG_T(
|
||||||
|
TAG,
|
||||||
|
"Read line: %s, len: %zu",
|
||||||
|
furi_string_get_cstr(next_line),
|
||||||
|
furi_string_size(next_line));
|
||||||
|
*/
|
||||||
|
if(!furi_string_start_with_str(next_line, "Sec")) continue;
|
||||||
|
const char* next_line_cstr = furi_string_get_cstr(next_line);
|
||||||
|
MfClassicNonce res = {0};
|
||||||
|
res.attack = mfkey32;
|
||||||
|
int i = 0;
|
||||||
|
char* endptr;
|
||||||
|
for(i = 0; i <= 17; i++) {
|
||||||
|
if(i != 0) {
|
||||||
|
next_line_cstr = strchr(next_line_cstr, ' ');
|
||||||
|
if(next_line_cstr) {
|
||||||
|
next_line_cstr++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsigned long value = strtoul(next_line_cstr, &endptr, 16);
|
||||||
|
switch(i) {
|
||||||
|
case 5:
|
||||||
|
res.uid = value;
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
res.nt0 = value;
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
res.nr0_enc = value;
|
||||||
|
break;
|
||||||
|
case 11:
|
||||||
|
res.ar0_enc = value;
|
||||||
|
break;
|
||||||
|
case 13:
|
||||||
|
res.nt1 = value;
|
||||||
|
break;
|
||||||
|
case 15:
|
||||||
|
res.nr1_enc = value;
|
||||||
|
break;
|
||||||
|
case 17:
|
||||||
|
res.ar1_enc = value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break; // Do nothing
|
||||||
|
}
|
||||||
|
next_line_cstr = endptr;
|
||||||
|
}
|
||||||
|
res.p64 = prng_successor(res.nt0, 64);
|
||||||
|
res.p64b = prng_successor(res.nt1, 64);
|
||||||
|
res.uid_xor_nt0 = res.uid ^ res.nt0;
|
||||||
|
res.uid_xor_nt1 = res.uid ^ res.nt1;
|
||||||
|
|
||||||
|
(program_state->total)++;
|
||||||
|
if((system_dict_exists && key_already_found_for_nonce_in_dict(system_dict, &res)) ||
|
||||||
|
(key_already_found_for_nonce_in_dict(user_dict, &res))) {
|
||||||
|
(program_state->cracked)++;
|
||||||
|
(program_state->num_completed)++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//FURI_LOG_I(TAG, "No key found for %8lx %8lx", res.uid, res.ar1_enc);
|
||||||
|
// TODO: Refactor
|
||||||
|
nonce_array->remaining_nonce_array = realloc( //-V701
|
||||||
|
nonce_array->remaining_nonce_array,
|
||||||
|
sizeof(MfClassicNonce) * ((nonce_array->remaining_nonces) + 1));
|
||||||
|
nonce_array->remaining_nonces++;
|
||||||
|
nonce_array->remaining_nonce_array[(nonce_array->remaining_nonces) - 1] = res;
|
||||||
|
nonce_array->total_nonces++;
|
||||||
|
}
|
||||||
|
furi_string_free(next_line);
|
||||||
|
buffered_file_stream_close(nonce_array->stream);
|
||||||
|
|
||||||
|
array_loaded = true;
|
||||||
|
//FURI_LOG_I(TAG, "Loaded %lu Mfkey32 nonces", nonce_array->total_nonces);
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return array_loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool load_nested_nonces(
|
||||||
|
MfClassicNonceArray* nonce_array,
|
||||||
|
ProgramState* program_state,
|
||||||
|
KeysDict* system_dict,
|
||||||
|
bool system_dict_exists,
|
||||||
|
KeysDict* user_dict) {
|
||||||
|
if(!buffered_file_stream_open(
|
||||||
|
nonce_array->stream, MF_CLASSIC_NESTED_NONCE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FuriString* next_line = furi_string_alloc();
|
||||||
|
bool array_loaded = false;
|
||||||
|
|
||||||
|
while(stream_read_line(nonce_array->stream, next_line)) {
|
||||||
|
const char* line = furi_string_get_cstr(next_line);
|
||||||
|
|
||||||
|
// Only process lines ending with "dist 0"
|
||||||
|
if(!strstr(line, "dist 0")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MfClassicNonce res = {0};
|
||||||
|
res.attack = static_encrypted;
|
||||||
|
|
||||||
|
int parsed = sscanf(
|
||||||
|
line,
|
||||||
|
"Sec %*d key %*c cuid %" PRIx32 " nt0 %" PRIx32 " ks0 %" PRIx32
|
||||||
|
" par0 %4[01] nt1 %" PRIx32 " ks1 %" PRIx32 " par1 %4[01]",
|
||||||
|
&res.uid,
|
||||||
|
&res.nt0,
|
||||||
|
&res.ks1_1_enc,
|
||||||
|
res.par_1_str,
|
||||||
|
&res.nt1,
|
||||||
|
&res.ks1_2_enc,
|
||||||
|
res.par_2_str);
|
||||||
|
|
||||||
|
if(parsed >= 4) { // At least one nonce is present
|
||||||
|
res.par_1 = binaryStringToInt(res.par_1_str);
|
||||||
|
res.uid_xor_nt0 = res.uid ^ res.nt0;
|
||||||
|
|
||||||
|
if(parsed == 7) { // Both nonces are present
|
||||||
|
res.attack = static_nested;
|
||||||
|
res.par_2 = binaryStringToInt(res.par_2_str);
|
||||||
|
res.uid_xor_nt1 = res.uid ^ res.nt1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(program_state->total)++;
|
||||||
|
if((system_dict_exists && key_already_found_for_nonce_in_dict(system_dict, &res)) ||
|
||||||
|
(key_already_found_for_nonce_in_dict(user_dict, &res))) {
|
||||||
|
(program_state->cracked)++;
|
||||||
|
(program_state->num_completed)++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce_array->remaining_nonce_array = realloc(
|
||||||
|
nonce_array->remaining_nonce_array,
|
||||||
|
sizeof(MfClassicNonce) * (nonce_array->remaining_nonces + 1));
|
||||||
|
nonce_array->remaining_nonce_array[nonce_array->remaining_nonces] = res;
|
||||||
|
nonce_array->remaining_nonces++;
|
||||||
|
nonce_array->total_nonces++;
|
||||||
|
array_loaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
furi_string_free(next_line);
|
||||||
|
buffered_file_stream_close(nonce_array->stream);
|
||||||
|
|
||||||
|
//FURI_LOG_I(TAG, "Loaded %lu Static Nested nonces", nonce_array->total_nonces);
|
||||||
|
return array_loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
MfClassicNonceArray* napi_mf_classic_nonce_array_alloc(
|
||||||
|
KeysDict* system_dict,
|
||||||
|
bool system_dict_exists,
|
||||||
|
KeysDict* user_dict,
|
||||||
|
ProgramState* program_state) {
|
||||||
|
MfClassicNonceArray* nonce_array = malloc(sizeof(MfClassicNonceArray));
|
||||||
|
MfClassicNonce* remaining_nonce_array_init = malloc(sizeof(MfClassicNonce) * 1);
|
||||||
|
nonce_array->remaining_nonce_array = remaining_nonce_array_init;
|
||||||
|
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||||
|
nonce_array->stream = buffered_file_stream_alloc(storage);
|
||||||
|
furi_record_close(RECORD_STORAGE);
|
||||||
|
|
||||||
|
if(program_state->mfkey32_present) {
|
||||||
|
load_mfkey32_nonces(
|
||||||
|
nonce_array, program_state, system_dict, system_dict_exists, user_dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(program_state->nested_present) {
|
||||||
|
load_nested_nonces(nonce_array, program_state, system_dict, system_dict_exists, user_dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nonce_array;
|
||||||
|
}
|
||||||
|
|
||||||
|
void napi_mf_classic_nonce_array_free(MfClassicNonceArray* nonce_array) {
|
||||||
|
// TODO: Track free state at the time this is called to ensure double free does not happen
|
||||||
|
furi_assert(nonce_array);
|
||||||
|
furi_assert(nonce_array->stream);
|
||||||
|
|
||||||
|
// TODO: Already closed?
|
||||||
|
buffered_file_stream_close(nonce_array->stream);
|
||||||
|
stream_free(nonce_array->stream);
|
||||||
|
free(nonce_array);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actual implementation of app<>plugin interface */
|
||||||
|
static const MfkeyPlugin init_plugin = {
|
||||||
|
.name = "Initialization Plugin",
|
||||||
|
.napi_mf_classic_mfkey32_nonces_check_presence =
|
||||||
|
&napi_mf_classic_mfkey32_nonces_check_presence,
|
||||||
|
.napi_mf_classic_nested_nonces_check_presence = &napi_mf_classic_nested_nonces_check_presence,
|
||||||
|
.napi_mf_classic_nonce_array_alloc = &napi_mf_classic_nonce_array_alloc,
|
||||||
|
.napi_mf_classic_nonce_array_free = &napi_mf_classic_nonce_array_free,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin descriptor to comply with basic plugin specification */
|
||||||
|
static const FlipperAppPluginDescriptor init_plugin_descriptor = {
|
||||||
|
.appid = PLUGIN_APP_ID,
|
||||||
|
.ep_api_version = PLUGIN_API_VERSION,
|
||||||
|
.entry_point = &init_plugin,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Plugin entry point - must return a pointer to const descriptor */
|
||||||
|
const FlipperAppPluginDescriptor* init_plugin_ep() {
|
||||||
|
return &init_plugin_descriptor;
|
||||||
|
}
|
||||||
915
applications/system/mfkey/mfkey.c
Normal file
915
applications/system/mfkey/mfkey.c
Normal file
@@ -0,0 +1,915 @@
|
|||||||
|
#pragma GCC optimize("O3")
|
||||||
|
#pragma GCC optimize("-funroll-all-loops")
|
||||||
|
|
||||||
|
// TODO: More efficient dictionary bruteforce by scanning through hardcoded very common keys and previously found dictionary keys first?
|
||||||
|
// (a cache for key_already_found_for_nonce_in_dict)
|
||||||
|
// TODO: Selectively unroll loops to reduce binary size
|
||||||
|
// TODO: Collect parity during Mfkey32 attacks to further optimize the attack
|
||||||
|
// TODO: Why different sscanf between Mfkey32 and Nested?
|
||||||
|
// TODO: "Read tag again with NFC app" message upon completion, "Complete. Keys added: <n>"
|
||||||
|
// TODO: Separate Mfkey32 and Nested functions where possible to reduce branch statements
|
||||||
|
// 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 <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include "mfkey_icons.h"
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <toolbox/keys_dict.h>
|
||||||
|
#include <bit_lib/bit_lib.h>
|
||||||
|
#include <toolbox/stream/buffered_file_stream.h>
|
||||||
|
#include <dolphin/dolphin.h>
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
#include <nfc/protocols/mf_classic/mf_classic.h>
|
||||||
|
#include "mfkey.h"
|
||||||
|
#include "crypto1.h"
|
||||||
|
#include "plugin_interface.h"
|
||||||
|
#include <flipper_application/flipper_application.h>
|
||||||
|
#include <loader/firmware_api/firmware_api.h>
|
||||||
|
#include <storage/storage.h>
|
||||||
|
|
||||||
|
#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 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 SWAPENDIAN(x) \
|
||||||
|
((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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
for(round = 1; round <= 12; round++) {
|
||||||
|
xks_bit = BIT(xks, round);
|
||||||
|
if(round > 4) {
|
||||||
|
round_in = ((in >> (2 * (round - 4))) & and_val) << 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(s = 0; s <= states_tail; s++) {
|
||||||
|
states_buffer[s] <<= 1;
|
||||||
|
|
||||||
|
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--];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
for(semi_state = 1 << 20; semi_state >= 0; semi_state--) {
|
||||||
|
if(semi_state % 32768 == 0) {
|
||||||
|
if(sync_state(program_state) == 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!found) {
|
||||||
|
tail = odd_msbs[msb - msb_head].tail++;
|
||||||
|
odd_msbs[msb - msb_head].states[tail] = states_buffer[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(i = 0; i <= states_tail; i++) {
|
||||||
|
msb = states_buffer[i] >> 24;
|
||||||
|
if((msb >= msb_head) && (msb < msb_tail)) {
|
||||||
|
found = 0;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!found) {
|
||||||
|
tail = even_msbs[msb - msb_head].tail++;
|
||||||
|
even_msbs[msb - msb_head].states[tail] = states_buffer[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oks >>= 12;
|
||||||
|
eks >>= 12;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void** allocate_blocks(const size_t* block_sizes, int num_blocks) {
|
||||||
|
void** block_pointers = malloc(num_blocks * sizeof(void*));
|
||||||
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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]);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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");
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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] = {};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t mfkey_main() {
|
||||||
|
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
||||||
|
|
||||||
|
ProgramState* program_state = malloc(sizeof(ProgramState));
|
||||||
|
|
||||||
|
mfkey_state_init(program_state);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
#pragma GCC pop_options
|
||||||
108
applications/system/mfkey/mfkey.h
Normal file
108
applications/system/mfkey/mfkey.h
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
#ifndef MFKEY_H
|
||||||
|
#define MFKEY_H
|
||||||
|
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <toolbox/keys_dict.h>
|
||||||
|
#include <toolbox/stream/buffered_file_stream.h>
|
||||||
|
#include <nfc/protocols/mf_classic/mf_classic.h>
|
||||||
|
|
||||||
|
struct Crypto1State {
|
||||||
|
uint32_t odd, even;
|
||||||
|
};
|
||||||
|
struct Msb {
|
||||||
|
int tail;
|
||||||
|
uint32_t states[768];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
MissingNonces,
|
||||||
|
ZeroNonces,
|
||||||
|
InsufficientRAM,
|
||||||
|
} MFKeyError;
|
||||||
|
|
||||||
|
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;
|
||||||
|
} ProgramState;
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} MfClassicNonce;
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // MFKEY_H
|
||||||
BIN
applications/system/mfkey/mfkey.png
Normal file
BIN
applications/system/mfkey/mfkey.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 107 B |
13
applications/system/mfkey/plugin_interface.h
Normal file
13
applications/system/mfkey/plugin_interface.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define PLUGIN_APP_ID "mfkey"
|
||||||
|
#define PLUGIN_API_VERSION 1
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char* name;
|
||||||
|
bool (*napi_mf_classic_mfkey32_nonces_check_presence)();
|
||||||
|
bool (*napi_mf_classic_nested_nonces_check_presence)();
|
||||||
|
MfClassicNonceArray* (
|
||||||
|
*napi_mf_classic_nonce_array_alloc)(KeysDict*, bool, KeysDict*, ProgramState*);
|
||||||
|
void (*napi_mf_classic_nonce_array_free)(MfClassicNonceArray*);
|
||||||
|
} MfkeyPlugin;
|
||||||
Reference in New Issue
Block a user