Add MFKey to firmware

This commit is contained in:
noproto
2024-10-08 22:42:30 -04:00
parent 3375bba18d
commit 619445da4b
17 changed files with 1747 additions and 17 deletions

View File

@@ -14,29 +14,29 @@ Flipper Zero resources:
Coming from the **Official Firmware** (OFW), you'll get:
* Newly supported cards and protocols
* Fixes and performance improvements for existing cards and protocols
* Reduced memory usage of core applications
* A curated list of community applications with a focus on utility
- Newly supported cards and protocols
- Fixes and performance improvements for existing cards and protocols
- Reduced memory usage of core applications
- A curated list of community applications with a focus on utility
Coming from other custom firmware, you'll get:
* All of the above
* General stability improvements
* Reduced memory usage
* Minimal theming
- All of the above
- General stability improvements
- Reduced memory usage
- Minimal theming
# Xero features
* **MIFARE Classic Key recovery improvements**
* *MIFARE Classic Accelerated dictionary attack*: dictionary attacks reduced to several seconds - checks ~3500 keys per second
* *MIFARE Classic Nested attack support*: collects nested nonces to be cracked by MFKey
* *MIFARE Classic Static encrypted backdoor support*: collects static encrypted nonces to be cracked by MFKey using NXP/Fudan backdoor
* **MFKey 3.0**: Mfkey32, Static Nested, and Static Encrypted attacks all on your Flipper Zero (coming soon!)
* **MIFARE Ultralight C Dictionary attack** (coming soon!)
* **MIFARE Ultralight C Emulation** (coming soon!)
* **NFC app memory improvements** (coming soon!)
* **Minimal theme** (coming soon!)
- **MIFARE Classic Key recovery improvements**
- *MIFARE Classic Accelerated dictionary attack*: dictionary attacks reduced to several seconds - checks ~3500 keys per second
- *MIFARE Classic Nested attack support*: collects nested nonces to be cracked by MFKey
- *MIFARE Classic Static encrypted backdoor support*: collects static encrypted nonces to be cracked by MFKey using NXP/Fudan backdoor
- **MFKey 3.0**: Mfkey32, Static Nested, and Static Encrypted attacks all on your Flipper Zero
- **MIFARE Ultralight C Dictionary attack** (coming soon!)
- **MIFARE Ultralight C Emulation** (coming soon!)
- **NFC app memory improvements** (coming soon!)
- **Minimal theme** (coming soon!)
# Contributing

View File

@@ -79,3 +79,4 @@ Utility apps not visible in other menus, plus few external apps pre-packaged wit
- `snake_game` - Snake game
- `storage_move_to_sd` - Data migration tool for internal storage
- `updater` - Update service & application
- `mfkey` - MIFARE Classic key recovery tool

View File

@@ -6,6 +6,7 @@ App(
"updater_app",
"js_app",
"js_app_start",
"mfkey",
# "archive",
],
)

View File

@@ -0,0 +1,12 @@
# Flipper Zero MFKey
This application allows you to calculate the keys of MIFARE Classic cards using the Mfkey32 and Nested algorithms directly on your Flipper Zero. After collecting the nonces using the Extract MF Keys feature of the NFC app, they can be used to calculate the keys to the card in the MFKey app.
## Usage
After collecting nonces using the Extract MF Keys option, press the Start button in the MFKey app and wait for it to finish. The calculation can take more than 10 minutes, so you'll have to be patient. After the calculation is complete, the keys will be saved to the user key dictionary.
## Credits
Developers: noproto, AG, Flipper Devices, WillyJL
Thanks: AloneLiberty, Foxushka, bettse, Equip

View File

@@ -0,0 +1,18 @@
## 3.0
- Added Static Encrypted Nested key recovery, added NFC app support, dropped FlipperNested support
## 2.7
- Mfkey32 recovery is 30% faster, fix UI and slowdown bugs
## 2.6
- Version bump for catalog build system
## 2.5
- Plugin path fixed
## 2.4
- Update API for app rename
## 2.3
- Update API v65.0
## 2.0
- Added Nested key recovery, use new KeysDict API, fix crashes, more efficient RAM utilization, faster
## 1.1
- Rework application with new NFC API
## 1.0
- Initial release

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View 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,
)

View 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;
}
}

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

View 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;
}

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 B

View 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;