Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
daed60a
nfc: introduce user dict for simplier modifying
gornekich Jul 17, 2023
df5848d
nfc app: add mfc keys scene
gornekich Jul 17, 2023
add9321
nfc app: add mf classic keys list scene
gornekich Jul 18, 2023
d9f7c7e
nfc: implement user dict delete scene
gornekich Jul 18, 2023
a6f7a57
nfc tests: add mf dict tests
gornekich Jul 18, 2023
2afa5e9
mf classic: fix dict attack
gornekich Jul 19, 2023
cdd1d51
mfc: update card start
gornekich Aug 1, 2023
e659557
mfc: introduce new MfClassicKeyCache
gornekich Aug 1, 2023
b887f8c
nfc app: correct scene switches
gornekich Aug 2, 2023
094e77f
nfc: fix key cache reading
gornekich Aug 2, 2023
f15fdf7
nfc app: add write and update scenes
gornekich Aug 3, 2023
91135af
nfc app: rework mfc update
gornekich Aug 3, 2023
3c99075
nfc app: rework mfc write to initial card
gornekich Aug 3, 2023
45072de
nfc app: introduce plaintain plugin
gornekich Aug 5, 2023
806c721
mf classic: rework write state machine
gornekich Aug 5, 2023
155e5fb
mfc: rework generic card detected and lost events
gornekich Aug 5, 2023
c9832cb
nfc: mfc update initial fixes
gornekich Aug 5, 2023
5a1f091
mf classic: add read with keys to sync api
gornekich Aug 6, 2023
6dbd6f4
mf classic: rework dict attack
gornekich Aug 7, 2023
8fffc13
nfc app: new dict attack
gornekich Aug 23, 2023
778f3d0
mfc: rework dict attack view
gornekich Aug 24, 2023
d6911d4
mfc: fix incorrect key byte order
gornekich Aug 25, 2023
8fa4753
nfc: fix card detect and loss
gornekich Aug 25, 2023
9322328
nfc: fix mf dict reopen
gornekich Aug 25, 2023
2529b0c
mf classic: rework read state machine
gornekich Aug 25, 2023
61bbcbb
nfc plugins: rework troika parser
gornekich Aug 25, 2023
3a3fac2
nfc plugins: rework plaintain parser
gornekich Aug 25, 2023
b1a75a1
mfc: fix block type, mfc detection
gornekich Aug 27, 2023
27962ec
nfc: fix all plugins
gornekich Aug 27, 2023
7ed4f0c
nfc: add todo for key cache handilng
gornekich Aug 28, 2023
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
1 change: 1 addition & 0 deletions applications/debug/unit_tests/application.fam
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ App(
entry_point="unit_tests_on_system_start",
cdefines=["APP_UNIT_TESTS"],
provides=["delay_test"],
requires=["nfc"],
order=100,
)

Expand Down
78 changes: 66 additions & 12 deletions applications/debug/unit_tests/nfc/nfc_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,16 @@
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync_api.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync_api.h>

#include <applications/main/nfc/helpers/mf_dict.h>

#include <lib/nfc/nfc.h>

#include "../minunit.h"

#define TAG "NfcTest"

#define NFC_TEST_NFC_DEV_PATH EXT_PATH("unit_tests/nfc/nfc_device_test.nfc")

// Maximum allowed time for buffer preparation to fit 500us nt message timeout
#define NFC_TEST_4_BYTE_BUILD_BUFFER_TIM_MAX (150)
#define NFC_TEST_16_BYTE_BUILD_BUFFER_TIM_MAX (640)
#define NFC_TEST_4_BYTE_BUILD_SIGNAL_TIM_MAX (110)
#define NFC_TEST_16_BYTE_BUILD_SIGNAL_TIM_MAX (440)

// Maximum allowed time for buffer preparation to fit 500us nt message timeout
#define NFC_TEST_4_BYTE_BUILD_BUFFER_TIM_MAX (150)
#define NFC_TEST_16_BYTE_BUILD_BUFFER_TIM_MAX (640)
#define NFC_TEST_4_BYTE_BUILD_SIGNAL_TIM_MAX (110)
#define NFC_TEST_16_BYTE_BUILD_SIGNAL_TIM_MAX (440)
#define MF_CLASSIC_DICT_UNIT_TEST_PATH EXT_PATH("unit_tests/mf_dict.nfc")

typedef struct {
Storage* storage;
Expand Down Expand Up @@ -432,6 +423,67 @@ static void mf_classic_value_block() {
nfc_free(poller);
}

MU_TEST(mf_classic_dict_test) {
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_common_stat(storage, MF_CLASSIC_DICT_UNIT_TEST_PATH, NULL) == FSE_OK) {
mu_assert(
storage_simply_remove(storage, MF_CLASSIC_DICT_UNIT_TEST_PATH),
"Remove test dict failed");
}

MfDict* dict = mf_dict_alloc(MfDictTypeUnitTest);
mu_assert(dict != NULL, "mf_dict_alloc() failed");

size_t dict_keys_total = mf_dict_get_total_keys(dict);
mu_assert(dict_keys_total == 0, "mf_dict_keys_total() failed");

const uint32_t test_key_num = 30;
MfClassicKey* key_arr_ref = malloc(test_key_num * sizeof(MfClassicKey));
for(size_t i = 0; i < test_key_num; i++) {
furi_hal_random_fill_buf(key_arr_ref[i].data, sizeof(MfClassicKey));
mu_assert(mf_dict_add_key(dict, &key_arr_ref[i]), "add key failed");

size_t dict_keys_total = mf_dict_get_total_keys(dict);
mu_assert(dict_keys_total == (i + 1), "mf_dict_keys_total() failed");
}

mf_dict_free(dict);

dict = mf_dict_alloc(MfDictTypeUnitTest);
mu_assert(dict != NULL, "mf_dict_alloc() failed");

dict_keys_total = mf_dict_get_total_keys(dict);
mu_assert(dict_keys_total == test_key_num, "mf_dict_keys_total() failed");

MfClassicKey key_dut = {};
size_t key_idx = 0;
while(mf_dict_get_next_key(dict, &key_dut)) {
mu_assert(
memcmp(key_arr_ref[key_idx].data, key_dut.data, sizeof(MfClassicKey)) == 0,
"Loaded key data mismatch");
key_idx++;
}

uint32_t delete_keys_idx[] = {1, 3, 9, 11, 19, 27};

for(size_t i = 0; i < COUNT_OF(delete_keys_idx); i++) {
MfClassicKey* key = &key_arr_ref[delete_keys_idx[i]];
mu_assert(mf_dict_is_key_present(dict, key), "mf_dict_is_key_present() failed");
mu_assert(mf_dict_delete_key(dict, key), "mf_dict_delete_key() failed");
}

dict_keys_total = mf_dict_get_total_keys(dict);
mu_assert(
dict_keys_total == test_key_num - COUNT_OF(delete_keys_idx),
"mf_dict_keys_total() failed");

mf_dict_free(dict);
free(key_arr_ref);

mu_assert(
storage_simply_remove(storage, MF_CLASSIC_DICT_UNIT_TEST_PATH), "Remove test dict failed");
}

MU_TEST_SUITE(nfc) {
nfc_test_alloc();

Expand Down Expand Up @@ -471,6 +523,8 @@ MU_TEST_SUITE(nfc) {
MU_RUN_TEST(mf_classic_write);
MU_RUN_TEST(mf_classic_value_block);

MU_RUN_TEST(mf_classic_dict_test);

nfc_test_free();
}

Expand Down
14 changes: 14 additions & 0 deletions applications/main/nfc/application.fam
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ App(
requires=["nfc"],
)

App(
appid="plantain_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="plantain_plugin_ep",
requires=["nfc"],
)

App(
appid="two_cities_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="two_cities_plugin_ep",
requires=["nfc"],
)

# App(
# appid="nfc_start",
# apptype=FlipperAppType.STARTUP,
Expand Down
211 changes: 211 additions & 0 deletions applications/main/nfc/helpers/mf_classic_key_cache.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
#include "mf_classic_key_cache.h"

#include <furi/furi.h>
#include <storage/storage.h>

#define NFC_APP_KEYS_EXTENSION ".keys"
#define NFC_APP_KEY_CACHE_FOLDER "/ext/nfc/.cache"

static const char* mf_classic_key_cache_file_header = "Flipper NFC keys";
static const uint32_t mf_classic_key_cache_file_version = 1;

struct MfClassicKeyCache {
MfClassicDeviceKeys keys;
MfClassicKeyType current_key_type;
uint8_t current_sector;
};

static void nfc_get_key_cache_file_path(const uint8_t* uid, size_t uid_len, FuriString* path) {
furi_string_printf(path, "%s/", NFC_APP_KEY_CACHE_FOLDER);
for(size_t i = 0; i < uid_len; i++) {
furi_string_cat_printf(path, "%02X", uid[i]);
}
furi_string_cat_printf(path, "%s", NFC_APP_KEYS_EXTENSION);
}

MfClassicKeyCache* mf_classic_key_cache_alloc() {
MfClassicKeyCache* instance = malloc(sizeof(MfClassicKeyCache));

return instance;
}

void mf_classic_key_cache_free(MfClassicKeyCache* instance) {
furi_assert(instance);

free(instance);
}

bool mf_classic_key_cache_save(MfClassicKeyCache* instance, const MfClassicData* data) {
UNUSED(instance);
furi_assert(data);

size_t uid_len = 0;
const uint8_t* uid = mf_classic_get_uid(data, &uid_len);
FuriString* file_path = furi_string_alloc();
nfc_get_key_cache_file_path(uid, uid_len, file_path);

Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);

FuriString* temp_str = furi_string_alloc();
bool save_success = false;
do {
if(!storage_simply_mkdir(storage, NFC_APP_KEY_CACHE_FOLDER)) break;
if(!storage_simply_remove(storage, furi_string_get_cstr(file_path))) break;
if(!flipper_format_buffered_file_open_always(ff, furi_string_get_cstr(file_path))) break;

if(!flipper_format_write_header_cstr(
ff, mf_classic_key_cache_file_header, mf_classic_key_cache_file_version))
break;
if(!flipper_format_write_string_cstr(
ff, "Mifare Classic type", mf_classic_get_device_name(data, NfcDeviceNameTypeShort)))
break;
if(!flipper_format_write_hex_uint64(ff, "Key A map", &data->key_a_mask, 1)) break;
if(!flipper_format_write_hex_uint64(ff, "Key B map", &data->key_b_mask, 1)) break;

uint8_t sector_num = mf_classic_get_total_sectors_num(data->type);
bool key_save_success = true;
for(size_t i = 0; (i < sector_num) && (key_save_success); i++) {
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i);
if(FURI_BIT(data->key_a_mask, i)) {
furi_string_printf(temp_str, "Key A sector %d", i);
key_save_success = flipper_format_write_hex(
ff, furi_string_get_cstr(temp_str), sec_tr->key_a.data, sizeof(MfClassicKey));
}
if(!key_save_success) break;
if(FURI_BIT(data->key_b_mask, i)) {
furi_string_printf(temp_str, "Key B sector %d", i);
key_save_success = flipper_format_write_hex(
ff, furi_string_get_cstr(temp_str), sec_tr->key_b.data, sizeof(MfClassicKey));
}
}
save_success = key_save_success;
} while(false);

flipper_format_free(ff);
furi_string_free(temp_str);
furi_string_free(file_path);
furi_record_close(RECORD_STORAGE);

return save_success;
}

bool mf_classic_key_cache_load(MfClassicKeyCache* instance, const uint8_t* uid, size_t uid_len) {
furi_assert(instance);
furi_assert(uid);

FuriString* file_path = furi_string_alloc();
nfc_get_key_cache_file_path(uid, uid_len, file_path);

Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);

FuriString* temp_str = furi_string_alloc();
bool load_success = false;
do {
if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(file_path))) break;

uint32_t version = 0;
if(!flipper_format_read_header(ff, temp_str, &version)) break;
if(furi_string_cmp_str(temp_str, mf_classic_key_cache_file_header)) break;
if(version != mf_classic_key_cache_file_version) break;

if(!flipper_format_read_hex_uint64(ff, "Key A map", &instance->keys.key_a_mask, 1)) break;
if(!flipper_format_read_hex_uint64(ff, "Key B map", &instance->keys.key_b_mask, 1)) break;

bool key_read_success = true;
for(size_t i = 0; (i < MF_CLASSIC_TOTAL_SECTORS_MAX) && (key_read_success); i++) {
if(FURI_BIT(instance->keys.key_a_mask, i)) {
furi_string_printf(temp_str, "Key A sector %d", i);
key_read_success = flipper_format_read_hex(
ff,
furi_string_get_cstr(temp_str),
instance->keys.key_a[i].data,
sizeof(MfClassicKey));
}
if(!key_read_success) break;
if(FURI_BIT(instance->keys.key_b_mask, i)) {
furi_string_printf(temp_str, "Key B sector %d", i);
key_read_success = flipper_format_read_hex(
ff,
furi_string_get_cstr(temp_str),
instance->keys.key_b[i].data,
sizeof(MfClassicKey));
}
}
load_success = key_read_success;
load_success = true;
} while(false);

flipper_format_buffered_file_close(ff);
flipper_format_free(ff);
furi_string_free(temp_str);
furi_string_free(file_path);
furi_record_close(RECORD_STORAGE);

return load_success;
}

void mf_classic_key_cache_load_from_data(MfClassicKeyCache* instance, const MfClassicData* data) {
furi_assert(instance);
furi_assert(data);

mf_classic_key_cache_reset(instance);
instance->keys.key_a_mask = data->key_a_mask;
instance->keys.key_b_mask = data->key_b_mask;
for(size_t i = 0; i < MF_CLASSIC_TOTAL_SECTORS_MAX; i++) {
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i);

if(FURI_BIT(data->key_a_mask, i)) {
instance->keys.key_a[i] = sec_tr->key_a;
}
if(FURI_BIT(data->key_b_mask, i)) {
instance->keys.key_b[i] = sec_tr->key_b;
}
}
}

bool mf_classic_key_cahce_get_next_key(
MfClassicKeyCache* instance,
uint8_t* sector_num,
MfClassicKey* key,
MfClassicKeyType* key_type) {
furi_assert(instance);
furi_assert(sector_num);
furi_assert(key);
furi_assert(key_type);

bool next_key_found = false;
for(uint8_t i = instance->current_sector; i < MF_CLASSIC_TOTAL_SECTORS_MAX; i++) {
if(FURI_BIT(instance->keys.key_a_mask, i)) {
FURI_BIT_CLEAR(instance->keys.key_a_mask, i);
*key = instance->keys.key_a[i];
*key_type = MfClassicKeyTypeA;
*sector_num = i;

next_key_found = true;
break;
}
if(FURI_BIT(instance->keys.key_b_mask, i)) {
FURI_BIT_CLEAR(instance->keys.key_b_mask, i);
*key = instance->keys.key_b[i];
*key_type = MfClassicKeyTypeB;
*sector_num = i;

next_key_found = true;
instance->current_sector = i;
break;
}
}

return next_key_found;
}

void mf_classic_key_cache_reset(MfClassicKeyCache* instance) {
furi_assert(instance);

instance->current_key_type = MfClassicKeyTypeA;
instance->current_sector = 0;
instance->keys.key_a_mask = 0;
instance->keys.key_b_mask = 0;
}
31 changes: 31 additions & 0 deletions applications/main/nfc/helpers/mf_classic_key_cache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include <nfc/protocols/mf_classic/mf_classic.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct MfClassicKeyCache MfClassicKeyCache;

MfClassicKeyCache* mf_classic_key_cache_alloc();

void mf_classic_key_cache_free(MfClassicKeyCache* instance);

bool mf_classic_key_cache_load(MfClassicKeyCache* instance, const uint8_t* uid, size_t uid_len);

void mf_classic_key_cache_load_from_data(MfClassicKeyCache* instance, const MfClassicData* data);

bool mf_classic_key_cahce_get_next_key(
MfClassicKeyCache* instance,
uint8_t* sector_num,
MfClassicKey* key,
MfClassicKeyType* key_type);

bool mf_classic_key_cache_save(MfClassicKeyCache* instance, const MfClassicData* data);

void mf_classic_key_cache_reset(MfClassicKeyCache* instance);

#ifdef __cplusplus
}
#endif
Loading