Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update MFKey to v3.0 #243

Draft
wants to merge 17 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions mfkey/.catalog/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +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 Detect Reader feature of the NFC app, they can be used to calculate the keys to the card in the MFKey app.
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 Detect Reader 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.
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
Thanks: bettse
Developers: noproto, AG, Flipper Devices, WillyJL
Thanks: AloneLiberty, Foxushka, bettse, Equip
2 changes: 2 additions & 0 deletions mfkey/.catalog/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
## 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
Expand Down
2 changes: 1 addition & 1 deletion mfkey/application.fam
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ App(
fap_icon_assets="images",
fap_weburl="https://github.com/noproto/FlipperMfkey",
fap_description="MIFARE Classic key recovery tool",
fap_version="2.7",
fap_version="3.0",
)

App(
Expand Down
49 changes: 49 additions & 0 deletions mfkey/crypto1.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#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)
Expand All @@ -20,6 +21,12 @@
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);
Expand Down Expand Up @@ -131,6 +138,48 @@
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)))

Check failure on line 176 in mfkey/crypto1.h

View workflow job for this annotation

GitHub Actions / build

implicit declaration of function 'nfc_util_even_parity8'; did you mean 'nfc_util_even_parity32'? [-Werror=implicit-function-declaration]

Check failure on line 176 in mfkey/crypto1.h

View workflow job for this annotation

GitHub Actions / build

implicit declaration of function 'nfc_util_even_parity8'; did you mean 'nfc_util_even_parity32'? [-Werror=implicit-function-declaration]

Check failure on line 176 in mfkey/crypto1.h

View workflow job for this annotation

GitHub Actions / build

implicit declaration of function 'nfc_util_even_parity8'; did you mean 'nfc_util_even_parity32'? [-Werror=implicit-function-declaration]

Check failure on line 176 in mfkey/crypto1.h

View workflow job for this annotation

GitHub Actions / build

implicit declaration of function 'nfc_util_even_parity8'; did you mean 'nfc_util_even_parity32'? [-Werror=implicit-function-declaration]
<< (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;
Expand Down
204 changes: 74 additions & 130 deletions mfkey/init_plugin.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@
#include "plugin_interface.h"
#include <flipper_application/flipper_application.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 MF_CLASSIC_NONCE_PATH EXT_PATH("nfc/.mfkey32.log")
#define MF_CLASSIC_NESTED_NONCE_PATH EXT_PATH("nfc/.nested")
#define TAG "MFKey"
#define MF_CLASSIC_NESTED_NONCE_PATH EXT_PATH("nfc/.nested.log")
#define MAX_NAME_LEN 32
#define MAX_PATH_LEN 64

Expand All @@ -30,6 +29,7 @@
((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);
Expand All @@ -47,7 +47,7 @@ bool key_already_found_for_nonce_in_dict(KeysDict* dict, MfClassicNonce* nonce)
found = true;
break;
}
} else if(nonce->attack == static_nested) {
} 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;
Expand All @@ -68,59 +68,30 @@ bool napi_mf_classic_mfkey32_nonces_check_presence() {
return nonces_present;
}

bool distance_in_nonces_file(const char* file_path, const char* file_name) {
char full_path[MAX_PATH_LEN];
snprintf(full_path, sizeof(full_path), "%s/%s", file_path, file_name);
bool distance_present = false;
Storage* storage = furi_record_open(RECORD_STORAGE);
Stream* file_stream = buffered_file_stream_alloc(storage);
FuriString* line_str;
line_str = furi_string_alloc();

if(buffered_file_stream_open(file_stream, full_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
while(true) {
if(!stream_read_line(file_stream, line_str)) break;
if(furi_string_search_str(line_str, "distance") != FURI_STRING_FAILURE) {
distance_present = true;
break;
}
}
}

buffered_file_stream_close(file_stream);
stream_free(file_stream);
furi_string_free(line_str);
furi_record_close(RECORD_STORAGE);

return distance_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();

if(!(storage_dir_exists(storage, MF_CLASSIC_NESTED_NONCE_PATH))) {
furi_record_close(RECORD_STORAGE);
return false;
}
do {
if(!buffered_file_stream_open(
stream, MF_CLASSIC_NESTED_NONCE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
break;
}

bool nonces_present = false;
File* dir = storage_file_alloc(storage);
char filename_buffer[MAX_NAME_LEN];
FileInfo file_info;

if(storage_dir_open(dir, MF_CLASSIC_NESTED_NONCE_PATH)) {
while(storage_dir_read(dir, &file_info, filename_buffer, MAX_NAME_LEN)) {
// We only care about Static Nested files
if(!(file_info.flags & FSF_DIRECTORY) && strstr(filename_buffer, ".nonces") &&
!(distance_in_nonces_file(MF_CLASSIC_NESTED_NONCE_PATH, filename_buffer))) {
while(stream_read_line(stream, line)) {
if(furi_string_search_str(line, "dist 0") != FURI_STRING_FAILURE) {
nonces_present = true;
break;
}
}
}

storage_dir_close(dir);
storage_file_free(dir);
} while(false);

furi_string_free(line);
buffered_file_stream_close(stream);
stream_free(stream);
furi_record_close(RECORD_STORAGE);

return nonces_present;
Expand Down Expand Up @@ -247,7 +218,6 @@ bool load_mfkey32_nonces(
}
furi_string_free(next_line);
buffered_file_stream_close(nonce_array->stream);
//stream_free(nonce_array->stream);

array_loaded = true;
//FURI_LOG_I(TAG, "Loaded %lu Mfkey32 nonces", nonce_array->total_nonces);
Expand All @@ -262,89 +232,70 @@ bool load_nested_nonces(
KeysDict* system_dict,
bool system_dict_exists,
KeysDict* user_dict) {
Storage* storage = furi_record_open(RECORD_STORAGE);
File* dir = storage_file_alloc(storage);
char filename_buffer[MAX_NAME_LEN];
FileInfo file_info;
FuriString* next_line = furi_string_alloc();

if(!storage_dir_open(dir, MF_CLASSIC_NESTED_NONCE_PATH)) {
storage_dir_close(dir);
storage_file_free(dir);
furi_record_close(RECORD_STORAGE);
furi_string_free(next_line);
if(!buffered_file_stream_open(
nonce_array->stream, MF_CLASSIC_NESTED_NONCE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) {
return false;
}

while(storage_dir_read(dir, &file_info, filename_buffer, MAX_NAME_LEN)) {
if(!(file_info.flags & FSF_DIRECTORY) && strstr(filename_buffer, ".nonces") &&
!(distance_in_nonces_file(MF_CLASSIC_NESTED_NONCE_PATH, filename_buffer))) {
char full_path[MAX_PATH_LEN];
snprintf(
full_path,
sizeof(full_path),
"%s/%s",
MF_CLASSIC_NESTED_NONCE_PATH,
filename_buffer);

// TODO: We should only need READ_WRITE here if we plan on adding a newline to the end of the file if has none
if(!buffered_file_stream_open(
nonce_array->stream, full_path, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) {
buffered_file_stream_close(nonce_array->stream);
continue;
}
FuriString* next_line = furi_string_alloc();
bool array_loaded = false;

while(stream_read_line(nonce_array->stream, next_line)) {
if(furi_string_search_str(next_line, "Nested:") != FURI_STRING_FAILURE) {
MfClassicNonce res = {0};
res.attack = static_nested;
int parsed = sscanf(
furi_string_get_cstr(next_line),
"Nested: %*s %*s cuid 0x%" PRIx32 " nt0 0x%" PRIx32 " ks0 0x%" PRIx32
" par0 %4[01] nt1 0x%" PRIx32 " ks1 0x%" 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 != 7) continue;
res.par_1 = binaryStringToInt(res.par_1_str);
res.par_2 = binaryStringToInt(res.par_2_str);
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;
}
while(stream_read_line(nonce_array->stream, next_line)) {
const char* line = furi_string_get_cstr(next_line);

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++;
}
// 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;
}

buffered_file_stream_close(nonce_array->stream);
(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;
}
}

storage_dir_close(dir);
storage_file_free(dir);
furi_record_close(RECORD_STORAGE);
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 true;
return array_loaded;
}

MfClassicNonceArray* napi_mf_classic_nonce_array_alloc(
Expand All @@ -359,21 +310,13 @@ MfClassicNonceArray* napi_mf_classic_nonce_array_alloc(
nonce_array->stream = buffered_file_stream_alloc(storage);
furi_record_close(RECORD_STORAGE);

bool array_loaded = false;

if(program_state->mfkey32_present) {
array_loaded = load_mfkey32_nonces(
load_mfkey32_nonces(
nonce_array, program_state, system_dict, system_dict_exists, user_dict);
}

if(program_state->nested_present) {
array_loaded |= load_nested_nonces(
nonce_array, program_state, system_dict, system_dict_exists, user_dict);
}

if(!array_loaded) {
free(nonce_array);
nonce_array = NULL;
load_nested_nonces(nonce_array, program_state, system_dict, system_dict_exists, user_dict);
}

return nonce_array;
Expand All @@ -384,6 +327,7 @@ void napi_mf_classic_nonce_array_free(MfClassicNonceArray* nonce_array) {
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);
Expand Down
Loading
Loading