From da8e5f9e6f1cccb82890eb7957ac471fed154b67 Mon Sep 17 00:00:00 2001 From: Vladimir Kondenko Date: Sat, 23 Sep 2023 13:58:33 +0400 Subject: [PATCH] Add card emulation logic --- application.fam | 3 +- lib/nfc/SConscript | 25 + lib/nfc/helpers/mf_classic_dict.c | 346 +++++ lib/nfc/helpers/mf_classic_dict.h | 107 ++ lib/nfc/helpers/mfkey32.c | 228 +++ lib/nfc/helpers/mfkey32.h | 34 + lib/nfc/helpers/nfc_debug_log.c | 71 + lib/nfc/helpers/nfc_debug_log.h | 17 + lib/nfc/helpers/nfc_debug_pcap.c | 130 ++ lib/nfc/helpers/nfc_debug_pcap.h | 17 + lib/nfc/helpers/nfc_generators.c | 528 +++++++ lib/nfc/helpers/nfc_generators.h | 14 + lib/nfc/helpers/reader_analyzer.c | 265 ++++ lib/nfc/helpers/reader_analyzer.h | 43 + lib/nfc/nfc_device.c | 1735 ++++++++++++++++++++++ lib/nfc/nfc_device.h | 127 ++ lib/nfc/nfc_types.c | 69 + lib/nfc/nfc_types.h | 19 + lib/nfc/nfc_worker.c | 1325 +++++++++++++++++ lib/nfc/nfc_worker.h | 108 ++ lib/nfc/nfc_worker_i.h | 59 + lib/nfc/parsers/all_in_one.c | 103 ++ lib/nfc/parsers/all_in_one.h | 9 + lib/nfc/parsers/nfc_supported_card.c | 82 ++ lib/nfc/parsers/nfc_supported_card.h | 47 + lib/nfc/parsers/opal.c | 204 +++ lib/nfc/parsers/opal.h | 5 + lib/nfc/parsers/plantain_4k_parser.c | 124 ++ lib/nfc/parsers/plantain_4k_parser.h | 9 + lib/nfc/parsers/plantain_parser.c | 97 ++ lib/nfc/parsers/plantain_parser.h | 11 + lib/nfc/parsers/troika_4k_parser.c | 106 ++ lib/nfc/parsers/troika_4k_parser.h | 9 + lib/nfc/parsers/troika_parser.c | 88 ++ lib/nfc/parsers/troika_parser.h | 9 + lib/nfc/parsers/two_cities.c | 146 ++ lib/nfc/parsers/two_cities.h | 9 + lib/nfc/protocols/crypto1.c | 128 ++ lib/nfc/protocols/crypto1.h | 45 + lib/nfc/protocols/emv.c | 444 ++++++ lib/nfc/protocols/emv.h | 80 + lib/nfc/protocols/mifare_classic.c | 1626 +++++++++++++++++++++ lib/nfc/protocols/mifare_classic.h | 251 ++++ lib/nfc/protocols/mifare_common.c | 18 + lib/nfc/protocols/mifare_common.h | 12 + lib/nfc/protocols/mifare_desfire.c | 665 +++++++++ lib/nfc/protocols/mifare_desfire.h | 179 +++ lib/nfc/protocols/mifare_ultralight.c | 1946 +++++++++++++++++++++++++ lib/nfc/protocols/mifare_ultralight.h | 269 ++++ lib/nfc/protocols/nfc_util.c | 70 + lib/nfc/protocols/nfc_util.h | 21 + lib/nfc/protocols/nfca.c | 140 ++ lib/nfc/protocols/nfca.h | 31 + lib/nfc/protocols/nfcv.c | 1438 ++++++++++++++++++ lib/nfc/protocols/nfcv.h | 334 +++++ lib/nfc/protocols/slix.c | 784 ++++++++++ lib/nfc/protocols/slix.h | 66 + mifare_fuzzer.c | 56 +- mifare_fuzzer_i.h | 3 +- mifare_fuzzer_worker.c | 94 +- mifare_fuzzer_worker.h | 9 +- scenes/mifare_fuzzer_scene_emulator.c | 90 +- 62 files changed, 15015 insertions(+), 112 deletions(-) create mode 100644 lib/nfc/SConscript create mode 100644 lib/nfc/helpers/mf_classic_dict.c create mode 100644 lib/nfc/helpers/mf_classic_dict.h create mode 100644 lib/nfc/helpers/mfkey32.c create mode 100644 lib/nfc/helpers/mfkey32.h create mode 100644 lib/nfc/helpers/nfc_debug_log.c create mode 100644 lib/nfc/helpers/nfc_debug_log.h create mode 100644 lib/nfc/helpers/nfc_debug_pcap.c create mode 100644 lib/nfc/helpers/nfc_debug_pcap.h create mode 100644 lib/nfc/helpers/nfc_generators.c create mode 100644 lib/nfc/helpers/nfc_generators.h create mode 100644 lib/nfc/helpers/reader_analyzer.c create mode 100644 lib/nfc/helpers/reader_analyzer.h create mode 100644 lib/nfc/nfc_device.c create mode 100644 lib/nfc/nfc_device.h create mode 100644 lib/nfc/nfc_types.c create mode 100644 lib/nfc/nfc_types.h create mode 100644 lib/nfc/nfc_worker.c create mode 100644 lib/nfc/nfc_worker.h create mode 100644 lib/nfc/nfc_worker_i.h create mode 100644 lib/nfc/parsers/all_in_one.c create mode 100644 lib/nfc/parsers/all_in_one.h create mode 100644 lib/nfc/parsers/nfc_supported_card.c create mode 100644 lib/nfc/parsers/nfc_supported_card.h create mode 100644 lib/nfc/parsers/opal.c create mode 100644 lib/nfc/parsers/opal.h create mode 100644 lib/nfc/parsers/plantain_4k_parser.c create mode 100644 lib/nfc/parsers/plantain_4k_parser.h create mode 100644 lib/nfc/parsers/plantain_parser.c create mode 100644 lib/nfc/parsers/plantain_parser.h create mode 100644 lib/nfc/parsers/troika_4k_parser.c create mode 100644 lib/nfc/parsers/troika_4k_parser.h create mode 100644 lib/nfc/parsers/troika_parser.c create mode 100644 lib/nfc/parsers/troika_parser.h create mode 100644 lib/nfc/parsers/two_cities.c create mode 100644 lib/nfc/parsers/two_cities.h create mode 100644 lib/nfc/protocols/crypto1.c create mode 100644 lib/nfc/protocols/crypto1.h create mode 100644 lib/nfc/protocols/emv.c create mode 100644 lib/nfc/protocols/emv.h create mode 100644 lib/nfc/protocols/mifare_classic.c create mode 100644 lib/nfc/protocols/mifare_classic.h create mode 100644 lib/nfc/protocols/mifare_common.c create mode 100644 lib/nfc/protocols/mifare_common.h create mode 100644 lib/nfc/protocols/mifare_desfire.c create mode 100644 lib/nfc/protocols/mifare_desfire.h create mode 100644 lib/nfc/protocols/mifare_ultralight.c create mode 100644 lib/nfc/protocols/mifare_ultralight.h create mode 100644 lib/nfc/protocols/nfc_util.c create mode 100644 lib/nfc/protocols/nfc_util.h create mode 100644 lib/nfc/protocols/nfca.c create mode 100644 lib/nfc/protocols/nfca.h create mode 100644 lib/nfc/protocols/nfcv.c create mode 100644 lib/nfc/protocols/nfcv.h create mode 100644 lib/nfc/protocols/slix.c create mode 100644 lib/nfc/protocols/slix.h diff --git a/application.fam b/application.fam index 40ef491..6d2d83e 100644 --- a/application.fam +++ b/application.fam @@ -4,12 +4,13 @@ App( apptype=FlipperAppType.EXTERNAL, entry_point="mifare_fuzzer_app", requires=[ + "nfc", "storage", "gui", ], stack_size=4 * 1024, order=30, fap_icon="./images/mifare_fuzzer_10px.png", - fap_category="Tools", + fap_category="NFC", fap_icon_assets="images", ) diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript new file mode 100644 index 0000000..7a0859e --- /dev/null +++ b/lib/nfc/SConscript @@ -0,0 +1,25 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/nfc", + ], + SDK_HEADERS=[ + File("nfc_device.h"), + File("nfc_worker.h"), + File("nfc_types.h"), + File("helpers/mfkey32.h"), + File("parsers/nfc_supported_card.h"), + File("helpers/nfc_generators.h"), + File("protocols/nfc_util.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="nfc") +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c*") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/nfc/helpers/mf_classic_dict.c b/lib/nfc/helpers/mf_classic_dict.c new file mode 100644 index 0000000..93098d4 --- /dev/null +++ b/lib/nfc/helpers/mf_classic_dict.c @@ -0,0 +1,346 @@ +#include "mf_classic_dict.h" + +#include +#include + +#define MF_CLASSIC_DICT_FLIPPER_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") +#define MF_CLASSIC_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") +#define MF_CLASSIC_DICT_UNIT_TEST_PATH EXT_PATH("unit_tests/mf_classic_dict.nfc") + +#define TAG "MfClassicDict" + +#define NFC_MF_CLASSIC_KEY_LEN (13) + +struct MfClassicDict { + Stream* stream; + uint32_t total_keys; +}; + +bool mf_classic_dict_check_presence(MfClassicDictType dict_type) { + Storage* storage = furi_record_open(RECORD_STORAGE); + + bool dict_present = false; + if(dict_type == MfClassicDictTypeSystem) { + dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_FLIPPER_PATH, NULL) == FSE_OK; + } else if(dict_type == MfClassicDictTypeUser) { + dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_USER_PATH, NULL) == FSE_OK; + } else if(dict_type == MfClassicDictTypeUnitTest) { + dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_UNIT_TEST_PATH, NULL) == + FSE_OK; + } + + furi_record_close(RECORD_STORAGE); + + return dict_present; +} + +MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type) { + MfClassicDict* dict = malloc(sizeof(MfClassicDict)); + Storage* storage = furi_record_open(RECORD_STORAGE); + dict->stream = buffered_file_stream_alloc(storage); + furi_record_close(RECORD_STORAGE); + + bool dict_loaded = false; + do { + if(dict_type == MfClassicDictTypeSystem) { + if(!buffered_file_stream_open( + dict->stream, + MF_CLASSIC_DICT_FLIPPER_PATH, + FSAM_READ_WRITE, + FSOM_OPEN_EXISTING)) { + buffered_file_stream_close(dict->stream); + break; + } + } else if(dict_type == MfClassicDictTypeUser) { + if(!buffered_file_stream_open( + dict->stream, MF_CLASSIC_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) { + buffered_file_stream_close(dict->stream); + break; + } + } else if(dict_type == MfClassicDictTypeUnitTest) { + if(!buffered_file_stream_open( + dict->stream, + MF_CLASSIC_DICT_UNIT_TEST_PATH, + FSAM_READ_WRITE, + FSOM_OPEN_ALWAYS)) { + buffered_file_stream_close(dict->stream); + break; + } + } + + // Check for new line ending + if(!stream_eof(dict->stream)) { + if(!stream_seek(dict->stream, -1, StreamOffsetFromEnd)) break; + uint8_t last_char = 0; + if(stream_read(dict->stream, &last_char, 1) != 1) break; + if(last_char != '\n') { + FURI_LOG_D(TAG, "Adding new line ending"); + if(stream_write_char(dict->stream, '\n') != 1) break; + } + if(!stream_rewind(dict->stream)) break; + } + + // Read total amount of keys + FuriString* next_line; + next_line = furi_string_alloc(); + while(true) { + if(!stream_read_line(dict->stream, next_line)) { + FURI_LOG_T(TAG, "No keys left in dict"); + break; + } + FURI_LOG_T( + TAG, + "Read line: %s, len: %zu", + furi_string_get_cstr(next_line), + furi_string_size(next_line)); + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + dict->total_keys++; + } + furi_string_free(next_line); + stream_rewind(dict->stream); + + dict_loaded = true; + FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys); + } while(false); + + if(!dict_loaded) { + buffered_file_stream_close(dict->stream); + free(dict); + dict = NULL; + } + + return dict; +} + +void mf_classic_dict_free(MfClassicDict* dict) { + furi_assert(dict); + furi_assert(dict->stream); + + buffered_file_stream_close(dict->stream); + stream_free(dict->stream); + free(dict); +} + +static void mf_classic_dict_int_to_str(uint8_t* key_int, FuriString* key_str) { + furi_string_reset(key_str); + for(size_t i = 0; i < 6; i++) { + furi_string_cat_printf(key_str, "%02X", key_int[i]); + } +} + +static void mf_classic_dict_str_to_int(FuriString* key_str, uint64_t* key_int) { + uint8_t key_byte_tmp; + + *key_int = 0ULL; + for(uint8_t i = 0; i < 12; i += 2) { + args_char_to_hex( + furi_string_get_char(key_str, i), furi_string_get_char(key_str, i + 1), &key_byte_tmp); + *key_int |= (uint64_t)key_byte_tmp << (8 * (5 - i / 2)); + } +} + +uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict) { + furi_assert(dict); + + return dict->total_keys; +} + +bool mf_classic_dict_rewind(MfClassicDict* dict) { + furi_assert(dict); + furi_assert(dict->stream); + + return stream_rewind(dict->stream); +} + +bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, FuriString* key) { + furi_assert(dict); + furi_assert(dict->stream); + + bool key_read = false; + furi_string_reset(key); + while(!key_read) { + if(!stream_read_line(dict->stream, key)) break; + if(furi_string_get_char(key, 0) == '#') continue; + if(furi_string_size(key) != NFC_MF_CLASSIC_KEY_LEN) continue; + furi_string_left(key, 12); + key_read = true; + } + + return key_read; +} + +bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) { + furi_assert(dict); + furi_assert(dict->stream); + + FuriString* temp_key; + temp_key = furi_string_alloc(); + bool key_read = mf_classic_dict_get_next_key_str(dict, temp_key); + if(key_read) { + mf_classic_dict_str_to_int(temp_key, key); + } + furi_string_free(temp_key); + return key_read; +} + +bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, FuriString* key) { + furi_assert(dict); + furi_assert(dict->stream); + + FuriString* next_line; + next_line = furi_string_alloc(); + + bool key_found = false; + stream_rewind(dict->stream); + while(!key_found) { //-V654 + if(!stream_read_line(dict->stream, next_line)) break; + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + furi_string_left(next_line, 12); + if(!furi_string_equal(key, next_line)) continue; + key_found = true; + } + + furi_string_free(next_line); + return key_found; +} + +bool mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key) { + FuriString* temp_key; + + temp_key = furi_string_alloc(); + mf_classic_dict_int_to_str(key, temp_key); + bool key_found = mf_classic_dict_is_key_present_str(dict, temp_key); + furi_string_free(temp_key); + return key_found; +} + +bool mf_classic_dict_add_key_str(MfClassicDict* dict, FuriString* key) { + furi_assert(dict); + furi_assert(dict->stream); + + furi_string_cat_printf(key, "\n"); + + bool key_added = false; + do { + if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break; + if(!stream_insert_string(dict->stream, key)) break; + dict->total_keys++; + key_added = true; + } while(false); + + furi_string_left(key, 12); + return key_added; +} + +bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key) { + furi_assert(dict); + furi_assert(dict->stream); + + FuriString* temp_key; + temp_key = furi_string_alloc(); + mf_classic_dict_int_to_str(key, temp_key); + bool key_added = mf_classic_dict_add_key_str(dict, temp_key); + + furi_string_free(temp_key); + return key_added; +} + +bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, FuriString* key, uint32_t target) { + furi_assert(dict); + furi_assert(dict->stream); + + FuriString* next_line; + uint32_t index = 0; + next_line = furi_string_alloc(); + furi_string_reset(key); + + bool key_found = false; + while(!key_found) { + if(!stream_read_line(dict->stream, next_line)) break; + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + if(index++ != target) continue; + furi_string_set_n(key, next_line, 0, 12); + key_found = true; + } + + furi_string_free(next_line); + return key_found; +} + +bool mf_classic_dict_get_key_at_index(MfClassicDict* dict, uint64_t* key, uint32_t target) { + furi_assert(dict); + furi_assert(dict->stream); + + FuriString* temp_key; + temp_key = furi_string_alloc(); + bool key_found = mf_classic_dict_get_key_at_index_str(dict, temp_key, target); + if(key_found) { + mf_classic_dict_str_to_int(temp_key, key); + } + furi_string_free(temp_key); + return key_found; +} + +bool mf_classic_dict_find_index_str(MfClassicDict* dict, FuriString* key, uint32_t* target) { + furi_assert(dict); + furi_assert(dict->stream); + + FuriString* next_line; + next_line = furi_string_alloc(); + + bool key_found = false; + uint32_t index = 0; + stream_rewind(dict->stream); + while(!key_found) { //-V654 + if(!stream_read_line(dict->stream, next_line)) break; + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + furi_string_left(next_line, 12); + if(!furi_string_equal(key, next_line)) continue; + key_found = true; + *target = index; + } + + furi_string_free(next_line); + return key_found; +} + +bool mf_classic_dict_find_index(MfClassicDict* dict, uint8_t* key, uint32_t* target) { + furi_assert(dict); + furi_assert(dict->stream); + + FuriString* temp_key; + temp_key = furi_string_alloc(); + mf_classic_dict_int_to_str(key, temp_key); + bool key_found = mf_classic_dict_find_index_str(dict, temp_key, target); + + furi_string_free(temp_key); + return key_found; +} + +bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target) { + furi_assert(dict); + furi_assert(dict->stream); + + FuriString* next_line; + next_line = furi_string_alloc(); + uint32_t index = 0; + + bool key_removed = false; + while(!key_removed) { + if(!stream_read_line(dict->stream, next_line)) break; + if(furi_string_get_char(next_line, 0) == '#') continue; + if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + if(index++ != target) continue; + stream_seek(dict->stream, -NFC_MF_CLASSIC_KEY_LEN, StreamOffsetFromCurrent); + if(!stream_delete(dict->stream, NFC_MF_CLASSIC_KEY_LEN)) break; + dict->total_keys--; + key_removed = true; + } + + furi_string_free(next_line); + return key_removed; +} diff --git a/lib/nfc/helpers/mf_classic_dict.h b/lib/nfc/helpers/mf_classic_dict.h new file mode 100644 index 0000000..b798b1c --- /dev/null +++ b/lib/nfc/helpers/mf_classic_dict.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + MfClassicDictTypeUser, + MfClassicDictTypeSystem, + MfClassicDictTypeUnitTest, +} MfClassicDictType; + +typedef struct MfClassicDict MfClassicDict; + +bool mf_classic_dict_check_presence(MfClassicDictType dict_type); + +/** Allocate MfClassicDict instance + * + * @param[in] dict_type The dictionary type + * + * @return MfClassicDict instance + */ +MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type); + +/** Free MfClassicDict instance + * + * @param dict MfClassicDict instance + */ +void mf_classic_dict_free(MfClassicDict* dict); + +/** Get total keys count + * + * @param dict MfClassicDict instance + * + * @return total keys count + */ +uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict); + +/** Rewind to the beginning + * + * @param dict MfClassicDict instance + * + * @return true on success + */ +bool mf_classic_dict_rewind(MfClassicDict* dict); + +bool mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key); + +bool mf_classic_dict_is_key_present_str(MfClassicDict* dict, FuriString* key); + +bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key); + +bool mf_classic_dict_get_next_key_str(MfClassicDict* dict, FuriString* key); + +/** Get key at target offset as uint64_t + * + * @param dict MfClassicDict instance + * @param[out] key Pointer to the uint64_t key + * @param[in] target Target offset from current position + * + * @return true on success + */ +bool mf_classic_dict_get_key_at_index(MfClassicDict* dict, uint64_t* key, uint32_t target); + +/** Get key at target offset as string_t + * + * @param dict MfClassicDict instance + * @param[out] key Found key destination buffer + * @param[in] target Target offset from current position + * + * @return true on success + */ +bool mf_classic_dict_get_key_at_index_str(MfClassicDict* dict, FuriString* key, uint32_t target); + +bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key); + +/** Add string representation of the key + * + * @param dict MfClassicDict instance + * @param[in] key String representation of the key + * + * @return true on success + */ +bool mf_classic_dict_add_key_str(MfClassicDict* dict, FuriString* key); + +bool mf_classic_dict_find_index(MfClassicDict* dict, uint8_t* key, uint32_t* target); + +bool mf_classic_dict_find_index_str(MfClassicDict* dict, FuriString* key, uint32_t* target); + +/** Delete key at target offset + * + * @param dict MfClassicDict instance + * @param[in] target Target offset from current position + * + * @return true on success + */ +bool mf_classic_dict_delete_index(MfClassicDict* dict, uint32_t target); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/helpers/mfkey32.c b/lib/nfc/helpers/mfkey32.c new file mode 100644 index 0000000..47e7e9f --- /dev/null +++ b/lib/nfc/helpers/mfkey32.c @@ -0,0 +1,228 @@ +#include "mfkey32.h" + +#include +#include +#include +#include +#include + +#include +#include + +#define TAG "Mfkey32" + +#define MFKEY32_LOGS_PATH EXT_PATH("nfc/.mfkey32.log") + +typedef enum { + Mfkey32StateIdle, + Mfkey32StateAuthReceived, + Mfkey32StateAuthNtSent, + Mfkey32StateAuthArNrReceived, +} Mfkey32State; + +typedef struct { + uint32_t cuid; + uint8_t sector; + MfClassicKey key; + uint32_t nt0; + uint32_t nr0; + uint32_t ar0; + uint32_t nt1; + uint32_t nr1; + uint32_t ar1; +} Mfkey32Params; + +ARRAY_DEF(Mfkey32Params, Mfkey32Params, M_POD_OPLIST); + +typedef struct { + uint8_t sector; + MfClassicKey key; + uint32_t nt; + uint32_t nr; + uint32_t ar; +} Mfkey32Nonce; + +struct Mfkey32 { + Mfkey32State state; + Stream* file_stream; + Mfkey32Params_t params_arr; + Mfkey32Nonce nonce; + uint32_t cuid; + Mfkey32ParseDataCallback callback; + void* context; +}; + +Mfkey32* mfkey32_alloc(uint32_t cuid) { + Mfkey32* instance = malloc(sizeof(Mfkey32)); + instance->cuid = cuid; + instance->state = Mfkey32StateIdle; + Storage* storage = furi_record_open(RECORD_STORAGE); + instance->file_stream = buffered_file_stream_alloc(storage); + if(!buffered_file_stream_open( + instance->file_stream, MFKEY32_LOGS_PATH, FSAM_WRITE, FSOM_OPEN_APPEND)) { + buffered_file_stream_close(instance->file_stream); + stream_free(instance->file_stream); + free(instance); + instance = NULL; + } else { + Mfkey32Params_init(instance->params_arr); + } + + furi_record_close(RECORD_STORAGE); + + return instance; +} + +void mfkey32_free(Mfkey32* instance) { + furi_assert(instance != NULL); + + Mfkey32Params_clear(instance->params_arr); + buffered_file_stream_close(instance->file_stream); + stream_free(instance->file_stream); + free(instance); +} + +void mfkey32_set_callback(Mfkey32* instance, Mfkey32ParseDataCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static bool mfkey32_write_params(Mfkey32* instance, Mfkey32Params* params) { + FuriString* str = furi_string_alloc_printf( + "Sec %d key %c cuid %08lx nt0 %08lx nr0 %08lx ar0 %08lx nt1 %08lx nr1 %08lx ar1 %08lx\n", + params->sector, + params->key == MfClassicKeyA ? 'A' : 'B', + params->cuid, + params->nt0, + params->nr0, + params->ar0, + params->nt1, + params->nr1, + params->ar1); + bool write_success = stream_write_string(instance->file_stream, str); + furi_string_free(str); + return write_success; +} + +static void mfkey32_add_params(Mfkey32* instance) { + Mfkey32Nonce* nonce = &instance->nonce; + bool nonce_added = false; + // Search if we partially collected params + if(Mfkey32Params_size(instance->params_arr)) { + Mfkey32Params_it_t it; + for(Mfkey32Params_it(it, instance->params_arr); !Mfkey32Params_end_p(it); + Mfkey32Params_next(it)) { + Mfkey32Params* params = Mfkey32Params_ref(it); + if((params->sector == nonce->sector) && (params->key == nonce->key)) { + params->nt1 = nonce->nt; + params->nr1 = nonce->nr; + params->ar1 = nonce->ar; + nonce_added = true; + FURI_LOG_I( + TAG, + "Params for sector %d key %c collected", + params->sector, + params->key == MfClassicKeyA ? 'A' : 'B'); + // Write on sd card + if(mfkey32_write_params(instance, params)) { + Mfkey32Params_remove(instance->params_arr, it); + if(instance->callback) { + instance->callback(Mfkey32EventParamCollected, instance->context); + } + } + } + } + } + if(!nonce_added) { + Mfkey32Params params = { + .sector = nonce->sector, + .key = nonce->key, + .cuid = instance->cuid, + .nt0 = nonce->nt, + .nr0 = nonce->nr, + .ar0 = nonce->ar, + }; + Mfkey32Params_push_back(instance->params_arr, params); + } +} + +void mfkey32_process_data( + Mfkey32* instance, + uint8_t* data, + uint16_t len, + bool reader_to_tag, + bool crc_dropped) { + furi_assert(instance); + furi_assert(data); + + Mfkey32Nonce* nonce = &instance->nonce; + uint16_t data_len = len; + if((data_len > 3) && !crc_dropped) { + data_len -= 2; + } + + bool data_processed = false; + if(instance->state == Mfkey32StateIdle) { + if(reader_to_tag) { + if((data[0] == 0x60) || (data[0] == 0x61)) { + nonce->key = data[0] == 0x60 ? MfClassicKeyA : MfClassicKeyB; + nonce->sector = mf_classic_get_sector_by_block(data[1]); + instance->state = Mfkey32StateAuthReceived; + data_processed = true; + } + } + } else if(instance->state == Mfkey32StateAuthReceived) { + if(!reader_to_tag) { + if(len == 4) { + nonce->nt = nfc_util_bytes2num(data, 4); + instance->state = Mfkey32StateAuthNtSent; + data_processed = true; + } + } + } else if(instance->state == Mfkey32StateAuthNtSent) { + if(reader_to_tag) { + if(len == 8) { + nonce->nr = nfc_util_bytes2num(data, 4); + nonce->ar = nfc_util_bytes2num(&data[4], 4); + mfkey32_add_params(instance); + instance->state = Mfkey32StateIdle; + } + } + } + if(!data_processed) { + instance->state = Mfkey32StateIdle; + } +} + +uint16_t mfkey32_get_auth_sectors(FuriString* data_str) { + furi_assert(data_str); + + uint16_t nonces_num = 0; + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* file_stream = buffered_file_stream_alloc(storage); + FuriString* temp_str; + temp_str = furi_string_alloc(); + + do { + if(!buffered_file_stream_open( + file_stream, MFKEY32_LOGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) + break; + while(true) { + if(!stream_read_line(file_stream, temp_str)) break; + size_t uid_pos = furi_string_search(temp_str, "cuid"); + furi_string_left(temp_str, uid_pos); + furi_string_push_back(temp_str, '\n'); + furi_string_cat(data_str, temp_str); + nonces_num++; + } + } while(false); + + buffered_file_stream_close(file_stream); + stream_free(file_stream); + furi_string_free(temp_str); + + return nonces_num; +} diff --git a/lib/nfc/helpers/mfkey32.h b/lib/nfc/helpers/mfkey32.h new file mode 100644 index 0000000..e290432 --- /dev/null +++ b/lib/nfc/helpers/mfkey32.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct Mfkey32 Mfkey32; + +typedef enum { + Mfkey32EventParamCollected, +} Mfkey32Event; + +typedef void (*Mfkey32ParseDataCallback)(Mfkey32Event event, void* context); + +Mfkey32* mfkey32_alloc(uint32_t cuid); + +void mfkey32_free(Mfkey32* instance); + +void mfkey32_process_data( + Mfkey32* instance, + uint8_t* data, + uint16_t len, + bool reader_to_tag, + bool crc_dropped); + +void mfkey32_set_callback(Mfkey32* instance, Mfkey32ParseDataCallback callback, void* context); + +uint16_t mfkey32_get_auth_sectors(FuriString* string); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/helpers/nfc_debug_log.c b/lib/nfc/helpers/nfc_debug_log.c new file mode 100644 index 0000000..0bfbc2c --- /dev/null +++ b/lib/nfc/helpers/nfc_debug_log.c @@ -0,0 +1,71 @@ +#include "nfc_debug_log.h" + +#include +#include + +#define TAG "NfcDebugLog" + +#define NFC_DEBUG_PCAP_FILENAME EXT_PATH("nfc/debug.txt") + +struct NfcDebugLog { + Stream* file_stream; + FuriString* data_str; +}; + +NfcDebugLog* nfc_debug_log_alloc() { + NfcDebugLog* instance = malloc(sizeof(NfcDebugLog)); + + Storage* storage = furi_record_open(RECORD_STORAGE); + instance->file_stream = buffered_file_stream_alloc(storage); + + if(!buffered_file_stream_open( + instance->file_stream, NFC_DEBUG_PCAP_FILENAME, FSAM_WRITE, FSOM_OPEN_APPEND)) { + buffered_file_stream_close(instance->file_stream); + stream_free(instance->file_stream); + instance->file_stream = NULL; + } + + if(!instance->file_stream) { + free(instance); + instance = NULL; + } else { + instance->data_str = furi_string_alloc(); + } + furi_record_close(RECORD_STORAGE); + + return instance; +} + +void nfc_debug_log_free(NfcDebugLog* instance) { + furi_assert(instance); + furi_assert(instance->file_stream); + furi_assert(instance->data_str); + + buffered_file_stream_close(instance->file_stream); + stream_free(instance->file_stream); + furi_string_free(instance->data_str); + + free(instance); +} + +void nfc_debug_log_process_data( + NfcDebugLog* instance, + uint8_t* data, + uint16_t len, + bool reader_to_tag, + bool crc_dropped) { + furi_assert(instance); + furi_assert(instance->file_stream); + furi_assert(instance->data_str); + furi_assert(data); + UNUSED(crc_dropped); + + furi_string_printf(instance->data_str, "%lu %c:", furi_get_tick(), reader_to_tag ? 'R' : 'T'); + uint16_t data_len = len; + for(size_t i = 0; i < data_len; i++) { + furi_string_cat_printf(instance->data_str, " %02x", data[i]); + } + furi_string_push_back(instance->data_str, '\n'); + + stream_write_string(instance->file_stream, instance->data_str); +} diff --git a/lib/nfc/helpers/nfc_debug_log.h b/lib/nfc/helpers/nfc_debug_log.h new file mode 100644 index 0000000..261b800 --- /dev/null +++ b/lib/nfc/helpers/nfc_debug_log.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +typedef struct NfcDebugLog NfcDebugLog; + +NfcDebugLog* nfc_debug_log_alloc(); + +void nfc_debug_log_free(NfcDebugLog* instance); + +void nfc_debug_log_process_data( + NfcDebugLog* instance, + uint8_t* data, + uint16_t len, + bool reader_to_tag, + bool crc_dropped); diff --git a/lib/nfc/helpers/nfc_debug_pcap.c b/lib/nfc/helpers/nfc_debug_pcap.c new file mode 100644 index 0000000..6c7654a --- /dev/null +++ b/lib/nfc/helpers/nfc_debug_pcap.c @@ -0,0 +1,130 @@ +#include "nfc_debug_pcap.h" + +#include +#include +#include +#include + +#define TAG "NfcDebugPcap" + +#define PCAP_MAGIC 0xa1b2c3d4 +#define PCAP_MAJOR 2 +#define PCAP_MINOR 4 +#define DLT_ISO_14443 264 + +#define DATA_PICC_TO_PCD 0xFF +#define DATA_PCD_TO_PICC 0xFE +#define DATA_PICC_TO_PCD_CRC_DROPPED 0xFB +#define DATA_PCD_TO_PICC_CRC_DROPPED 0xFA + +#define NFC_DEBUG_PCAP_FILENAME EXT_PATH("nfc/debug.pcap") + +struct NfcDebugPcap { + Stream* file_stream; +}; + +static Stream* nfc_debug_pcap_open(Storage* storage) { + Stream* stream = NULL; + stream = buffered_file_stream_alloc(storage); + if(!buffered_file_stream_open(stream, NFC_DEBUG_PCAP_FILENAME, FSAM_WRITE, FSOM_OPEN_APPEND)) { + buffered_file_stream_close(stream); + stream_free(stream); + stream = NULL; + } else { + if(!stream_tell(stream)) { + struct { + uint32_t magic; + uint16_t major, minor; + uint32_t reserved[2]; + uint32_t snaplen; + uint32_t link_type; + } __attribute__((__packed__)) pcap_hdr = { + .magic = PCAP_MAGIC, + .major = PCAP_MAJOR, + .minor = PCAP_MINOR, + .snaplen = FURI_HAL_NFC_DATA_BUFF_SIZE, + .link_type = DLT_ISO_14443, + }; + if(stream_write(stream, (uint8_t*)&pcap_hdr, sizeof(pcap_hdr)) != sizeof(pcap_hdr)) { + FURI_LOG_E(TAG, "Failed to write pcap header"); + buffered_file_stream_close(stream); + stream_free(stream); + stream = NULL; + } + } + } + return stream; +} + +NfcDebugPcap* nfc_debug_pcap_alloc() { + NfcDebugPcap* instance = malloc(sizeof(NfcDebugPcap)); + + Storage* storage = furi_record_open(RECORD_STORAGE); + instance->file_stream = nfc_debug_pcap_open(storage); + if(!instance->file_stream) { + free(instance); + instance = NULL; + } + furi_record_close(RECORD_STORAGE); + + return instance; +} + +void nfc_debug_pcap_free(NfcDebugPcap* instance) { + furi_assert(instance); + furi_assert(instance->file_stream); + + buffered_file_stream_close(instance->file_stream); + stream_free(instance->file_stream); + + free(instance); +} + +void nfc_debug_pcap_process_data( + NfcDebugPcap* instance, + uint8_t* data, + uint16_t len, + bool reader_to_tag, + bool crc_dropped) { + furi_assert(instance); + furi_assert(data); + FuriHalRtcDateTime datetime; + furi_hal_rtc_get_datetime(&datetime); + + uint8_t event = 0; + if(reader_to_tag) { + if(crc_dropped) { + event = DATA_PCD_TO_PICC_CRC_DROPPED; + } else { + event = DATA_PCD_TO_PICC; + } + } else { + if(crc_dropped) { + event = DATA_PICC_TO_PCD_CRC_DROPPED; + } else { + event = DATA_PICC_TO_PCD; + } + } + + struct { + // https://wiki.wireshark.org/Development/LibpcapFileFormat#record-packet-header + uint32_t ts_sec; + uint32_t ts_usec; + uint32_t incl_len; + uint32_t orig_len; + // https://www.kaiser.cx/posts/pcap-iso14443/#_packet_data + uint8_t version; + uint8_t event; + uint16_t len; + } __attribute__((__packed__)) pkt_hdr = { + .ts_sec = furi_hal_rtc_datetime_to_timestamp(&datetime), + .ts_usec = 0, + .incl_len = len + 4, + .orig_len = len + 4, + .version = 0, + .event = event, + .len = len << 8 | len >> 8, + }; + stream_write(instance->file_stream, (uint8_t*)&pkt_hdr, sizeof(pkt_hdr)); + stream_write(instance->file_stream, data, len); +} diff --git a/lib/nfc/helpers/nfc_debug_pcap.h b/lib/nfc/helpers/nfc_debug_pcap.h new file mode 100644 index 0000000..eeddc56 --- /dev/null +++ b/lib/nfc/helpers/nfc_debug_pcap.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +typedef struct NfcDebugPcap NfcDebugPcap; + +NfcDebugPcap* nfc_debug_pcap_alloc(); + +void nfc_debug_pcap_free(NfcDebugPcap* instance); + +void nfc_debug_pcap_process_data( + NfcDebugPcap* instance, + uint8_t* data, + uint16_t len, + bool reader_to_tag, + bool crc_dropped); diff --git a/lib/nfc/helpers/nfc_generators.c b/lib/nfc/helpers/nfc_generators.c new file mode 100644 index 0000000..50c89ab --- /dev/null +++ b/lib/nfc/helpers/nfc_generators.c @@ -0,0 +1,528 @@ +#include +#include "nfc_generators.h" + +#define NXP_MANUFACTURER_ID (0x04) + +static const uint8_t version_bytes_mf0ulx1[] = {0x00, 0x04, 0x03, 0x00, 0x01, 0x00, 0x00, 0x03}; +static const uint8_t version_bytes_ntag21x[] = {0x00, 0x04, 0x04, 0x02, 0x01, 0x00, 0x00, 0x03}; +static const uint8_t version_bytes_ntag_i2c[] = {0x00, 0x04, 0x04, 0x05, 0x02, 0x00, 0x00, 0x03}; +static const uint8_t default_data_ntag203[] = + {0xE1, 0x10, 0x12, 0x00, 0x01, 0x03, 0xA0, 0x10, 0x44, 0x03, 0x00, 0xFE}; +static const uint8_t default_data_ntag213[] = {0x01, 0x03, 0xA0, 0x0C, 0x34, 0x03, 0x00, 0xFE}; +static const uint8_t default_data_ntag215_216[] = {0x03, 0x00, 0xFE}; +static const uint8_t default_data_ntag_i2c[] = {0xE1, 0x10, 0x00, 0x00, 0x03, 0x00, 0xFE}; +static const uint8_t default_config_ntag_i2c[] = {0x01, 0x00, 0xF8, 0x48, 0x08, 0x01, 0x00, 0x00}; + +static void nfc_generate_common_start(NfcDeviceData* data) { + nfc_device_data_clear(data); +} + +static void nfc_generate_mf_ul_uid(uint8_t* uid) { + uid[0] = NXP_MANUFACTURER_ID; + furi_hal_random_fill_buf(&uid[1], 6); + // I'm not sure how this is generated, but the upper nybble always seems to be 8 + uid[6] &= 0x0F; + uid[6] |= 0x80; +} + +static void nfc_generate_mf_classic_uid(uint8_t* uid, uint8_t length) { + uid[0] = NXP_MANUFACTURER_ID; + furi_hal_random_fill_buf(&uid[1], length - 1); +} + +static void nfc_generate_mf_classic_block_0( + uint8_t* block, + uint8_t uid_len, + uint8_t sak, + uint8_t atqa0, + uint8_t atqa1) { + // Block length is always 16 bytes, and the UID can be either 4 or 7 bytes + furi_assert(uid_len == 4 || uid_len == 7); + furi_assert(block); + + if(uid_len == 4) { + // Calculate BCC + block[uid_len] = 0; + + for(int i = 0; i < uid_len; i++) { + block[uid_len] ^= block[i]; + } + } else { + uid_len -= 1; + } + + block[uid_len + 1] = sak; + block[uid_len + 2] = atqa0; + block[uid_len + 3] = atqa1; + + for(int i = uid_len + 4; i < 16; i++) { + block[i] = 0xFF; + } +} + +static void nfc_generate_mf_classic_sector_trailer(MfClassicData* data, uint8_t block) { + // All keys are set to FFFF FFFF FFFFh at chip delivery and the bytes 6, 7 and 8 are set to FF0780h. + MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)data->block[block].value; + sec_tr->access_bits[0] = 0xFF; + sec_tr->access_bits[1] = 0x07; + sec_tr->access_bits[2] = 0x80; + sec_tr->access_bits[3] = 0x69; // Nice + + memset(sec_tr->key_a, 0xff, sizeof(sec_tr->key_a)); + memset(sec_tr->key_b, 0xff, sizeof(sec_tr->key_b)); + + mf_classic_set_block_read(data, block, &data->block[block]); + mf_classic_set_key_found( + data, mf_classic_get_sector_by_block(block), MfClassicKeyA, 0xFFFFFFFFFFFF); + mf_classic_set_key_found( + data, mf_classic_get_sector_by_block(block), MfClassicKeyB, 0xFFFFFFFFFFFF); +} + +static void nfc_generate_mf_ul_common(NfcDeviceData* data) { + data->nfc_data.type = FuriHalNfcTypeA; + data->nfc_data.interface = FuriHalNfcInterfaceRf; + data->nfc_data.uid_len = 7; + nfc_generate_mf_ul_uid(data->nfc_data.uid); + data->nfc_data.atqa[0] = 0x44; + data->nfc_data.atqa[1] = 0x00; + data->nfc_data.sak = 0x00; + data->protocol = NfcDeviceProtocolMifareUl; +} + +static void + nfc_generate_mf_classic_common(NfcDeviceData* data, uint8_t uid_len, MfClassicType type) { + data->nfc_data.type = FuriHalNfcTypeA; + data->nfc_data.interface = FuriHalNfcInterfaceRf; + data->nfc_data.uid_len = uid_len; + data->nfc_data.atqa[0] = 0x44; + data->nfc_data.atqa[1] = 0x00; + data->nfc_data.sak = 0x08; + data->protocol = NfcDeviceProtocolMifareClassic; + data->mf_classic_data.type = type; +} + +static void nfc_generate_calc_bcc(uint8_t* uid, uint8_t* bcc0, uint8_t* bcc1) { + *bcc0 = 0x88 ^ uid[0] ^ uid[1] ^ uid[2]; + *bcc1 = uid[3] ^ uid[4] ^ uid[5] ^ uid[6]; +} + +static void nfc_generate_mf_ul_copy_uid_with_bcc(NfcDeviceData* data) { + MfUltralightData* mful = &data->mf_ul_data; + memcpy(mful->data, data->nfc_data.uid, 3); + memcpy(&mful->data[4], &data->nfc_data.uid[3], 4); + nfc_generate_calc_bcc(data->nfc_data.uid, &mful->data[3], &mful->data[8]); +} + +static void nfc_generate_mf_ul_orig(NfcDeviceData* data) { + nfc_generate_common_start(data); + nfc_generate_mf_ul_common(data); + + MfUltralightData* mful = &data->mf_ul_data; + mful->type = MfUltralightTypeUnknown; + mful->data_size = 16 * 4; + mful->data_read = mful->data_size; + nfc_generate_mf_ul_copy_uid_with_bcc(data); + // TODO: what's internal byte on page 2? + memset(&mful->data[4 * 4], 0xFF, 4); +} + +static void nfc_generate_mf_ul_ntag203(NfcDeviceData* data) { + nfc_generate_common_start(data); + nfc_generate_mf_ul_common(data); + + MfUltralightData* mful = &data->mf_ul_data; + mful->type = MfUltralightTypeNTAG203; + mful->data_size = 42 * 4; + mful->data_read = mful->data_size; + nfc_generate_mf_ul_copy_uid_with_bcc(data); + mful->data[9] = 0x48; // Internal byte + memcpy(&mful->data[3 * 4], default_data_ntag203, sizeof(default_data_ntag203)); +} + +static void nfc_generate_mf_ul_with_config_common(NfcDeviceData* data, uint8_t num_pages) { + nfc_generate_common_start(data); + nfc_generate_mf_ul_common(data); + + MfUltralightData* mful = &data->mf_ul_data; + mful->data_size = num_pages * 4; + mful->data_read = mful->data_size; + nfc_generate_mf_ul_copy_uid_with_bcc(data); + uint16_t config_index = (num_pages - 4) * 4; + mful->data[config_index] = 0x04; // STRG_MOD_EN + mful->data[config_index + 3] = 0xFF; // AUTH0 + mful->data[config_index + 5] = 0x05; // VCTID + memset(&mful->data[config_index + 8], 0xFF, 4); // Default PWD + if(num_pages > 20) mful->data[config_index - 1] = MF_UL_TEARING_FLAG_DEFAULT; +} + +static void nfc_generate_mf_ul_ev1_common(NfcDeviceData* data, uint8_t num_pages) { + nfc_generate_mf_ul_with_config_common(data, num_pages); + MfUltralightData* mful = &data->mf_ul_data; + memcpy(&mful->version, version_bytes_mf0ulx1, sizeof(version_bytes_mf0ulx1)); + for(size_t i = 0; i < 3; ++i) { + mful->tearing[i] = MF_UL_TEARING_FLAG_DEFAULT; + } + // TODO: what's internal byte on page 2? +} + +static void nfc_generate_mf_ul_11(NfcDeviceData* data) { + nfc_generate_mf_ul_ev1_common(data, 20); + MfUltralightData* mful = &data->mf_ul_data; + mful->type = MfUltralightTypeUL11; + mful->version.prod_subtype = 0x01; + mful->version.storage_size = 0x0B; + mful->data[16 * 4] = 0x00; // Low capacitance version does not have STRG_MOD_EN +} + +static void nfc_generate_mf_ul_h11(NfcDeviceData* data) { + nfc_generate_mf_ul_ev1_common(data, 20); + MfUltralightData* mful = &data->mf_ul_data; + mful->type = MfUltralightTypeUL11; + mful->version.prod_subtype = 0x02; + mful->version.storage_size = 0x0B; +} + +static void nfc_generate_mf_ul_21(NfcDeviceData* data) { + nfc_generate_mf_ul_ev1_common(data, 41); + MfUltralightData* mful = &data->mf_ul_data; + mful->type = MfUltralightTypeUL21; + mful->version.prod_subtype = 0x01; + mful->version.storage_size = 0x0E; + mful->data[37 * 4] = 0x00; // Low capacitance version does not have STRG_MOD_EN +} + +static void nfc_generate_mf_ul_h21(NfcDeviceData* data) { + nfc_generate_mf_ul_ev1_common(data, 41); + MfUltralightData* mful = &data->mf_ul_data; + mful->type = MfUltralightTypeUL21; + mful->version.prod_subtype = 0x02; + mful->version.storage_size = 0x0E; +} + +static void nfc_generate_ntag21x_common(NfcDeviceData* data, uint8_t num_pages) { + nfc_generate_mf_ul_with_config_common(data, num_pages); + MfUltralightData* mful = &data->mf_ul_data; + memcpy(&mful->version, version_bytes_ntag21x, sizeof(version_bytes_mf0ulx1)); + mful->data[9] = 0x48; // Internal byte + // Capability container + mful->data[12] = 0xE1; + mful->data[13] = 0x10; +} + +static void nfc_generate_ntag213(NfcDeviceData* data) { + nfc_generate_ntag21x_common(data, 45); + MfUltralightData* mful = &data->mf_ul_data; + mful->type = MfUltralightTypeNTAG213; + mful->version.storage_size = 0x0F; + mful->data[14] = 0x12; + // Default contents + memcpy(&mful->data[16], default_data_ntag213, sizeof(default_data_ntag213)); +} + +static void nfc_generate_ntag215(NfcDeviceData* data) { + nfc_generate_ntag21x_common(data, 135); + MfUltralightData* mful = &data->mf_ul_data; + mful->type = MfUltralightTypeNTAG215; + mful->version.storage_size = 0x11; + mful->data[14] = 0x3E; + // Default contents + memcpy(&mful->data[16], default_data_ntag215_216, sizeof(default_data_ntag215_216)); +} + +static void nfc_generate_ntag216(NfcDeviceData* data) { + nfc_generate_ntag21x_common(data, 231); + MfUltralightData* mful = &data->mf_ul_data; + mful->type = MfUltralightTypeNTAG216; + mful->version.storage_size = 0x13; + mful->data[14] = 0x6D; + // Default contents + memcpy(&mful->data[16], default_data_ntag215_216, sizeof(default_data_ntag215_216)); +} + +static void + nfc_generate_ntag_i2c_common(NfcDeviceData* data, MfUltralightType type, uint16_t num_pages) { + nfc_generate_common_start(data); + nfc_generate_mf_ul_common(data); + + MfUltralightData* mful = &data->mf_ul_data; + mful->type = type; + memcpy(&mful->version, version_bytes_ntag_i2c, sizeof(version_bytes_ntag_i2c)); + mful->data_size = num_pages * 4; + mful->data_read = mful->data_size; + memcpy(mful->data, data->nfc_data.uid, data->nfc_data.uid_len); + mful->data[7] = data->nfc_data.sak; + mful->data[8] = data->nfc_data.atqa[0]; + mful->data[9] = data->nfc_data.atqa[1]; + + uint16_t config_register_page; + uint16_t session_register_page; + + // Sync with mifare_ultralight.c + switch(type) { + case MfUltralightTypeNTAGI2C1K: + config_register_page = 227; + session_register_page = 229; + break; + case MfUltralightTypeNTAGI2C2K: + config_register_page = 481; + session_register_page = 483; + break; + case MfUltralightTypeNTAGI2CPlus1K: + case MfUltralightTypeNTAGI2CPlus2K: + config_register_page = 232; + session_register_page = 234; + break; + default: + furi_crash("Unknown MFUL"); + break; + } + + memcpy( + &mful->data[config_register_page * 4], + default_config_ntag_i2c, + sizeof(default_config_ntag_i2c)); + memcpy( + &mful->data[session_register_page * 4], + default_config_ntag_i2c, + sizeof(default_config_ntag_i2c)); +} + +static void nfc_generate_ntag_i2c_1k(NfcDeviceData* data) { + nfc_generate_ntag_i2c_common(data, MfUltralightTypeNTAGI2C1K, 231); + MfUltralightData* mful = &data->mf_ul_data; + mful->version.prod_ver_minor = 0x01; + mful->version.storage_size = 0x13; + + memcpy(&mful->data[12], default_data_ntag_i2c, sizeof(default_data_ntag_i2c)); + mful->data[14] = 0x6D; // Size of tag in CC +} + +static void nfc_generate_ntag_i2c_2k(NfcDeviceData* data) { + nfc_generate_ntag_i2c_common(data, MfUltralightTypeNTAGI2C2K, 485); + MfUltralightData* mful = &data->mf_ul_data; + mful->version.prod_ver_minor = 0x01; + mful->version.storage_size = 0x15; + + memcpy(&mful->data[12], default_data_ntag_i2c, sizeof(default_data_ntag_i2c)); + mful->data[14] = 0xEA; // Size of tag in CC +} + +static void nfc_generate_ntag_i2c_plus_common( + NfcDeviceData* data, + MfUltralightType type, + uint16_t num_pages) { + nfc_generate_ntag_i2c_common(data, type, num_pages); + + MfUltralightData* mful = &data->mf_ul_data; + uint16_t config_index = 227 * 4; + mful->data[config_index + 3] = 0xFF; // AUTH0 + memset(&mful->data[config_index + 8], 0xFF, 4); // Default PWD +} + +static void nfc_generate_ntag_i2c_plus_1k(NfcDeviceData* data) { + nfc_generate_ntag_i2c_plus_common(data, MfUltralightTypeNTAGI2CPlus1K, 236); + MfUltralightData* mful = &data->mf_ul_data; + mful->version.prod_ver_minor = 0x02; + mful->version.storage_size = 0x13; +} + +static void nfc_generate_ntag_i2c_plus_2k(NfcDeviceData* data) { + nfc_generate_ntag_i2c_plus_common(data, MfUltralightTypeNTAGI2CPlus2K, 492); + MfUltralightData* mful = &data->mf_ul_data; + mful->version.prod_ver_minor = 0x02; + mful->version.storage_size = 0x15; +} + +void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType type) { + nfc_generate_common_start(data); + nfc_generate_mf_classic_uid(data->mf_classic_data.block[0].value, uid_len); + nfc_generate_mf_classic_common(data, uid_len, type); + + // Set the UID + data->nfc_data.uid[0] = NXP_MANUFACTURER_ID; + for(int i = 1; i < uid_len; i++) { + data->nfc_data.uid[i] = data->mf_classic_data.block[0].value[i]; + } + + MfClassicData* mfc = &data->mf_classic_data; + mf_classic_set_block_read(mfc, 0, &mfc->block[0]); + + if(type == MfClassicType4k) { + // Set every block to 0xFF + for(uint16_t i = 1; i < 256; i += 1) { + if(mf_classic_is_sector_trailer(i)) { + nfc_generate_mf_classic_sector_trailer(mfc, i); + } else { + memset(&mfc->block[i].value, 0xFF, 16); + } + mf_classic_set_block_read(mfc, i, &mfc->block[i]); + } + // Set SAK to 18 + data->nfc_data.sak = 0x18; + } else if(type == MfClassicType1k) { + // Set every block to 0xFF + for(uint16_t i = 1; i < MF_CLASSIC_1K_TOTAL_SECTORS_NUM * 4; i += 1) { + if(mf_classic_is_sector_trailer(i)) { + nfc_generate_mf_classic_sector_trailer(mfc, i); + } else { + memset(&mfc->block[i].value, 0xFF, 16); + } + mf_classic_set_block_read(mfc, i, &mfc->block[i]); + } + // Set SAK to 08 + data->nfc_data.sak = 0x08; + } else if(type == MfClassicTypeMini) { + // Set every block to 0xFF + for(uint16_t i = 1; i < MF_MINI_TOTAL_SECTORS_NUM * 4; i += 1) { + if(mf_classic_is_sector_trailer(i)) { + nfc_generate_mf_classic_sector_trailer(mfc, i); + } else { + memset(&mfc->block[i].value, 0xFF, 16); + } + mf_classic_set_block_read(mfc, i, &mfc->block[i]); + } + // Set SAK to 09 + data->nfc_data.sak = 0x09; + } + + nfc_generate_mf_classic_block_0( + data->mf_classic_data.block[0].value, + uid_len, + data->nfc_data.sak, + data->nfc_data.atqa[0], + data->nfc_data.atqa[1]); + + mfc->type = type; +} + +static void nfc_generate_mf_mini(NfcDeviceData* data) { + nfc_generate_mf_classic(data, 4, MfClassicTypeMini); +} + +static void nfc_generate_mf_classic_1k_4b_uid(NfcDeviceData* data) { + nfc_generate_mf_classic(data, 4, MfClassicType1k); +} + +static void nfc_generate_mf_classic_1k_7b_uid(NfcDeviceData* data) { + nfc_generate_mf_classic(data, 7, MfClassicType1k); +} + +static void nfc_generate_mf_classic_4k_4b_uid(NfcDeviceData* data) { + nfc_generate_mf_classic(data, 4, MfClassicType4k); +} + +static void nfc_generate_mf_classic_4k_7b_uid(NfcDeviceData* data) { + nfc_generate_mf_classic(data, 7, MfClassicType4k); +} + +static const NfcGenerator mf_ul_generator = { + .name = "Mifare Ultralight", + .generator_func = nfc_generate_mf_ul_orig, +}; + +static const NfcGenerator mf_ul_11_generator = { + .name = "Mifare Ultralight EV1 11", + .generator_func = nfc_generate_mf_ul_11, +}; + +static const NfcGenerator mf_ul_h11_generator = { + .name = "Mifare Ultralight EV1 H11", + .generator_func = nfc_generate_mf_ul_h11, +}; + +static const NfcGenerator mf_ul_21_generator = { + .name = "Mifare Ultralight EV1 21", + .generator_func = nfc_generate_mf_ul_21, +}; + +static const NfcGenerator mf_ul_h21_generator = { + .name = "Mifare Ultralight EV1 H21", + .generator_func = nfc_generate_mf_ul_h21, +}; + +static const NfcGenerator ntag203_generator = { + .name = "NTAG203", + .generator_func = nfc_generate_mf_ul_ntag203, +}; + +static const NfcGenerator ntag213_generator = { + .name = "NTAG213", + .generator_func = nfc_generate_ntag213, +}; + +static const NfcGenerator ntag215_generator = { + .name = "NTAG215", + .generator_func = nfc_generate_ntag215, +}; + +static const NfcGenerator ntag216_generator = { + .name = "NTAG216", + .generator_func = nfc_generate_ntag216, +}; + +static const NfcGenerator ntag_i2c_1k_generator = { + .name = "NTAG I2C 1k", + .generator_func = nfc_generate_ntag_i2c_1k, +}; + +static const NfcGenerator ntag_i2c_2k_generator = { + .name = "NTAG I2C 2k", + .generator_func = nfc_generate_ntag_i2c_2k, +}; + +static const NfcGenerator ntag_i2c_plus_1k_generator = { + .name = "NTAG I2C Plus 1k", + .generator_func = nfc_generate_ntag_i2c_plus_1k, +}; + +static const NfcGenerator ntag_i2c_plus_2k_generator = { + .name = "NTAG I2C Plus 2k", + .generator_func = nfc_generate_ntag_i2c_plus_2k, +}; + +static const NfcGenerator mifare_mini_generator = { + .name = "Mifare Mini", + .generator_func = nfc_generate_mf_mini, +}; + +static const NfcGenerator mifare_classic_1k_4b_uid_generator = { + .name = "Mifare Classic 1k 4byte UID", + .generator_func = nfc_generate_mf_classic_1k_4b_uid, +}; + +static const NfcGenerator mifare_classic_1k_7b_uid_generator = { + .name = "Mifare Classic 1k 7byte UID", + .generator_func = nfc_generate_mf_classic_1k_7b_uid, +}; + +static const NfcGenerator mifare_classic_4k_4b_uid_generator = { + .name = "Mifare Classic 4k 4byte UID", + .generator_func = nfc_generate_mf_classic_4k_4b_uid, +}; + +static const NfcGenerator mifare_classic_4k_7b_uid_generator = { + .name = "Mifare Classic 4k 7byte UID", + .generator_func = nfc_generate_mf_classic_4k_7b_uid, +}; + +const NfcGenerator* const nfc_generators[] = { + &mf_ul_generator, + &mf_ul_11_generator, + &mf_ul_h11_generator, + &mf_ul_21_generator, + &mf_ul_h21_generator, + &ntag203_generator, + &ntag213_generator, + &ntag215_generator, + &ntag216_generator, + &ntag_i2c_1k_generator, + &ntag_i2c_2k_generator, + &ntag_i2c_plus_1k_generator, + &ntag_i2c_plus_2k_generator, + &mifare_mini_generator, + &mifare_classic_1k_4b_uid_generator, + &mifare_classic_1k_7b_uid_generator, + &mifare_classic_4k_4b_uid_generator, + &mifare_classic_4k_7b_uid_generator, + NULL, +}; diff --git a/lib/nfc/helpers/nfc_generators.h b/lib/nfc/helpers/nfc_generators.h new file mode 100644 index 0000000..8cee670 --- /dev/null +++ b/lib/nfc/helpers/nfc_generators.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../nfc_device.h" + +typedef void (*NfcGeneratorFunc)(NfcDeviceData* data); + +typedef struct { + const char* name; + NfcGeneratorFunc generator_func; +} NfcGenerator; + +extern const NfcGenerator* const nfc_generators[]; + +void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType type); diff --git a/lib/nfc/helpers/reader_analyzer.c b/lib/nfc/helpers/reader_analyzer.c new file mode 100644 index 0000000..9bf37a6 --- /dev/null +++ b/lib/nfc/helpers/reader_analyzer.c @@ -0,0 +1,265 @@ +#include "reader_analyzer.h" +#include +#include +#include + +#include "mfkey32.h" +#include "nfc_debug_pcap.h" +#include "nfc_debug_log.h" + +#define TAG "ReaderAnalyzer" + +#define READER_ANALYZER_MAX_BUFF_SIZE (1024) + +typedef struct { + bool reader_to_tag; + bool crc_dropped; + uint16_t len; +} ReaderAnalyzerHeader; + +typedef enum { + ReaderAnalyzerNfcDataMfClassic, +} ReaderAnalyzerNfcData; + +struct ReaderAnalyzer { + FuriHalNfcDevData nfc_data; + + bool alive; + FuriStreamBuffer* stream; + FuriThread* thread; + + ReaderAnalyzerParseDataCallback callback; + void* context; + + ReaderAnalyzerMode mode; + Mfkey32* mfkey32; + NfcDebugLog* debug_log; + NfcDebugPcap* pcap; +}; + +const FuriHalNfcDevData reader_analyzer_nfc_data[] = { + [ReaderAnalyzerNfcDataMfClassic] = + {.sak = 0x08, + .atqa = {0x44, 0x00}, + .interface = FuriHalNfcInterfaceRf, + .type = FuriHalNfcTypeA, + .uid_len = 7, + .uid = {0x04, 0x77, 0x70, 0x2A, 0x23, 0x4F, 0x80}, + .cuid = 0x2A234F80}, +}; + +void reader_analyzer_parse(ReaderAnalyzer* instance, uint8_t* buffer, size_t size) { + if(size < sizeof(ReaderAnalyzerHeader)) return; + + size_t bytes_i = 0; + while(bytes_i < size) { + ReaderAnalyzerHeader* header = (ReaderAnalyzerHeader*)&buffer[bytes_i]; + uint16_t len = header->len; + if(bytes_i + len > size) break; + bytes_i += sizeof(ReaderAnalyzerHeader); + if(instance->mfkey32) { + mfkey32_process_data( + instance->mfkey32, + &buffer[bytes_i], + len, + header->reader_to_tag, + header->crc_dropped); + } + if(instance->pcap) { + nfc_debug_pcap_process_data( + instance->pcap, &buffer[bytes_i], len, header->reader_to_tag, header->crc_dropped); + } + if(instance->debug_log) { + nfc_debug_log_process_data( + instance->debug_log, + &buffer[bytes_i], + len, + header->reader_to_tag, + header->crc_dropped); + } + bytes_i += len; + } +} + +int32_t reader_analyzer_thread(void* context) { + ReaderAnalyzer* reader_analyzer = context; + uint8_t buffer[READER_ANALYZER_MAX_BUFF_SIZE] = {}; + + while(reader_analyzer->alive || !furi_stream_buffer_is_empty(reader_analyzer->stream)) { + size_t ret = furi_stream_buffer_receive( + reader_analyzer->stream, buffer, READER_ANALYZER_MAX_BUFF_SIZE, 50); + if(ret) { + reader_analyzer_parse(reader_analyzer, buffer, ret); + } + } + + return 0; +} + +ReaderAnalyzer* reader_analyzer_alloc() { + ReaderAnalyzer* instance = malloc(sizeof(ReaderAnalyzer)); + + instance->nfc_data = reader_analyzer_nfc_data[ReaderAnalyzerNfcDataMfClassic]; + instance->alive = false; + instance->stream = + furi_stream_buffer_alloc(READER_ANALYZER_MAX_BUFF_SIZE, sizeof(ReaderAnalyzerHeader)); + + instance->thread = + furi_thread_alloc_ex("ReaderAnalyzerWorker", 2048, reader_analyzer_thread, instance); + furi_thread_set_priority(instance->thread, FuriThreadPriorityLow); + + return instance; +} + +static void reader_analyzer_mfkey_callback(Mfkey32Event event, void* context) { + furi_assert(context); + ReaderAnalyzer* instance = context; + + if(event == Mfkey32EventParamCollected) { + if(instance->callback) { + instance->callback(ReaderAnalyzerEventMfkeyCollected, instance->context); + } + } +} + +void reader_analyzer_start(ReaderAnalyzer* instance, ReaderAnalyzerMode mode) { + furi_assert(instance); + + furi_stream_buffer_reset(instance->stream); + if(mode & ReaderAnalyzerModeDebugLog) { + instance->debug_log = nfc_debug_log_alloc(); + } + if(mode & ReaderAnalyzerModeMfkey) { + instance->mfkey32 = mfkey32_alloc(instance->nfc_data.cuid); + if(instance->mfkey32) { + mfkey32_set_callback(instance->mfkey32, reader_analyzer_mfkey_callback, instance); + } + } + if(mode & ReaderAnalyzerModeDebugPcap) { + instance->pcap = nfc_debug_pcap_alloc(); + } + + instance->alive = true; + furi_thread_start(instance->thread); +} + +void reader_analyzer_stop(ReaderAnalyzer* instance) { + furi_assert(instance); + + instance->alive = false; + furi_thread_join(instance->thread); + + if(instance->debug_log) { + nfc_debug_log_free(instance->debug_log); + instance->debug_log = NULL; + } + if(instance->mfkey32) { + mfkey32_free(instance->mfkey32); + instance->mfkey32 = NULL; + } + if(instance->pcap) { + nfc_debug_pcap_free(instance->pcap); + instance->pcap = NULL; + } +} + +void reader_analyzer_free(ReaderAnalyzer* instance) { + furi_assert(instance); + + reader_analyzer_stop(instance); + furi_thread_free(instance->thread); + furi_stream_buffer_free(instance->stream); + free(instance); +} + +void reader_analyzer_set_callback( + ReaderAnalyzer* instance, + ReaderAnalyzerParseDataCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +NfcProtocol + reader_analyzer_guess_protocol(ReaderAnalyzer* instance, uint8_t* buff_rx, uint16_t len) { + furi_assert(instance); + furi_assert(buff_rx); + UNUSED(len); + NfcProtocol protocol = NfcDeviceProtocolUnknown; + + if((buff_rx[0] == 0x60) || (buff_rx[0] == 0x61)) { + protocol = NfcDeviceProtocolMifareClassic; + } + + return protocol; +} + +FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance) { + furi_assert(instance); + instance->nfc_data = reader_analyzer_nfc_data[ReaderAnalyzerNfcDataMfClassic]; + return &instance->nfc_data; +} + +void reader_analyzer_set_nfc_data(ReaderAnalyzer* instance, FuriHalNfcDevData* nfc_data) { + furi_assert(instance); + furi_assert(nfc_data); + + memcpy(&instance->nfc_data, nfc_data, sizeof(FuriHalNfcDevData)); +} + +static void reader_analyzer_write( + ReaderAnalyzer* instance, + uint8_t* data, + uint16_t len, + bool reader_to_tag, + bool crc_dropped) { + ReaderAnalyzerHeader header = { + .reader_to_tag = reader_to_tag, .crc_dropped = crc_dropped, .len = len}; + size_t data_sent = 0; + data_sent = furi_stream_buffer_send( + instance->stream, &header, sizeof(ReaderAnalyzerHeader), FuriWaitForever); + if(data_sent != sizeof(ReaderAnalyzerHeader)) { + FURI_LOG_W(TAG, "Sent %zu out of %zu bytes", data_sent, sizeof(ReaderAnalyzerHeader)); + } + data_sent = furi_stream_buffer_send(instance->stream, data, len, FuriWaitForever); + if(data_sent != len) { + FURI_LOG_W(TAG, "Sent %zu out of %u bytes", data_sent, len); + } +} + +static void + reader_analyzer_write_rx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) { + UNUSED(crc_dropped); + ReaderAnalyzer* reader_analyzer = context; + uint16_t bytes = bits < 8 ? 1 : bits / 8; + reader_analyzer_write(reader_analyzer, data, bytes, false, crc_dropped); +} + +static void + reader_analyzer_write_tx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) { + UNUSED(crc_dropped); + ReaderAnalyzer* reader_analyzer = context; + uint16_t bytes = bits < 8 ? 1 : bits / 8; + reader_analyzer_write(reader_analyzer, data, bytes, true, crc_dropped); +} + +void reader_analyzer_prepare_tx_rx( + ReaderAnalyzer* instance, + FuriHalNfcTxRxContext* tx_rx, + bool is_picc) { + furi_assert(instance); + furi_assert(tx_rx); + + if(is_picc) { + tx_rx->sniff_tx = reader_analyzer_write_rx; + tx_rx->sniff_rx = reader_analyzer_write_tx; + } else { + tx_rx->sniff_rx = reader_analyzer_write_rx; + tx_rx->sniff_tx = reader_analyzer_write_tx; + } + + tx_rx->sniff_context = instance; +} diff --git a/lib/nfc/helpers/reader_analyzer.h b/lib/nfc/helpers/reader_analyzer.h new file mode 100644 index 0000000..13bf4d7 --- /dev/null +++ b/lib/nfc/helpers/reader_analyzer.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +typedef enum { + ReaderAnalyzerModeDebugLog = 0x01, + ReaderAnalyzerModeMfkey = 0x02, + ReaderAnalyzerModeDebugPcap = 0x04, +} ReaderAnalyzerMode; + +typedef enum { + ReaderAnalyzerEventMfkeyCollected, +} ReaderAnalyzerEvent; + +typedef struct ReaderAnalyzer ReaderAnalyzer; + +typedef void (*ReaderAnalyzerParseDataCallback)(ReaderAnalyzerEvent event, void* context); + +ReaderAnalyzer* reader_analyzer_alloc(); + +void reader_analyzer_free(ReaderAnalyzer* instance); + +void reader_analyzer_set_callback( + ReaderAnalyzer* instance, + ReaderAnalyzerParseDataCallback callback, + void* context); + +void reader_analyzer_start(ReaderAnalyzer* instance, ReaderAnalyzerMode mode); + +void reader_analyzer_stop(ReaderAnalyzer* instance); + +NfcProtocol + reader_analyzer_guess_protocol(ReaderAnalyzer* instance, uint8_t* buff_rx, uint16_t len); + +FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance); + +void reader_analyzer_set_nfc_data(ReaderAnalyzer* instance, FuriHalNfcDevData* nfc_data); + +void reader_analyzer_prepare_tx_rx( + ReaderAnalyzer* instance, + FuriHalNfcTxRxContext* tx_rx, + bool is_picc); diff --git a/lib/nfc/nfc_device.c b/lib/nfc/nfc_device.c new file mode 100644 index 0000000..c5c8b43 --- /dev/null +++ b/lib/nfc/nfc_device.c @@ -0,0 +1,1735 @@ +#include "nfc_device.h" +#include "assets_icons.h" +#include "nfc_types.h" + +#include +#include +#include +#include + +#define TAG "NfcDevice" +#define NFC_DEVICE_KEYS_FOLDER EXT_PATH("nfc/.cache") +#define NFC_DEVICE_KEYS_EXTENSION ".keys" + +static const char* nfc_file_header = "Flipper NFC device"; +static const uint32_t nfc_file_version = 3; + +static const char* nfc_keys_file_header = "Flipper NFC keys"; +static const uint32_t nfc_keys_file_version = 1; + +// Protocols format versions +static const uint32_t nfc_mifare_classic_data_format_version = 2; +static const uint32_t nfc_mifare_ultralight_data_format_version = 1; + +NfcDevice* nfc_device_alloc() { + NfcDevice* nfc_dev = malloc(sizeof(NfcDevice)); + nfc_dev->storage = furi_record_open(RECORD_STORAGE); + nfc_dev->dialogs = furi_record_open(RECORD_DIALOGS); + nfc_dev->load_path = furi_string_alloc(); + nfc_dev->dev_data.parsed_data = furi_string_alloc(); + nfc_dev->folder = furi_string_alloc(); + + // Rename cache folder name for backward compatibility + if(storage_common_stat(nfc_dev->storage, "/ext/nfc/cache", NULL) == FSE_OK) { + storage_common_rename(nfc_dev->storage, "/ext/nfc/cache", NFC_DEVICE_KEYS_FOLDER); + } + return nfc_dev; +} + +void nfc_device_free(NfcDevice* nfc_dev) { + furi_assert(nfc_dev); + nfc_device_clear(nfc_dev); + furi_record_close(RECORD_STORAGE); + furi_record_close(RECORD_DIALOGS); + furi_string_free(nfc_dev->load_path); + furi_string_free(nfc_dev->dev_data.parsed_data); + furi_string_free(nfc_dev->folder); + free(nfc_dev); +} + +static void nfc_device_prepare_format_string(NfcDevice* dev, FuriString* format_string) { + if(dev->format == NfcDeviceSaveFormatUid) { + furi_string_set(format_string, "UID"); + } else if(dev->format == NfcDeviceSaveFormatBankCard) { + furi_string_set(format_string, "Bank card"); + } else if(dev->format == NfcDeviceSaveFormatMifareUl) { + furi_string_set(format_string, nfc_mf_ul_type(dev->dev_data.mf_ul_data.type, true)); + } else if(dev->format == NfcDeviceSaveFormatMifareClassic) { + furi_string_set(format_string, "Mifare Classic"); + } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { + furi_string_set(format_string, "Mifare DESFire"); + } else if(dev->format == NfcDeviceSaveFormatNfcV) { + furi_string_set(format_string, "ISO15693"); + } else { + furi_string_set(format_string, "Unknown"); + } +} + +static bool nfc_device_parse_format_string(NfcDevice* dev, FuriString* format_string) { + if(furi_string_start_with_str(format_string, "UID")) { + dev->format = NfcDeviceSaveFormatUid; + dev->dev_data.protocol = NfcDeviceProtocolUnknown; + return true; + } + if(furi_string_start_with_str(format_string, "Bank card")) { + dev->format = NfcDeviceSaveFormatBankCard; + dev->dev_data.protocol = NfcDeviceProtocolEMV; + return true; + } + // Check Mifare Ultralight types + for(MfUltralightType type = MfUltralightTypeUnknown; type < MfUltralightTypeNum; type++) { + if(furi_string_equal(format_string, nfc_mf_ul_type(type, true))) { + dev->format = NfcDeviceSaveFormatMifareUl; + dev->dev_data.protocol = NfcDeviceProtocolMifareUl; + dev->dev_data.mf_ul_data.type = type; + return true; + } + } + if(furi_string_start_with_str(format_string, "Mifare Classic")) { + dev->format = NfcDeviceSaveFormatMifareClassic; + dev->dev_data.protocol = NfcDeviceProtocolMifareClassic; + return true; + } + if(furi_string_start_with_str(format_string, "Mifare DESFire")) { + dev->format = NfcDeviceSaveFormatMifareDesfire; + dev->dev_data.protocol = NfcDeviceProtocolMifareDesfire; + return true; + } + if(furi_string_start_with_str(format_string, "ISO15693")) { + dev->format = NfcDeviceSaveFormatNfcV; + dev->dev_data.protocol = NfcDeviceProtocolNfcV; + return true; + } + return false; +} + +static bool nfc_device_save_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + MfUltralightData* data = &dev->dev_data.mf_ul_data; + FuriString* temp_str; + temp_str = furi_string_alloc(); + + // Save Mifare Ultralight specific data + do { + if(!flipper_format_write_comment_cstr(file, "Mifare Ultralight specific data")) break; + if(!flipper_format_write_uint32( + file, "Data format version", &nfc_mifare_ultralight_data_format_version, 1)) + break; + if(!flipper_format_write_hex(file, "Signature", data->signature, sizeof(data->signature))) + break; + if(!flipper_format_write_hex( + file, "Mifare version", (uint8_t*)&data->version, sizeof(data->version))) + break; + // Write conters and tearing flags data + bool counters_saved = true; + for(uint8_t i = 0; i < 3; i++) { + furi_string_printf(temp_str, "Counter %d", i); + if(!flipper_format_write_uint32( + file, furi_string_get_cstr(temp_str), &data->counter[i], 1)) { + counters_saved = false; + break; + } + furi_string_printf(temp_str, "Tearing %d", i); + if(!flipper_format_write_hex( + file, furi_string_get_cstr(temp_str), &data->tearing[i], 1)) { + counters_saved = false; + break; + } + } + if(!counters_saved) break; + // Write pages data + uint32_t pages_total = data->data_size / 4; + if(!flipper_format_write_uint32(file, "Pages total", &pages_total, 1)) break; + uint32_t pages_read = data->data_read / 4; + if(!flipper_format_write_uint32(file, "Pages read", &pages_read, 1)) break; + bool pages_saved = true; + for(uint16_t i = 0; i < data->data_size; i += 4) { + furi_string_printf(temp_str, "Page %d", i / 4); + if(!flipper_format_write_hex(file, furi_string_get_cstr(temp_str), &data->data[i], 4)) { + pages_saved = false; + break; + } + } + if(!pages_saved) break; + + // Write authentication counter + uint32_t auth_counter = data->curr_authlim; + if(!flipper_format_write_uint32(file, "Failed authentication attempts", &auth_counter, 1)) + break; + + saved = true; + } while(false); + + furi_string_free(temp_str); + return saved; +} + +bool nfc_device_load_mifare_ul_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + MfUltralightData* data = &dev->dev_data.mf_ul_data; + FuriString* temp_str; + temp_str = furi_string_alloc(); + uint32_t data_format_version = 0; + + do { + // Read Mifare Ultralight format version + if(!flipper_format_read_uint32(file, "Data format version", &data_format_version, 1)) { + if(!flipper_format_rewind(file)) break; + } + + // Read signature + if(!flipper_format_read_hex(file, "Signature", data->signature, sizeof(data->signature))) + break; + // Read Mifare version + if(!flipper_format_read_hex( + file, "Mifare version", (uint8_t*)&data->version, sizeof(data->version))) + break; + // Read counters and tearing flags + bool counters_parsed = true; + for(uint8_t i = 0; i < 3; i++) { + furi_string_printf(temp_str, "Counter %d", i); + if(!flipper_format_read_uint32( + file, furi_string_get_cstr(temp_str), &data->counter[i], 1)) { + counters_parsed = false; + break; + } + furi_string_printf(temp_str, "Tearing %d", i); + if(!flipper_format_read_hex( + file, furi_string_get_cstr(temp_str), &data->tearing[i], 1)) { + counters_parsed = false; + break; + } + } + if(!counters_parsed) break; + // Read pages + uint32_t pages_total = 0; + if(!flipper_format_read_uint32(file, "Pages total", &pages_total, 1)) break; + uint32_t pages_read = 0; + if(data_format_version < nfc_mifare_ultralight_data_format_version) { + pages_read = pages_total; + } else { + if(!flipper_format_read_uint32(file, "Pages read", &pages_read, 1)) break; + } + data->data_size = pages_total * 4; + data->data_read = pages_read * 4; + if(data->data_size > MF_UL_MAX_DUMP_SIZE || data->data_read > MF_UL_MAX_DUMP_SIZE) break; + bool pages_parsed = true; + for(uint16_t i = 0; i < pages_total; i++) { + furi_string_printf(temp_str, "Page %d", i); + if(!flipper_format_read_hex( + file, furi_string_get_cstr(temp_str), &data->data[i * 4], 4)) { + pages_parsed = false; + break; + } + } + if(!pages_parsed) break; + + // Read authentication counter + uint32_t auth_counter; + if(!flipper_format_read_uint32(file, "Failed authentication attempts", &auth_counter, 1)) + auth_counter = 0; + data->curr_authlim = auth_counter; + + data->auth_success = mf_ul_is_full_capture(data); + + parsed = true; + } while(false); + + furi_string_free(temp_str); + return parsed; +} + +static bool nfc_device_save_mifare_df_key_settings( + FlipperFormat* file, + MifareDesfireKeySettings* ks, + const char* prefix) { + bool saved = false; + FuriString* key; + key = furi_string_alloc(); + + do { + furi_string_printf(key, "%s Change Key ID", prefix); + if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &ks->change_key_id, 1)) + break; + furi_string_printf(key, "%s Config Changeable", prefix); + if(!flipper_format_write_bool(file, furi_string_get_cstr(key), &ks->config_changeable, 1)) + break; + furi_string_printf(key, "%s Free Create Delete", prefix); + if(!flipper_format_write_bool(file, furi_string_get_cstr(key), &ks->free_create_delete, 1)) + break; + furi_string_printf(key, "%s Free Directory List", prefix); + if(!flipper_format_write_bool(file, furi_string_get_cstr(key), &ks->free_directory_list, 1)) + break; + furi_string_printf(key, "%s Key Changeable", prefix); + if(!flipper_format_write_bool( + file, furi_string_get_cstr(key), &ks->master_key_changeable, 1)) + break; + if(ks->flags) { + furi_string_printf(key, "%s Flags", prefix); + if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &ks->flags, 1)) break; + } + furi_string_printf(key, "%s Max Keys", prefix); + if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &ks->max_keys, 1)) break; + for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { + furi_string_printf(key, "%s Key %d Version", prefix, kv->id); + if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &kv->version, 1)) break; + } + saved = true; + } while(false); + + furi_string_free(key); + return saved; +} + +bool nfc_device_load_mifare_df_key_settings( + FlipperFormat* file, + MifareDesfireKeySettings* ks, + const char* prefix) { + bool parsed = false; + FuriString* key; + key = furi_string_alloc(); + + do { + furi_string_printf(key, "%s Change Key ID", prefix); + if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &ks->change_key_id, 1)) break; + furi_string_printf(key, "%s Config Changeable", prefix); + if(!flipper_format_read_bool(file, furi_string_get_cstr(key), &ks->config_changeable, 1)) + break; + furi_string_printf(key, "%s Free Create Delete", prefix); + if(!flipper_format_read_bool(file, furi_string_get_cstr(key), &ks->free_create_delete, 1)) + break; + furi_string_printf(key, "%s Free Directory List", prefix); + if(!flipper_format_read_bool(file, furi_string_get_cstr(key), &ks->free_directory_list, 1)) + break; + furi_string_printf(key, "%s Key Changeable", prefix); + if(!flipper_format_read_bool( + file, furi_string_get_cstr(key), &ks->master_key_changeable, 1)) + break; + furi_string_printf(key, "%s Flags", prefix); + if(flipper_format_key_exist(file, furi_string_get_cstr(key))) { + if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &ks->flags, 1)) break; + } + furi_string_printf(key, "%s Max Keys", prefix); + if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &ks->max_keys, 1)) break; + ks->flags |= ks->max_keys >> 4; + ks->max_keys &= 0xF; + MifareDesfireKeyVersion** kv_head = &ks->key_version_head; + for(int key_id = 0; key_id < ks->max_keys; key_id++) { + furi_string_printf(key, "%s Key %d Version", prefix, key_id); + uint8_t version; + if(flipper_format_read_hex(file, furi_string_get_cstr(key), &version, 1)) { + MifareDesfireKeyVersion* kv = malloc(sizeof(MifareDesfireKeyVersion)); + memset(kv, 0, sizeof(MifareDesfireKeyVersion)); + kv->id = key_id; + kv->version = version; + *kv_head = kv; + kv_head = &kv->next; + } + } + parsed = true; + } while(false); + + furi_string_free(key); + return parsed; +} + +static bool nfc_device_save_mifare_df_app(FlipperFormat* file, MifareDesfireApplication* app) { + bool saved = false; + FuriString *prefix, *key; + prefix = + furi_string_alloc_printf("Application %02x%02x%02x", app->id[0], app->id[1], app->id[2]); + key = furi_string_alloc(); + uint8_t* tmp = NULL; + + do { + if(app->key_settings) { + if(!nfc_device_save_mifare_df_key_settings( + file, app->key_settings, furi_string_get_cstr(prefix))) + break; + } + if(!app->file_head) break; + uint32_t n_files = 0; + for(MifareDesfireFile* f = app->file_head; f; f = f->next) { + n_files++; + } + tmp = malloc(n_files); + int i = 0; + for(MifareDesfireFile* f = app->file_head; f; f = f->next) { + tmp[i++] = f->id; + } + furi_string_printf(key, "%s File IDs", furi_string_get_cstr(prefix)); + if(!flipper_format_write_hex(file, furi_string_get_cstr(key), tmp, n_files)) break; + bool saved_files = true; + for(MifareDesfireFile* f = app->file_head; f; f = f->next) { + saved_files = false; + furi_string_printf(key, "%s File %d Type", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &f->type, 1)) break; + furi_string_printf( + key, "%s File %d Communication Settings", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_write_hex(file, furi_string_get_cstr(key), &f->comm, 1)) break; + furi_string_printf( + key, "%s File %d Access Rights", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_write_hex( + file, furi_string_get_cstr(key), (uint8_t*)&f->access_rights, 2)) + break; + uint16_t size = 0; + if(f->type == MifareDesfireFileTypeStandard || + f->type == MifareDesfireFileTypeBackup) { + size = f->settings.data.size; + furi_string_printf(key, "%s File %d Size", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, furi_string_get_cstr(key), &f->settings.data.size, 1)) + break; + } else if(f->type == MifareDesfireFileTypeValue) { + furi_string_printf( + key, "%s File %d Hi Limit", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, furi_string_get_cstr(key), &f->settings.value.hi_limit, 1)) + break; + furi_string_printf( + key, "%s File %d Lo Limit", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, furi_string_get_cstr(key), &f->settings.value.lo_limit, 1)) + break; + furi_string_printf( + key, "%s File %d Limited Credit Value", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, furi_string_get_cstr(key), &f->settings.value.limited_credit_value, 1)) + break; + furi_string_printf( + key, "%s File %d Limited Credit Enabled", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_write_bool( + file, + furi_string_get_cstr(key), + &f->settings.value.limited_credit_enabled, + 1)) + break; + size = 4; + } else if( + f->type == MifareDesfireFileTypeLinearRecord || + f->type == MifareDesfireFileTypeCyclicRecord) { + furi_string_printf(key, "%s File %d Size", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, furi_string_get_cstr(key), &f->settings.record.size, 1)) + break; + furi_string_printf(key, "%s File %d Max", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, furi_string_get_cstr(key), &f->settings.record.max, 1)) + break; + furi_string_printf(key, "%s File %d Cur", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, furi_string_get_cstr(key), &f->settings.record.cur, 1)) + break; + size = f->settings.record.size * f->settings.record.cur; + } + if(f->contents) { + furi_string_printf(key, "%s File %d", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_write_hex(file, furi_string_get_cstr(key), f->contents, size)) + break; + } + saved_files = true; + } + if(!saved_files) { + break; + } + saved = true; + } while(false); + + free(tmp); + furi_string_free(prefix); + furi_string_free(key); + return saved; +} + +bool nfc_device_load_mifare_df_app(FlipperFormat* file, MifareDesfireApplication* app) { + bool parsed = false; + FuriString *prefix, *key; + prefix = + furi_string_alloc_printf("Application %02x%02x%02x", app->id[0], app->id[1], app->id[2]); + key = furi_string_alloc(); + uint8_t* tmp = NULL; + MifareDesfireFile* f = NULL; + + do { + app->key_settings = malloc(sizeof(MifareDesfireKeySettings)); + memset(app->key_settings, 0, sizeof(MifareDesfireKeySettings)); + if(!nfc_device_load_mifare_df_key_settings( + file, app->key_settings, furi_string_get_cstr(prefix))) { + free(app->key_settings); + app->key_settings = NULL; + break; + } + furi_string_printf(key, "%s File IDs", furi_string_get_cstr(prefix)); + uint32_t n_files; + if(!flipper_format_get_value_count(file, furi_string_get_cstr(key), &n_files)) break; + tmp = malloc(n_files); + if(!flipper_format_read_hex(file, furi_string_get_cstr(key), tmp, n_files)) break; + MifareDesfireFile** file_head = &app->file_head; + bool parsed_files = true; + for(uint32_t i = 0; i < n_files; i++) { + parsed_files = false; + f = malloc(sizeof(MifareDesfireFile)); + memset(f, 0, sizeof(MifareDesfireFile)); + f->id = tmp[i]; + furi_string_printf(key, "%s File %d Type", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &f->type, 1)) break; + furi_string_printf( + key, "%s File %d Communication Settings", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_read_hex(file, furi_string_get_cstr(key), &f->comm, 1)) break; + furi_string_printf( + key, "%s File %d Access Rights", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_read_hex( + file, furi_string_get_cstr(key), (uint8_t*)&f->access_rights, 2)) + break; + if(f->type == MifareDesfireFileTypeStandard || + f->type == MifareDesfireFileTypeBackup) { + furi_string_printf(key, "%s File %d Size", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, furi_string_get_cstr(key), &f->settings.data.size, 1)) + break; + } else if(f->type == MifareDesfireFileTypeValue) { + furi_string_printf( + key, "%s File %d Hi Limit", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, furi_string_get_cstr(key), &f->settings.value.hi_limit, 1)) + break; + furi_string_printf( + key, "%s File %d Lo Limit", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, furi_string_get_cstr(key), &f->settings.value.lo_limit, 1)) + break; + furi_string_printf( + key, "%s File %d Limited Credit Value", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, furi_string_get_cstr(key), &f->settings.value.limited_credit_value, 1)) + break; + furi_string_printf( + key, "%s File %d Limited Credit Enabled", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_read_bool( + file, + furi_string_get_cstr(key), + &f->settings.value.limited_credit_enabled, + 1)) + break; + } else if( + f->type == MifareDesfireFileTypeLinearRecord || + f->type == MifareDesfireFileTypeCyclicRecord) { + furi_string_printf(key, "%s File %d Size", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, furi_string_get_cstr(key), &f->settings.record.size, 1)) + break; + furi_string_printf(key, "%s File %d Max", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, furi_string_get_cstr(key), &f->settings.record.max, 1)) + break; + furi_string_printf(key, "%s File %d Cur", furi_string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, furi_string_get_cstr(key), &f->settings.record.cur, 1)) + break; + } + furi_string_printf(key, "%s File %d", furi_string_get_cstr(prefix), f->id); + if(flipper_format_key_exist(file, furi_string_get_cstr(key))) { + uint32_t size; + if(!flipper_format_get_value_count(file, furi_string_get_cstr(key), &size)) break; + f->contents = malloc(size); + if(!flipper_format_read_hex(file, furi_string_get_cstr(key), f->contents, size)) + break; + } + *file_head = f; + file_head = &f->next; + f = NULL; + parsed_files = true; + } + if(!parsed_files) { + break; + } + parsed = true; + } while(false); + + if(f) { + free(f->contents); + free(f); + } + free(tmp); + furi_string_free(prefix); + furi_string_free(key); + return parsed; +} + +static bool nfc_device_save_mifare_df_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + MifareDesfireData* data = &dev->dev_data.mf_df_data; + uint8_t* tmp = NULL; + + do { + if(!flipper_format_write_comment_cstr(file, "Mifare DESFire specific data")) break; + if(!flipper_format_write_hex( + file, "PICC Version", (uint8_t*)&data->version, sizeof(data->version))) + break; + if(data->free_memory) { + if(!flipper_format_write_uint32(file, "PICC Free Memory", &data->free_memory->bytes, 1)) + break; + } + if(data->master_key_settings) { + if(!nfc_device_save_mifare_df_key_settings(file, data->master_key_settings, "PICC")) + break; + } + uint32_t n_apps = 0; + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + n_apps++; + } + if(!flipper_format_write_uint32(file, "Application Count", &n_apps, 1)) break; + if(n_apps) { + tmp = malloc(n_apps * 3); + int i = 0; + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + memcpy(tmp + i, app->id, 3); //-V769 + i += 3; + } + if(!flipper_format_write_hex(file, "Application IDs", tmp, n_apps * 3)) break; + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + if(!nfc_device_save_mifare_df_app(file, app)) break; + } + } + saved = true; + } while(false); + + free(tmp); + return saved; +} + +bool nfc_device_load_mifare_df_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + MifareDesfireData* data = &dev->dev_data.mf_df_data; + memset(data, 0, sizeof(MifareDesfireData)); + uint8_t* tmp = NULL; + + do { + if(!flipper_format_read_hex( + file, "PICC Version", (uint8_t*)&data->version, sizeof(data->version))) + break; + if(flipper_format_key_exist(file, "PICC Free Memory")) { + data->free_memory = malloc(sizeof(MifareDesfireFreeMemory)); + memset(data->free_memory, 0, sizeof(MifareDesfireFreeMemory)); + if(!flipper_format_read_uint32( + file, "PICC Free Memory", &data->free_memory->bytes, 1)) { + free(data->free_memory); + break; + } + } + if(flipper_format_key_exist(file, "PICC Change Key ID")) { + data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings)); + memset(data->master_key_settings, 0, sizeof(MifareDesfireKeySettings)); + if(!nfc_device_load_mifare_df_key_settings(file, data->master_key_settings, "PICC")) { + free(data->master_key_settings); + data->master_key_settings = NULL; + break; + } + } + uint32_t n_apps; + if(!flipper_format_read_uint32(file, "Application Count", &n_apps, 1)) break; + if(n_apps) { + tmp = malloc(n_apps * 3); + if(!flipper_format_read_hex(file, "Application IDs", tmp, n_apps * 3)) break; + bool parsed_apps = true; + MifareDesfireApplication** app_head = &data->app_head; + for(uint32_t i = 0; i < n_apps; i++) { + MifareDesfireApplication* app = malloc(sizeof(MifareDesfireApplication)); + memset(app, 0, sizeof(MifareDesfireApplication)); + memcpy(app->id, &tmp[i * 3], 3); + if(!nfc_device_load_mifare_df_app(file, app)) { + free(app); + parsed_apps = false; + break; + } + *app_head = app; + app_head = &app->next; + } + if(!parsed_apps) { + // accept non-parsed apps, just log a warning: + FURI_LOG_W(TAG, "Non-parsed apps found!"); + } + } + parsed = true; + } while(false); + + free(tmp); + return parsed; +} + +static bool nfc_device_save_slix_data( + FlipperFormat* file, + NfcDevice* dev, + SlixTypeFeatures features, + const char* type) { + bool saved = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + + do { + char msg[64]; + snprintf(msg, sizeof(msg), "%s specific data", type); + if(!flipper_format_write_comment_cstr(file, msg)) break; + if(!flipper_format_write_comment_cstr( + file, "Passwords are optional. If password is omitted, any password is accepted")) + break; + + if(features & SlixFeatureRead) { + if(data->flags & NfcVSlixDataFlagsHasKeyRead) { + if(!flipper_format_write_hex( + file, "Password Read", data->key_read, sizeof(data->key_read))) + break; + } + } + if(features & SlixFeatureWrite) { + if(data->flags & NfcVSlixDataFlagsHasKeyWrite) { + if(!flipper_format_write_hex( + file, "Password Write", data->key_write, sizeof(data->key_write))) + break; + } + } + if(features & SlixFeaturePrivacy) { + if(data->flags & NfcVSlixDataFlagsHasKeyPrivacy) { + if(!flipper_format_write_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) + break; + } + } + if(features & SlixFeatureDestroy) { + if(data->flags & NfcVSlixDataFlagsHasKeyDestroy) { + if(!flipper_format_write_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) + break; + } + } + if(features & SlixFeatureEas) { + if(data->flags & NfcVSlixDataFlagsHasKeyEas) { + if(!flipper_format_write_hex( + file, "Password EAS", data->key_eas, sizeof(data->key_eas))) + break; + } + } + if(features & SlixFeatureSignature) { + if(!flipper_format_write_comment_cstr( + file, + "This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key.")) + break; + if(!flipper_format_write_hex( + file, "Signature", data->signature, sizeof(data->signature))) + break; + } + if(features & SlixFeaturePrivacy) { + bool privacy = (data->flags & NfcVSlixDataFlagsPrivacy) ? true : false; + if(!flipper_format_write_bool(file, "Privacy Mode", &privacy, 1)) break; + } + if(features & SlixFeatureProtection) { + if(!flipper_format_write_comment_cstr(file, "Protection pointer configuration")) break; + if(!flipper_format_write_hex(file, "Protection pointer", &data->pp_pointer, 1)) break; + if(!flipper_format_write_hex(file, "Protection condition", &data->pp_condition, 1)) + break; + } + saved = true; + } while(false); + + return saved; +} + +bool nfc_device_load_slix_data(FlipperFormat* file, NfcDevice* dev, SlixTypeFeatures features) { + bool parsed = false; + NfcVSlixData* data = &dev->dev_data.nfcv_data.sub_data.slix; + memset(data, 0, sizeof(NfcVSlixData)); + + do { + data->flags = 0; + + if(features & SlixFeatureRead) { + if(flipper_format_key_exist(file, "Password Read")) { + if(!flipper_format_read_hex( + file, "Password Read", data->key_read, sizeof(data->key_read))) { + FURI_LOG_D(TAG, "Failed reading Password Read"); + break; + } + data->flags |= NfcVSlixDataFlagsHasKeyRead; + } + } + if(features & SlixFeatureWrite) { + if(flipper_format_key_exist(file, "Password Write")) { + if(!flipper_format_read_hex( + file, "Password Write", data->key_write, sizeof(data->key_write))) { + FURI_LOG_D(TAG, "Failed reading Password Write"); + break; + } + data->flags |= NfcVSlixDataFlagsHasKeyWrite; + } + } + if(features & SlixFeaturePrivacy) { + if(flipper_format_key_exist(file, "Password Privacy")) { + if(!flipper_format_read_hex( + file, "Password Privacy", data->key_privacy, sizeof(data->key_privacy))) { + FURI_LOG_D(TAG, "Failed reading Password Privacy"); + break; + } + data->flags |= NfcVSlixDataFlagsHasKeyPrivacy; + } + } + if(features & SlixFeatureDestroy) { + if(flipper_format_key_exist(file, "Password Destroy")) { + if(!flipper_format_read_hex( + file, "Password Destroy", data->key_destroy, sizeof(data->key_destroy))) { + FURI_LOG_D(TAG, "Failed reading Password Destroy"); + break; + } + data->flags |= NfcVSlixDataFlagsHasKeyDestroy; + } + } + if(features & SlixFeatureEas) { + if(flipper_format_key_exist(file, "Password EAS")) { + if(!flipper_format_read_hex( + file, "Password EAS", data->key_eas, sizeof(data->key_eas))) { + FURI_LOG_D(TAG, "Failed reading Password EAS"); + break; + } + data->flags |= NfcVSlixDataFlagsHasKeyEas; + } + } + if(features & SlixFeatureSignature) { + if(!flipper_format_read_hex( + file, "Signature", data->signature, sizeof(data->signature))) { + FURI_LOG_D(TAG, "Failed reading Signature"); + break; + } + } + if(features & SlixFeaturePrivacy) { + bool privacy; + if(!flipper_format_read_bool(file, "Privacy Mode", &privacy, 1)) { + FURI_LOG_D(TAG, "Failed reading Privacy Mode"); + break; + } + if(privacy) { + data->flags |= NfcVSlixDataFlagsPrivacy; + } + } + if(features & SlixFeatureProtection) { + if(!flipper_format_read_hex(file, "Protection pointer", &(data->pp_pointer), 1)) { + FURI_LOG_D(TAG, "Failed reading Protection pointer"); + break; + } + if(!flipper_format_read_hex(file, "Protection condition", &(data->pp_condition), 1)) { + FURI_LOG_D(TAG, "Failed reading Protection condition"); + break; + } + } + parsed = true; + } while(false); + + return parsed; +} + +static bool nfc_device_save_nfcv_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + NfcVData* data = &dev->dev_data.nfcv_data; + + do { + uint32_t temp_uint32 = 0; + uint8_t temp_uint8 = 0; + + if(!flipper_format_write_comment_cstr(file, "Data Storage Format Identifier")) break; + if(!flipper_format_write_hex(file, "DSFID", &(data->dsfid), 1)) break; + if(!flipper_format_write_comment_cstr(file, "Application Family Identifier")) break; + if(!flipper_format_write_hex(file, "AFI", &(data->afi), 1)) break; + if(!flipper_format_write_hex(file, "IC Reference", &(data->ic_ref), 1)) break; + temp_uint32 = data->block_num; + if(!flipper_format_write_comment_cstr(file, "Number of memory blocks, usually 0 to 256")) + break; + if(!flipper_format_write_uint32(file, "Block Count", &temp_uint32, 1)) break; + if(!flipper_format_write_comment_cstr(file, "Size of a single memory block, usually 4")) + break; + if(!flipper_format_write_hex(file, "Block Size", &(data->block_size), 1)) break; + if(!flipper_format_write_hex( + file, "Data Content", data->data, data->block_num * data->block_size)) + break; + if(!flipper_format_write_comment_cstr( + file, + "First byte: DSFID (0x01) / AFI (0x02) / EAS (0x04) / PPL (0x08) lock info, others: block lock info")) + break; + if(!flipper_format_write_hex( + file, "Security Status", data->security_status, 1 + data->block_num)) + break; + if(!flipper_format_write_comment_cstr( + file, + "Subtype of this card (0 = ISO15693, 1 = SLIX, 2 = SLIX-S, 3 = SLIX-L, 4 = SLIX2)")) + break; + temp_uint8 = (uint8_t)data->sub_type; + if(!flipper_format_write_hex(file, "Subtype", &temp_uint8, 1)) break; + + switch(data->sub_type) { + case NfcVTypePlain: + if(!flipper_format_write_comment_cstr(file, "End of ISO15693 parameters")) break; + saved = true; + break; + case NfcVTypeSlix: + saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlix, "SLIX"); + break; + case NfcVTypeSlixS: + saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlixS, "SLIX-S"); + break; + case NfcVTypeSlixL: + saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlixL, "SLIX-L"); + break; + case NfcVTypeSlix2: + saved = nfc_device_save_slix_data(file, dev, SlixFeatureSlix2, "SLIX2"); + break; + default: + break; + } + } while(false); + + return saved; +} + +bool nfc_device_load_nfcv_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + NfcVData* data = &dev->dev_data.nfcv_data; + + memset(data, 0x00, sizeof(NfcVData)); + + do { + uint32_t temp_uint32 = 0; + uint8_t temp_value = 0; + + if(!flipper_format_read_hex(file, "DSFID", &(data->dsfid), 1)) { + FURI_LOG_D(TAG, "Failed reading DSFID"); + break; + } + if(!flipper_format_read_hex(file, "AFI", &(data->afi), 1)) { + FURI_LOG_D(TAG, "Failed reading AFI"); + break; + } + if(!flipper_format_read_hex(file, "IC Reference", &(data->ic_ref), 1)) { + FURI_LOG_D(TAG, "Failed reading IC Reference"); + break; + } + if(!flipper_format_read_uint32(file, "Block Count", &temp_uint32, 1)) { + FURI_LOG_D(TAG, "Failed reading Block Count"); + break; + } + data->block_num = temp_uint32; + if(!flipper_format_read_hex(file, "Block Size", &(data->block_size), 1)) { + FURI_LOG_D(TAG, "Failed reading Block Size"); + break; + } + if(!flipper_format_read_hex( + file, "Data Content", data->data, data->block_num * data->block_size)) { + FURI_LOG_D(TAG, "Failed reading Data Content"); + break; + } + + /* optional, as added later */ + if(flipper_format_key_exist(file, "Security Status")) { + if(!flipper_format_read_hex( + file, "Security Status", data->security_status, 1 + data->block_num)) { + FURI_LOG_D(TAG, "Failed reading Security Status"); + break; + } + } + if(!flipper_format_read_hex(file, "Subtype", &temp_value, 1)) { + FURI_LOG_D(TAG, "Failed reading Subtype"); + break; + } + data->sub_type = temp_value; + + switch(data->sub_type) { + case NfcVTypePlain: + parsed = true; + break; + case NfcVTypeSlix: + parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlix); + break; + case NfcVTypeSlixS: + parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlixS); + break; + case NfcVTypeSlixL: + parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlixL); + break; + case NfcVTypeSlix2: + parsed = nfc_device_load_slix_data(file, dev, SlixFeatureSlix2); + break; + default: + break; + } + } while(false); + + return parsed; +} + +static bool nfc_device_save_bank_card_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + EmvData* data = &dev->dev_data.emv_data; + uint32_t data_temp = 0; + + do { + // Write Bank card specific data + if(!flipper_format_write_comment_cstr(file, "Bank card specific data")) break; + if(!flipper_format_write_hex(file, "AID", data->aid, data->aid_len)) break; + if(!flipper_format_write_string_cstr(file, "Name", data->name)) break; + if(!flipper_format_write_hex(file, "Number", data->number, data->number_len)) break; + if(data->exp_mon) { + uint8_t exp_data[2] = {data->exp_mon, data->exp_year}; + if(!flipper_format_write_hex(file, "Exp data", exp_data, sizeof(exp_data))) break; + } + if(data->country_code) { + data_temp = data->country_code; + if(!flipper_format_write_uint32(file, "Country code", &data_temp, 1)) break; + } + if(data->currency_code) { + data_temp = data->currency_code; + if(!flipper_format_write_uint32(file, "Currency code", &data_temp, 1)) break; + } + saved = true; + } while(false); + + return saved; +} + +bool nfc_device_load_bank_card_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + EmvData* data = &dev->dev_data.emv_data; + memset(data, 0, sizeof(EmvData)); + uint32_t data_cnt = 0; + FuriString* temp_str; + temp_str = furi_string_alloc(); + + do { + // Load essential data + if(!flipper_format_get_value_count(file, "AID", &data_cnt)) break; + data->aid_len = data_cnt; + if(!flipper_format_read_hex(file, "AID", data->aid, data->aid_len)) break; + if(!flipper_format_read_string(file, "Name", temp_str)) break; + strlcpy(data->name, furi_string_get_cstr(temp_str), sizeof(data->name)); + if(!flipper_format_get_value_count(file, "Number", &data_cnt)) break; + data->number_len = data_cnt; + if(!flipper_format_read_hex(file, "Number", data->number, data->number_len)) break; + parsed = true; + // Load optional data + uint8_t exp_data[2] = {}; + if(flipper_format_read_hex(file, "Exp data", exp_data, 2)) { + data->exp_mon = exp_data[0]; + data->exp_year = exp_data[1]; + } + if(flipper_format_read_uint32(file, "Country code", &data_cnt, 1)) { + data->country_code = data_cnt; + } + if(flipper_format_read_uint32(file, "Currency code", &data_cnt, 1)) { + data->currency_code = data_cnt; + } + } while(false); + + furi_string_free(temp_str); + return parsed; +} + +static void nfc_device_write_mifare_classic_block( + FuriString* block_str, + MfClassicData* data, + uint8_t block_num) { + furi_string_reset(block_str); + bool is_sec_trailer = mf_classic_is_sector_trailer(block_num); + if(is_sec_trailer) { + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num); + // Write key A + for(size_t i = 0; i < sizeof(sec_tr->key_a); i++) { + if(mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) { + furi_string_cat_printf(block_str, "%02X ", sec_tr->key_a[i]); + } else { + furi_string_cat_printf(block_str, "?? "); + } + } + // Write Access bytes + for(size_t i = 0; i < MF_CLASSIC_ACCESS_BYTES_SIZE; i++) { + if(mf_classic_is_block_read(data, block_num)) { + furi_string_cat_printf(block_str, "%02X ", sec_tr->access_bits[i]); + } else { + furi_string_cat_printf(block_str, "?? "); + } + } + // Write key B + for(size_t i = 0; i < sizeof(sec_tr->key_b); i++) { + if(mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) { + furi_string_cat_printf(block_str, "%02X ", sec_tr->key_b[i]); + } else { + furi_string_cat_printf(block_str, "?? "); + } + } + } else { + // Write data block + for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) { + if(mf_classic_is_block_read(data, block_num)) { + furi_string_cat_printf(block_str, "%02X ", data->block[block_num].value[i]); + } else { + furi_string_cat_printf(block_str, "?? "); + } + } + } + furi_string_trim(block_str); +} + +static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice* dev) { + bool saved = false; + MfClassicData* data = &dev->dev_data.mf_classic_data; + FuriString* temp_str; + temp_str = furi_string_alloc(); + uint16_t blocks = 0; + + // Save Mifare Classic specific data + do { + if(!flipper_format_write_comment_cstr(file, "Mifare Classic specific data")) break; + + if(data->type == MfClassicTypeMini) { + if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "MINI")) break; + blocks = 20; + } else if(data->type == MfClassicType1k) { + if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break; + blocks = 64; + } else if(data->type == MfClassicType4k) { + if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) break; + blocks = 256; + } + if(!flipper_format_write_uint32( + file, "Data format version", &nfc_mifare_classic_data_format_version, 1)) + break; + if(!flipper_format_write_comment_cstr( + file, "Mifare Classic blocks, \'??\' means unknown data")) + break; + bool block_saved = true; + FuriString* block_str; + block_str = furi_string_alloc(); + for(size_t i = 0; i < blocks; i++) { + furi_string_printf(temp_str, "Block %d", i); + nfc_device_write_mifare_classic_block(block_str, data, i); + if(!flipper_format_write_string(file, furi_string_get_cstr(temp_str), block_str)) { + block_saved = false; + break; + } + } + furi_string_free(block_str); + if(!block_saved) break; + saved = true; + } while(false); + + furi_string_free(temp_str); + return saved; +} + +static void nfc_device_load_mifare_classic_block( + FuriString* block_str, + MfClassicData* data, + uint8_t block_num) { + furi_string_trim(block_str); + MfClassicBlock block_tmp = {}; + bool is_sector_trailer = mf_classic_is_sector_trailer(block_num); + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + uint16_t block_unknown_bytes_mask = 0; + + furi_string_trim(block_str); + for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) { + char hi = furi_string_get_char(block_str, 3 * i); + char low = furi_string_get_char(block_str, 3 * i + 1); + uint8_t byte = 0; + if(hex_char_to_uint8(hi, low, &byte)) { + block_tmp.value[i] = byte; + } else { + FURI_BIT_SET(block_unknown_bytes_mask, i); + } + } + + if(block_unknown_bytes_mask == 0xffff) { + // All data is unknown, exit + return; + } + + if(is_sector_trailer) { + MfClassicSectorTrailer* sec_tr_tmp = (MfClassicSectorTrailer*)&block_tmp; + // Load Key A + // Key A mask 0b0000000000111111 = 0x003f + if((block_unknown_bytes_mask & 0x003f) == 0) { + uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_a, sizeof(sec_tr_tmp->key_a)); + mf_classic_set_key_found(data, sector_num, MfClassicKeyA, key); + } + // Load Access Bits + // Access bits mask 0b0000001111000000 = 0x03c0 + if((block_unknown_bytes_mask & 0x03c0) == 0) { + mf_classic_set_block_read(data, block_num, &block_tmp); + } + // Load Key B + // Key B mask 0b1111110000000000 = 0xfc00 + if((block_unknown_bytes_mask & 0xfc00) == 0) { + uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_b, sizeof(sec_tr_tmp->key_b)); + mf_classic_set_key_found(data, sector_num, MfClassicKeyB, key); + } + } else { + if(block_unknown_bytes_mask == 0) { + mf_classic_set_block_read(data, block_num, &block_tmp); + } + } +} + +static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice* dev) { + bool parsed = false; + MfClassicData* data = &dev->dev_data.mf_classic_data; + FuriString* temp_str; + uint32_t data_format_version = 0; + temp_str = furi_string_alloc(); + uint16_t data_blocks = 0; + memset(data, 0, sizeof(MfClassicData)); + + do { + // Read Mifare Classic type + if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break; + if(!furi_string_cmp(temp_str, "MINI")) { + data->type = MfClassicTypeMini; + data_blocks = 20; + } else if(!furi_string_cmp(temp_str, "1K")) { + data->type = MfClassicType1k; + data_blocks = 64; + } else if(!furi_string_cmp(temp_str, "4K")) { + data->type = MfClassicType4k; + data_blocks = 256; + } else { + break; + } + + bool old_format = false; + // Read Mifare Classic format version + if(!flipper_format_read_uint32(file, "Data format version", &data_format_version, 1)) { + // Load unread sectors with zero keys access for backward compatibility + if(!flipper_format_rewind(file)) break; + old_format = true; + } else { + if(data_format_version < nfc_mifare_classic_data_format_version) { + old_format = true; + } + } + + // Read Mifare Classic blocks + bool block_read = true; + FuriString* block_str; + block_str = furi_string_alloc(); + for(size_t i = 0; i < data_blocks; i++) { + furi_string_printf(temp_str, "Block %d", i); + if(!flipper_format_read_string(file, furi_string_get_cstr(temp_str), block_str)) { + block_read = false; + break; + } + nfc_device_load_mifare_classic_block(block_str, data, i); + } + furi_string_free(block_str); + if(!block_read) break; + + // Set keys and blocks as unknown for backward compatibility + if(old_format) { + data->key_a_mask = 0ULL; + data->key_b_mask = 0ULL; + memset(data->block_read_mask, 0, sizeof(data->block_read_mask)); + } + + parsed = true; + } while(false); + + furi_string_free(temp_str); + return parsed; +} + +static void nfc_device_get_key_cache_file_path(NfcDevice* dev, FuriString* file_path) { + uint8_t* uid = dev->dev_data.nfc_data.uid; + uint8_t uid_len = dev->dev_data.nfc_data.uid_len; + furi_string_set(file_path, NFC_DEVICE_KEYS_FOLDER "/"); + for(size_t i = 0; i < uid_len; i++) { + furi_string_cat_printf(file_path, "%02X", uid[i]); + } + furi_string_cat_printf(file_path, NFC_DEVICE_KEYS_EXTENSION); +} + +static bool nfc_device_save_mifare_classic_keys(NfcDevice* dev) { + FlipperFormat* file = flipper_format_file_alloc(dev->storage); + MfClassicData* data = &dev->dev_data.mf_classic_data; + FuriString* temp_str; + temp_str = furi_string_alloc(); + + nfc_device_get_key_cache_file_path(dev, temp_str); + bool save_success = false; + do { + if(!storage_simply_mkdir(dev->storage, NFC_DEVICE_KEYS_FOLDER)) break; + if(!storage_simply_remove(dev->storage, furi_string_get_cstr(temp_str))) break; + if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break; + if(!flipper_format_write_header_cstr(file, nfc_keys_file_header, nfc_keys_file_version)) + break; + if(data->type == MfClassicTypeMini) { + if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "MINI")) break; + } else if(data->type == MfClassicType1k) { + if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break; + } else if(data->type == MfClassicType4k) { + if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) break; + } + if(!flipper_format_write_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break; + if(!flipper_format_write_hex_uint64(file, "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( + file, furi_string_get_cstr(temp_str), sec_tr->key_a, 6); + } + 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( + file, furi_string_get_cstr(temp_str), sec_tr->key_b, 6); + } + } + save_success = key_save_success; + } while(false); + + flipper_format_free(file); + furi_string_free(temp_str); + return save_success; +} + +bool nfc_device_load_key_cache(NfcDevice* dev) { + furi_assert(dev); + FuriString* temp_str; + temp_str = furi_string_alloc(); + + MfClassicData* data = &dev->dev_data.mf_classic_data; + nfc_device_get_key_cache_file_path(dev, temp_str); + FlipperFormat* file = flipper_format_file_alloc(dev->storage); + + bool load_success = false; + do { + if(storage_common_stat(dev->storage, furi_string_get_cstr(temp_str), NULL) != FSE_OK) + break; + if(!flipper_format_file_open_existing(file, furi_string_get_cstr(temp_str))) break; + uint32_t version = 0; + if(!flipper_format_read_header(file, temp_str, &version)) break; + if(furi_string_cmp_str(temp_str, nfc_keys_file_header)) break; + if(version != nfc_keys_file_version) break; + if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break; + if(!furi_string_cmp(temp_str, "MINI")) { + data->type = MfClassicTypeMini; + } else if(!furi_string_cmp(temp_str, "1K")) { + data->type = MfClassicType1k; + } else if(!furi_string_cmp(temp_str, "4K")) { + data->type = MfClassicType4k; + } else { + break; + } + if(!flipper_format_read_hex_uint64(file, "Key A map", &data->key_a_mask, 1)) break; + if(!flipper_format_read_hex_uint64(file, "Key B map", &data->key_b_mask, 1)) break; + uint8_t sectors = mf_classic_get_total_sectors_num(data->type); + bool key_read_success = true; + for(size_t i = 0; (i < sectors) && (key_read_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_read_success = flipper_format_read_hex( + file, furi_string_get_cstr(temp_str), sec_tr->key_a, 6); + } + if(!key_read_success) break; + if(FURI_BIT(data->key_b_mask, i)) { + furi_string_printf(temp_str, "Key B sector %d", i); + key_read_success = flipper_format_read_hex( + file, furi_string_get_cstr(temp_str), sec_tr->key_b, 6); + } + } + load_success = key_read_success; + } while(false); + + furi_string_free(temp_str); + flipper_format_free(file); + + return load_success; +} + +void nfc_device_set_name(NfcDevice* dev, const char* name) { + furi_assert(dev); + + strlcpy(dev->dev_name, name, NFC_DEV_NAME_MAX_LEN); +} + +static void nfc_device_get_path_without_ext(FuriString* orig_path, FuriString* shadow_path) { + // TODO: this won't work if there is ".nfc" anywhere in the path other than + // at the end + size_t ext_start = furi_string_search(orig_path, NFC_APP_FILENAME_EXTENSION); + furi_string_set_n(shadow_path, orig_path, 0, ext_start); +} + +static void nfc_device_get_shadow_path(FuriString* orig_path, FuriString* shadow_path) { + nfc_device_get_path_without_ext(orig_path, shadow_path); + furi_string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION); +} + +static void nfc_device_get_folder_from_path(FuriString* path, FuriString* folder) { + size_t last_slash = furi_string_search_rchar(path, '/'); + if(last_slash == FURI_STRING_FAILURE) { + // No slashes in the path, treat the whole path as a folder + furi_string_set(folder, path); + } else { + furi_string_set_n(folder, path, 0, last_slash); + } +} + +bool nfc_device_save(NfcDevice* dev, const char* dev_name) { + furi_assert(dev); + + bool saved = false; + FlipperFormat* file = flipper_format_file_alloc(dev->storage); + FuriHalNfcDevData* data = &dev->dev_data.nfc_data; + FuriString* temp_str; + temp_str = furi_string_alloc(); + + do { + // Create directory if necessary + FuriString* folder = furi_string_alloc(); + // Get folder from filename (filename is in the form of "folder/filename.nfc", so the folder is "folder/") + furi_string_set(temp_str, dev_name); + // Get folder from filename + nfc_device_get_folder_from_path(temp_str, folder); + FURI_LOG_I("Nfc", "Saving to folder %s", furi_string_get_cstr(folder)); + if(!storage_simply_mkdir(dev->storage, furi_string_get_cstr(folder))) { + FURI_LOG_E("Nfc", "Failed to create folder %s", furi_string_get_cstr(folder)); + break; + } + furi_string_free(folder); + // First remove nfc device file if it was saved + // Open file + if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break; + // Write header + if(!flipper_format_write_header_cstr(file, nfc_file_header, nfc_file_version)) break; + // Write nfc device type + if(!flipper_format_write_comment_cstr( + file, "Nfc device type can be UID, Mifare Ultralight, Mifare Classic or ISO15693")) + break; + nfc_device_prepare_format_string(dev, temp_str); + if(!flipper_format_write_string(file, "Device type", temp_str)) break; + // Write UID + if(!flipper_format_write_comment_cstr(file, "UID is common for all formats")) break; + if(!flipper_format_write_hex(file, "UID", data->uid, data->uid_len)) break; + + if(dev->format != NfcDeviceSaveFormatNfcV) { + // Write ATQA, SAK + if(!flipper_format_write_comment_cstr(file, "ISO14443 specific fields")) break; + // Save ATQA in MSB order for correct companion apps display + uint8_t atqa[2] = {data->atqa[1], data->atqa[0]}; + if(!flipper_format_write_hex(file, "ATQA", atqa, 2)) break; + if(!flipper_format_write_hex(file, "SAK", &data->sak, 1)) break; + } + + // Save more data if necessary + if(dev->format == NfcDeviceSaveFormatMifareUl) { + if(!nfc_device_save_mifare_ul_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { + if(!nfc_device_save_mifare_df_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatNfcV) { + if(!nfc_device_save_nfcv_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatBankCard) { + if(!nfc_device_save_bank_card_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatMifareClassic) { + // Save data + if(!nfc_device_save_mifare_classic_data(file, dev)) break; + // Save keys cache + if(!nfc_device_save_mifare_classic_keys(dev)) break; + } + saved = true; + } while(0); + + if(!saved) { //-V547 + dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file"); + } + furi_string_free(temp_str); + flipper_format_free(file); + return saved; +} + +bool nfc_device_save_shadow(NfcDevice* dev, const char* path) { + dev->shadow_file_exist = true; + // Replace extension from .nfc to .shd if necessary + FuriString* orig_path = furi_string_alloc(); + furi_string_set_str(orig_path, path); + FuriString* shadow_path = furi_string_alloc(); + nfc_device_get_shadow_path(orig_path, shadow_path); + + bool file_saved = nfc_device_save(dev, furi_string_get_cstr(shadow_path)); + furi_string_free(orig_path); + furi_string_free(shadow_path); + + return file_saved; +} + +static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dialog) { + bool parsed = false; + FlipperFormat* file = flipper_format_file_alloc(dev->storage); + FuriHalNfcDevData* data = &dev->dev_data.nfc_data; + uint32_t data_cnt = 0; + FuriString* temp_str; + temp_str = furi_string_alloc(); + bool deprecated_version = false; + + // Version 2 of file format had ATQA bytes swapped + uint32_t version_with_lsb_atqa = 2; + + if(dev->loading_cb) { + dev->loading_cb(dev->loading_cb_ctx, true); + } + + do { + // Check existence of shadow file + nfc_device_get_shadow_path(path, temp_str); + dev->shadow_file_exist = + storage_common_stat(dev->storage, furi_string_get_cstr(temp_str), NULL) == FSE_OK; + // Open shadow file if it exists. If not - open original + if(dev->shadow_file_exist) { + if(!flipper_format_file_open_existing(file, furi_string_get_cstr(temp_str))) break; + } else { + if(!flipper_format_file_open_existing(file, furi_string_get_cstr(path))) break; + } + // Read and verify file header + uint32_t version = 0; + if(!flipper_format_read_header(file, temp_str, &version)) break; + if(furi_string_cmp_str(temp_str, nfc_file_header)) break; + if(version != nfc_file_version) { + if(version < version_with_lsb_atqa) { + deprecated_version = true; + break; + } + } + // Read Nfc device type + if(!flipper_format_read_string(file, "Device type", temp_str)) break; + if(!nfc_device_parse_format_string(dev, temp_str)) break; + // Read and parse UID, ATQA and SAK + if(!flipper_format_get_value_count(file, "UID", &data_cnt)) break; + if(!(data_cnt == 4 || data_cnt == 7 || data_cnt == 8)) break; + data->uid_len = data_cnt; + if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break; + if(dev->format != NfcDeviceSaveFormatNfcV) { + if(version == version_with_lsb_atqa) { + if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break; + } else { + uint8_t atqa[2] = {}; + if(!flipper_format_read_hex(file, "ATQA", atqa, 2)) break; + data->atqa[0] = atqa[1]; + data->atqa[1] = atqa[0]; + } + if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break; + } + // Load CUID + uint8_t* cuid_start = data->uid; + if(data->uid_len == 7) { + cuid_start = &data->uid[3]; + } + data->cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) | + (cuid_start[3]); + // Parse other data + if(dev->format == NfcDeviceSaveFormatMifareUl) { + if(!nfc_device_load_mifare_ul_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatMifareClassic) { + if(!nfc_device_load_mifare_classic_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { + if(!nfc_device_load_mifare_df_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatNfcV) { + if(!nfc_device_load_nfcv_data(file, dev)) break; + } else if(dev->format == NfcDeviceSaveFormatBankCard) { + if(!nfc_device_load_bank_card_data(file, dev)) break; + } + parsed = true; + } while(false); + + if(dev->loading_cb) { + dev->loading_cb(dev->loading_cb_ctx, false); + } + + if((!parsed) && (show_dialog)) { + if(deprecated_version) { + dialog_message_show_storage_error(dev->dialogs, "File format deprecated"); + } else { + dialog_message_show_storage_error(dev->dialogs, "Can not parse\nfile"); + } + } + + furi_string_free(temp_str); + flipper_format_free(file); + return parsed; +} + +bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog) { + furi_assert(dev); + furi_assert(file_path); + + // Load device data + furi_string_set(dev->load_path, file_path); + bool dev_load = nfc_device_load_data(dev, dev->load_path, show_dialog); + if(dev_load) { + // Set device name + FuriString* filename; + filename = furi_string_alloc(); + path_extract_filename_no_ext(file_path, filename); + nfc_device_set_name(dev, furi_string_get_cstr(filename)); + furi_string_free(filename); + } + + return dev_load; +} + +bool nfc_file_select(NfcDevice* dev) { + furi_assert(dev); + const char* folder = furi_string_get_cstr(dev->folder); + + // Input events and views are managed by file_browser + + const DialogsFileBrowserOptions browser_options = { + .extension = NFC_APP_FILENAME_EXTENSION, + .skip_assets = true, + .hide_dot_files = true, + .icon = &I_Nfc_10px, + .hide_ext = true, + .item_loader_callback = NULL, + .item_loader_context = NULL, + .base_path = folder, + }; + + bool res = + dialog_file_browser_show(dev->dialogs, dev->load_path, dev->load_path, &browser_options); + + if(res) { + FuriString* filename; + filename = furi_string_alloc(); + path_extract_filename(dev->load_path, filename, true); + strncpy(dev->dev_name, furi_string_get_cstr(filename), NFC_DEV_NAME_MAX_LEN); + res = nfc_device_load_data(dev, dev->load_path, true); + if(res) { + nfc_device_set_name(dev, dev->dev_name); + } + furi_string_free(filename); + } + + return res; +} + +void nfc_device_data_clear(NfcDeviceData* dev_data) { + if(dev_data->protocol == NfcDeviceProtocolMifareDesfire) { + mf_df_clear(&dev_data->mf_df_data); + } else if(dev_data->protocol == NfcDeviceProtocolMifareClassic) { + memset(&dev_data->mf_classic_data, 0, sizeof(MfClassicData)); + } else if(dev_data->protocol == NfcDeviceProtocolMifareUl) { + mf_ul_reset(&dev_data->mf_ul_data); + } else if(dev_data->protocol == NfcDeviceProtocolEMV) { + memset(&dev_data->emv_data, 0, sizeof(EmvData)); + } + + furi_string_reset(dev_data->parsed_data); + + memset(&dev_data->nfc_data, 0, sizeof(FuriHalNfcDevData)); + dev_data->protocol = NfcDeviceProtocolUnknown; + furi_string_reset(dev_data->parsed_data); +} + +void nfc_device_clear(NfcDevice* dev) { + furi_assert(dev); + + nfc_device_set_name(dev, ""); + nfc_device_data_clear(&dev->dev_data); + dev->format = NfcDeviceSaveFormatUid; + furi_string_reset(dev->load_path); +} + +bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { + furi_assert(dev); + + bool deleted = false; + FuriString* file_path; + file_path = furi_string_alloc(); + + do { + // Delete original file + if(use_load_path && !furi_string_empty(dev->load_path)) { + furi_string_set(file_path, dev->load_path); + } else { + furi_string_printf( + file_path, + "%s/%s%s", + furi_string_get_cstr(dev->folder), + dev->dev_name, + NFC_APP_FILENAME_EXTENSION); + } + if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; + // Delete shadow file if it exists + if(dev->shadow_file_exist) { + if(use_load_path && !furi_string_empty(dev->load_path)) { + nfc_device_get_shadow_path(dev->load_path, file_path); + } else { + furi_string_printf( + file_path, + "%s/%s%s", + furi_string_get_cstr(dev->folder), + dev->dev_name, + NFC_APP_SHADOW_EXTENSION); + } + if(!storage_simply_remove(dev->storage, furi_string_get_cstr(file_path))) break; + } + deleted = true; + } while(0); + + if(!deleted) { + dialog_message_show_storage_error(dev->dialogs, "Can not remove file"); + } + + furi_string_free(file_path); + return deleted; +} + +bool nfc_device_restore(NfcDevice* dev, bool use_load_path) { + furi_assert(dev); + furi_assert(dev->shadow_file_exist); + + bool restored = false; + FuriString* path; + + path = furi_string_alloc(); + + do { + if(use_load_path && !furi_string_empty(dev->load_path)) { + nfc_device_get_shadow_path(dev->load_path, path); + } else { + furi_string_printf( + path, + "%s/%s%s", + furi_string_get_cstr(dev->folder), + dev->dev_name, + NFC_APP_SHADOW_EXTENSION); + } + if(!storage_simply_remove(dev->storage, furi_string_get_cstr(path))) break; + dev->shadow_file_exist = false; + if(use_load_path && !furi_string_empty(dev->load_path)) { + furi_string_set(path, dev->load_path); + } else { + furi_string_printf( + path, + "%s/%s%s", + furi_string_get_cstr(dev->folder), + dev->dev_name, + NFC_APP_FILENAME_EXTENSION); + } + if(!nfc_device_load_data(dev, path, true)) break; + restored = true; + } while(0); + + furi_string_free(path); + return restored; +} + +void nfc_device_set_loading_callback(NfcDevice* dev, NfcLoadingCallback callback, void* context) { + furi_assert(dev); + + dev->loading_cb = callback; + dev->loading_cb_ctx = context; +} diff --git a/lib/nfc/nfc_device.h b/lib/nfc/nfc_device.h new file mode 100644 index 0000000..76de0b6 --- /dev/null +++ b/lib/nfc/nfc_device.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define NFC_DEV_NAME_MAX_LEN 22 +#define NFC_READER_DATA_MAX_SIZE 64 +#define NFC_DICT_KEY_BATCH_SIZE 10 + +#define NFC_APP_FILENAME_PREFIX "NFC" +#define NFC_APP_FILENAME_EXTENSION ".nfc" +#define NFC_APP_SHADOW_EXTENSION ".shd" + +typedef void (*NfcLoadingCallback)(void* context, bool state); + +typedef enum { + NfcDeviceProtocolUnknown, + NfcDeviceProtocolEMV, + NfcDeviceProtocolMifareUl, + NfcDeviceProtocolMifareClassic, + NfcDeviceProtocolMifareDesfire, + NfcDeviceProtocolNfcV +} NfcProtocol; + +typedef enum { + NfcDeviceSaveFormatUid, + NfcDeviceSaveFormatBankCard, + NfcDeviceSaveFormatMifareUl, + NfcDeviceSaveFormatMifareClassic, + NfcDeviceSaveFormatMifareDesfire, + NfcDeviceSaveFormatNfcV, +} NfcDeviceSaveFormat; + +typedef struct { + uint8_t data[NFC_READER_DATA_MAX_SIZE]; + uint16_t size; +} NfcReaderRequestData; + +typedef struct { + MfClassicDict* dict; + uint8_t current_sector; +} NfcMfClassicDictAttackData; + +typedef enum { + NfcReadModeAuto, + NfcReadModeMfClassic, + NfcReadModeMfUltralight, + NfcReadModeMfDesfire, + NfcReadModeNFCA, +} NfcReadMode; + +typedef struct { + FuriHalNfcDevData nfc_data; + NfcProtocol protocol; + NfcReadMode read_mode; + union { + NfcReaderRequestData reader_data; + NfcMfClassicDictAttackData mf_classic_dict_attack_data; + MfUltralightAuth mf_ul_auth; + }; + union { + EmvData emv_data; + MfUltralightData mf_ul_data; + MfClassicData mf_classic_data; + MifareDesfireData mf_df_data; + NfcVData nfcv_data; + }; + FuriString* parsed_data; +} NfcDeviceData; + +typedef struct { + Storage* storage; + DialogsApp* dialogs; + NfcDeviceData dev_data; + char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; + FuriString* load_path; + FuriString* folder; + NfcDeviceSaveFormat format; + bool shadow_file_exist; + + NfcLoadingCallback loading_cb; + void* loading_cb_ctx; +} NfcDevice; + +NfcDevice* nfc_device_alloc(); + +void nfc_device_free(NfcDevice* nfc_dev); + +void nfc_device_set_name(NfcDevice* dev, const char* name); + +bool nfc_device_save(NfcDevice* dev, const char* dev_name); + +bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name); + +bool nfc_device_load(NfcDevice* dev, const char* file_path, bool show_dialog); + +bool nfc_device_load_key_cache(NfcDevice* dev); + +bool nfc_file_select(NfcDevice* dev); + +void nfc_device_data_clear(NfcDeviceData* dev); + +void nfc_device_clear(NfcDevice* dev); + +bool nfc_device_delete(NfcDevice* dev, bool use_load_path); + +bool nfc_device_restore(NfcDevice* dev, bool use_load_path); + +void nfc_device_set_loading_callback(NfcDevice* dev, NfcLoadingCallback callback, void* context); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/nfc_types.c b/lib/nfc/nfc_types.c new file mode 100644 index 0000000..96b9264 --- /dev/null +++ b/lib/nfc/nfc_types.c @@ -0,0 +1,69 @@ +#include "nfc_types.h" + +const char* nfc_get_dev_type(FuriHalNfcType type) { + if(type == FuriHalNfcTypeA) { + return "NFC-A"; + } else if(type == FuriHalNfcTypeB) { + return "NFC-B"; + } else if(type == FuriHalNfcTypeF) { + return "NFC-F"; + } else if(type == FuriHalNfcTypeV) { + return "NFC-V"; + } else { + return "Unknown"; + } +} + +const char* nfc_guess_protocol(NfcProtocol protocol) { + if(protocol == NfcDeviceProtocolEMV) { + return "EMV bank card"; + } else if(protocol == NfcDeviceProtocolMifareUl) { + return "Mifare Ultral/NTAG"; + } else if(protocol == NfcDeviceProtocolMifareClassic) { + return "Mifare Classic"; + } else if(protocol == NfcDeviceProtocolMifareDesfire) { + return "Mifare DESFire"; + } else { + return "Unrecognized"; + } +} + +const char* nfc_mf_ul_type(MfUltralightType type, bool full_name) { + if(type == MfUltralightTypeNTAG213) { + return "NTAG213"; + } else if(type == MfUltralightTypeNTAG215) { + return "NTAG215"; + } else if(type == MfUltralightTypeNTAG216) { + return "NTAG216"; + } else if(type == MfUltralightTypeNTAGI2C1K) { + return "NTAG I2C 1K"; + } else if(type == MfUltralightTypeNTAGI2C2K) { + return "NTAG I2C 2K"; + } else if(type == MfUltralightTypeNTAGI2CPlus1K) { + return "NTAG I2C Plus 1K"; + } else if(type == MfUltralightTypeNTAGI2CPlus2K) { + return "NTAG I2C Plus 2K"; + } else if(type == MfUltralightTypeNTAG203) { + return "NTAG203"; + } else if(type == MfUltralightTypeULC) { + return "Mifare Ultralight C"; + } else if(type == MfUltralightTypeUL11 && full_name) { + return "Mifare Ultralight 11"; + } else if(type == MfUltralightTypeUL21 && full_name) { + return "Mifare Ultralight 21"; + } else { + return "Mifare Ultralight"; + } +} + +const char* nfc_mf_classic_type(MfClassicType type) { + if(type == MfClassicTypeMini) { + return "Mifare Mini 0.3K"; + } else if(type == MfClassicType1k) { + return "Mifare Classic 1K"; + } else if(type == MfClassicType4k) { + return "Mifare Classic 4K"; + } else { + return "Mifare Classic"; + } +} diff --git a/lib/nfc/nfc_types.h b/lib/nfc/nfc_types.h new file mode 100644 index 0000000..5ebb2d2 --- /dev/null +++ b/lib/nfc/nfc_types.h @@ -0,0 +1,19 @@ +#pragma once + +#include "nfc_device.h" + +#ifdef __cplusplus +extern "C" { +#endif + +const char* nfc_get_dev_type(FuriHalNfcType type); + +const char* nfc_guess_protocol(NfcProtocol protocol); + +const char* nfc_mf_ul_type(MfUltralightType type, bool full_name); + +const char* nfc_mf_classic_type(MfClassicType type); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c new file mode 100644 index 0000000..36776d8 --- /dev/null +++ b/lib/nfc/nfc_worker.c @@ -0,0 +1,1325 @@ +#include "nfc_worker_i.h" +#include + +#include +#include "parsers/nfc_supported_card.h" + +#define TAG "NfcWorker" + +/***************************** NFC Worker API *******************************/ + +NfcWorker* nfc_worker_alloc() { + NfcWorker* nfc_worker = malloc(sizeof(NfcWorker)); + + // Worker thread attributes + nfc_worker->thread = furi_thread_alloc_ex("NfcWorker", 8192, nfc_worker_task, nfc_worker); + + nfc_worker->callback = NULL; + nfc_worker->context = NULL; + nfc_worker->storage = furi_record_open(RECORD_STORAGE); + + // Initialize rfal + while(furi_hal_nfc_is_busy()) { + furi_delay_ms(10); + } + nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); + + nfc_worker->reader_analyzer = reader_analyzer_alloc(nfc_worker->storage); + + return nfc_worker; +} + +void nfc_worker_free(NfcWorker* nfc_worker) { + furi_assert(nfc_worker); + + furi_thread_free(nfc_worker->thread); + + furi_record_close(RECORD_STORAGE); + + reader_analyzer_free(nfc_worker->reader_analyzer); + + free(nfc_worker); +} + +NfcWorkerState nfc_worker_get_state(NfcWorker* nfc_worker) { + return nfc_worker->state; +} + +void nfc_worker_start( + NfcWorker* nfc_worker, + NfcWorkerState state, + NfcDeviceData* dev_data, + NfcWorkerCallback callback, + void* context) { + furi_assert(nfc_worker); + furi_assert(dev_data); + while(furi_hal_nfc_is_busy()) { + furi_delay_ms(10); + } + furi_hal_nfc_deinit(); + furi_hal_nfc_init(); + + nfc_worker->callback = callback; + nfc_worker->context = context; + nfc_worker->dev_data = dev_data; + nfc_worker_change_state(nfc_worker, state); + furi_thread_start(nfc_worker->thread); +} + +void nfc_worker_stop(NfcWorker* nfc_worker) { + furi_assert(nfc_worker); + furi_assert(nfc_worker->thread); + if(furi_thread_get_state(nfc_worker->thread) != FuriThreadStateStopped) { + furi_hal_nfc_stop(); + nfc_worker_change_state(nfc_worker, NfcWorkerStateStop); + furi_thread_join(nfc_worker->thread); + } +} + +void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state) { + nfc_worker->state = state; +} + +/***************************** NFC Worker Thread *******************************/ + +int32_t nfc_worker_task(void* context) { + NfcWorker* nfc_worker = context; + + furi_hal_nfc_exit_sleep(); + + if(nfc_worker->state == NfcWorkerStateRead) { + if(nfc_worker->dev_data->read_mode == NfcReadModeAuto) { + nfc_worker_read(nfc_worker); + } else { + nfc_worker_read_type(nfc_worker); + } + } else if(nfc_worker->state == NfcWorkerStateUidEmulate) { + nfc_worker_emulate_uid(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateEmulateApdu) { + nfc_worker_emulate_apdu(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) { + nfc_worker_emulate_mf_ultralight(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { + nfc_worker_emulate_mf_classic(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateMfClassicWrite) { + nfc_worker_write_mf_classic(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateMfClassicUpdate) { + nfc_worker_update_mf_classic(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateReadMfUltralightReadAuth) { + nfc_worker_mf_ultralight_read_auth(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { + nfc_worker_mf_classic_dict_attack(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateAnalyzeReader) { + nfc_worker_analyze_reader(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateNfcVEmulate) { + nfc_worker_nfcv_emulate(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateNfcVSniff) { + nfc_worker_nfcv_sniff(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateNfcVUnlock) { + nfc_worker_nfcv_unlock(nfc_worker); + } else if(nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave) { + nfc_worker_nfcv_unlock(nfc_worker); + } + furi_hal_nfc_sleep(); + nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); + + return 0; +} + +static bool nfc_worker_read_nfcv(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + bool read_success = false; + NfcVReader reader = {}; + + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; + + furi_hal_nfc_sleep(); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + do { + if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 200)) break; + if(!nfcv_read_card(&reader, nfc_data, nfcv_data)) break; + + read_success = true; + } while(false); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } + + return read_success; +} + +void nfc_worker_nfcv_emulate(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + nfcv_emu_init(nfc_data, nfcv_data); + while(nfc_worker->state == NfcWorkerStateNfcVEmulate) { + if(nfcv_emu_loop(&tx_rx, nfc_data, nfcv_data, 100)) { + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventNfcVCommandExecuted, nfc_worker->context); + if(nfcv_data->modified) { + nfc_worker->callback(NfcWorkerEventNfcVContentChanged, nfc_worker->context); + nfcv_data->modified = false; + } + } + } + } + nfcv_emu_deinit(nfcv_data); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } +} + +void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + nfcv_data->sub_type = NfcVTypeSniff; + nfcv_emu_init(nfc_data, nfcv_data); + + while(nfc_worker->state == NfcWorkerStateNfcVSniff) { + if(nfcv_emu_loop(&tx_rx, nfc_data, nfcv_data, 100)) { + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventNfcVCommandExecuted, nfc_worker->context); + } + } + } + nfcv_emu_deinit(nfcv_data); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } +} + +void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker) { + furi_assert(nfc_worker); + furi_assert(nfc_worker->callback); + + NfcVData* nfcv_data = &nfc_worker->dev_data->nfcv_data; + FuriHalNfcTxRxContext tx_rx = {}; + uint8_t* key_data = nfcv_data->sub_data.slix.key_privacy; + uint32_t key = 0; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + furi_hal_nfc_sleep(); + + while((nfc_worker->state == NfcWorkerStateNfcVUnlock) || + (nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave)) { + furi_hal_nfc_exit_sleep(); + furi_hal_nfc_ll_txrx_on(); + furi_hal_nfc_ll_poll(); + if(furi_hal_nfc_ll_set_mode( + FuriHalNfcModePollNfcv, FuriHalNfcBitrate26p48, FuriHalNfcBitrate26p48) != + FuriHalNfcReturnOk) { + break; + } + + furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCV_POLLER); + furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCV_POLLER); + furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc); + furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCV); + + FURI_LOG_D(TAG, "Detect presence"); + ReturnCode ret = slix_get_random(nfcv_data); + + if(ret == ERR_NONE) { + /* there is some chip, responding with a RAND */ + nfc_worker->dev_data->protocol = NfcDeviceProtocolNfcV; + FURI_LOG_D(TAG, " Chip detected. In privacy?"); + ret = nfcv_inventory(NULL); + + if(ret == ERR_NONE) { + /* chip is also visible, so no action required, just save */ + if(nfc_worker->state == NfcWorkerStateNfcVUnlockAndSave) { + NfcVReader reader = {}; + + if(!nfcv_read_card(&reader, &nfc_worker->dev_data->nfc_data, nfcv_data)) { + FURI_LOG_D(TAG, " => failed, wait for chip to disappear."); + snprintf(nfcv_data->error, sizeof(nfcv_data->error), "Read card\nfailed"); + nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context); + } else { + FURI_LOG_D(TAG, " => success, wait for chip to disappear."); + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + } + } else { + FURI_LOG_D(TAG, " => success, wait for chip to disappear."); + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + } + + while(slix_get_random(NULL) == ERR_NONE) { + furi_delay_ms(100); + } + + FURI_LOG_D(TAG, " => chip is already visible, wait for chip to disappear.\r\n"); + nfc_worker->callback(NfcWorkerEventAborted, nfc_worker->context); + while(slix_get_random(NULL) == ERR_NONE) { + furi_delay_ms(100); + } + + key_data[0] = 0; + key_data[1] = 0; + key_data[2] = 0; + key_data[3] = 0; + + } else { + /* chip is invisible, try to unlock */ + FURI_LOG_D(TAG, " chip is invisible, unlocking"); + + if(nfcv_data->auth_method == NfcVAuthMethodManual) { + key |= key_data[0] << 24; + key |= key_data[1] << 16; + key |= key_data[2] << 8; + key |= key_data[3] << 0; + + ret = slix_unlock(nfcv_data, 4); + } else { + key = 0x7FFD6E5B; + key_data[0] = (key >> 24) & 0xFF; + key_data[1] = (key >> 16) & 0xFF; + key_data[2] = (key >> 8) & 0xFF; + key_data[3] = (key >> 0) & 0xFF; + ret = slix_unlock(nfcv_data, 4); + + if(ret != ERR_NONE) { + /* main key failed, trying second one */ + FURI_LOG_D(TAG, " trying second key after resetting"); + + /* reset chip */ + furi_hal_nfc_ll_txrx_off(); + furi_delay_ms(20); + furi_hal_nfc_ll_txrx_on(); + + if(slix_get_random(nfcv_data) != ERR_NONE) { + FURI_LOG_D(TAG, " reset failed"); + } + + key = 0x0F0F0F0F; + key_data[0] = (key >> 24) & 0xFF; + key_data[1] = (key >> 16) & 0xFF; + key_data[2] = (key >> 8) & 0xFF; + key_data[3] = (key >> 0) & 0xFF; + ret = slix_unlock(nfcv_data, 4); + } + } + if(ret != ERR_NONE) { + /* unlock failed */ + FURI_LOG_D(TAG, " => failed, wait for chip to disappear."); + snprintf( + nfcv_data->error, sizeof(nfcv_data->error), "Passwords not\naccepted"); + nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context); + + /* reset chip */ + furi_hal_nfc_ll_txrx_off(); + furi_delay_ms(20); + furi_hal_nfc_ll_txrx_on(); + + /* wait for disappearing */ + while(slix_get_random(NULL) == ERR_NONE) { + furi_delay_ms(100); + } + } + } + } else { + nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); + } + + furi_hal_nfc_ll_txrx_off(); + furi_hal_nfc_sleep(); + furi_delay_ms(100); + } + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } +} + +static bool nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + bool read_success = false; + MfUltralightReader reader = {}; + MfUltralightData data = {}; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + do { + // Try to read supported card + FURI_LOG_I(TAG, "Trying to read a supported card ..."); + for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) { + if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareUl) { + if(nfc_supported_card[i].verify(nfc_worker, tx_rx)) { + if(nfc_supported_card[i].read(nfc_worker, tx_rx)) { + read_success = true; + nfc_supported_card[i].parse(nfc_worker->dev_data); + break; + } + } else { + furi_hal_nfc_sleep(); + } + } + } + if(read_success) break; + furi_hal_nfc_sleep(); + + // Otherwise, try to read as usual + if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 200)) break; + if(!mf_ul_read_card(tx_rx, &reader, &data)) break; + // Copy data + nfc_worker->dev_data->mf_ul_data = data; + read_success = true; + } while(false); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } + + return read_success; +} + +static bool nfc_worker_read_mf_classic(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker->callback); + bool read_success = false; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + do { + // Try to read supported card + FURI_LOG_I(TAG, "Trying to read a supported card ..."); + for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) { + if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareClassic) { + if(nfc_supported_card[i].verify(nfc_worker, tx_rx)) { + if(nfc_supported_card[i].read(nfc_worker, tx_rx)) { + read_success = true; + nfc_supported_card[i].parse(nfc_worker->dev_data); + break; + } + } else { + furi_hal_nfc_sleep(); + } + } + } + if(read_success) break; + // Try to read card with key cache + FURI_LOG_I(TAG, "Search for key cache ..."); + if(nfc_worker->callback(NfcWorkerEventReadMfClassicLoadKeyCache, nfc_worker->context)) { + FURI_LOG_I(TAG, "Load keys cache success. Start reading"); + uint8_t sectors_read = + mf_classic_update_card(tx_rx, &nfc_worker->dev_data->mf_classic_data); + uint8_t sectors_total = + mf_classic_get_total_sectors_num(nfc_worker->dev_data->mf_classic_data.type); + FURI_LOG_I(TAG, "Read %d sectors out of %d total", sectors_read, sectors_total); + read_success = mf_classic_is_card_read(&nfc_worker->dev_data->mf_classic_data); + } + } while(false); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } + return read_success; +} + +static bool nfc_worker_read_mf_desfire(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + bool read_success = false; + MifareDesfireData* data = &nfc_worker->dev_data->mf_df_data; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, tx_rx, false); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + do { + if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break; + if(!mf_df_read_card(tx_rx, data)) break; + FURI_LOG_I(TAG, "Trying to parse a supported card ..."); + + // The model for parsing DESFire is a little different to other cards; + // we don't have parsers to provide encryption keys, so we can read the + // data normally, and then pass the read data to a parser. + // + // There are fully-protected DESFire cards, but providing keys for them + // is difficult (and unnessesary for many transit cards). + for(size_t i = 0; i < NfcSupportedCardTypeEnd; i++) { + if(nfc_supported_card[i].protocol == NfcDeviceProtocolMifareDesfire) { + if(nfc_supported_card[i].parse(nfc_worker->dev_data)) break; + } + } + read_success = true; + } while(false); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } + + return read_success; +} + +static bool nfc_worker_read_nfca(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + + bool card_read = false; + furi_hal_nfc_sleep(); + if(mf_ul_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) { + FURI_LOG_I(TAG, "Mifare Ultralight / NTAG detected"); + nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareUl; + card_read = nfc_worker_read_mf_ultralight(nfc_worker, tx_rx); + } else if(mf_classic_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) { + FURI_LOG_I(TAG, "Mifare Classic detected"); + nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareClassic; + nfc_worker->dev_data->mf_classic_data.type = + mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); + card_read = nfc_worker_read_mf_classic(nfc_worker, tx_rx); + } else if(mf_df_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) { + FURI_LOG_I(TAG, "Mifare DESFire detected"); + nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareDesfire; + if(!nfc_worker_read_mf_desfire(nfc_worker, tx_rx)) { + FURI_LOG_I(TAG, "Unknown card. Save UID"); + nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown; + } + card_read = true; + } else { + nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown; + card_read = true; + } + + return card_read; +} + +void nfc_worker_read(NfcWorker* nfc_worker) { + furi_assert(nfc_worker); + furi_assert(nfc_worker->callback); + + nfc_device_data_clear(nfc_worker->dev_data); + NfcDeviceData* dev_data = nfc_worker->dev_data; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + FuriHalNfcTxRxContext tx_rx = {}; + NfcWorkerEvent event = 0; + bool card_not_detected_notified = false; + + while(nfc_worker->state == NfcWorkerStateRead) { + if(furi_hal_nfc_detect(nfc_data, 300)) { + // Process first found device + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + card_not_detected_notified = false; + if(nfc_data->type == FuriHalNfcTypeA) { + if(nfc_worker_read_nfca(nfc_worker, &tx_rx)) { + if(dev_data->protocol == NfcDeviceProtocolMifareUl) { + event = NfcWorkerEventReadMfUltralight; + break; + } else if(dev_data->protocol == NfcDeviceProtocolMifareClassic) { + event = NfcWorkerEventReadMfClassicDone; + break; + } else if(dev_data->protocol == NfcDeviceProtocolMifareDesfire) { + event = NfcWorkerEventReadMfDesfire; + break; + } else if(dev_data->protocol == NfcDeviceProtocolUnknown) { + event = NfcWorkerEventReadUidNfcA; + break; + } + } else { + if(dev_data->protocol == NfcDeviceProtocolMifareClassic) { + event = NfcWorkerEventReadMfClassicDictAttackRequired; + break; + } + } + } else if(nfc_data->type == FuriHalNfcTypeB) { + event = NfcWorkerEventReadUidNfcB; + break; + } else if(nfc_data->type == FuriHalNfcTypeF) { + event = NfcWorkerEventReadUidNfcF; + break; + } else if(nfc_data->type == FuriHalNfcTypeV) { + FURI_LOG_I(TAG, "NfcV detected"); + nfc_worker->dev_data->protocol = NfcDeviceProtocolNfcV; + if(nfc_worker_read_nfcv(nfc_worker, &tx_rx)) { + FURI_LOG_I(TAG, "nfc_worker_read_nfcv success"); + } + event = NfcWorkerEventReadNfcV; + break; + } + } else { + if(!card_not_detected_notified) { + nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); + card_not_detected_notified = true; + } + } + furi_hal_nfc_sleep(); + furi_delay_ms(100); + } + // Notify caller and exit + if(event > NfcWorkerEventReserved) { + nfc_worker->callback(event, nfc_worker->context); + } +} + +void nfc_worker_read_type(NfcWorker* nfc_worker) { + furi_assert(nfc_worker); + furi_assert(nfc_worker->callback); + + NfcReadMode read_mode = nfc_worker->dev_data->read_mode; + nfc_device_data_clear(nfc_worker->dev_data); + NfcDeviceData* dev_data = nfc_worker->dev_data; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + FuriHalNfcTxRxContext tx_rx = {}; + NfcWorkerEvent event = 0; + bool card_not_detected_notified = false; + + while(nfc_worker->state == NfcWorkerStateRead) { + if(furi_hal_nfc_detect(nfc_data, 300)) { + FURI_LOG_D(TAG, "Card detected"); + furi_hal_nfc_sleep(); + // Process first found device + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + card_not_detected_notified = false; + if(nfc_data->type == FuriHalNfcTypeA) { + if(read_mode == NfcReadModeMfClassic) { + nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareClassic; + nfc_worker->dev_data->mf_classic_data.type = mf_classic_get_classic_type( + nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); + if(nfc_worker_read_mf_classic(nfc_worker, &tx_rx)) { + FURI_LOG_D(TAG, "Card read"); + dev_data->protocol = NfcDeviceProtocolMifareClassic; + event = NfcWorkerEventReadMfClassicDone; + break; + } else { + FURI_LOG_D(TAG, "Card read failed"); + dev_data->protocol = NfcDeviceProtocolMifareClassic; + event = NfcWorkerEventReadMfClassicDictAttackRequired; + break; + } + } else if(read_mode == NfcReadModeMfUltralight) { + FURI_LOG_I(TAG, "Mifare Ultralight / NTAG"); + nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareUl; + if(nfc_worker_read_mf_ultralight(nfc_worker, &tx_rx)) { + event = NfcWorkerEventReadMfUltralight; + break; + } + } else if(read_mode == NfcReadModeMfDesfire) { + nfc_worker->dev_data->protocol = NfcDeviceProtocolMifareDesfire; + if(nfc_worker_read_mf_desfire(nfc_worker, &tx_rx)) { + event = NfcWorkerEventReadMfDesfire; + break; + } + } else if(read_mode == NfcReadModeNFCA) { + nfc_worker->dev_data->protocol = NfcDeviceProtocolUnknown; + event = NfcWorkerEventReadUidNfcA; + break; + } + } + } else { + if(!card_not_detected_notified) { + nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); + card_not_detected_notified = true; + } + } + furi_hal_nfc_sleep(); + furi_delay_ms(100); + } + // Notify caller and exit + if(event > NfcWorkerEventReserved) { + nfc_worker->callback(event, nfc_worker->context); + } +} + +void nfc_worker_emulate_uid(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx = {}; + FuriHalNfcDevData* data = &nfc_worker->dev_data->nfc_data; + NfcReaderRequestData* reader_data = &nfc_worker->dev_data->reader_data; + + // TODO add support for RATS + // Need to save ATS to support ISO-14443A-4 emulation + + while(nfc_worker->state == NfcWorkerStateUidEmulate) { + if(furi_hal_nfc_listen(data->uid, data->uid_len, data->atqa, data->sak, false, 100)) { + if(furi_hal_nfc_tx_rx(&tx_rx, 100)) { + reader_data->size = tx_rx.rx_bits / 8; + if(reader_data->size > 0) { + memcpy(reader_data->data, tx_rx.rx_data, reader_data->size); + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); + } + } + } else { + FURI_LOG_E(TAG, "Failed to get reader commands"); + } + } + } +} + +void nfc_worker_emulate_apdu(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx = {}; + FuriHalNfcDevData params = { + .uid = {0xCF, 0x72, 0xd4, 0x40}, + .uid_len = 4, + .atqa = {0x00, 0x04}, + .sak = 0x20, + .type = FuriHalNfcTypeA, + }; + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + while(nfc_worker->state == NfcWorkerStateEmulateApdu) { //-V1044 + if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, false, 300)) { + FURI_LOG_D(TAG, "POS terminal detected"); + if(emv_card_emulation(&tx_rx)) { + FURI_LOG_D(TAG, "EMV card emulated"); + } + } else { + FURI_LOG_D(TAG, "Can't find reader"); + } + furi_hal_nfc_sleep(); + furi_delay_ms(20); + } + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } +} + +void nfc_worker_mf_ultralight_auth_received_callback(MfUltralightAuth auth, void* context) { + furi_assert(context); + + NfcWorker* nfc_worker = context; + nfc_worker->dev_data->mf_ul_auth = auth; + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventMfUltralightPwdAuth, nfc_worker->context); + } +} + +void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker) { + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + MfUltralightEmulator emulator = {}; + mf_ul_prepare_emulation(&emulator, &nfc_worker->dev_data->mf_ul_data); + + // TODO rework with reader analyzer + emulator.auth_received_callback = nfc_worker_mf_ultralight_auth_received_callback; + emulator.context = nfc_worker; + + rfal_platform_spi_acquire(); + + while(nfc_worker->state == NfcWorkerStateMfUltralightEmulate) { + mf_ul_reset_emulation(&emulator, true); + furi_hal_nfc_emulate_nfca( + nfc_data->uid, + nfc_data->uid_len, + nfc_data->atqa, + nfc_data->sak, + mf_ul_prepare_emulation_response, + &emulator, + 5000); + // Check if data was modified + if(emulator.data_changed) { + nfc_worker->dev_data->mf_ul_data = emulator.data; + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); + } + emulator.data_changed = false; + } + } + + rfal_platform_spi_release(); +} + +static bool nfc_worker_mf_get_b_key_from_sector_trailer( + FuriHalNfcTxRxContext* tx_rx, + uint16_t sector, + uint64_t key, + uint64_t* found_key) { + // Some access conditions allow reading B key via A key + + uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); + + Crypto1 crypto = {}; + MfClassicBlock block_tmp = {}; + MfClassicAuthContext auth_context = {.sector = sector, .key_a = MF_CLASSIC_NO_KEY, .key_b = 0}; + + furi_hal_nfc_sleep(); + + if(mf_classic_auth_attempt(tx_rx, &crypto, &auth_context, key)) { + if(mf_classic_read_block(tx_rx, &crypto, block, &block_tmp)) { + *found_key = nfc_util_bytes2num(&block_tmp.value[10], sizeof(uint8_t) * 6); + + return *found_key; + } + } + + return false; +} + +static void nfc_worker_mf_classic_key_attack( + NfcWorker* nfc_worker, + uint64_t key, + FuriHalNfcTxRxContext* tx_rx, + uint16_t start_sector) { + furi_assert(nfc_worker); + furi_assert(nfc_worker->callback); + + bool card_found_notified = true; + bool card_removed_notified = false; + + MfClassicData* data = &nfc_worker->dev_data->mf_classic_data; + NfcMfClassicDictAttackData* dict_attack_data = + &nfc_worker->dev_data->mf_classic_dict_attack_data; + uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type); + + furi_assert(start_sector < total_sectors); + + nfc_worker->callback(NfcWorkerEventKeyAttackStart, nfc_worker->context); + + // Check every sector's A and B keys with the given key + for(size_t i = start_sector; i < total_sectors; i++) { + nfc_worker->callback(NfcWorkerEventKeyAttackNextSector, nfc_worker->context); + dict_attack_data->current_sector = i; + furi_hal_nfc_sleep(); + if(furi_hal_nfc_activate_nfca(200, NULL)) { + furi_hal_nfc_sleep(); + if(!card_found_notified) { + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + card_found_notified = true; + card_removed_notified = false; + } + uint8_t block_num = mf_classic_get_sector_trailer_block_num_by_sector(i); + if(mf_classic_is_sector_read(data, i)) continue; + if(!mf_classic_is_key_found(data, i, MfClassicKeyA)) { + FURI_LOG_D(TAG, "Trying A key for sector %d, key: %012llX", i, key); + if(mf_classic_authenticate(tx_rx, block_num, key, MfClassicKeyA)) { + mf_classic_set_key_found(data, i, MfClassicKeyA, key); + FURI_LOG_D(TAG, "Key A found: %012llX", key); + nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); + + uint64_t found_key; + if(nfc_worker_mf_get_b_key_from_sector_trailer(tx_rx, i, key, &found_key)) { + FURI_LOG_D(TAG, "Found B key via reading sector %d", i); + mf_classic_set_key_found(data, i, MfClassicKeyB, found_key); + + if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { + nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); + } + } + } + furi_hal_nfc_sleep(); + } + if(!mf_classic_is_key_found(data, i, MfClassicKeyB)) { + FURI_LOG_D(TAG, "Trying B key for sector %d, key: %012llX", i, key); + if(mf_classic_authenticate(tx_rx, block_num, key, MfClassicKeyB)) { + mf_classic_set_key_found(data, i, MfClassicKeyB, key); + FURI_LOG_D(TAG, "Key B found: %012llX", key); + nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); + } + } + + if(mf_classic_is_sector_read(data, i)) continue; + mf_classic_read_sector(tx_rx, data, i); + } else { + if(!card_removed_notified) { + nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); + card_removed_notified = true; + card_found_notified = false; + } + } + if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; + } + nfc_worker->callback(NfcWorkerEventKeyAttackStop, nfc_worker->context); +} + +void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) { + furi_assert(nfc_worker); + furi_assert(nfc_worker->callback); + + MfClassicData* data = &nfc_worker->dev_data->mf_classic_data; + NfcMfClassicDictAttackData* dict_attack_data = + &nfc_worker->dev_data->mf_classic_dict_attack_data; + uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type); + uint64_t key = 0; + uint64_t prev_key = 0; + FuriHalNfcTxRxContext tx_rx = {}; + bool card_found_notified = true; + bool card_removed_notified = false; + + // Load dictionary + MfClassicDict* dict = dict_attack_data->dict; + if(!dict) { + FURI_LOG_E(TAG, "Dictionary not found"); + nfc_worker->callback(NfcWorkerEventNoDictFound, nfc_worker->context); + return; + } + + FURI_LOG_D( + TAG, "Start Dictionary attack, Key Count %lu", mf_classic_dict_get_total_keys(dict)); + for(size_t i = 0; i < total_sectors; i++) { + FURI_LOG_I(TAG, "Sector %d", i); + nfc_worker->callback(NfcWorkerEventNewSector, nfc_worker->context); + uint8_t block_num = mf_classic_get_sector_trailer_block_num_by_sector(i); + if(mf_classic_is_sector_read(data, i)) continue; + if(mf_classic_is_key_found(data, i, MfClassicKeyA) && + mf_classic_is_key_found(data, i, MfClassicKeyB)) + continue; + uint16_t key_index = 0; + while(mf_classic_dict_get_next_key(dict, &key)) { + FURI_LOG_T(TAG, "Key %d", key_index); + if(++key_index % NFC_DICT_KEY_BATCH_SIZE == 0) { + nfc_worker->callback(NfcWorkerEventNewDictKeyBatch, nfc_worker->context); + } + furi_hal_nfc_sleep(); + uint32_t cuid; + if(furi_hal_nfc_activate_nfca(200, &cuid)) { + bool deactivated = false; + if(!card_found_notified) { + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + card_found_notified = true; + card_removed_notified = false; + nfc_worker_mf_classic_key_attack(nfc_worker, prev_key, &tx_rx, i); + deactivated = true; + } + FURI_LOG_D(TAG, "Try to auth to sector %d with key %012llX", i, key); + if(!mf_classic_is_key_found(data, i, MfClassicKeyA)) { + if(mf_classic_authenticate_skip_activate( + &tx_rx, block_num, key, MfClassicKeyA, !deactivated, cuid)) { + mf_classic_set_key_found(data, i, MfClassicKeyA, key); + FURI_LOG_D(TAG, "Key A found: %012llX", key); + nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); + + uint64_t found_key; + if(nfc_worker_mf_get_b_key_from_sector_trailer( + &tx_rx, i, key, &found_key)) { + FURI_LOG_D(TAG, "Found B key via reading sector %d", i); + mf_classic_set_key_found(data, i, MfClassicKeyB, found_key); + + if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { + nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); + } + + nfc_worker_mf_classic_key_attack(nfc_worker, found_key, &tx_rx, i + 1); + break; + } + nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); + } + furi_hal_nfc_sleep(); + deactivated = true; + } else { + // If the key A is marked as found and matches the searching key, invalidate it + MfClassicSectorTrailer* sec_trailer = + mf_classic_get_sector_trailer_by_sector(data, i); + + uint8_t current_key[6]; + nfc_util_num2bytes(key, 6, current_key); + + if(mf_classic_is_key_found(data, i, MfClassicKeyA) && + memcmp(sec_trailer->key_a, current_key, 6) == 0) { + if(!mf_classic_authenticate_skip_activate( + &tx_rx, block_num, key, MfClassicKeyA, !deactivated, cuid)) { + mf_classic_set_key_not_found(data, i, MfClassicKeyA); + FURI_LOG_D(TAG, "Key %dA not found in attack", i); + } + } + furi_hal_nfc_sleep(); + deactivated = true; + } + if(!mf_classic_is_key_found(data, i, MfClassicKeyB)) { + if(mf_classic_authenticate_skip_activate( + &tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) { //-V547 + FURI_LOG_D(TAG, "Key B found: %012llX", key); + mf_classic_set_key_found(data, i, MfClassicKeyB, key); + nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); + nfc_worker_mf_classic_key_attack(nfc_worker, key, &tx_rx, i + 1); + } + deactivated = true; //-V1048 + } else { + // If the key B is marked as found and matches the searching key, invalidate it + MfClassicSectorTrailer* sec_trailer = + mf_classic_get_sector_trailer_by_sector(data, i); + + uint8_t current_key[6]; + nfc_util_num2bytes(key, 6, current_key); + + if(mf_classic_is_key_found(data, i, MfClassicKeyB) && + memcmp(sec_trailer->key_b, current_key, 6) == 0) { + if(!mf_classic_authenticate_skip_activate( + &tx_rx, block_num, key, MfClassicKeyB, !deactivated, cuid)) { //-V547 + mf_classic_set_key_not_found(data, i, MfClassicKeyB); + FURI_LOG_D(TAG, "Key %dB not found in attack", i); + } + furi_hal_nfc_sleep(); + deactivated = true; //-V1048 + } + } + if(mf_classic_is_key_found(data, i, MfClassicKeyA) && + mf_classic_is_key_found(data, i, MfClassicKeyB)) + break; + if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; + } else { + if(!card_removed_notified) { + nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); + card_removed_notified = true; + card_found_notified = false; + } + if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; + } + prev_key = key; + } + if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; + mf_classic_read_sector(&tx_rx, data, i); + mf_classic_dict_rewind(dict); + } + if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { + nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); + } else { + nfc_worker->callback(NfcWorkerEventAborted, nfc_worker->context); + } +} + +void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + MfClassicEmulator emulator = { + .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4), + .data = nfc_worker->dev_data->mf_classic_data, + .data_changed = false, + }; + NfcaSignal* nfca_signal = nfca_signal_alloc(); + tx_rx.nfca_signal = nfca_signal; + + rfal_platform_spi_acquire(); + + furi_hal_nfc_listen_start(nfc_data); + while(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { //-V1044 + if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { + if(!mf_classic_emulator(&emulator, &tx_rx, false)) { + furi_hal_nfc_listen_start(nfc_data); + } + } + } + if(emulator.data_changed) { + nfc_worker->dev_data->mf_classic_data = emulator.data; + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); + } + emulator.data_changed = false; + } + + nfca_signal_free(nfca_signal); + + rfal_platform_spi_release(); +} + +void nfc_worker_write_mf_classic(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx = {}; + bool card_found_notified = false; + FuriHalNfcDevData nfc_data = {}; + MfClassicData* src_data = &nfc_worker->dev_data->mf_classic_data; + MfClassicData dest_data = *src_data; + + while(nfc_worker->state == NfcWorkerStateMfClassicWrite) { + if(furi_hal_nfc_detect(&nfc_data, 200)) { + if(!card_found_notified) { + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + card_found_notified = true; + } + furi_hal_nfc_sleep(); + + FURI_LOG_I(TAG, "Check low level nfc data"); + if(memcmp(&nfc_data, &nfc_worker->dev_data->nfc_data, sizeof(FuriHalNfcDevData)) != + 0) { + FURI_LOG_E(TAG, "Wrong card"); + nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); + break; + } + + FURI_LOG_I(TAG, "Check mf classic type"); + MfClassicType type = + mf_classic_get_classic_type(nfc_data.atqa[0], nfc_data.atqa[1], nfc_data.sak); + if(type != nfc_worker->dev_data->mf_classic_data.type) { + FURI_LOG_E(TAG, "Wrong mf classic type"); + nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); + break; + } + + // Set blocks not read + mf_classic_set_sector_data_not_read(&dest_data); + FURI_LOG_I(TAG, "Updating card sectors"); + uint8_t total_sectors = mf_classic_get_total_sectors_num(type); + bool write_success = true; + for(uint8_t i = 0; i < total_sectors; i++) { + FURI_LOG_I(TAG, "Reading sector %d", i); + mf_classic_read_sector(&tx_rx, &dest_data, i); + bool old_data_read = mf_classic_is_sector_data_read(src_data, i); + bool new_data_read = mf_classic_is_sector_data_read(&dest_data, i); + if(old_data_read != new_data_read) { + FURI_LOG_E(TAG, "Failed to update sector %d", i); + write_success = false; + break; + } + if(nfc_worker->state != NfcWorkerStateMfClassicWrite) break; + if(!mf_classic_write_sector(&tx_rx, &dest_data, src_data, i)) { + FURI_LOG_E(TAG, "Failed to write %d sector", i); + write_success = false; + break; + } + } + if(nfc_worker->state != NfcWorkerStateMfClassicWrite) break; + if(write_success) { + nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); + break; + } else { + nfc_worker->callback(NfcWorkerEventFail, nfc_worker->context); + break; + } + + } else { + if(card_found_notified) { + nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); + card_found_notified = false; + } + } + furi_delay_ms(300); + } +} + +void nfc_worker_update_mf_classic(NfcWorker* nfc_worker) { + FuriHalNfcTxRxContext tx_rx = {}; + bool card_found_notified = false; + FuriHalNfcDevData nfc_data = {}; + MfClassicData* old_data = &nfc_worker->dev_data->mf_classic_data; + MfClassicData new_data = *old_data; + + while(nfc_worker->state == NfcWorkerStateMfClassicUpdate) { + if(furi_hal_nfc_detect(&nfc_data, 200)) { + if(!card_found_notified) { + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + card_found_notified = true; + } + furi_hal_nfc_sleep(); + + FURI_LOG_I(TAG, "Check low level nfc data"); + if(memcmp(&nfc_data, &nfc_worker->dev_data->nfc_data, sizeof(FuriHalNfcDevData)) != + 0) { + FURI_LOG_E(TAG, "Low level nfc data mismatch"); + nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); + break; + } + + FURI_LOG_I(TAG, "Check MF classic type"); + MfClassicType type = + mf_classic_get_classic_type(nfc_data.atqa[0], nfc_data.atqa[1], nfc_data.sak); + if(type != nfc_worker->dev_data->mf_classic_data.type) { + FURI_LOG_E(TAG, "MF classic type mismatch"); + nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); + break; + } + + // Set blocks not read + mf_classic_set_sector_data_not_read(&new_data); + FURI_LOG_I(TAG, "Updating card sectors"); + uint8_t total_sectors = mf_classic_get_total_sectors_num(type); + bool update_success = true; + for(uint8_t i = 0; i < total_sectors; i++) { + FURI_LOG_I(TAG, "Reading sector %d", i); + mf_classic_read_sector(&tx_rx, &new_data, i); + bool old_data_read = mf_classic_is_sector_data_read(old_data, i); + bool new_data_read = mf_classic_is_sector_data_read(&new_data, i); + if(old_data_read != new_data_read) { + FURI_LOG_E(TAG, "Failed to update sector %d", i); + update_success = false; + break; + } + if(nfc_worker->state != NfcWorkerStateMfClassicUpdate) break; + } + if(nfc_worker->state != NfcWorkerStateMfClassicUpdate) break; + + // Check updated data + if(update_success) { + *old_data = new_data; + nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); + break; + } + } else { + if(card_found_notified) { + nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); + card_found_notified = false; + } + } + furi_delay_ms(300); + } +} + +void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker) { + furi_assert(nfc_worker); + furi_assert(nfc_worker->callback); + + MfUltralightData* data = &nfc_worker->dev_data->mf_ul_data; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + FuriHalNfcTxRxContext tx_rx = {}; + MfUltralightReader reader = {}; + mf_ul_reset(data); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_prepare_tx_rx(nfc_worker->reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeDebugLog); + } + + uint32_t key = 0; + uint16_t pack = 0; + while(nfc_worker->state == NfcWorkerStateReadMfUltralightReadAuth) { + furi_hal_nfc_sleep(); + if(furi_hal_nfc_detect(nfc_data, 300) && nfc_data->type == FuriHalNfcTypeA) { + if(mf_ul_check_card_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak)) { + nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); + if(data->auth_method == MfUltralightAuthMethodManual || + data->auth_method == MfUltralightAuthMethodAuto) { + nfc_worker->callback(NfcWorkerEventMfUltralightPassKey, nfc_worker->context); + key = nfc_util_bytes2num(data->auth_key, 4); + } else if(data->auth_method == MfUltralightAuthMethodAmeebo) { + key = mf_ul_pwdgen_amiibo(nfc_data); + } else if(data->auth_method == MfUltralightAuthMethodXiaomi) { + key = mf_ul_pwdgen_xiaomi(nfc_data); + } else { + FURI_LOG_E(TAG, "Incorrect auth method"); + break; + } + + data->auth_success = mf_ultralight_authenticate(&tx_rx, key, &pack); + + if(!data->auth_success) { + // Reset card + furi_hal_nfc_sleep(); + if(!furi_hal_nfc_activate_nfca(300, NULL)) { + nfc_worker->callback(NfcWorkerEventFail, nfc_worker->context); + break; + } + } + + mf_ul_read_card(&tx_rx, &reader, data); + if(data->auth_success) { + MfUltralightConfigPages* config_pages = mf_ultralight_get_config_pages(data); + if(config_pages != NULL) { + config_pages->auth_data.pwd.value = REVERSE_BYTES_U32(key); + config_pages->auth_data.pack.value = pack; + } + nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); + break; + } else { + nfc_worker->callback(NfcWorkerEventFail, nfc_worker->context); + break; + } + } else { + nfc_worker->callback(NfcWorkerEventWrongCardDetected, nfc_worker->context); + furi_delay_ms(10); + } + } else { + nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); + furi_delay_ms(10); + } + } + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + reader_analyzer_stop(nfc_worker->reader_analyzer); + } +} + +static void nfc_worker_reader_analyzer_callback(ReaderAnalyzerEvent event, void* context) { + furi_assert(context); + NfcWorker* nfc_worker = context; + + if((nfc_worker->state == NfcWorkerStateAnalyzeReader) && + (event == ReaderAnalyzerEventMfkeyCollected)) { + if(nfc_worker->callback) { + nfc_worker->callback(NfcWorkerEventDetectReaderMfkeyCollected, nfc_worker->context); + } + } +} + +void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { + furi_assert(nfc_worker); + furi_assert(nfc_worker->callback); + + FuriHalNfcTxRxContext tx_rx = {}; + + ReaderAnalyzer* reader_analyzer = nfc_worker->reader_analyzer; + FuriHalNfcDevData* nfc_data = NULL; + if(nfc_worker->dev_data->protocol == NfcDeviceProtocolMifareClassic) { + nfc_data = &nfc_worker->dev_data->nfc_data; + reader_analyzer_set_nfc_data(reader_analyzer, nfc_data); + } else { + nfc_data = reader_analyzer_get_nfc_data(reader_analyzer); + } + MfClassicEmulator emulator = { + .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4), + .data = nfc_worker->dev_data->mf_classic_data, + .data_changed = false, + }; + NfcaSignal* nfca_signal = nfca_signal_alloc(); + tx_rx.nfca_signal = nfca_signal; + reader_analyzer_prepare_tx_rx(reader_analyzer, &tx_rx, true); + reader_analyzer_start(nfc_worker->reader_analyzer, ReaderAnalyzerModeMfkey); + reader_analyzer_set_callback(reader_analyzer, nfc_worker_reader_analyzer_callback, nfc_worker); + + rfal_platform_spi_acquire(); + + FURI_LOG_D(TAG, "Start reader analyzer"); + + uint8_t reader_no_data_received_cnt = 0; + bool reader_no_data_notified = true; + + while(nfc_worker->state == NfcWorkerStateAnalyzeReader) { + furi_hal_nfc_listen_start(nfc_data); + if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { + if(reader_no_data_notified) { + nfc_worker->callback(NfcWorkerEventDetectReaderDetected, nfc_worker->context); + } + reader_no_data_received_cnt = 0; + reader_no_data_notified = false; + NfcProtocol protocol = + reader_analyzer_guess_protocol(reader_analyzer, tx_rx.rx_data, tx_rx.rx_bits / 8); + if(protocol == NfcDeviceProtocolMifareClassic) { + if(!mf_classic_emulator(&emulator, &tx_rx, true)) { + furi_hal_nfc_listen_start(nfc_data); + } + } + } else { + reader_no_data_received_cnt++; + if(!reader_no_data_notified && (reader_no_data_received_cnt > 5)) { + nfc_worker->callback(NfcWorkerEventDetectReaderLost, nfc_worker->context); + reader_no_data_received_cnt = 0; + reader_no_data_notified = true; + } + FURI_LOG_D(TAG, "No data from reader"); + continue; + } + furi_delay_ms(1); + } + + rfal_platform_spi_release(); + + reader_analyzer_stop(nfc_worker->reader_analyzer); + + nfca_signal_free(nfca_signal); +} diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h new file mode 100644 index 0000000..f9f5900 --- /dev/null +++ b/lib/nfc/nfc_worker.h @@ -0,0 +1,108 @@ +#pragma once + +#include "nfc_device.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct NfcWorker NfcWorker; + +typedef enum { + // Init states + NfcWorkerStateNone, + NfcWorkerStateReady, + // Main worker states + NfcWorkerStateRead, + NfcWorkerStateUidEmulate, + NfcWorkerStateMfUltralightEmulate, + NfcWorkerStateMfClassicEmulate, + NfcWorkerStateMfClassicWrite, + NfcWorkerStateMfClassicUpdate, + NfcWorkerStateReadMfUltralightReadAuth, + NfcWorkerStateMfClassicDictAttack, + NfcWorkerStateAnalyzeReader, + NfcWorkerStateNfcVEmulate, + NfcWorkerStateNfcVUnlock, + NfcWorkerStateNfcVUnlockAndSave, + NfcWorkerStateNfcVSniff, + // Debug + NfcWorkerStateEmulateApdu, + NfcWorkerStateField, + // Transition + NfcWorkerStateStop, +} NfcWorkerState; + +typedef enum { + // Reserve first 50 events for application events + NfcWorkerEventReserved = 50, + + // Nfc read events + NfcWorkerEventReadUidNfcB, + NfcWorkerEventReadUidNfcV, + NfcWorkerEventReadUidNfcF, + NfcWorkerEventReadUidNfcA, + NfcWorkerEventReadMfUltralight, + NfcWorkerEventReadMfDesfire, + NfcWorkerEventReadMfClassicDone, + NfcWorkerEventReadMfClassicLoadKeyCache, + NfcWorkerEventReadMfClassicDictAttackRequired, + NfcWorkerEventReadNfcV, + + // Nfc worker common events + NfcWorkerEventSuccess, + NfcWorkerEventFail, + NfcWorkerEventAborted, + NfcWorkerEventCardDetected, + NfcWorkerEventNoCardDetected, + NfcWorkerEventWrongCardDetected, + + // Read Mifare Classic events + NfcWorkerEventNoDictFound, + NfcWorkerEventNewSector, + NfcWorkerEventNewDictKeyBatch, + NfcWorkerEventFoundKeyA, + NfcWorkerEventFoundKeyB, + NfcWorkerEventKeyAttackStart, + NfcWorkerEventKeyAttackStop, + NfcWorkerEventKeyAttackNextSector, + + // Write Mifare Classic events + NfcWorkerEventWrongCard, + + // Detect Reader events + NfcWorkerEventDetectReaderDetected, + NfcWorkerEventDetectReaderLost, + NfcWorkerEventDetectReaderMfkeyCollected, + + // Mifare Ultralight events + NfcWorkerEventMfUltralightPassKey, // NFC worker requesting manual key + NfcWorkerEventMfUltralightPwdAuth, // Reader sent auth command + NfcWorkerEventNfcVPassKey, // NFC worker requesting manual key + NfcWorkerEventNfcVCommandExecuted, + NfcWorkerEventNfcVContentChanged, +} NfcWorkerEvent; + +typedef bool (*NfcWorkerCallback)(NfcWorkerEvent event, void* context); + +NfcWorker* nfc_worker_alloc(); + +NfcWorkerState nfc_worker_get_state(NfcWorker* nfc_worker); + +void nfc_worker_free(NfcWorker* nfc_worker); + +void nfc_worker_start( + NfcWorker* nfc_worker, + NfcWorkerState state, + NfcDeviceData* dev_data, + NfcWorkerCallback callback, + void* context); + +void nfc_worker_stop(NfcWorker* nfc_worker); +void nfc_worker_nfcv_unlock(NfcWorker* nfc_worker); +void nfc_worker_nfcv_emulate(NfcWorker* nfc_worker); +void nfc_worker_nfcv_sniff(NfcWorker* nfc_worker); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/nfc_worker_i.h b/lib/nfc/nfc_worker_i.h new file mode 100644 index 0000000..b678573 --- /dev/null +++ b/lib/nfc/nfc_worker_i.h @@ -0,0 +1,59 @@ +#pragma once + +#include "nfc_worker.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct NfcWorker { + FuriThread* thread; + Storage* storage; + Stream* dict_stream; + + NfcDeviceData* dev_data; + + NfcWorkerCallback callback; + void* context; + + NfcWorkerState state; + + ReaderAnalyzer* reader_analyzer; +}; + +void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state); + +int32_t nfc_worker_task(void* context); + +void nfc_worker_read(NfcWorker* nfc_worker); + +void nfc_worker_read_type(NfcWorker* nfc_worker); + +void nfc_worker_emulate_uid(NfcWorker* nfc_worker); + +void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker); + +void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker); + +void nfc_worker_write_mf_classic(NfcWorker* nfc_worker); + +void nfc_worker_update_mf_classic(NfcWorker* nfc_worker); + +void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker); + +void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker); + +void nfc_worker_mf_ul_auth_attack(NfcWorker* nfc_worker); + +void nfc_worker_emulate_apdu(NfcWorker* nfc_worker); + +void nfc_worker_analyze_reader(NfcWorker* nfc_worker); diff --git a/lib/nfc/parsers/all_in_one.c b/lib/nfc/parsers/all_in_one.c new file mode 100644 index 0000000..edcc0d0 --- /dev/null +++ b/lib/nfc/parsers/all_in_one.c @@ -0,0 +1,103 @@ +#include "nfc_supported_card.h" +#include "all_in_one.h" + +#include +#include + +#include + +#define ALL_IN_ONE_LAYOUT_UNKNOWN 0 +#define ALL_IN_ONE_LAYOUT_A 1 +#define ALL_IN_ONE_LAYOUT_D 2 +#define ALL_IN_ONE_LAYOUT_E2 3 +#define ALL_IN_ONE_LAYOUT_E3 4 +#define ALL_IN_ONE_LAYOUT_E5 5 +#define ALL_IN_ONE_LAYOUT_2 6 + +uint8_t all_in_one_get_layout(NfcDeviceData* dev_data) { + // I absolutely hate what's about to happen here. + + // Switch on the second half of the third byte of page 5 + FURI_LOG_I("all_in_one", "Layout byte: %02x", dev_data->mf_ul_data.data[(4 * 5) + 2]); + FURI_LOG_I( + "all_in_one", "Layout half-byte: %02x", dev_data->mf_ul_data.data[(4 * 5) + 3] & 0x0F); + switch(dev_data->mf_ul_data.data[(4 * 5) + 2] & 0x0F) { + // If it is A, the layout type is a type A layout + case 0x0A: + return ALL_IN_ONE_LAYOUT_A; + case 0x0D: + return ALL_IN_ONE_LAYOUT_D; + case 0x02: + return ALL_IN_ONE_LAYOUT_2; + default: + FURI_LOG_I( + "all_in_one", + "Unknown layout type: %d", + dev_data->mf_ul_data.data[(4 * 5) + 2] & 0x0F); + return ALL_IN_ONE_LAYOUT_UNKNOWN; + } +} + +bool all_in_one_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + UNUSED(nfc_worker); + // If this is a all_in_one pass, first 2 bytes of page 4 are 0x45 0xD9 + MfUltralightReader reader = {}; + MfUltralightData data = {}; + + if(!mf_ul_read_card(tx_rx, &reader, &data)) { + return false; + } else { + if(data.data[4 * 4] == 0x45 && data.data[4 * 4 + 1] == 0xD9) { + FURI_LOG_I("all_in_one", "Pass verified"); + return true; + } + } + return false; +} + +bool all_in_one_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + MfUltralightReader reader = {}; + MfUltralightData data = {}; + if(!mf_ul_read_card(tx_rx, &reader, &data)) { + return false; + } else { + memcpy(&nfc_worker->dev_data->mf_ul_data, &data, sizeof(data)); + FURI_LOG_I("all_in_one", "Card read"); + return true; + } +} + +bool all_in_one_parser_parse(NfcDeviceData* dev_data) { + if(dev_data->mf_ul_data.data[4 * 4] != 0x45 || dev_data->mf_ul_data.data[4 * 4 + 1] != 0xD9) { + FURI_LOG_I("all_in_one", "Pass not verified"); + return false; + } + + uint8_t ride_count = 0; + uint32_t serial = 0; + if(all_in_one_get_layout(dev_data) == ALL_IN_ONE_LAYOUT_A) { + // If the layout is A then the ride count is stored in the first byte of page 8 + ride_count = dev_data->mf_ul_data.data[4 * 8]; + } else if(all_in_one_get_layout(dev_data) == ALL_IN_ONE_LAYOUT_D) { + // If the layout is D, the ride count is stored in the second byte of page 9 + ride_count = dev_data->mf_ul_data.data[4 * 9 + 1]; + } else { + FURI_LOG_I("all_in_one", "Unknown layout: %d", all_in_one_get_layout(dev_data)); + ride_count = 137; + } + + // I hate this with a burning passion. + + // The number starts at the second half of the third byte on page 4, and is 32 bits long + // So we get the second half of the third byte, then bytes 4-6, and then the first half of the 7th byte + // B8 17 A2 A4 BD becomes 81 7A 2A 4B + serial = + (dev_data->mf_ul_data.data[4 * 4 + 2] & 0x0F) << 28 | + dev_data->mf_ul_data.data[4 * 4 + 3] << 20 | dev_data->mf_ul_data.data[4 * 4 + 4] << 12 | + dev_data->mf_ul_data.data[4 * 4 + 5] << 4 | (dev_data->mf_ul_data.data[4 * 4 + 6] >> 4); + + // Format string for rides count + furi_string_printf( + dev_data->parsed_data, "\e#All-In-One\nNumber: %lu\nRides left: %u", serial, ride_count); + return true; +} diff --git a/lib/nfc/parsers/all_in_one.h b/lib/nfc/parsers/all_in_one.h new file mode 100644 index 0000000..9b646d4 --- /dev/null +++ b/lib/nfc/parsers/all_in_one.h @@ -0,0 +1,9 @@ +#pragma once + +#include "nfc_supported_card.h" + +bool all_in_one_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool all_in_one_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool all_in_one_parser_parse(NfcDeviceData* dev_data); \ No newline at end of file diff --git a/lib/nfc/parsers/nfc_supported_card.c b/lib/nfc/parsers/nfc_supported_card.c new file mode 100644 index 0000000..153d4d3 --- /dev/null +++ b/lib/nfc/parsers/nfc_supported_card.c @@ -0,0 +1,82 @@ +#include "nfc_supported_card.h" + +#include "plantain_parser.h" +#include "troika_parser.h" +#include "plantain_4k_parser.h" +#include "troika_4k_parser.h" +#include "two_cities.h" +#include "all_in_one.h" +#include "opal.h" + +NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = { + [NfcSupportedCardTypePlantain] = + { + .protocol = NfcDeviceProtocolMifareClassic, + .verify = plantain_parser_verify, + .read = plantain_parser_read, + .parse = plantain_parser_parse, + }, + [NfcSupportedCardTypeTroika] = + { + .protocol = NfcDeviceProtocolMifareClassic, + .verify = troika_parser_verify, + .read = troika_parser_read, + .parse = troika_parser_parse, + }, + [NfcSupportedCardTypePlantain4K] = + { + .protocol = NfcDeviceProtocolMifareClassic, + .verify = plantain_4k_parser_verify, + .read = plantain_4k_parser_read, + .parse = plantain_4k_parser_parse, + }, + [NfcSupportedCardTypeTroika4K] = + { + .protocol = NfcDeviceProtocolMifareClassic, + .verify = troika_4k_parser_verify, + .read = troika_4k_parser_read, + .parse = troika_4k_parser_parse, + }, + [NfcSupportedCardTypeTwoCities] = + { + .protocol = NfcDeviceProtocolMifareClassic, + .verify = two_cities_parser_verify, + .read = two_cities_parser_read, + .parse = two_cities_parser_parse, + }, + [NfcSupportedCardTypeAllInOne] = + { + .protocol = NfcDeviceProtocolMifareUl, + .verify = all_in_one_parser_verify, + .read = all_in_one_parser_read, + .parse = all_in_one_parser_parse, + }, + [NfcSupportedCardTypeOpal] = + { + .protocol = NfcDeviceProtocolMifareDesfire, + .verify = stub_parser_verify_read, + .read = stub_parser_verify_read, + .parse = opal_parser_parse, + }, + +}; + +bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data) { + furi_assert(dev_data); + + bool card_parsed = false; + for(size_t i = 0; i < COUNT_OF(nfc_supported_card); i++) { + if(nfc_supported_card[i].parse(dev_data)) { + card_parsed = true; + break; + } + } + + return card_parsed; +} + +bool stub_parser_verify_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + UNUSED(nfc_worker); + UNUSED(tx_rx); + return false; +} diff --git a/lib/nfc/parsers/nfc_supported_card.h b/lib/nfc/parsers/nfc_supported_card.h new file mode 100644 index 0000000..2e8c48a --- /dev/null +++ b/lib/nfc/parsers/nfc_supported_card.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include "../nfc_worker.h" +#include "../nfc_device.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + NfcSupportedCardTypePlantain, + NfcSupportedCardTypeTroika, + NfcSupportedCardTypePlantain4K, + NfcSupportedCardTypeTroika4K, + NfcSupportedCardTypeTwoCities, + NfcSupportedCardTypeAllInOne, + NfcSupportedCardTypeOpal, + + NfcSupportedCardTypeEnd, +} NfcSupportedCardType; + +typedef bool (*NfcSupportedCardVerify)(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +typedef bool (*NfcSupportedCardRead)(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +typedef bool (*NfcSupportedCardParse)(NfcDeviceData* dev_data); + +typedef struct { + NfcProtocol protocol; + NfcSupportedCardVerify verify; + NfcSupportedCardRead read; + NfcSupportedCardParse parse; +} NfcSupportedCard; + +extern NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd]; + +bool nfc_supported_card_verify_and_parse(NfcDeviceData* dev_data); + +// stub_parser_verify_read does nothing, and always reports that it does not +// support the card. This is needed for DESFire card parsers which can't +// provide keys, and only use NfcSupportedCard->parse. +bool stub_parser_verify_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/parsers/opal.c b/lib/nfc/parsers/opal.c new file mode 100644 index 0000000..2a6b5d1 --- /dev/null +++ b/lib/nfc/parsers/opal.c @@ -0,0 +1,204 @@ +/* + * opal.c - Parser for Opal card (Sydney, Australia). + * + * Copyright 2023 Michael Farrell + * + * This will only read "standard" MIFARE DESFire-based Opal cards. Free travel + * cards (including School Opal cards, veteran, vision-impaired persons and + * TfNSW employees' cards) and single-trip tickets are MIFARE Ultralight C + * cards and not supported. + * + * Reference: https://github.com/metrodroid/metrodroid/wiki/Opal + * + * Note: The card values are all little-endian (like Flipper), but the above + * reference was originally written based on Java APIs, which are big-endian. + * This implementation presumes a little-endian system. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "nfc_supported_card.h" +#include "opal.h" + +#include +#include +#include + +#include + +static const uint8_t opal_aid[3] = {0x31, 0x45, 0x53}; +static const char* opal_modes[5] = + {"Rail / Metro", "Ferry / Light Rail", "Bus", "Unknown mode", "Manly Ferry"}; +static const char* opal_usages[14] = { + "New / Unused", + "Tap on: new journey", + "Tap on: transfer from same mode", + "Tap on: transfer from other mode", + "", // Manly Ferry: new journey + "", // Manly Ferry: transfer from ferry + "", // Manly Ferry: transfer from other + "Tap off: distance fare", + "Tap off: flat fare", + "Automated tap off: failed to tap off", + "Tap off: end of trip without start", + "Tap off: reversal", + "Tap on: rejected", + "Unknown usage", +}; + +// Opal file 0x7 structure. Assumes a little-endian CPU. +typedef struct __attribute__((__packed__)) { + uint32_t serial : 32; + uint8_t check_digit : 4; + bool blocked : 1; + uint16_t txn_number : 16; + int32_t balance : 21; + uint16_t days : 15; + uint16_t minutes : 11; + uint8_t mode : 3; + uint16_t usage : 4; + bool auto_topup : 1; + uint8_t weekly_journeys : 4; + uint16_t checksum : 16; +} OpalFile; + +static_assert(sizeof(OpalFile) == 16); + +// Converts an Opal timestamp to FuriHalRtcDateTime. +// +// Opal measures days since 1980-01-01 and minutes since midnight, and presumes +// all days are 1440 minutes. +void opal_date_time_to_furi(uint16_t days, uint16_t minutes, FuriHalRtcDateTime* out) { + if(!out) return; + uint16_t diy; + out->year = 1980; + out->month = 1; + // 1980-01-01 is a Tuesday + out->weekday = ((days + 1) % 7) + 1; + out->hour = minutes / 60; + out->minute = minutes % 60; + out->second = 0; + + // What year is it? + for(;;) { + diy = furi_hal_rtc_get_days_per_year(out->year); + if(days < diy) break; + days -= diy; + out->year++; + } + + // 1-index the day of the year + days++; + // What month is it? + bool is_leap = furi_hal_rtc_is_leap_year(out->year); + + for(;;) { + uint8_t dim = furi_hal_rtc_get_days_per_month(is_leap, out->month); + if(days <= dim) break; + days -= dim; + out->month++; + } + + out->day = days; +} + +bool opal_parser_parse(NfcDeviceData* dev_data) { + if(dev_data->protocol != NfcDeviceProtocolMifareDesfire) { + return false; + } + + MifareDesfireApplication* app = mf_df_get_application(&dev_data->mf_df_data, &opal_aid); + if(app == NULL) { + return false; + } + MifareDesfireFile* f = mf_df_get_file(app, 0x07); + if(f == NULL || f->type != MifareDesfireFileTypeStandard || f->settings.data.size != 16 || + !f->contents) { + return false; + } + + OpalFile* o = (OpalFile*)f->contents; + + uint8_t serial2 = o->serial / 10000000; + uint16_t serial3 = (o->serial / 1000) % 10000; + uint16_t serial4 = (o->serial % 1000); + + if(o->check_digit > 9) { + return false; + } + + char* sign = ""; + if(o->balance < 0) { + // Negative balance. Make this a positive value again and record the + // sign separately, because then we can handle balances of -99..-1 + // cents, as the "dollars" division below would result in a positive + // zero value. + o->balance = abs(o->balance); //-V1081 + sign = "-"; + } + uint8_t cents = o->balance % 100; + int32_t dollars = o->balance / 100; + + FuriHalRtcDateTime timestamp; + opal_date_time_to_furi(o->days, o->minutes, ×tamp); + + if(o->mode >= 3) { + // 3..7 are "reserved", but we use 4 to indicate the Manly Ferry. + o->mode = 3; + } + + if(o->usage >= 4 && o->usage <= 6) { + // Usages 4..6 associated with the Manly Ferry, which correspond to + // usages 1..3 for other modes. + o->usage -= 3; + o->mode = 4; + } + + const char* mode_str = (o->mode <= 4 ? opal_modes[o->mode] : opal_modes[3]); //-V547 + const char* usage_str = (o->usage <= 12 ? opal_usages[o->usage] : opal_usages[13]); + + furi_string_printf( + dev_data->parsed_data, + "\e#Opal: $%s%ld.%02hu\n3085 22%02hhu %04hu %03hu%01hhu\n%s, %s\n", + sign, + dollars, + cents, + serial2, + serial3, + serial4, + o->check_digit, + mode_str, + usage_str); + FuriString* timestamp_str = furi_string_alloc(); + locale_format_date(timestamp_str, ×tamp, locale_get_date_format(), "-"); + furi_string_cat(dev_data->parsed_data, timestamp_str); + furi_string_cat_str(dev_data->parsed_data, " at "); + + locale_format_time(timestamp_str, ×tamp, locale_get_time_format(), false); + furi_string_cat(dev_data->parsed_data, timestamp_str); + + furi_string_free(timestamp_str); + furi_string_cat_printf( + dev_data->parsed_data, + "\nWeekly journeys: %hhu, Txn #%hu\n", + o->weekly_journeys, + o->txn_number); + + if(o->auto_topup) { + furi_string_cat_str(dev_data->parsed_data, "Auto-topup enabled\n"); + } + if(o->blocked) { + furi_string_cat_str(dev_data->parsed_data, "Card blocked\n"); + } + return true; +} diff --git a/lib/nfc/parsers/opal.h b/lib/nfc/parsers/opal.h new file mode 100644 index 0000000..42caf9a --- /dev/null +++ b/lib/nfc/parsers/opal.h @@ -0,0 +1,5 @@ +#pragma once + +#include "nfc_supported_card.h" + +bool opal_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/parsers/plantain_4k_parser.c b/lib/nfc/parsers/plantain_4k_parser.c new file mode 100644 index 0000000..19da0b5 --- /dev/null +++ b/lib/nfc/parsers/plantain_4k_parser.c @@ -0,0 +1,124 @@ +#include "nfc_supported_card.h" + +#include +#include + +#include + +static const MfClassicAuthContext plantain_keys_4k[] = { + {.sector = 0, .key_a = 0xFFFFFFFFFFFF, .key_b = 0xFFFFFFFFFFFF}, + {.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 2, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 3, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b}, + {.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a}, + {.sector = 6, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 8, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7}, + {.sector = 9, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3}, + {.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb}, + {.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56}, + {.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26}, + {.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 15, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 16, .key_a = 0x72f96bdd3714, .key_b = 0x462225cd34cf}, + {.sector = 17, .key_a = 0x044ce1872bc3, .key_b = 0x8c90c70cff4a}, + {.sector = 18, .key_a = 0xbc2d1791dec1, .key_b = 0xca96a487de0b}, + {.sector = 19, .key_a = 0x8791b2ccb5c4, .key_b = 0xc956c3b80da3}, + {.sector = 20, .key_a = 0x8e26e45e7d65, .key_b = 0x8e65b3af7d22}, + {.sector = 21, .key_a = 0x0f318130ed18, .key_b = 0x0c420a20e056}, + {.sector = 22, .key_a = 0x045ceca15535, .key_b = 0x31bec3d9e510}, + {.sector = 23, .key_a = 0x9d993c5d4ef4, .key_b = 0x86120e488abf}, + {.sector = 24, .key_a = 0xc65d4eaa645b, .key_b = 0xb69d40d1a439}, + {.sector = 25, .key_a = 0x3a8a139c20b4, .key_b = 0x8818a9c5d406}, + {.sector = 26, .key_a = 0xbaff3053b496, .key_b = 0x4b7cb25354d3}, + {.sector = 27, .key_a = 0x7413b599c4ea, .key_b = 0xb0a2AAF3A1BA}, + {.sector = 28, .key_a = 0x0ce7cd2cc72b, .key_b = 0xfa1fbb3f0f1f}, + {.sector = 29, .key_a = 0x0be5fac8b06a, .key_b = 0x6f95887a4fd3}, + {.sector = 30, .key_a = 0x0eb23cc8110b, .key_b = 0x04dc35277635}, + {.sector = 31, .key_a = 0xbc4580b7f20b, .key_b = 0xd0a4131fb290}, + {.sector = 32, .key_a = 0x7a396f0d633d, .key_b = 0xad2bdc097023}, + {.sector = 33, .key_a = 0xa3faa6daff67, .key_b = 0x7600e889adf9}, + {.sector = 34, .key_a = 0xfd8705e721b0, .key_b = 0x296fc317a513}, + {.sector = 35, .key_a = 0x22052b480d11, .key_b = 0xe19504c39461}, + {.sector = 36, .key_a = 0xa7141147d430, .key_b = 0xff16014fefc7}, + {.sector = 37, .key_a = 0x8a8d88151a00, .key_b = 0x038b5f9b5a2a}, + {.sector = 38, .key_a = 0xb27addfb64b0, .key_b = 0x152fd0c420a7}, + {.sector = 39, .key_a = 0x7259fa0197c6, .key_b = 0x5583698df085}, +}; + +bool plantain_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + UNUSED(nfc_worker); + + if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) { + return false; + } + + uint8_t sector = 8; + uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); + FURI_LOG_D("Plant4K", "Verifying sector %d", sector); + if(mf_classic_authenticate(tx_rx, block, 0x26973ea74321, MfClassicKeyA)) { + FURI_LOG_D("Plant4K", "Sector %d verified", sector); + return true; + } + return false; +} + +bool plantain_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + + MfClassicReader reader = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); + for(size_t i = 0; i < COUNT_OF(plantain_keys_4k); i++) { + mf_classic_reader_add_sector( + &reader, + plantain_keys_4k[i].sector, + plantain_keys_4k[i].key_a, + plantain_keys_4k[i].key_b); + FURI_LOG_T("plant4k", "Added sector %d", plantain_keys_4k[i].sector); + } + for(int i = 0; i < 5; i++) { + if(mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40) { + return true; + } + } + return false; +} + +bool plantain_4k_parser_parse(NfcDeviceData* dev_data) { + MfClassicData* data = &dev_data->mf_classic_data; + + // Verify key + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); + if(key != plantain_keys_4k[8].key_a) return false; + + // Point to block 0 of sector 4, value 0 + uint8_t* temp_ptr = &data->block[4 * 4].value[0]; + // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t + // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal + uint32_t balance = + ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; + // Read card number + // Point to block 0 of sector 0, value 0 + temp_ptr = &data->block[0 * 4].value[0]; + // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t + // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal + uint8_t card_number_arr[7]; + for(size_t i = 0; i < 7; i++) { + card_number_arr[i] = temp_ptr[6 - i]; + } + // Copy card number to uint64_t + uint64_t card_number = 0; + for(size_t i = 0; i < 7; i++) { + card_number = (card_number << 8) | card_number_arr[i]; + } + + furi_string_printf( + dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance); + + return true; +} diff --git a/lib/nfc/parsers/plantain_4k_parser.h b/lib/nfc/parsers/plantain_4k_parser.h new file mode 100644 index 0000000..29998af --- /dev/null +++ b/lib/nfc/parsers/plantain_4k_parser.h @@ -0,0 +1,9 @@ +#pragma once + +#include "nfc_supported_card.h" + +bool plantain_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool plantain_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool plantain_4k_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/parsers/plantain_parser.c b/lib/nfc/parsers/plantain_parser.c new file mode 100644 index 0000000..2e4091d --- /dev/null +++ b/lib/nfc/parsers/plantain_parser.c @@ -0,0 +1,97 @@ +#include "nfc_supported_card.h" + +#include +#include + +#include + +static const MfClassicAuthContext plantain_keys[] = { + {.sector = 0, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 2, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 3, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b}, + {.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a}, + {.sector = 6, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 8, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7}, + {.sector = 9, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3}, + {.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb}, + {.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56}, + {.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26}, + {.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 15, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, +}; + +bool plantain_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + UNUSED(nfc_worker); + if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType1k) { + return false; + } + + uint8_t sector = 8; + uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); + FURI_LOG_D("Plant", "Verifying sector %d", sector); + if(mf_classic_authenticate(tx_rx, block, 0x26973ea74321, MfClassicKeyA)) { + FURI_LOG_D("Plant", "Sector %d verified", sector); + return true; + } + return false; +} + +bool plantain_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + + MfClassicReader reader = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); + for(size_t i = 0; i < COUNT_OF(plantain_keys); i++) { + mf_classic_reader_add_sector( + &reader, plantain_keys[i].sector, plantain_keys[i].key_a, plantain_keys[i].key_b); + } + + return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16; +} + +uint8_t plantain_calculate_luhn(uint64_t number) { + // No. + UNUSED(number); + return 0; +} + +bool plantain_parser_parse(NfcDeviceData* dev_data) { + MfClassicData* data = &dev_data->mf_classic_data; + + // Verify key + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); + if(key != plantain_keys[8].key_a) return false; + + // Point to block 0 of sector 4, value 0 + uint8_t* temp_ptr = &data->block[4 * 4].value[0]; + // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t + // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal + uint32_t balance = + ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; + // Read card number + // Point to block 0 of sector 0, value 0 + temp_ptr = &data->block[0 * 4].value[0]; + // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t + // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal + uint8_t card_number_arr[7]; + for(size_t i = 0; i < 7; i++) { + card_number_arr[i] = temp_ptr[6 - i]; + } + // Copy card number to uint64_t + uint64_t card_number = 0; + for(size_t i = 0; i < 7; i++) { + card_number = (card_number << 8) | card_number_arr[i]; + } + + furi_string_printf( + dev_data->parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance); + + return true; +} diff --git a/lib/nfc/parsers/plantain_parser.h b/lib/nfc/parsers/plantain_parser.h new file mode 100644 index 0000000..1af8c50 --- /dev/null +++ b/lib/nfc/parsers/plantain_parser.h @@ -0,0 +1,11 @@ +#pragma once + +#include "nfc_supported_card.h" + +bool plantain_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool plantain_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool plantain_parser_parse(NfcDeviceData* dev_data); + +uint8_t plantain_calculate_luhn(uint64_t number); diff --git a/lib/nfc/parsers/troika_4k_parser.c b/lib/nfc/parsers/troika_4k_parser.c new file mode 100644 index 0000000..d248feb --- /dev/null +++ b/lib/nfc/parsers/troika_4k_parser.c @@ -0,0 +1,106 @@ +#include "nfc_supported_card.h" + +#include +#include + +static const MfClassicAuthContext troika_4k_keys[] = { + {.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0xfbf225dc5d58}, + {.sector = 1, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880}, + {.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 4, .key_a = 0x73068f118c13, .key_b = 0x2b7f3253fac5}, + {.sector = 5, .key_a = 0xFBC2793D540B, .key_b = 0xd3a297dc2698}, + {.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 7, .key_a = 0xae3d65a3dad4, .key_b = 0x0f1c63013dbb}, + {.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81}, + {.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763}, + {.sector = 10, .key_a = 0x9becdf3d9273, .key_b = 0xf8493407799d}, + {.sector = 11, .key_a = 0x08b386463229, .key_b = 0x5efbaecef46b}, + {.sector = 12, .key_a = 0xcd4c61c26e3d, .key_b = 0x31c7610de3b0}, + {.sector = 13, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880}, + {.sector = 14, .key_a = 0x0e8f64340ba4, .key_b = 0x4acec1205d75}, + {.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 16, .key_a = 0x6b02733bb6ec, .key_b = 0x7038cd25c408}, + {.sector = 17, .key_a = 0x403d706ba880, .key_b = 0xb39d19a280df}, + {.sector = 18, .key_a = 0xc11f4597efb5, .key_b = 0x70d901648cb9}, + {.sector = 19, .key_a = 0x0db520c78c1c, .key_b = 0x73e5b9d9d3a4}, + {.sector = 20, .key_a = 0x3ebce0925b2f, .key_b = 0x372cc880f216}, + {.sector = 21, .key_a = 0x16a27af45407, .key_b = 0x9868925175ba}, + {.sector = 22, .key_a = 0xaba208516740, .key_b = 0xce26ecb95252}, + {.sector = 23, .key_a = 0xCD64E567ABCD, .key_b = 0x8f79c4fd8a01}, + {.sector = 24, .key_a = 0x764cd061f1e6, .key_b = 0xa74332f74994}, + {.sector = 25, .key_a = 0x1cc219e9fec1, .key_b = 0xb90de525ceb6}, + {.sector = 26, .key_a = 0x2fe3cb83ea43, .key_b = 0xfba88f109b32}, + {.sector = 27, .key_a = 0x07894ffec1d6, .key_b = 0xefcb0e689db3}, + {.sector = 28, .key_a = 0x04c297b91308, .key_b = 0xc8454c154cb5}, + {.sector = 29, .key_a = 0x7a38e3511a38, .key_b = 0xab16584c972a}, + {.sector = 30, .key_a = 0x7545df809202, .key_b = 0xecf751084a80}, + {.sector = 31, .key_a = 0x5125974cd391, .key_b = 0xd3eafb5df46d}, + {.sector = 32, .key_a = 0x7a86aa203788, .key_b = 0xe41242278ca2}, + {.sector = 33, .key_a = 0xafcef64c9913, .key_b = 0x9db96dca4324}, + {.sector = 34, .key_a = 0x04eaa462f70b, .key_b = 0xac17b93e2fae}, + {.sector = 35, .key_a = 0xe734c210f27e, .key_b = 0x29ba8c3e9fda}, + {.sector = 36, .key_a = 0xd5524f591eed, .key_b = 0x5daf42861b4d}, + {.sector = 37, .key_a = 0xe4821a377b75, .key_b = 0xe8709e486465}, + {.sector = 38, .key_a = 0x518dc6eea089, .key_b = 0x97c64ac98ca4}, + {.sector = 39, .key_a = 0xbb52f8cce07f, .key_b = 0x6b6119752c70}, +}; + +bool troika_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + + if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) { + return false; + } + + uint8_t sector = 11; + uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); + FURI_LOG_D("Troika", "Verifying sector %d", sector); + if(mf_classic_authenticate(tx_rx, block, 0x08b386463229, MfClassicKeyA)) { + FURI_LOG_D("Troika", "Sector %d verified", sector); + return true; + } + return false; +} + +bool troika_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + + MfClassicReader reader = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); + for(size_t i = 0; i < COUNT_OF(troika_4k_keys); i++) { + mf_classic_reader_add_sector( + &reader, troika_4k_keys[i].sector, troika_4k_keys[i].key_a, troika_4k_keys[i].key_b); + } + + return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40; +} + +bool troika_4k_parser_parse(NfcDeviceData* dev_data) { + MfClassicData* data = &dev_data->mf_classic_data; + + // Verify key + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); + if(key != troika_4k_keys[4].key_a) return false; + + // Verify card type + if(data->type != MfClassicType4k) return false; + + uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5]; + uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; + temp_ptr = &data->block[8 * 4].value[2]; + uint32_t number = 0; + for(size_t i = 1; i < 5; i++) { + number <<= 8; + number |= temp_ptr[i]; + } + number >>= 4; + number |= (temp_ptr[0] & 0xf) << 28; + + furi_string_printf( + dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance); + + return true; +} diff --git a/lib/nfc/parsers/troika_4k_parser.h b/lib/nfc/parsers/troika_4k_parser.h new file mode 100644 index 0000000..c1d6f01 --- /dev/null +++ b/lib/nfc/parsers/troika_4k_parser.h @@ -0,0 +1,9 @@ +#pragma once + +#include "nfc_supported_card.h" + +bool troika_4k_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool troika_4k_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool troika_4k_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/parsers/troika_parser.c b/lib/nfc/parsers/troika_parser.c new file mode 100644 index 0000000..6d8ae98 --- /dev/null +++ b/lib/nfc/parsers/troika_parser.c @@ -0,0 +1,88 @@ +#include "nfc_supported_card.h" + +#include +#include + +static const MfClassicAuthContext troika_keys[] = { + {.sector = 0, .key_a = 0xa0a1a2a3a4a5, .key_b = 0xfbf225dc5d58}, + {.sector = 1, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880}, + {.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 4, .key_a = 0x73068f118c13, .key_b = 0x2b7f3253fac5}, + {.sector = 5, .key_a = 0xfbc2793d540b, .key_b = 0xd3a297dc2698}, + {.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 7, .key_a = 0xae3d65a3dad4, .key_b = 0x0f1c63013dba}, + {.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81}, + {.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763}, + {.sector = 10, .key_a = 0x9becdf3d9273, .key_b = 0xf8493407799d}, + {.sector = 11, .key_a = 0x08b386463229, .key_b = 0x5efbaecef46b}, + {.sector = 12, .key_a = 0xcd4c61c26e3d, .key_b = 0x31c7610de3b0}, + {.sector = 13, .key_a = 0xa82607b01c0d, .key_b = 0x2910989b6880}, + {.sector = 14, .key_a = 0x0e8f64340ba4, .key_b = 0x4acec1205d75}, + {.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, +}; + +bool troika_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + UNUSED(nfc_worker); + if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType1k) { + return false; + } + + uint8_t sector = 11; + uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); + FURI_LOG_D("Troika", "Verifying sector %d", sector); + if(mf_classic_authenticate(tx_rx, block, 0x08b386463229, MfClassicKeyA)) { + FURI_LOG_D("Troika", "Sector %d verified", sector); + return true; + } + return false; +} + +bool troika_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + + MfClassicReader reader = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); + + for(size_t i = 0; i < COUNT_OF(troika_keys); i++) { + mf_classic_reader_add_sector( + &reader, troika_keys[i].sector, troika_keys[i].key_a, troika_keys[i].key_b); + } + + return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16; +} + +bool troika_parser_parse(NfcDeviceData* dev_data) { + MfClassicData* data = &dev_data->mf_classic_data; + bool troika_parsed = false; + + do { + // Verify key + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 8); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); + if(key != troika_keys[8].key_a) break; + + // Verify card type + if(data->type != MfClassicType1k) break; + + // Parse data + uint8_t* temp_ptr = &data->block[8 * 4 + 1].value[5]; + uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; + temp_ptr = &data->block[8 * 4].value[2]; + uint32_t number = 0; + for(size_t i = 1; i < 5; i++) { + number <<= 8; + number |= temp_ptr[i]; + } + number >>= 4; + number |= (temp_ptr[0] & 0xf) << 28; + + furi_string_printf( + dev_data->parsed_data, "\e#Troika\nNum: %lu\nBalance: %u rur.", number, balance); + troika_parsed = true; + } while(false); + + return troika_parsed; +} diff --git a/lib/nfc/parsers/troika_parser.h b/lib/nfc/parsers/troika_parser.h new file mode 100644 index 0000000..2aae48d --- /dev/null +++ b/lib/nfc/parsers/troika_parser.h @@ -0,0 +1,9 @@ +#pragma once + +#include "nfc_supported_card.h" + +bool troika_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool troika_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool troika_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/parsers/two_cities.c b/lib/nfc/parsers/two_cities.c new file mode 100644 index 0000000..d6d4279 --- /dev/null +++ b/lib/nfc/parsers/two_cities.c @@ -0,0 +1,146 @@ +#include "nfc_supported_card.h" +#include "plantain_parser.h" // For plantain-specific stuff + +#include +#include + +#include + +static const MfClassicAuthContext two_cities_keys_4k[] = { + {.sector = 0, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 1, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 2, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 3, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 4, .key_a = 0xe56ac127dd45, .key_b = 0x19fc84a3784b}, + {.sector = 5, .key_a = 0x77dabc9825e1, .key_b = 0x9764fec3154a}, + {.sector = 6, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 7, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 8, .key_a = 0xa73f5dc1d333, .key_b = 0xe35173494a81}, + {.sector = 9, .key_a = 0x69a32f1c2f19, .key_b = 0x6b8bd9860763}, + {.sector = 10, .key_a = 0xea0fd73cb149, .key_b = 0x29c35fa068fb}, + {.sector = 11, .key_a = 0xc76bf71a2509, .key_b = 0x9ba241db3f56}, + {.sector = 12, .key_a = 0xacffffffffff, .key_b = 0x71f3a315ad26}, + {.sector = 13, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 14, .key_a = 0xffffffffffff, .key_b = 0xffffffffffff}, + {.sector = 15, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 16, .key_a = 0x72f96bdd3714, .key_b = 0x462225cd34cf}, + {.sector = 17, .key_a = 0x044ce1872bc3, .key_b = 0x8c90c70cff4a}, + {.sector = 18, .key_a = 0xbc2d1791dec1, .key_b = 0xca96a487de0b}, + {.sector = 19, .key_a = 0x8791b2ccb5c4, .key_b = 0xc956c3b80da3}, + {.sector = 20, .key_a = 0x8e26e45e7d65, .key_b = 0x8e65b3af7d22}, + {.sector = 21, .key_a = 0x0f318130ed18, .key_b = 0x0c420a20e056}, + {.sector = 22, .key_a = 0x045ceca15535, .key_b = 0x31bec3d9e510}, + {.sector = 23, .key_a = 0x9d993c5d4ef4, .key_b = 0x86120e488abf}, + {.sector = 24, .key_a = 0xc65d4eaa645b, .key_b = 0xb69d40d1a439}, + {.sector = 25, .key_a = 0x3a8a139c20b4, .key_b = 0x8818a9c5d406}, + {.sector = 26, .key_a = 0xbaff3053b496, .key_b = 0x4b7cb25354d3}, + {.sector = 27, .key_a = 0x7413b599c4ea, .key_b = 0xb0a2AAF3A1BA}, + {.sector = 28, .key_a = 0x0ce7cd2cc72b, .key_b = 0xfa1fbb3f0f1f}, + {.sector = 29, .key_a = 0x0be5fac8b06a, .key_b = 0x6f95887a4fd3}, + {.sector = 30, .key_a = 0x26973ea74321, .key_b = 0xd27058c6e2c7}, + {.sector = 31, .key_a = 0xeb0a8ff88ade, .key_b = 0x578a9ada41e3}, + {.sector = 32, .key_a = 0x7a396f0d633d, .key_b = 0xad2bdc097023}, + {.sector = 33, .key_a = 0xa3faa6daff67, .key_b = 0x7600e889adf9}, + {.sector = 34, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 35, .key_a = 0x2aa05ed1856f, .key_b = 0xeaac88e5dc99}, + {.sector = 36, .key_a = 0xa7141147d430, .key_b = 0xff16014fefc7}, + {.sector = 37, .key_a = 0x8a8d88151a00, .key_b = 0x038b5f9b5a2a}, + {.sector = 38, .key_a = 0xb27addfb64b0, .key_b = 0x152fd0c420a7}, + {.sector = 39, .key_a = 0x7259fa0197c6, .key_b = 0x5583698df085}, +}; + +bool two_cities_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + UNUSED(nfc_worker); + + if(nfc_worker->dev_data->mf_classic_data.type != MfClassicType4k) { + return false; + } + + uint8_t sector = 4; + uint8_t block = mf_classic_get_sector_trailer_block_num_by_sector(sector); + FURI_LOG_D("2cities", "Verifying sector %d", sector); + if(mf_classic_authenticate(tx_rx, block, 0xe56ac127dd45, MfClassicKeyA)) { + FURI_LOG_D("2cities", "Sector %d verified", sector); + return true; + } + return false; +} + +bool two_cities_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + + MfClassicReader reader = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + reader.type = mf_classic_get_classic_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak); + for(size_t i = 0; i < COUNT_OF(two_cities_keys_4k); i++) { + mf_classic_reader_add_sector( + &reader, + two_cities_keys_4k[i].sector, + two_cities_keys_4k[i].key_a, + two_cities_keys_4k[i].key_b); + FURI_LOG_T("2cities", "Added sector %d", two_cities_keys_4k[i].sector); + } + + return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 40; +} + +bool two_cities_parser_parse(NfcDeviceData* dev_data) { + MfClassicData* data = &dev_data->mf_classic_data; + + // Verify key + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); + if(key != two_cities_keys_4k[4].key_a) return false; + + // ===== + // PLANTAIN + // ===== + + // Point to block 0 of sector 4, value 0 + uint8_t* temp_ptr = &data->block[4 * 4].value[0]; + // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t + // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal + uint32_t balance = + ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100; + // Read card number + // Point to block 0 of sector 0, value 0 + temp_ptr = &data->block[0 * 4].value[0]; + // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t + // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal + uint8_t card_number_arr[7]; + for(size_t i = 0; i < 7; i++) { + card_number_arr[i] = temp_ptr[6 - i]; + } + // Copy card number to uint64_t + uint64_t card_number = 0; + for(size_t i = 0; i < 7; i++) { + card_number = (card_number << 8) | card_number_arr[i]; + } + + // ===== + // --PLANTAIN-- + // ===== + // TROIKA + // ===== + + uint8_t* troika_temp_ptr = &data->block[8 * 4 + 1].value[5]; + uint16_t troika_balance = ((troika_temp_ptr[0] << 8) | troika_temp_ptr[1]) / 25; + troika_temp_ptr = &data->block[8 * 4].value[3]; + uint32_t troika_number = 0; + for(size_t i = 0; i < 4; i++) { + troika_number <<= 8; + troika_number |= troika_temp_ptr[i]; + } + troika_number >>= 4; + + furi_string_printf( + dev_data->parsed_data, + "\e#Troika+Plantain\nPN: %llu-\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n", + card_number, + balance, + troika_number, + troika_balance); + + return true; +} diff --git a/lib/nfc/parsers/two_cities.h b/lib/nfc/parsers/two_cities.h new file mode 100644 index 0000000..e735bea --- /dev/null +++ b/lib/nfc/parsers/two_cities.h @@ -0,0 +1,9 @@ +#pragma once + +#include "nfc_supported_card.h" + +bool two_cities_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool two_cities_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool two_cities_parser_parse(NfcDeviceData* dev_data); diff --git a/lib/nfc/protocols/crypto1.c b/lib/nfc/protocols/crypto1.c new file mode 100644 index 0000000..f59651c --- /dev/null +++ b/lib/nfc/protocols/crypto1.c @@ -0,0 +1,128 @@ +#include "crypto1.h" +#include "nfc_util.h" +#include + +// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git + +#define SWAPENDIAN(x) \ + ((x) = ((x) >> 8 & 0xff00ff) | ((x)&0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) +#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_EVEN (0x870804) + +#define BEBIT(x, n) FURI_BIT(x, (n) ^ 24) + +void crypto1_reset(Crypto1* crypto1) { + furi_assert(crypto1); + crypto1->even = 0; + crypto1->odd = 0; +} + +void crypto1_init(Crypto1* crypto1, uint64_t key) { + furi_assert(crypto1); + crypto1->even = 0; + crypto1->odd = 0; + for(int8_t i = 47; i > 0; i -= 2) { + crypto1->odd = crypto1->odd << 1 | FURI_BIT(key, (i - 1) ^ 7); + crypto1->even = crypto1->even << 1 | FURI_BIT(key, i ^ 7); + } +} + +uint32_t crypto1_filter(uint32_t in) { + uint32_t out = 0; + out = 0xf22c0 >> (in & 0xf) & 16; + out |= 0x6c9c0 >> (in >> 4 & 0xf) & 8; + out |= 0x3c8b0 >> (in >> 8 & 0xf) & 4; + out |= 0x1e458 >> (in >> 12 & 0xf) & 2; + out |= 0x0d938 >> (in >> 16 & 0xf) & 1; + return FURI_BIT(0xEC57E80A, out); +} + +uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted) { + furi_assert(crypto1); + uint8_t out = crypto1_filter(crypto1->odd); + uint32_t feed = out & (!!is_encrypted); + feed ^= !!in; + feed ^= LF_POLY_ODD & crypto1->odd; + feed ^= LF_POLY_EVEN & crypto1->even; + crypto1->even = crypto1->even << 1 | (nfc_util_even_parity32(feed)); + + FURI_SWAP(crypto1->odd, crypto1->even); + return out; +} + +uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) { + furi_assert(crypto1); + uint8_t out = 0; + for(uint8_t i = 0; i < 8; i++) { + out |= crypto1_bit(crypto1, FURI_BIT(in, i), is_encrypted) << i; + } + return out; +} + +uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) { + furi_assert(crypto1); + uint32_t out = 0; + for(uint8_t i = 0; i < 32; i++) { + out |= crypto1_bit(crypto1, BEBIT(in, i), is_encrypted) << (24 ^ i); + } + return out; +} + +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); +} + +void crypto1_decrypt( + Crypto1* crypto, + uint8_t* encrypted_data, + uint16_t encrypted_data_bits, + uint8_t* decrypted_data) { + furi_assert(crypto); + furi_assert(encrypted_data); + furi_assert(decrypted_data); + + if(encrypted_data_bits < 8) { + uint8_t decrypted_byte = 0; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 0)) << 0; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 1)) << 1; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 2)) << 2; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 3)) << 3; + decrypted_data[0] = decrypted_byte; + } else { + for(size_t i = 0; i < encrypted_data_bits / 8; i++) { + decrypted_data[i] = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i]; + } + } +} + +void crypto1_encrypt( + Crypto1* crypto, + uint8_t* keystream, + uint8_t* plain_data, + uint16_t plain_data_bits, + uint8_t* encrypted_data, + uint8_t* encrypted_parity) { + furi_assert(crypto); + furi_assert(plain_data); + furi_assert(encrypted_data); + furi_assert(encrypted_parity); + + if(plain_data_bits < 8) { + encrypted_data[0] = 0; + for(size_t i = 0; i < plain_data_bits; i++) { + encrypted_data[0] |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i; + } + } else { + memset(encrypted_parity, 0, plain_data_bits / 8 + 1); + for(uint8_t i = 0; i < plain_data_bits / 8; i++) { + encrypted_data[i] = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^ + plain_data[i]; + encrypted_parity[i / 8] |= + (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01) + << (7 - (i & 0x0007))); + } + } +} diff --git a/lib/nfc/protocols/crypto1.h b/lib/nfc/protocols/crypto1.h new file mode 100644 index 0000000..bbf6dc2 --- /dev/null +++ b/lib/nfc/protocols/crypto1.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint32_t odd; + uint32_t even; +} Crypto1; + +void crypto1_reset(Crypto1* crypto1); + +void crypto1_init(Crypto1* crypto1, uint64_t key); + +uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted); + +uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted); + +uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted); + +uint32_t crypto1_filter(uint32_t in); + +uint32_t prng_successor(uint32_t x, uint32_t n); + +void crypto1_decrypt( + Crypto1* crypto, + uint8_t* encrypted_data, + uint16_t encrypted_data_bits, + uint8_t* decrypted_data); + +void crypto1_encrypt( + Crypto1* crypto, + uint8_t* keystream, + uint8_t* plain_data, + uint16_t plain_data_bits, + uint8_t* encrypted_data, + uint8_t* encrypted_parity); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/emv.c b/lib/nfc/protocols/emv.c new file mode 100644 index 0000000..4c4ac85 --- /dev/null +++ b/lib/nfc/protocols/emv.c @@ -0,0 +1,444 @@ +#include "emv.h" + +#include + +#define TAG "Emv" + +const PDOLValue pdol_term_info = {0x9F59, {0xC8, 0x80, 0x00}}; // Terminal transaction information +const PDOLValue pdol_term_type = {0x9F5A, {0x00}}; // Terminal transaction type +const PDOLValue pdol_merchant_type = {0x9F58, {0x01}}; // Merchant type indicator +const PDOLValue pdol_term_trans_qualifies = { + 0x9F66, + {0x79, 0x00, 0x40, 0x80}}; // Terminal transaction qualifiers +const PDOLValue pdol_addtnl_term_qualifies = { + 0x9F40, + {0x79, 0x00, 0x40, 0x80}}; // Terminal transaction qualifiers +const PDOLValue pdol_amount_authorise = { + 0x9F02, + {0x00, 0x00, 0x00, 0x10, 0x00, 0x00}}; // Amount, authorised +const PDOLValue pdol_amount = {0x9F03, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // Amount +const PDOLValue pdol_country_code = {0x9F1A, {0x01, 0x24}}; // Terminal country code +const PDOLValue pdol_currency_code = {0x5F2A, {0x01, 0x24}}; // Transaction currency code +const PDOLValue pdol_term_verification = { + 0x95, + {0x00, 0x00, 0x00, 0x00, 0x00}}; // Terminal verification results +const PDOLValue pdol_transaction_date = {0x9A, {0x19, 0x01, 0x01}}; // Transaction date +const PDOLValue pdol_transaction_type = {0x9C, {0x00}}; // Transaction type +const PDOLValue pdol_transaction_cert = {0x98, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; // Transaction cert +const PDOLValue pdol_unpredict_number = {0x9F37, {0x82, 0x3D, 0xDE, 0x7A}}; // Unpredictable number + +const PDOLValue* const pdol_values[] = { + &pdol_term_info, + &pdol_term_type, + &pdol_merchant_type, + &pdol_term_trans_qualifies, + &pdol_addtnl_term_qualifies, + &pdol_amount_authorise, + &pdol_amount, + &pdol_country_code, + &pdol_currency_code, + &pdol_term_verification, + &pdol_transaction_date, + &pdol_transaction_type, + &pdol_transaction_cert, + &pdol_unpredict_number, +}; + +static const uint8_t select_ppse_ans[] = {0x6F, 0x29, 0x84, 0x0E, 0x32, 0x50, 0x41, 0x59, 0x2E, + 0x53, 0x59, 0x53, 0x2E, 0x44, 0x44, 0x46, 0x30, 0x31, + 0xA5, 0x17, 0xBF, 0x0C, 0x14, 0x61, 0x12, 0x4F, 0x07, + 0xA0, 0x00, 0x00, 0x00, 0x03, 0x10, 0x10, 0x50, 0x04, + 0x56, 0x49, 0x53, 0x41, 0x87, 0x01, 0x01, 0x90, 0x00}; +static const uint8_t select_app_ans[] = {0x6F, 0x20, 0x84, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x03, + 0x10, 0x10, 0xA5, 0x15, 0x50, 0x04, 0x56, 0x49, 0x53, + 0x41, 0x9F, 0x38, 0x0C, 0x9F, 0x66, 0x04, 0x9F, 0x02, + 0x06, 0x9F, 0x37, 0x04, 0x5F, 0x2A, 0x02, 0x90, 0x00}; +static const uint8_t pdol_ans[] = {0x77, 0x40, 0x82, 0x02, 0x20, 0x00, 0x57, 0x13, 0x55, 0x70, + 0x73, 0x83, 0x85, 0x87, 0x73, 0x31, 0xD1, 0x80, 0x22, 0x01, + 0x38, 0x84, 0x77, 0x94, 0x00, 0x00, 0x1F, 0x5F, 0x34, 0x01, + 0x00, 0x9F, 0x10, 0x07, 0x06, 0x01, 0x11, 0x03, 0x80, 0x00, + 0x00, 0x9F, 0x26, 0x08, 0x7A, 0x65, 0x7F, 0xD3, 0x52, 0x96, + 0xC9, 0x85, 0x9F, 0x27, 0x01, 0x00, 0x9F, 0x36, 0x02, 0x06, + 0x0C, 0x9F, 0x6C, 0x02, 0x10, 0x00, 0x90, 0x00}; + +static void emv_trace(FuriHalNfcTxRxContext* tx_rx, const char* message) { + if(furi_log_get_level() == FuriLogLevelTrace) { + FURI_LOG_T(TAG, "%s", message); + printf("TX: "); + for(size_t i = 0; i < tx_rx->tx_bits / 8; i++) { + printf("%02X ", tx_rx->tx_data[i]); + } + printf("\r\nRX: "); + for(size_t i = 0; i < tx_rx->rx_bits / 8; i++) { + printf("%02X ", tx_rx->rx_data[i]); + } + printf("\r\n"); + } +} + +static bool emv_decode_response(uint8_t* buff, uint16_t len, EmvApplication* app) { + uint16_t i = 0; + uint16_t tag = 0, first_byte = 0; + uint16_t tlen = 0; + bool success = false; + + while(i < len) { + first_byte = buff[i]; + if((first_byte & 31) == 31) { // 2-byte tag + tag = buff[i] << 8 | buff[i + 1]; + i++; + FURI_LOG_T(TAG, " 2-byte TLV EMV tag: %x", tag); + } else { + tag = buff[i]; + FURI_LOG_T(TAG, " 1-byte TLV EMV tag: %x", tag); + } + i++; + tlen = buff[i]; + if((tlen & 128) == 128) { // long length value + i++; + tlen = buff[i]; + FURI_LOG_T(TAG, " 2-byte TLV length: %d", tlen); + } else { + FURI_LOG_T(TAG, " 1-byte TLV length: %d", tlen); + } + i++; + if((first_byte & 32) == 32) { // "Constructed" -- contains more TLV data to parse + FURI_LOG_T(TAG, "Constructed TLV %x", tag); + if(!emv_decode_response(&buff[i], tlen, app)) { + FURI_LOG_T(TAG, "Failed to decode response for %x", tag); + // return false; + } else { + success = true; + } + } else { + switch(tag) { + case EMV_TAG_AID: + app->aid_len = tlen; + memcpy(app->aid, &buff[i], tlen); + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_AID %x", tag); + break; + case EMV_TAG_PRIORITY: + memcpy(&app->priority, &buff[i], tlen); + success = true; + break; + case EMV_TAG_CARD_NAME: + memcpy(app->name, &buff[i], tlen); + app->name[tlen] = '\0'; + app->name_found = true; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_CARD_NAME %x : %s", tag, app->name); + break; + case EMV_TAG_PDOL: + memcpy(app->pdol.data, &buff[i], tlen); + app->pdol.size = tlen; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_PDOL %x (len=%d)", tag, tlen); + break; + case EMV_TAG_AFL: + memcpy(app->afl.data, &buff[i], tlen); + app->afl.size = tlen; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_AFL %x (len=%d)", tag, tlen); + break; + case EMV_TAG_TRACK_1_EQUIV: { + char track_1_equiv[80]; + memcpy(track_1_equiv, &buff[i], tlen); + track_1_equiv[tlen] = '\0'; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_TRACK_1_EQUIV %x : %s", tag, track_1_equiv); + break; + } + case EMV_TAG_TRACK_2_EQUIV: { + // 0xD0 delimits PAN from expiry (YYMM) + for(int x = 1; x < tlen; x++) { + if(buff[i + x + 1] > 0xD0) { + memcpy(app->card_number, &buff[i], x + 1); + app->card_number_len = x + 1; + app->exp_year = (buff[i + x + 1] << 4) | (buff[i + x + 2] >> 4); + app->exp_month = (buff[i + x + 2] << 4) | (buff[i + x + 3] >> 4); + break; + } + } + + // Convert 4-bit to ASCII representation + char track_2_equiv[41]; + uint8_t track_2_equiv_len = 0; + for(int x = 0; x < tlen; x++) { + char top = (buff[i + x] >> 4) + '0'; + char bottom = (buff[i + x] & 0x0F) + '0'; + track_2_equiv[x * 2] = top; + track_2_equiv_len++; + if(top == '?') break; + track_2_equiv[x * 2 + 1] = bottom; + track_2_equiv_len++; + if(bottom == '?') break; + } + track_2_equiv[track_2_equiv_len] = '\0'; + success = true; + FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2_EQUIV %x : %s", tag, track_2_equiv); + break; + } + case EMV_TAG_PAN: + memcpy(app->card_number, &buff[i], tlen); + app->card_number_len = tlen; + success = true; + break; + case EMV_TAG_EXP_DATE: + app->exp_year = buff[i]; + app->exp_month = buff[i + 1]; + success = true; + break; + case EMV_TAG_CURRENCY_CODE: + app->currency_code = (buff[i] << 8 | buff[i + 1]); + success = true; + break; + case EMV_TAG_COUNTRY_CODE: + app->country_code = (buff[i] << 8 | buff[i + 1]); + success = true; + break; + } + } + i += tlen; + } + return success; +} + +static bool emv_select_ppse(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { + bool app_aid_found = false; + const uint8_t emv_select_ppse_cmd[] = { + 0x00, 0xA4, // SELECT ppse + 0x04, 0x00, // P1:By name, P2: empty + 0x0e, // Lc: Data length + 0x32, 0x50, 0x41, 0x59, 0x2e, 0x53, 0x59, // Data string: + 0x53, 0x2e, 0x44, 0x44, 0x46, 0x30, 0x31, // 2PAY.SYS.DDF01 (PPSE) + 0x00 // Le + }; + + memcpy(tx_rx->tx_data, emv_select_ppse_cmd, sizeof(emv_select_ppse_cmd)); + tx_rx->tx_bits = sizeof(emv_select_ppse_cmd) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + + FURI_LOG_D(TAG, "Send select PPSE"); + if(furi_hal_nfc_tx_rx(tx_rx, 300)) { + emv_trace(tx_rx, "Select PPSE answer:"); + if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { + app_aid_found = true; + } else { + FURI_LOG_E(TAG, "Failed to parse application"); + } + } else { + FURI_LOG_E(TAG, "Failed select PPSE"); + } + + return app_aid_found; +} + +static bool emv_select_app(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { + app->app_started = false; + const uint8_t emv_select_header[] = { + 0x00, + 0xA4, // SELECT application + 0x04, + 0x00 // P1:By name, P2:First or only occurence + }; + uint16_t size = sizeof(emv_select_header); + + // Copy header + memcpy(tx_rx->tx_data, emv_select_header, size); + // Copy AID + tx_rx->tx_data[size++] = app->aid_len; + memcpy(&tx_rx->tx_data[size], app->aid, app->aid_len); + size += app->aid_len; + tx_rx->tx_data[size++] = 0x00; + tx_rx->tx_bits = size * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + + FURI_LOG_D(TAG, "Start application"); + if(furi_hal_nfc_tx_rx(tx_rx, 300)) { + emv_trace(tx_rx, "Start application answer:"); + if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { + app->app_started = true; + } else { + FURI_LOG_E(TAG, "Failed to read PAN or PDOL"); + } + } else { + FURI_LOG_E(TAG, "Failed to start application"); + } + + return app->app_started; +} + +static uint16_t emv_prepare_pdol(APDU* dest, APDU* src) { + bool tag_found; + for(uint16_t i = 0; i < src->size; i++) { + tag_found = false; + for(uint8_t j = 0; j < sizeof(pdol_values) / sizeof(PDOLValue*); j++) { + if(src->data[i] == pdol_values[j]->tag) { + // Found tag with 1 byte length + uint8_t len = src->data[++i]; + memcpy(dest->data + dest->size, pdol_values[j]->data, len); + dest->size += len; + tag_found = true; + break; + } else if(((src->data[i] << 8) | src->data[i + 1]) == pdol_values[j]->tag) { + // Found tag with 2 byte length + i += 2; + uint8_t len = src->data[i]; + memcpy(dest->data + dest->size, pdol_values[j]->data, len); + dest->size += len; + tag_found = true; + break; + } + } + if(!tag_found) { + // Unknown tag, fill zeros + i += 2; + uint8_t len = src->data[i]; + memset(dest->data + dest->size, 0, len); + dest->size += len; + } + } + return dest->size; +} + +static bool emv_get_processing_options(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { + bool card_num_read = false; + const uint8_t emv_gpo_header[] = {0x80, 0xA8, 0x00, 0x00}; + uint16_t size = sizeof(emv_gpo_header); + + // Copy header + memcpy(tx_rx->tx_data, emv_gpo_header, size); + APDU pdol_data = {0, {0}}; + // Prepare and copy pdol parameters + emv_prepare_pdol(&pdol_data, &app->pdol); + tx_rx->tx_data[size++] = 0x02 + pdol_data.size; + tx_rx->tx_data[size++] = 0x83; + tx_rx->tx_data[size++] = pdol_data.size; + memcpy(tx_rx->tx_data + size, pdol_data.data, pdol_data.size); + size += pdol_data.size; + tx_rx->tx_data[size++] = 0; + tx_rx->tx_bits = size * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + + FURI_LOG_D(TAG, "Get proccessing options"); + if(furi_hal_nfc_tx_rx(tx_rx, 300)) { + emv_trace(tx_rx, "Get processing options answer:"); + if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { + if(app->card_number_len > 0) { + card_num_read = true; + } + } + } else { + FURI_LOG_E(TAG, "Failed to get processing options"); + } + + return card_num_read; +} + +static bool emv_read_sfi_record( + FuriHalNfcTxRxContext* tx_rx, + EmvApplication* app, + uint8_t sfi, + uint8_t record_num) { + bool card_num_read = false; + uint8_t sfi_param = (sfi << 3) | (1 << 2); + uint8_t emv_sfi_header[] = { + 0x00, + 0xB2, // READ RECORD + record_num, // P1:record_number + sfi_param, // P2:SFI + 0x00 // Le + }; + + memcpy(tx_rx->tx_data, emv_sfi_header, sizeof(emv_sfi_header)); + tx_rx->tx_bits = sizeof(emv_sfi_header) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + + if(furi_hal_nfc_tx_rx(tx_rx, 300)) { + emv_trace(tx_rx, "SFI record:"); + if(emv_decode_response(tx_rx->rx_data, tx_rx->rx_bits / 8, app)) { + card_num_read = true; + } + } else { + FURI_LOG_E(TAG, "Failed to read SFI record %d", record_num); + } + + return card_num_read; +} + +static bool emv_read_files(FuriHalNfcTxRxContext* tx_rx, EmvApplication* app) { + bool card_num_read = false; + + if(app->afl.size == 0) { + return false; + } + + FURI_LOG_D(TAG, "Search PAN in SFI"); + // Iterate through all files + for(size_t i = 0; i < app->afl.size; i += 4) { + uint8_t sfi = app->afl.data[i] >> 3; + uint8_t record_start = app->afl.data[i + 1]; + uint8_t record_end = app->afl.data[i + 2]; + // Iterate through all records in file + for(uint8_t record = record_start; record <= record_end; ++record) { + card_num_read |= emv_read_sfi_record(tx_rx, app, sfi, record); + } + } + + return card_num_read; +} + +bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app) { + furi_assert(tx_rx); + furi_assert(emv_app); + bool card_num_read = false; + memset(emv_app, 0, sizeof(EmvApplication)); + + do { + if(!emv_select_ppse(tx_rx, emv_app)) break; + if(!emv_select_app(tx_rx, emv_app)) break; + if(emv_get_processing_options(tx_rx, emv_app)) { + card_num_read = true; + } else { + card_num_read = emv_read_files(tx_rx, emv_app); + } + } while(false); + + return card_num_read; +} + +bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx) { + furi_assert(tx_rx); + bool emulation_complete = false; + tx_rx->tx_bits = 0; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + + do { + FURI_LOG_D(TAG, "Read select PPSE command"); + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; + + memcpy(tx_rx->tx_data, select_ppse_ans, sizeof(select_ppse_ans)); + tx_rx->tx_bits = sizeof(select_ppse_ans) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + FURI_LOG_D(TAG, "Send select PPSE answer and read select App command"); + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; + + memcpy(tx_rx->tx_data, select_app_ans, sizeof(select_app_ans)); + tx_rx->tx_bits = sizeof(select_app_ans) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + FURI_LOG_D(TAG, "Send select App answer and read get PDOL command"); + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; + + memcpy(tx_rx->tx_data, pdol_ans, sizeof(pdol_ans)); + tx_rx->tx_bits = sizeof(pdol_ans) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + FURI_LOG_D(TAG, "Send get PDOL answer"); + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; + + emulation_complete = true; + } while(false); + + return emulation_complete; +} diff --git a/lib/nfc/protocols/emv.h b/lib/nfc/protocols/emv.h new file mode 100644 index 0000000..c5b089f --- /dev/null +++ b/lib/nfc/protocols/emv.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#define MAX_APDU_LEN 255 + +#define EMV_TAG_APP_TEMPLATE 0x61 +#define EMV_TAG_AID 0x4F +#define EMV_TAG_PRIORITY 0x87 +#define EMV_TAG_PDOL 0x9F38 +#define EMV_TAG_CARD_NAME 0x50 +#define EMV_TAG_FCI 0xBF0C +#define EMV_TAG_LOG_CTRL 0x9F4D +#define EMV_TAG_TRACK_1_EQUIV 0x56 +#define EMV_TAG_TRACK_2_EQUIV 0x57 +#define EMV_TAG_PAN 0x5A +#define EMV_TAG_AFL 0x94 +#define EMV_TAG_EXP_DATE 0x5F24 +#define EMV_TAG_COUNTRY_CODE 0x5F28 +#define EMV_TAG_CURRENCY_CODE 0x9F42 +#define EMV_TAG_CARDHOLDER_NAME 0x5F20 + +typedef struct { + char name[32]; + uint8_t aid[16]; + uint16_t aid_len; + uint8_t number[10]; + uint8_t number_len; + uint8_t exp_mon; + uint8_t exp_year; + uint16_t country_code; + uint16_t currency_code; +} EmvData; + +typedef struct { + uint16_t tag; + uint8_t data[]; +} PDOLValue; + +typedef struct { + uint8_t size; + uint8_t data[MAX_APDU_LEN]; +} APDU; + +typedef struct { + uint8_t priority; + uint8_t aid[16]; + uint8_t aid_len; + bool app_started; + char name[32]; + bool name_found; + uint8_t card_number[10]; + uint8_t card_number_len; + uint8_t exp_month; + uint8_t exp_year; + uint16_t country_code; + uint16_t currency_code; + APDU pdol; + APDU afl; +} EmvApplication; + +/** Read bank card data + * @note Search EMV Application, start it, try to read AID, PAN, card name, + * expiration date, currency and country codes + * + * @param tx_rx FuriHalNfcTxRxContext instance + * @param emv_app EmvApplication instance + * + * @return true on success + */ +bool emv_read_bank_card(FuriHalNfcTxRxContext* tx_rx, EmvApplication* emv_app); + +/** Emulate bank card + * @note Answer to application selection and PDOL + * + * @param tx_rx FuriHalNfcTxRxContext instance + * + * @return true on success + */ +bool emv_card_emulation(FuriHalNfcTxRxContext* tx_rx); diff --git a/lib/nfc/protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c new file mode 100644 index 0000000..a72d4aa --- /dev/null +++ b/lib/nfc/protocols/mifare_classic.c @@ -0,0 +1,1626 @@ +#include "mifare_classic.h" +#include "nfca.h" +#include "nfc_util.h" +#include + +// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git + +#define TAG "MfClassic" + +#define MF_CLASSIC_ACK_CMD 0xAU +#define MF_CLASSIC_NACK_BUF_VALID_CMD 0x0U +#define MF_CLASSIC_NACK_BUF_INVALID_CMD 0x4U +#define MF_CLASSIC_AUTH_KEY_A_CMD 0x60U +#define MF_CLASSIC_AUTH_KEY_B_CMD 0x61U +#define MF_CLASSIC_READ_BLOCK_CMD 0x30U +#define MF_CLASSIC_WRITE_BLOCK_CMD 0xA0U +#define MF_CLASSIC_TRANSFER_CMD 0xB0U +#define MF_CLASSIC_DECREMENT_CMD 0xC0U +#define MF_CLASSIC_INCREMENT_CMD 0xC1U +#define MF_CLASSIC_RESTORE_CMD 0xC2U + +const char* mf_classic_get_type_str(MfClassicType type) { + if(type == MfClassicTypeMini) { + return "MIFARE Mini 0.3K"; + } else if(type == MfClassicType1k) { + return "MIFARE Classic 1K"; + } else if(type == MfClassicType4k) { + return "MIFARE Classic 4K"; + } else { + return "Unknown"; + } +} + +static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) { + furi_assert(sector < 40); + if(sector < 32) { + return sector * 4; + } else { + return 32 * 4 + (sector - 32) * 16; + } +} + +uint8_t mf_classic_get_sector_trailer_block_num_by_sector(uint8_t sector) { + furi_assert(sector < 40); + if(sector < 32) { + return sector * 4 + 3; + } else { + return 32 * 4 + (sector - 32) * 16 + 15; + } +} + +uint8_t mf_classic_get_sector_by_block(uint8_t block) { + if(block < 128) { + return (block | 0x03) / 4; + } else { + return 32 + ((block | 0xf) - 32 * 4) / 16; + } +} + +static uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector) { + furi_assert(sector < 40); + return sector < 32 ? 4 : 16; +} + +uint8_t mf_classic_get_sector_trailer_num_by_block(uint8_t block) { + if(block < 128) { + return block | 0x03; + } else { + return block | 0x0f; + } +} + +bool mf_classic_is_sector_trailer(uint8_t block) { + return block == mf_classic_get_sector_trailer_num_by_block(block); +} + +MfClassicSectorTrailer* + mf_classic_get_sector_trailer_by_sector(MfClassicData* data, uint8_t sector) { + furi_assert(data); + uint8_t sec_tr_block_num = mf_classic_get_sector_trailer_block_num_by_sector(sector); + return (MfClassicSectorTrailer*)data->block[sec_tr_block_num].value; +} + +uint8_t mf_classic_get_total_sectors_num(MfClassicType type) { + if(type == MfClassicTypeMini) { + return MF_MINI_TOTAL_SECTORS_NUM; + } else if(type == MfClassicType1k) { + return MF_CLASSIC_1K_TOTAL_SECTORS_NUM; + } else if(type == MfClassicType4k) { + return MF_CLASSIC_4K_TOTAL_SECTORS_NUM; + } else { + return 0; + } +} + +uint16_t mf_classic_get_total_block_num(MfClassicType type) { + if(type == MfClassicTypeMini) { + return 20; + } else if(type == MfClassicType1k) { + return 64; + } else if(type == MfClassicType4k) { + return 256; + } else { + return 0; + } +} + +bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num) { + furi_assert(data); + + return (FURI_BIT(data->block_read_mask[block_num / 32], block_num % 32) == 1); +} + +void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data) { + furi_assert(data); + + if(mf_classic_is_sector_trailer(block_num)) { + memcpy(&data->block[block_num].value[6], &block_data->value[6], 4); + } else { + memcpy(data->block[block_num].value, block_data->value, MF_CLASSIC_BLOCK_SIZE); + } + FURI_BIT_SET(data->block_read_mask[block_num / 32], block_num % 32); +} + +bool mf_classic_is_sector_data_read(MfClassicData* data, uint8_t sector_num) { + furi_assert(data); + + uint8_t first_block = mf_classic_get_first_block_num_of_sector(sector_num); + uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num); + bool data_read = true; + for(size_t i = first_block; i < first_block + total_blocks; i++) { + data_read &= mf_classic_is_block_read(data, i); + } + + return data_read; +} + +void mf_classic_set_sector_data_not_read(MfClassicData* data) { + furi_assert(data); + memset(data->block_read_mask, 0, sizeof(data->block_read_mask)); +} + +bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type) { + furi_assert(data); + + bool key_found = false; + if(key_type == MfClassicKeyA) { + key_found = (FURI_BIT(data->key_a_mask, sector_num) == 1); + } else if(key_type == MfClassicKeyB) { + key_found = (FURI_BIT(data->key_b_mask, sector_num) == 1); + } + + return key_found; +} + +void mf_classic_set_key_found( + MfClassicData* data, + uint8_t sector_num, + MfClassicKey key_type, + uint64_t key) { + furi_assert(data); + + uint8_t key_arr[6] = {}; + MfClassicSectorTrailer* sec_trailer = + mf_classic_get_sector_trailer_by_sector(data, sector_num); + nfc_util_num2bytes(key, 6, key_arr); + if(key_type == MfClassicKeyA) { + memcpy(sec_trailer->key_a, key_arr, sizeof(sec_trailer->key_a)); + FURI_BIT_SET(data->key_a_mask, sector_num); + } else if(key_type == MfClassicKeyB) { + memcpy(sec_trailer->key_b, key_arr, sizeof(sec_trailer->key_b)); + FURI_BIT_SET(data->key_b_mask, sector_num); + } +} + +void mf_classic_set_key_not_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type) { + furi_assert(data); + + if(key_type == MfClassicKeyA) { + FURI_BIT_CLEAR(data->key_a_mask, sector_num); + } else if(key_type == MfClassicKeyB) { + FURI_BIT_CLEAR(data->key_b_mask, sector_num); + } +} + +bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num) { + furi_assert(data); + + bool sector_read = false; + do { + if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyA)) break; + if(!mf_classic_is_key_found(data, sector_num, MfClassicKeyB)) break; + uint8_t start_block = mf_classic_get_first_block_num_of_sector(sector_num); + uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num); + uint8_t block_read = true; + for(size_t i = start_block; i < start_block + total_blocks; i++) { + block_read = mf_classic_is_block_read(data, i); + if(!block_read) break; + } + sector_read = block_read; + } while(false); + + return sector_read; +} + +void mf_classic_get_read_sectors_and_keys( + MfClassicData* data, + uint8_t* sectors_read, + uint8_t* keys_found) { + furi_assert(data); + furi_assert(sectors_read); + furi_assert(keys_found); + + *sectors_read = 0; + *keys_found = 0; + uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); + for(size_t i = 0; i < sectors_total; i++) { + if(mf_classic_is_key_found(data, i, MfClassicKeyA)) { + *keys_found += 1; + } + if(mf_classic_is_key_found(data, i, MfClassicKeyB)) { + *keys_found += 1; + } + uint8_t first_block = mf_classic_get_first_block_num_of_sector(i); + uint8_t total_blocks_in_sec = mf_classic_get_blocks_num_in_sector(i); + bool blocks_read = true; + for(size_t j = first_block; j < first_block + total_blocks_in_sec; j++) { + blocks_read = mf_classic_is_block_read(data, j); + if(!blocks_read) break; + } + if(blocks_read) { + *sectors_read += 1; + } + } +} + +bool mf_classic_is_card_read(MfClassicData* data) { + furi_assert(data); + + uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); + uint8_t sectors_read = 0; + uint8_t keys_found = 0; + mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); + bool card_read = (sectors_read == sectors_total) && (keys_found == sectors_total * 2); + + return card_read; +} + +bool mf_classic_is_allowed_access_sector_trailer( + MfClassicData* data, + uint8_t block_num, + MfClassicKey key, + MfClassicAction action) { + uint8_t* sector_trailer = data->block[block_num].value; + uint8_t AC = ((sector_trailer[7] >> 5) & 0x04) | ((sector_trailer[8] >> 2) & 0x02) | + ((sector_trailer[8] >> 7) & 0x01); + switch(action) { + case MfClassicActionKeyARead: { + return false; + } + case MfClassicActionKeyAWrite: + case MfClassicActionKeyBWrite: { + return ( + (key == MfClassicKeyA && (AC == 0x00 || AC == 0x01)) || + (key == MfClassicKeyB && (AC == 0x04 || AC == 0x03))); + } + case MfClassicActionKeyBRead: { + return (key == MfClassicKeyA && (AC == 0x00 || AC == 0x02 || AC == 0x01)); + } + case MfClassicActionACRead: { + return ( + (key == MfClassicKeyA) || + (key == MfClassicKeyB && !(AC == 0x00 || AC == 0x02 || AC == 0x01))); + } + case MfClassicActionACWrite: { + return ( + (key == MfClassicKeyA && (AC == 0x01)) || + (key == MfClassicKeyB && (AC == 0x03 || AC == 0x05))); + } + default: + return false; + } + return true; +} + +bool mf_classic_is_allowed_access_data_block( + MfClassicData* data, + uint8_t block_num, + MfClassicKey key, + MfClassicAction action) { + uint8_t* sector_trailer = + data->block[mf_classic_get_sector_trailer_num_by_block(block_num)].value; + + if(block_num == 0 && action == MfClassicActionDataWrite) { + return false; + } + + uint8_t sector_block; + if(block_num <= 128) { + sector_block = block_num & 0x03; + } else { + sector_block = (block_num & 0x0f) / 5; + } + + uint8_t AC; + switch(sector_block) { + case 0x00: { + AC = ((sector_trailer[7] >> 2) & 0x04) | ((sector_trailer[8] << 1) & 0x02) | + ((sector_trailer[8] >> 4) & 0x01); + break; + } + case 0x01: { + AC = ((sector_trailer[7] >> 3) & 0x04) | ((sector_trailer[8] >> 0) & 0x02) | + ((sector_trailer[8] >> 5) & 0x01); + break; + } + case 0x02: { + AC = ((sector_trailer[7] >> 4) & 0x04) | ((sector_trailer[8] >> 1) & 0x02) | + ((sector_trailer[8] >> 6) & 0x01); + break; + } + default: + return false; + } + + switch(action) { + case MfClassicActionDataRead: { + return ( + (key == MfClassicKeyA && !(AC == 0x03 || AC == 0x05 || AC == 0x07)) || + (key == MfClassicKeyB && !(AC == 0x07))); + } + case MfClassicActionDataWrite: { + return ( + (key == MfClassicKeyA && (AC == 0x00)) || + (key == MfClassicKeyB && (AC == 0x00 || AC == 0x04 || AC == 0x06 || AC == 0x03))); + } + case MfClassicActionDataInc: { + return ( + (key == MfClassicKeyA && (AC == 0x00)) || + (key == MfClassicKeyB && (AC == 0x00 || AC == 0x06))); + } + case MfClassicActionDataDec: { + return ( + (key == MfClassicKeyA && (AC == 0x00 || AC == 0x06 || AC == 0x01)) || + (key == MfClassicKeyB && (AC == 0x00 || AC == 0x06 || AC == 0x01))); + } + default: + return false; + } + + return false; +} + +static bool mf_classic_is_allowed_access( + MfClassicEmulator* emulator, + uint8_t block_num, + MfClassicKey key, + MfClassicAction action) { + if(mf_classic_is_sector_trailer(block_num)) { + return mf_classic_is_allowed_access_sector_trailer( + &emulator->data, block_num, key, action); + } else { + return mf_classic_is_allowed_access_data_block(&emulator->data, block_num, key, action); + } +} + +bool mf_classic_is_value_block(MfClassicData* data, uint8_t block_num) { + // Check if key A can write, if it can, it's transport configuration, not data block + return !mf_classic_is_allowed_access_data_block( + data, block_num, MfClassicKeyA, MfClassicActionDataWrite) && + (mf_classic_is_allowed_access_data_block( + data, block_num, MfClassicKeyB, MfClassicActionDataInc) || + mf_classic_is_allowed_access_data_block( + data, block_num, MfClassicKeyB, MfClassicActionDataDec)); +} + +bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { + UNUSED(ATQA1); + if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) { + return true; + } else if((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01)) { + //skylanders support + return true; + } else if( + ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) || + ((ATQA0 == 0x02 || ATQA0 == 0x04 || ATQA0 == 0x08) && (SAK == 0x38))) { + return true; + } else { + return false; + } +} + +MfClassicType mf_classic_get_classic_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { + UNUSED(ATQA1); + if((ATQA0 == 0x44 || ATQA0 == 0x04)) { + if((SAK == 0x08 || SAK == 0x88)) { + return MfClassicType1k; + } else if((SAK == 0x38)) { + return MfClassicType4k; + } else if(SAK == 0x09) { + return MfClassicTypeMini; + } + } else if((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01)) { + //skylanders support + return MfClassicType1k; + } else if( + ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) || + ((ATQA0 == 0x02 || ATQA0 == 0x08) && (SAK == 0x38))) { + return MfClassicType4k; + } + return MfClassicType1k; +} + +void mf_classic_reader_add_sector( + MfClassicReader* reader, + uint8_t sector, + uint64_t key_a, + uint64_t key_b) { + furi_assert(reader); + furi_assert(sector < MF_CLASSIC_SECTORS_MAX); + furi_assert((key_a != MF_CLASSIC_NO_KEY) || (key_b != MF_CLASSIC_NO_KEY)); + + if(reader->sectors_to_read < MF_CLASSIC_SECTORS_MAX) { + reader->sector_reader[reader->sectors_to_read].key_a = key_a; + reader->sector_reader[reader->sectors_to_read].key_b = key_b; + reader->sector_reader[reader->sectors_to_read].sector_num = sector; + reader->sectors_to_read++; + } +} + +bool mf_classic_block_to_value(const uint8_t* block, int32_t* value, uint8_t* addr) { + uint32_t v = *(uint32_t*)&block[0]; + uint32_t v_inv = *(uint32_t*)&block[4]; + uint32_t v1 = *(uint32_t*)&block[8]; + + bool val_checks = + ((v == v1) && (v == ~v_inv) && (block[12] == (~block[13] & 0xFF)) && + (block[14] == (~block[15] & 0xFF)) && (block[12] == block[14])); + if(value) { + *value = (int32_t)v; + } + if(addr) { + *addr = block[12]; + } + return val_checks; +} + +void mf_classic_value_to_block(int32_t value, uint8_t addr, uint8_t* block) { + uint32_t v_inv = ~((uint32_t)value); + + memcpy(block, &value, 4); //-V1086 + memcpy(block + 4, &v_inv, 4); //-V1086 + memcpy(block + 8, &value, 4); //-V1086 + + block[12] = addr; + block[13] = ~addr & 0xFF; + block[14] = addr; + block[15] = ~addr & 0xFF; +} + +void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector) { + furi_assert(auth_ctx); + auth_ctx->sector = sector; + auth_ctx->key_a = MF_CLASSIC_NO_KEY; + auth_ctx->key_b = MF_CLASSIC_NO_KEY; +} + +static bool mf_classic_auth( + FuriHalNfcTxRxContext* tx_rx, + uint32_t block, + uint64_t key, + MfClassicKey key_type, + Crypto1* crypto, + bool skip_activate, + uint32_t cuid) { + bool auth_success = false; + memset(tx_rx->tx_data, 0, sizeof(tx_rx->tx_data)); + memset(tx_rx->tx_parity, 0, sizeof(tx_rx->tx_parity)); + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + + do { + if(!skip_activate && !furi_hal_nfc_activate_nfca(200, &cuid)) break; + if(key_type == MfClassicKeyA) { + tx_rx->tx_data[0] = MF_CLASSIC_AUTH_KEY_A_CMD; + } else { + tx_rx->tx_data[0] = MF_CLASSIC_AUTH_KEY_B_CMD; + } + tx_rx->tx_data[1] = block; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRxNoCrc; + tx_rx->tx_bits = 2 * 8; + if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break; + + uint32_t nt = (uint32_t)nfc_util_bytes2num(tx_rx->rx_data, 4); + crypto1_init(crypto, key); + crypto1_word(crypto, nt ^ cuid, 0); + uint8_t nr[4] = {}; + nfc_util_num2bytes(prng_successor(DWT->CYCCNT, 32), 4, nr); + for(uint8_t i = 0; i < 4; i++) { + tx_rx->tx_data[i] = crypto1_byte(crypto, nr[i], 0) ^ nr[i]; + tx_rx->tx_parity[0] |= + (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nr[i])) & 0x01) << (7 - i)); + } + nt = prng_successor(nt, 32); + for(uint8_t i = 4; i < 8; i++) { + nt = prng_successor(nt, 8); + tx_rx->tx_data[i] = crypto1_byte(crypto, 0x00, 0) ^ (nt & 0xff); + tx_rx->tx_parity[0] |= + (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt & 0xff)) & 0x01) + << (7 - i)); + } + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; + tx_rx->tx_bits = 8 * 8; + if(!furi_hal_nfc_tx_rx(tx_rx, 6)) break; + if(tx_rx->rx_bits == 32) { + crypto1_word(crypto, 0, 0); + auth_success = true; + } + } while(false); + + return auth_success; +} + +bool mf_classic_authenticate( + FuriHalNfcTxRxContext* tx_rx, + uint8_t block_num, + uint64_t key, + MfClassicKey key_type) { + furi_assert(tx_rx); + + Crypto1 crypto = {}; + bool key_found = mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0); + furi_hal_nfc_sleep(); + return key_found; +} + +bool mf_classic_authenticate_skip_activate( + FuriHalNfcTxRxContext* tx_rx, + uint8_t block_num, + uint64_t key, + MfClassicKey key_type, + bool skip_activate, + uint32_t cuid) { + furi_assert(tx_rx); + + Crypto1 crypto = {}; + bool key_found = + mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, skip_activate, cuid); + furi_hal_nfc_sleep(); + return key_found; +} + +bool mf_classic_auth_attempt( + FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, + MfClassicAuthContext* auth_ctx, + uint64_t key) { + furi_assert(tx_rx); + furi_assert(auth_ctx); + bool found_key = false; + bool need_halt = (auth_ctx->key_a == MF_CLASSIC_NO_KEY) && + (auth_ctx->key_b == MF_CLASSIC_NO_KEY); + + if(auth_ctx->key_a == MF_CLASSIC_NO_KEY) { + // Try AUTH with key A + if(mf_classic_auth( + tx_rx, + mf_classic_get_sector_trailer_block_num_by_sector(auth_ctx->sector), + key, + MfClassicKeyA, + crypto, + false, + 0)) { + auth_ctx->key_a = key; + found_key = true; + } + } + + if(need_halt) { + furi_hal_nfc_sleep(); + } + + if(auth_ctx->key_b == MF_CLASSIC_NO_KEY) { + // Try AUTH with key B + if(mf_classic_auth( + tx_rx, + mf_classic_get_sector_trailer_block_num_by_sector(auth_ctx->sector), + key, + MfClassicKeyB, + crypto, + false, + 0)) { + auth_ctx->key_b = key; + found_key = true; + } + } + + return found_key; +} + +bool mf_classic_read_block( + FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, + uint8_t block_num, + MfClassicBlock* block) { + furi_assert(tx_rx); + furi_assert(crypto); + furi_assert(block); + + bool read_block_success = false; + uint8_t plain_cmd[4] = {MF_CLASSIC_READ_BLOCK_CMD, block_num, 0x00, 0x00}; + nfca_append_crc16(plain_cmd, 2); + + crypto1_encrypt( + crypto, NULL, plain_cmd, sizeof(plain_cmd) * 8, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_bits = sizeof(plain_cmd) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; + + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + if(tx_rx->rx_bits == 8 * (MF_CLASSIC_BLOCK_SIZE + 2)) { + uint8_t block_received[MF_CLASSIC_BLOCK_SIZE + 2]; + crypto1_decrypt(crypto, tx_rx->rx_data, tx_rx->rx_bits, block_received); + uint16_t crc_calc = nfca_get_crc16(block_received, MF_CLASSIC_BLOCK_SIZE); + uint16_t crc_received = (block_received[MF_CLASSIC_BLOCK_SIZE + 1] << 8) | + block_received[MF_CLASSIC_BLOCK_SIZE]; + if(crc_received != crc_calc) { + FURI_LOG_E( + TAG, + "Incorrect CRC while reading block %d. Expected %04X, Received %04X", + block_num, + crc_received, + crc_calc); + } else { + memcpy(block->value, block_received, MF_CLASSIC_BLOCK_SIZE); + read_block_success = true; + } + } + } + return read_block_success; +} + +void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, uint8_t sec_num) { + furi_assert(tx_rx); + furi_assert(data); + + furi_hal_nfc_sleep(); + bool key_a_found = mf_classic_is_key_found(data, sec_num, MfClassicKeyA); + bool key_b_found = mf_classic_is_key_found(data, sec_num, MfClassicKeyB); + uint8_t start_block = mf_classic_get_first_block_num_of_sector(sec_num); + uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sec_num); + MfClassicBlock block_tmp = {}; + uint64_t key = 0; + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sec_num); + Crypto1 crypto = {}; + + uint8_t blocks_read = 0; + do { + if(!key_a_found) break; + FURI_LOG_D(TAG, "Try to read blocks with key A"); + key = nfc_util_bytes2num(sec_tr->key_a, sizeof(sec_tr->key_a)); + if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyA, &crypto, false, 0)) { + mf_classic_set_key_not_found(data, sec_num, MfClassicKeyA); + FURI_LOG_D(TAG, "Key %dA not found in read", sec_num); + break; + } + + for(size_t i = start_block; i < start_block + total_blocks; i++) { + if(!mf_classic_is_block_read(data, i)) { + if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { + mf_classic_set_block_read(data, i, &block_tmp); + blocks_read++; + } else if(i > start_block) { + // Try to re-auth to read block in case prevous block was protected from read + furi_hal_nfc_sleep(); + if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyA, &crypto, false, 0)) { + mf_classic_set_key_not_found(data, sec_num, MfClassicKeyA); + FURI_LOG_D(TAG, "Key %dA not found in read", sec_num); + break; + } + if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { + mf_classic_set_block_read(data, i, &block_tmp); + blocks_read++; + } + } + } else { + blocks_read++; + } + } + FURI_LOG_D(TAG, "Read %d blocks out of %d", blocks_read, total_blocks); + } while(false); + do { + if(blocks_read == total_blocks) break; + if(!key_b_found) break; + if(key_a_found) { + furi_hal_nfc_sleep(); + } + FURI_LOG_D(TAG, "Try to read blocks with key B"); + key = nfc_util_bytes2num(sec_tr->key_b, sizeof(sec_tr->key_b)); + if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto, false, 0)) { + mf_classic_set_key_not_found(data, sec_num, MfClassicKeyB); + FURI_LOG_D(TAG, "Key %dB not found in read", sec_num); + break; + } + + for(size_t i = start_block; i < start_block + total_blocks; i++) { + if(!mf_classic_is_block_read(data, i)) { + if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { + mf_classic_set_block_read(data, i, &block_tmp); + blocks_read++; + } else if(i > start_block) { + // Try to re-auth to read block in case prevous block was protected from read + furi_hal_nfc_sleep(); + if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyB, &crypto, false, 0)) { + mf_classic_set_key_not_found(data, sec_num, MfClassicKeyB); + FURI_LOG_D(TAG, "Key %dB not found in read", sec_num); + break; + } + if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) { + mf_classic_set_block_read(data, i, &block_tmp); + blocks_read++; + } + } + } else { + blocks_read++; + } + } + FURI_LOG_D(TAG, "Read %d blocks out of %d", blocks_read, total_blocks); + } while(false); +} + +static bool mf_classic_read_sector_with_reader( + FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, + MfClassicSectorReader* sector_reader, + MfClassicSector* sector) { + furi_assert(tx_rx); + furi_assert(sector_reader); + furi_assert(sector); + + uint64_t key; + MfClassicKey key_type; + uint8_t first_block; + bool sector_read = false; + + furi_hal_nfc_sleep(); + do { + // Activate card + first_block = mf_classic_get_first_block_num_of_sector(sector_reader->sector_num); + if(sector_reader->key_a != MF_CLASSIC_NO_KEY) { + key = sector_reader->key_a; + key_type = MfClassicKeyA; + } else if(sector_reader->key_b != MF_CLASSIC_NO_KEY) { + key = sector_reader->key_b; + key_type = MfClassicKeyB; + } else { + break; + } + + // Auth to first block in sector + if(!mf_classic_auth(tx_rx, first_block, key, key_type, crypto, false, 0)) { + // Set key to MF_CLASSIC_NO_KEY to prevent further attempts + if(key_type == MfClassicKeyA) { + sector_reader->key_a = MF_CLASSIC_NO_KEY; + } else { + sector_reader->key_b = MF_CLASSIC_NO_KEY; + } + break; + } + sector->total_blocks = mf_classic_get_blocks_num_in_sector(sector_reader->sector_num); + + // Read blocks + for(uint8_t i = 0; i < sector->total_blocks; i++) { + if(mf_classic_read_block(tx_rx, crypto, first_block + i, §or->block[i])) continue; + if(i == 0) continue; + // Try to auth to read next block in case previous is locked + furi_hal_nfc_sleep(); + if(!mf_classic_auth(tx_rx, first_block + i, key, key_type, crypto, false, 0)) continue; + mf_classic_read_block(tx_rx, crypto, first_block + i, §or->block[i]); + } + // Save sector keys in last block + if(sector_reader->key_a != MF_CLASSIC_NO_KEY) { + nfc_util_num2bytes( + sector_reader->key_a, 6, §or->block[sector->total_blocks - 1].value[0]); + } + if(sector_reader->key_b != MF_CLASSIC_NO_KEY) { + nfc_util_num2bytes( + sector_reader->key_b, 6, §or->block[sector->total_blocks - 1].value[10]); + } + + sector_read = true; + } while(false); + + return sector_read; +} + +uint8_t mf_classic_read_card( + FuriHalNfcTxRxContext* tx_rx, + MfClassicReader* reader, + MfClassicData* data) { + furi_assert(tx_rx); + furi_assert(reader); + furi_assert(data); + + uint8_t sectors_read = 0; + data->type = reader->type; + data->key_a_mask = 0; + data->key_b_mask = 0; + MfClassicSector temp_sector = {}; + for(uint8_t i = 0; i < reader->sectors_to_read; i++) { + if(mf_classic_read_sector_with_reader( + tx_rx, &reader->crypto, &reader->sector_reader[i], &temp_sector)) { + uint8_t first_block = + mf_classic_get_first_block_num_of_sector(reader->sector_reader[i].sector_num); + for(uint8_t j = 0; j < temp_sector.total_blocks; j++) { + mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]); + } + if(reader->sector_reader[i].key_a != MF_CLASSIC_NO_KEY) { + mf_classic_set_key_found( + data, + reader->sector_reader[i].sector_num, + MfClassicKeyA, + reader->sector_reader[i].key_a); + } + if(reader->sector_reader[i].key_b != MF_CLASSIC_NO_KEY) { + mf_classic_set_key_found( + data, + reader->sector_reader[i].sector_num, + MfClassicKeyB, + reader->sector_reader[i].key_b); + } + sectors_read++; + } + } + + return sectors_read; +} + +uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data) { + furi_assert(tx_rx); + furi_assert(data); + + uint8_t total_sectors = mf_classic_get_total_sectors_num(data->type); + + for(size_t i = 0; i < total_sectors; i++) { + mf_classic_read_sector(tx_rx, data, i); + } + uint8_t sectors_read = 0; + uint8_t keys_found = 0; + mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found); + FURI_LOG_D(TAG, "Read %d sectors and %d keys", sectors_read, keys_found); + + return sectors_read; +} + +bool mf_classic_emulator( + MfClassicEmulator* emulator, + FuriHalNfcTxRxContext* tx_rx, + bool is_reader_analyzer) { + furi_assert(emulator); + furi_assert(tx_rx); + uint8_t plain_data[MF_CLASSIC_MAX_DATA_SIZE]; + MfClassicKey access_key = MfClassicKeyA; + bool need_reset = false; + bool need_nack = false; + bool is_encrypted = false; + uint8_t sector = 0; + + // Used for decrement and increment - copy to block on transfer + uint8_t transfer_buf[MF_CLASSIC_BLOCK_SIZE]; + bool transfer_buf_valid = false; + + // Process commands + while(!need_reset && !need_nack) { //-V654 + memset(plain_data, 0, MF_CLASSIC_MAX_DATA_SIZE); + if(!is_encrypted) { + crypto1_reset(&emulator->crypto); + memcpy(plain_data, tx_rx->rx_data, tx_rx->rx_bits / 8); + } else { + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { + FURI_LOG_D( + TAG, + "Error in tx rx. Tx: %d bits, Rx: %d bits", + tx_rx->tx_bits, + tx_rx->rx_bits); + need_reset = true; + break; + } + crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); + } + + // After increment, decrement or restore the only allowed command is transfer + uint8_t cmd = plain_data[0]; + if(transfer_buf_valid && cmd != MF_CLASSIC_TRANSFER_CMD) { + need_nack = true; + break; + } + + if(cmd == NFCA_CMD_HALT && plain_data[1] == 0x00) { + FURI_LOG_T(TAG, "Halt received"); + need_reset = true; + break; + } + + if(cmd == NFCA_CMD_RATS) { + // Mifare Classic doesn't support ATS, NACK it and start listening again + FURI_LOG_T(TAG, "RATS received"); + need_nack = true; + break; + } + + if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD || cmd == MF_CLASSIC_AUTH_KEY_B_CMD) { + uint8_t block = plain_data[1]; + uint64_t key = 0; + uint8_t sector_trailer_block = mf_classic_get_sector_trailer_num_by_block(block); + sector = mf_classic_get_sector_by_block(block); + MfClassicSectorTrailer* sector_trailer = + (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value; + if(cmd == MF_CLASSIC_AUTH_KEY_A_CMD) { + if(mf_classic_is_key_found( + &emulator->data, mf_classic_get_sector_by_block(block), MfClassicKeyA) || + is_reader_analyzer) { + key = nfc_util_bytes2num(sector_trailer->key_a, 6); + access_key = MfClassicKeyA; + } else { + FURI_LOG_D(TAG, "Key not known"); + need_nack = true; + break; + } + } else { + if(mf_classic_is_key_found( + &emulator->data, mf_classic_get_sector_by_block(block), MfClassicKeyB) || + is_reader_analyzer) { + key = nfc_util_bytes2num(sector_trailer->key_b, 6); + access_key = MfClassicKeyB; + } else { + FURI_LOG_D(TAG, "Key not known"); + need_nack = true; + break; + } + } + + uint32_t nonce = prng_successor(DWT->CYCCNT, 32) ^ 0xAA; + uint8_t nt[4]; + uint8_t nt_keystream[4]; + nfc_util_num2bytes(nonce, 4, nt); + nfc_util_num2bytes(nonce ^ emulator->cuid, 4, nt_keystream); + crypto1_init(&emulator->crypto, key); + if(!is_encrypted) { + crypto1_word(&emulator->crypto, emulator->cuid ^ nonce, 0); + memcpy(tx_rx->tx_data, nt, sizeof(nt)); + tx_rx->tx_parity[0] = 0; + nfc_util_odd_parity(tx_rx->tx_data, tx_rx->tx_parity, sizeof(nt)); + tx_rx->tx_bits = sizeof(nt) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + } else { + crypto1_encrypt( + &emulator->crypto, + nt_keystream, + nt, + sizeof(nt) * 8, + tx_rx->tx_data, + tx_rx->tx_parity); + tx_rx->tx_bits = sizeof(nt) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + } + + if(!furi_hal_nfc_tx_rx(tx_rx, 500)) { + FURI_LOG_E(TAG, "Error in NT exchange"); + need_reset = true; + break; + } + + if(tx_rx->rx_bits != 64) { + need_reset = true; + break; + } + + uint32_t nr = nfc_util_bytes2num(tx_rx->rx_data, 4); + uint32_t ar = nfc_util_bytes2num(&tx_rx->rx_data[4], 4); + + crypto1_word(&emulator->crypto, nr, 1); + uint32_t cardRr = ar ^ crypto1_word(&emulator->crypto, 0, 0); + if(cardRr != prng_successor(nonce, 64)) { + FURI_LOG_T( + TAG, + "Wrong AUTH on block %u! %08lX != %08lX", + block, + cardRr, + prng_successor(nonce, 64)); + // Don't send NACK, as the tag doesn't send it + need_reset = true; + break; + } + + uint32_t ans = prng_successor(nonce, 96); + uint8_t response[4] = {}; + nfc_util_num2bytes(ans, 4, response); + crypto1_encrypt( + &emulator->crypto, + NULL, + response, + sizeof(response) * 8, + tx_rx->tx_data, + tx_rx->tx_parity); + tx_rx->tx_bits = sizeof(response) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + + is_encrypted = true; + continue; + } + + if(!is_encrypted) { + FURI_LOG_T(TAG, "Invalid command before auth session established: %02X", cmd); + need_nack = true; + break; + } + + // Mifare Classic commands always have block number after command + uint8_t block = plain_data[1]; + if(mf_classic_get_sector_by_block(block) != sector) { + // Don't allow access to sectors other than authorized + FURI_LOG_T( + TAG, + "Trying to access block %u from not authorized sector (command: %02X)", + block, + cmd); + need_nack = true; + break; + } + + switch(cmd) { + case MF_CLASSIC_READ_BLOCK_CMD: { + uint8_t block_data[MF_CLASSIC_BLOCK_SIZE + 2] = {}; + memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); + if(mf_classic_is_sector_trailer(block)) { + if(!mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionKeyARead)) { + memset(block_data, 0, 6); //-V1086 + } + if(!mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionKeyBRead)) { + memset(&block_data[10], 0, 6); + } + if(!mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionACRead)) { + memset(&block_data[6], 0, 4); + } + } else if( + !mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionDataRead) || + !mf_classic_is_block_read(&emulator->data, block)) { + need_nack = true; + break; + } + + nfca_append_crc16(block_data, 16); + + crypto1_encrypt( + &emulator->crypto, + NULL, + block_data, + sizeof(block_data) * 8, + tx_rx->tx_data, + tx_rx->tx_parity); + tx_rx->tx_bits = (MF_CLASSIC_BLOCK_SIZE + 2) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + break; + } + + case MF_CLASSIC_WRITE_BLOCK_CMD: { + // Send ACK + uint8_t ack = MF_CLASSIC_ACK_CMD; + crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + tx_rx->tx_bits = 4; + + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { + need_reset = true; + break; + } + + if(tx_rx->rx_bits != (MF_CLASSIC_BLOCK_SIZE + 2) * 8) { + need_reset = true; + break; + } + + crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); + uint8_t block_data[MF_CLASSIC_BLOCK_SIZE] = {}; + memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); + + if(!mf_classic_is_block_read(&emulator->data, block)) { + // Don't allow writing to the block for which we haven't read data yet + need_nack = true; + break; + } + + if(mf_classic_is_sector_trailer(block)) { + if(mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionKeyAWrite)) { + memcpy(block_data, plain_data, 6); //-V1086 + } + if(mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionKeyBWrite)) { + memcpy(&block_data[10], &plain_data[10], 6); + } + if(mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionACWrite)) { + memcpy(&block_data[6], &plain_data[6], 4); + } + } else { + if(mf_classic_is_allowed_access( + emulator, block, access_key, MfClassicActionDataWrite)) { + memcpy(block_data, plain_data, MF_CLASSIC_BLOCK_SIZE); + } else { + need_nack = true; + break; + } + } + + if(memcmp(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) != 0) { + memcpy(emulator->data.block[block].value, block_data, MF_CLASSIC_BLOCK_SIZE); + emulator->data_changed = true; + } + + // Send ACK + ack = MF_CLASSIC_ACK_CMD; + crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + tx_rx->tx_bits = 4; + break; + } + + case MF_CLASSIC_DECREMENT_CMD: + case MF_CLASSIC_INCREMENT_CMD: + case MF_CLASSIC_RESTORE_CMD: { + MfClassicAction action = (cmd == MF_CLASSIC_INCREMENT_CMD) ? MfClassicActionDataInc : + MfClassicActionDataDec; + + if(!mf_classic_is_allowed_access(emulator, block, access_key, action)) { + need_nack = true; + break; + } + + int32_t prev_value; + uint8_t addr; + if(!mf_classic_block_to_value(emulator->data.block[block].value, &prev_value, &addr)) { + need_nack = true; + break; + } + + // Send ACK + uint8_t ack = MF_CLASSIC_ACK_CMD; + crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + tx_rx->tx_bits = 4; + + if(!furi_hal_nfc_tx_rx(tx_rx, 300)) { + need_reset = true; + break; + } + + if(tx_rx->rx_bits != (sizeof(int32_t) + 2) * 8) { + need_reset = true; + break; + } + + crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); + int32_t value = *(int32_t*)&plain_data[0]; + if(value < 0) { + value = -value; + } + if(cmd == MF_CLASSIC_DECREMENT_CMD) { + value = -value; + } else if(cmd == MF_CLASSIC_RESTORE_CMD) { + value = 0; + } + + mf_classic_value_to_block(prev_value + value, addr, transfer_buf); + transfer_buf_valid = true; + // Commands do not ACK + tx_rx->tx_bits = 0; + break; + } + + case MF_CLASSIC_TRANSFER_CMD: { + if(!mf_classic_is_allowed_access(emulator, block, access_key, MfClassicActionDataDec)) { + need_nack = true; + break; + } + if(memcmp(transfer_buf, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE) != + 0) { + memcpy(emulator->data.block[block].value, transfer_buf, MF_CLASSIC_BLOCK_SIZE); + emulator->data_changed = true; + } + transfer_buf_valid = false; + + uint8_t ack = MF_CLASSIC_ACK_CMD; + crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + tx_rx->tx_bits = 4; + break; + } + + default: + FURI_LOG_T(TAG, "Unknown command: %02X", cmd); + need_nack = true; + break; + } + } + + if(need_nack && !need_reset) { + // Send NACK + uint8_t nack = transfer_buf_valid ? MF_CLASSIC_NACK_BUF_VALID_CMD : + MF_CLASSIC_NACK_BUF_INVALID_CMD; + if(is_encrypted) { + crypto1_encrypt(&emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity); + } else { + tx_rx->tx_data[0] = nack; + } + tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; + tx_rx->tx_bits = 4; + furi_hal_nfc_tx_rx(tx_rx, 300); + need_reset = true; + } + + return !need_reset; +} + +void mf_classic_halt(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto) { + furi_assert(tx_rx); + + uint8_t plain_data[4] = {NFCA_CMD_HALT, 0x00, 0x00, 0x00}; + + nfca_append_crc16(plain_data, 2); + if(crypto) { + crypto1_encrypt( + crypto, NULL, plain_data, sizeof(plain_data) * 8, tx_rx->tx_data, tx_rx->tx_parity); + } else { + memcpy(tx_rx->tx_data, plain_data, sizeof(plain_data)); + nfc_util_odd_parity(tx_rx->tx_data, tx_rx->tx_parity, sizeof(plain_data)); + } + + tx_rx->tx_bits = sizeof(plain_data) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; + furi_hal_nfc_tx_rx(tx_rx, 50); +} + +bool mf_classic_write_block( + FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, + uint8_t block_num, + MfClassicBlock* src_block) { + furi_assert(tx_rx); + furi_assert(crypto); + furi_assert(src_block); + + bool write_success = false; + uint8_t plain_data[MF_CLASSIC_BLOCK_SIZE + 2] = {}; + uint8_t resp; + + do { + // Send write command + plain_data[0] = MF_CLASSIC_WRITE_BLOCK_CMD; + plain_data[1] = block_num; + nfca_append_crc16(plain_data, 2); + crypto1_encrypt(crypto, NULL, plain_data, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_bits = 4 * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; + + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + if(tx_rx->rx_bits == 4) { + crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); + if(resp != 0x0A) { + FURI_LOG_D(TAG, "NACK received on write cmd: %02X", resp); + break; + } + } else { + FURI_LOG_D(TAG, "Not ACK received"); + break; + } + } else { + FURI_LOG_D(TAG, "Failed to send write cmd"); + break; + } + + // Send data + memcpy(plain_data, src_block->value, MF_CLASSIC_BLOCK_SIZE); + nfca_append_crc16(plain_data, MF_CLASSIC_BLOCK_SIZE); + crypto1_encrypt( + crypto, + NULL, + plain_data, + (MF_CLASSIC_BLOCK_SIZE + 2) * 8, + tx_rx->tx_data, + tx_rx->tx_parity); + tx_rx->tx_bits = (MF_CLASSIC_BLOCK_SIZE + 2) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + if(tx_rx->rx_bits == 4) { + crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); + if(resp != MF_CLASSIC_ACK_CMD) { + FURI_LOG_D(TAG, "NACK received on sending data"); + break; + } + } else { + FURI_LOG_D(TAG, "Not ACK received"); + break; + } + } else { + FURI_LOG_D(TAG, "Failed to send data"); + break; + } + + write_success = true; + } while(false); + + return write_success; +} + +bool mf_classic_auth_write_block( + FuriHalNfcTxRxContext* tx_rx, + MfClassicBlock* src_block, + uint8_t block_num, + MfClassicKey key_type, + uint64_t key) { + furi_assert(tx_rx); + furi_assert(src_block); + + Crypto1 crypto = {}; + bool write_success = false; + + do { + furi_hal_nfc_sleep(); + if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0)) { + FURI_LOG_D(TAG, "Auth fail"); + break; + } + + if(!mf_classic_write_block(tx_rx, &crypto, block_num, src_block)) { + FURI_LOG_D(TAG, "Write fail"); + break; + } + write_success = true; + + mf_classic_halt(tx_rx, &crypto); + } while(false); + + return write_success; +} + +bool mf_classic_transfer(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto, uint8_t block_num) { + furi_assert(tx_rx); + furi_assert(crypto); + + // Send transfer command + uint8_t plain_data[4] = {MF_CLASSIC_TRANSFER_CMD, block_num, 0, 0}; + uint8_t resp = 0; + bool transfer_success = false; + + nfca_append_crc16(plain_data, 2); + crypto1_encrypt( + crypto, NULL, plain_data, sizeof(plain_data) * 8, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_bits = sizeof(plain_data) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; + + do { + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + if(tx_rx->rx_bits == 4) { + crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); + if(resp != 0x0A) { + FURI_LOG_D(TAG, "NACK received on transfer cmd: %02X", resp); + break; + } + } else { + FURI_LOG_D(TAG, "Not ACK received"); + break; + } + } else { + FURI_LOG_D(TAG, "Failed to send transfer cmd"); + break; + } + + transfer_success = true; + } while(false); + + return transfer_success; +} + +bool mf_classic_value_cmd( + FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, + uint8_t block_num, + uint8_t cmd, + int32_t d_value) { + furi_assert(tx_rx); + furi_assert(crypto); + furi_assert( + cmd == MF_CLASSIC_INCREMENT_CMD || cmd == MF_CLASSIC_DECREMENT_CMD || + cmd == MF_CLASSIC_RESTORE_CMD); + furi_assert(d_value >= 0); + + uint8_t plain_data[sizeof(d_value) + 2] = {}; + uint8_t resp = 0; + bool success = false; + + do { + // Send cmd + plain_data[0] = cmd; + plain_data[1] = block_num; + nfca_append_crc16(plain_data, 2); + crypto1_encrypt(crypto, NULL, plain_data, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_bits = 4 * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; + + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + if(tx_rx->rx_bits == 4) { + crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); + if(resp != 0x0A) { + FURI_LOG_D(TAG, "NACK received on write cmd: %02X", resp); + break; + } + } else { + FURI_LOG_D(TAG, "Not ACK received"); + break; + } + } else { + FURI_LOG_D(TAG, "Failed to send write cmd"); + break; + } + + // Send data + memcpy(plain_data, &d_value, sizeof(d_value)); + nfca_append_crc16(plain_data, sizeof(d_value)); + crypto1_encrypt( + crypto, NULL, plain_data, (sizeof(d_value) + 2) * 8, tx_rx->tx_data, tx_rx->tx_parity); + tx_rx->tx_bits = (sizeof(d_value) + 2) * 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; + // inc, dec, restore do not ACK, but they do NACK + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + if(tx_rx->rx_bits == 4) { + crypto1_decrypt(crypto, tx_rx->rx_data, 4, &resp); + if(resp != 0x0A) { + FURI_LOG_D(TAG, "NACK received on transfer cmd: %02X", resp); + break; + } + } else { + FURI_LOG_D(TAG, "Not NACK received"); + break; + } + } + + success = true; + + } while(false); + + return success; +} + +bool mf_classic_value_cmd_full( + FuriHalNfcTxRxContext* tx_rx, + MfClassicBlock* src_block, + uint8_t block_num, + MfClassicKey key_type, + uint64_t key, + int32_t d_value) { + furi_assert(tx_rx); + furi_assert(src_block); + + Crypto1 crypto = {}; + uint8_t cmd; + bool success = false; + + if(d_value > 0) { + cmd = MF_CLASSIC_INCREMENT_CMD; + } else if(d_value < 0) { + cmd = MF_CLASSIC_DECREMENT_CMD; + d_value = -d_value; + } else { + cmd = MF_CLASSIC_RESTORE_CMD; + } + + do { + furi_hal_nfc_sleep(); + if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto, false, 0)) { + FURI_LOG_D(TAG, "Value cmd auth fail"); + break; + } + if(!mf_classic_value_cmd(tx_rx, &crypto, block_num, cmd, d_value)) { + FURI_LOG_D(TAG, "Value cmd inc/dec/res fail"); + break; + } + + if(!mf_classic_transfer(tx_rx, &crypto, block_num)) { + FURI_LOG_D(TAG, "Value cmd transfer fail"); + break; + } + + success = true; + + // Send Halt + mf_classic_halt(tx_rx, &crypto); + } while(false); + + return success; +} + +bool mf_classic_write_sector( + FuriHalNfcTxRxContext* tx_rx, + MfClassicData* dest_data, + MfClassicData* src_data, + uint8_t sec_num) { + furi_assert(tx_rx); + furi_assert(dest_data); + furi_assert(src_data); + + uint8_t first_block = mf_classic_get_first_block_num_of_sector(sec_num); + uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sec_num); + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(dest_data, sec_num); + bool key_a_found = mf_classic_is_key_found(dest_data, sec_num, MfClassicKeyA); + bool key_b_found = mf_classic_is_key_found(dest_data, sec_num, MfClassicKeyB); + + bool write_success = true; + for(size_t i = first_block; i < first_block + total_blocks; i++) { + // Compare blocks + if(memcmp(dest_data->block[i].value, src_data->block[i].value, MF_CLASSIC_BLOCK_SIZE) != + 0) { + if(mf_classic_is_value_block(dest_data, i)) { + bool key_a_inc_allowed = mf_classic_is_allowed_access_data_block( + dest_data, i, MfClassicKeyA, MfClassicActionDataInc); + bool key_b_inc_allowed = mf_classic_is_allowed_access_data_block( + dest_data, i, MfClassicKeyB, MfClassicActionDataInc); + bool key_a_dec_allowed = mf_classic_is_allowed_access_data_block( + dest_data, i, MfClassicKeyA, MfClassicActionDataDec); + bool key_b_dec_allowed = mf_classic_is_allowed_access_data_block( + dest_data, i, MfClassicKeyB, MfClassicActionDataDec); + + int32_t src_value, dst_value; + + mf_classic_block_to_value(src_data->block[i].value, &src_value, NULL); + mf_classic_block_to_value(dest_data->block[i].value, &dst_value, NULL); + + int32_t diff = src_value - dst_value; + + if(diff > 0) { + if(key_a_found && key_a_inc_allowed) { + FURI_LOG_I(TAG, "Incrementing block %d with key A by %ld", i, diff); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); + if(!mf_classic_value_cmd_full( + tx_rx, &src_data->block[i], i, MfClassicKeyA, key, diff)) { + FURI_LOG_E(TAG, "Failed to increment block %d", i); + write_success = false; + break; + } + } else if(key_b_found && key_b_inc_allowed) { + FURI_LOG_I(TAG, "Incrementing block %d with key B by %ld", i, diff); + uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6); + if(!mf_classic_value_cmd_full( + tx_rx, &src_data->block[i], i, MfClassicKeyB, key, diff)) { + FURI_LOG_E(TAG, "Failed to increment block %d", i); + write_success = false; + break; + } + } else { + FURI_LOG_E(TAG, "Failed to increment block %d", i); + } + } else if(diff < 0) { + if(key_a_found && key_a_dec_allowed) { + FURI_LOG_I(TAG, "Decrementing block %d with key A by %ld", i, -diff); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); + if(!mf_classic_value_cmd_full( + tx_rx, &src_data->block[i], i, MfClassicKeyA, key, diff)) { + FURI_LOG_E(TAG, "Failed to decrement block %d", i); + write_success = false; + break; + } + } else if(key_b_found && key_b_dec_allowed) { + FURI_LOG_I(TAG, "Decrementing block %d with key B by %ld", i, diff); + uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6); + if(!mf_classic_value_cmd_full( + tx_rx, &src_data->block[i], i, MfClassicKeyB, key, diff)) { + FURI_LOG_E(TAG, "Failed to decrement block %d", i); + write_success = false; + break; + } + } else { + FURI_LOG_E(TAG, "Failed to decrement block %d", i); + } + } else { + FURI_LOG_E(TAG, "Value block %d address changed, cannot write it", i); + } + } else { + bool key_a_write_allowed = mf_classic_is_allowed_access_data_block( + dest_data, i, MfClassicKeyA, MfClassicActionDataWrite); + bool key_b_write_allowed = mf_classic_is_allowed_access_data_block( + dest_data, i, MfClassicKeyB, MfClassicActionDataWrite); + + if(key_a_found && key_a_write_allowed) { + FURI_LOG_I(TAG, "Writing block %d with key A", i); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); + if(!mf_classic_auth_write_block( + tx_rx, &src_data->block[i], i, MfClassicKeyA, key)) { + FURI_LOG_E(TAG, "Failed to write block %d", i); + write_success = false; + break; + } + } else if(key_b_found && key_b_write_allowed) { + FURI_LOG_I(TAG, "Writing block %d with key A", i); + uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6); + if(!mf_classic_auth_write_block( + tx_rx, &src_data->block[i], i, MfClassicKeyB, key)) { + FURI_LOG_E(TAG, "Failed to write block %d", i); + write_success = false; + break; + } + } else { + FURI_LOG_E(TAG, "Failed to find key with write access"); + write_success = false; + break; + } + } + } else { + FURI_LOG_D(TAG, "Blocks %d are equal", i); + } + } + + return write_success; +} diff --git a/lib/nfc/protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h new file mode 100644 index 0000000..7efe81b --- /dev/null +++ b/lib/nfc/protocols/mifare_classic.h @@ -0,0 +1,251 @@ +#pragma once + +#include + +#include "crypto1.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define MF_CLASSIC_BLOCK_SIZE (16) +#define MF_CLASSIC_TOTAL_BLOCKS_MAX (256) +#define MF_MINI_TOTAL_SECTORS_NUM (5) +#define MF_CLASSIC_1K_TOTAL_SECTORS_NUM (16) +#define MF_CLASSIC_4K_TOTAL_SECTORS_NUM (40) + +#define MF_CLASSIC_SECTORS_MAX (40) +#define MF_CLASSIC_BLOCKS_IN_SECTOR_MAX (16) + +#define MF_CLASSIC_NO_KEY (0xFFFFFFFFFFFFFFFF) +#define MF_CLASSIC_MAX_DATA_SIZE (16) +#define MF_CLASSIC_KEY_SIZE (6) +#define MF_CLASSIC_ACCESS_BYTES_SIZE (4) + +typedef enum { + MfClassicType1k, + MfClassicType4k, + MfClassicTypeMini, +} MfClassicType; + +typedef enum { + MfClassicKeyA, + MfClassicKeyB, +} MfClassicKey; + +typedef enum { + MfClassicActionDataRead, + MfClassicActionDataWrite, + MfClassicActionDataInc, + MfClassicActionDataDec, + + MfClassicActionKeyARead, + MfClassicActionKeyAWrite, + MfClassicActionKeyBRead, + MfClassicActionKeyBWrite, + MfClassicActionACRead, + MfClassicActionACWrite, +} MfClassicAction; + +typedef struct { + uint8_t value[MF_CLASSIC_BLOCK_SIZE]; +} MfClassicBlock; + +typedef struct { + uint8_t key_a[MF_CLASSIC_KEY_SIZE]; + uint8_t access_bits[MF_CLASSIC_ACCESS_BYTES_SIZE]; + uint8_t key_b[MF_CLASSIC_KEY_SIZE]; +} MfClassicSectorTrailer; + +typedef struct { + uint8_t total_blocks; + MfClassicBlock block[MF_CLASSIC_BLOCKS_IN_SECTOR_MAX]; +} MfClassicSector; + +typedef struct { + MfClassicType type; + uint32_t block_read_mask[MF_CLASSIC_TOTAL_BLOCKS_MAX / 32]; + uint64_t key_a_mask; + uint64_t key_b_mask; + MfClassicBlock block[MF_CLASSIC_TOTAL_BLOCKS_MAX]; +} MfClassicData; + +typedef struct { + uint8_t sector; + uint64_t key_a; + uint64_t key_b; +} MfClassicAuthContext; + +typedef struct { + uint8_t sector_num; + uint64_t key_a; + uint64_t key_b; +} MfClassicSectorReader; + +typedef struct { + MfClassicType type; + Crypto1 crypto; + uint8_t sectors_to_read; + MfClassicSectorReader sector_reader[MF_CLASSIC_SECTORS_MAX]; +} MfClassicReader; + +typedef struct { + uint32_t cuid; + Crypto1 crypto; + MfClassicData data; + bool data_changed; +} MfClassicEmulator; + +const char* mf_classic_get_type_str(MfClassicType type); + +bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); + +MfClassicType mf_classic_get_classic_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); + +uint8_t mf_classic_get_total_sectors_num(MfClassicType type); + +uint16_t mf_classic_get_total_block_num(MfClassicType type); + +uint8_t mf_classic_get_sector_trailer_block_num_by_sector(uint8_t sector); + +bool mf_classic_is_sector_trailer(uint8_t block); + +uint8_t mf_classic_get_sector_by_block(uint8_t block); + +bool mf_classic_is_allowed_access_sector_trailer( + MfClassicData* data, + uint8_t block_num, + MfClassicKey key, + MfClassicAction action); + +bool mf_classic_is_allowed_access_data_block( + MfClassicData* data, + uint8_t block_num, + MfClassicKey key, + MfClassicAction action); + +bool mf_classic_is_value_block(MfClassicData* data, uint8_t block_num); + +bool mf_classic_block_to_value(const uint8_t* block, int32_t* value, uint8_t* addr); + +void mf_classic_value_to_block(int32_t value, uint8_t addr, uint8_t* block); + +bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type); + +void mf_classic_set_key_found( + MfClassicData* data, + uint8_t sector_num, + MfClassicKey key_type, + uint64_t key); + +void mf_classic_set_key_not_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type); + +bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num); + +void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data); + +bool mf_classic_is_sector_data_read(MfClassicData* data, uint8_t sector_num); + +void mf_classic_set_sector_data_not_read(MfClassicData* data); + +bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num); + +bool mf_classic_is_card_read(MfClassicData* data); + +void mf_classic_get_read_sectors_and_keys( + MfClassicData* data, + uint8_t* sectors_read, + uint8_t* keys_found); + +MfClassicSectorTrailer* + mf_classic_get_sector_trailer_by_sector(MfClassicData* data, uint8_t sector); + +void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector); + +bool mf_classic_authenticate( + FuriHalNfcTxRxContext* tx_rx, + uint8_t block_num, + uint64_t key, + MfClassicKey key_type); + +bool mf_classic_authenticate_skip_activate( + FuriHalNfcTxRxContext* tx_rx, + uint8_t block_num, + uint64_t key, + MfClassicKey key_type, + bool skip_activate, + uint32_t cuid); + +bool mf_classic_auth_attempt( + FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, + MfClassicAuthContext* auth_ctx, + uint64_t key); + +void mf_classic_reader_add_sector( + MfClassicReader* reader, + uint8_t sector, + uint64_t key_a, + uint64_t key_b); + +bool mf_classic_read_block( + FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, + uint8_t block_num, + MfClassicBlock* block); + +void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, uint8_t sec_num); + +uint8_t mf_classic_read_card( + FuriHalNfcTxRxContext* tx_rx, + MfClassicReader* reader, + MfClassicData* data); + +uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data); + +bool mf_classic_emulator( + MfClassicEmulator* emulator, + FuriHalNfcTxRxContext* tx_rx, + bool is_reader_analyzer); + +void mf_classic_halt(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto); + +bool mf_classic_write_block( + FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, + uint8_t block_num, + MfClassicBlock* src_block); + +bool mf_classic_auth_write_block( + FuriHalNfcTxRxContext* tx_rx, + MfClassicBlock* src_block, + uint8_t block_num, + MfClassicKey key_type, + uint64_t key); + +bool mf_classic_transfer(FuriHalNfcTxRxContext* tx_rx, Crypto1* crypto, uint8_t block_num); + +bool mf_classic_value_cmd( + FuriHalNfcTxRxContext* tx_rx, + Crypto1* crypto, + uint8_t block_num, + uint8_t cmd, + int32_t d_value); + +bool mf_classic_value_cmd_full( + FuriHalNfcTxRxContext* tx_rx, + MfClassicBlock* src_block, + uint8_t block_num, + MfClassicKey key_type, + uint64_t key, + int32_t d_value); + +bool mf_classic_write_sector( + FuriHalNfcTxRxContext* tx_rx, + MfClassicData* dest_data, + MfClassicData* src_data, + uint8_t sec_num); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mifare_common.c b/lib/nfc/protocols/mifare_common.c new file mode 100644 index 0000000..90b57e1 --- /dev/null +++ b/lib/nfc/protocols/mifare_common.c @@ -0,0 +1,18 @@ +#include "mifare_common.h" + +MifareType mifare_common_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { + MifareType type = MifareTypeUnknown; + + if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) { + type = MifareTypeUltralight; + } else if( + ((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) || + ((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) || + ((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01))) { + type = MifareTypeClassic; + } else if(ATQA0 == 0x44 && ATQA1 == 0x03 && SAK == 0x20) { + type = MifareTypeDesfire; + } + + return type; +} diff --git a/lib/nfc/protocols/mifare_common.h b/lib/nfc/protocols/mifare_common.h new file mode 100644 index 0000000..2b694d9 --- /dev/null +++ b/lib/nfc/protocols/mifare_common.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +typedef enum { + MifareTypeUnknown, + MifareTypeUltralight, + MifareTypeClassic, + MifareTypeDesfire, +} MifareType; + +MifareType mifare_common_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); diff --git a/lib/nfc/protocols/mifare_desfire.c b/lib/nfc/protocols/mifare_desfire.c new file mode 100644 index 0000000..e0ead73 --- /dev/null +++ b/lib/nfc/protocols/mifare_desfire.c @@ -0,0 +1,665 @@ +#include "mifare_desfire.h" +#include +#include + +#define TAG "MifareDESFire" + +void mf_df_clear(MifareDesfireData* data) { + free(data->free_memory); + if(data->master_key_settings) { + MifareDesfireKeyVersion* key_version = data->master_key_settings->key_version_head; + while(key_version) { + MifareDesfireKeyVersion* next_key_version = key_version->next; + free(key_version); + key_version = next_key_version; + } + } + free(data->master_key_settings); + MifareDesfireApplication* app = data->app_head; + while(app) { + MifareDesfireApplication* next_app = app->next; + if(app->key_settings) { + MifareDesfireKeyVersion* key_version = app->key_settings->key_version_head; + while(key_version) { + MifareDesfireKeyVersion* next_key_version = key_version->next; + free(key_version); + key_version = next_key_version; + } + } + free(app->key_settings); + MifareDesfireFile* file = app->file_head; + while(file) { + MifareDesfireFile* next_file = file->next; + free(file->contents); + free(file); + file = next_file; + } + free(app); + app = next_app; + } + data->free_memory = NULL; + data->master_key_settings = NULL; + data->app_head = NULL; +} + +MifareDesfireApplication* mf_df_get_application(MifareDesfireData* data, const uint8_t (*aid)[3]) { + if(!data) { + return NULL; + } + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + if(memcmp(aid, app->id, 3) == 0) { + return app; + } + } + return NULL; +} + +MifareDesfireFile* mf_df_get_file(MifareDesfireApplication* app, uint8_t id) { + if(!app) { + return NULL; + } + for(MifareDesfireFile* file = app->file_head; file; file = file->next) { + if(file->id == id) { + return file; + } + } + return NULL; +} + +void mf_df_cat_data(MifareDesfireData* data, FuriString* out) { + mf_df_cat_card_info(data, out); + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + mf_df_cat_application(app, out); + } +} + +void mf_df_cat_card_info(MifareDesfireData* data, FuriString* out) { + mf_df_cat_version(&data->version, out); + if(data->free_memory) { + mf_df_cat_free_mem(data->free_memory, out); + } + if(data->master_key_settings) { + mf_df_cat_key_settings(data->master_key_settings, out); + } +} + +void mf_df_cat_version(MifareDesfireVersion* version, FuriString* out) { + furi_string_cat_printf( + out, + "%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + version->uid[0], + version->uid[1], + version->uid[2], + version->uid[3], + version->uid[4], + version->uid[5], + version->uid[6]); + furi_string_cat_printf( + out, + "hw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + version->hw_vendor, + version->hw_type, + version->hw_subtype, + version->hw_major, + version->hw_minor, + version->hw_storage, + version->hw_proto); + furi_string_cat_printf( + out, + "sw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + version->sw_vendor, + version->sw_type, + version->sw_subtype, + version->sw_major, + version->sw_minor, + version->sw_storage, + version->sw_proto); + furi_string_cat_printf( + out, + "batch %02x:%02x:%02x:%02x:%02x\n" + "week %d year %d\n", + version->batch[0], + version->batch[1], + version->batch[2], + version->batch[3], + version->batch[4], + version->prod_week, + version->prod_year); +} + +void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, FuriString* out) { + furi_string_cat_printf(out, "freeMem %lu\n", free_mem->bytes); +} + +void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, FuriString* out) { + furi_string_cat_printf(out, "changeKeyID %d\n", ks->change_key_id); + furi_string_cat_printf(out, "configChangeable %d\n", ks->config_changeable); + furi_string_cat_printf(out, "freeCreateDelete %d\n", ks->free_create_delete); + furi_string_cat_printf(out, "freeDirectoryList %d\n", ks->free_directory_list); + furi_string_cat_printf(out, "masterChangeable %d\n", ks->master_key_changeable); + if(ks->flags) { + furi_string_cat_printf(out, "flags %d\n", ks->flags); + } + furi_string_cat_printf(out, "maxKeys %d\n", ks->max_keys); + for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { + furi_string_cat_printf(out, "key %d version %d\n", kv->id, kv->version); + } +} + +void mf_df_cat_application_info(MifareDesfireApplication* app, FuriString* out) { + furi_string_cat_printf(out, "Application %02x%02x%02x\n", app->id[0], app->id[1], app->id[2]); + if(app->key_settings) { + mf_df_cat_key_settings(app->key_settings, out); + } +} + +void mf_df_cat_application(MifareDesfireApplication* app, FuriString* out) { + mf_df_cat_application_info(app, out); + for(MifareDesfireFile* file = app->file_head; file; file = file->next) { + mf_df_cat_file(file, out); + } +} + +void mf_df_cat_file(MifareDesfireFile* file, FuriString* out) { + char* type = "unknown"; + switch(file->type) { + case MifareDesfireFileTypeStandard: + type = "standard"; + break; + case MifareDesfireFileTypeBackup: + type = "backup"; + break; + case MifareDesfireFileTypeValue: + type = "value"; + break; + case MifareDesfireFileTypeLinearRecord: + type = "linear"; + break; + case MifareDesfireFileTypeCyclicRecord: + type = "cyclic"; + break; + } + char* comm = "unknown"; + switch(file->comm) { + case MifareDesfireFileCommunicationSettingsPlaintext: + comm = "plain"; + break; + case MifareDesfireFileCommunicationSettingsAuthenticated: + comm = "auth"; + break; + case MifareDesfireFileCommunicationSettingsEnciphered: + comm = "enciphered"; + break; + } + furi_string_cat_printf(out, "File %d\n", file->id); + furi_string_cat_printf(out, "%s %s\n", type, comm); + furi_string_cat_printf( + out, + "r %d w %d rw %d c %d\n", + file->access_rights >> 12 & 0xF, + file->access_rights >> 8 & 0xF, + file->access_rights >> 4 & 0xF, + file->access_rights & 0xF); + uint16_t size = 0; + uint16_t num = 1; + switch(file->type) { + case MifareDesfireFileTypeStandard: + case MifareDesfireFileTypeBackup: + size = file->settings.data.size; + furi_string_cat_printf(out, "size %d\n", size); + break; + case MifareDesfireFileTypeValue: + size = 4; + furi_string_cat_printf( + out, "lo %lu hi %lu\n", file->settings.value.lo_limit, file->settings.value.hi_limit); + furi_string_cat_printf( + out, + "limit %lu enabled %d\n", + file->settings.value.limited_credit_value, + file->settings.value.limited_credit_enabled); + break; + case MifareDesfireFileTypeLinearRecord: + case MifareDesfireFileTypeCyclicRecord: + size = file->settings.record.size; + num = file->settings.record.cur; + furi_string_cat_printf(out, "size %d\n", size); + furi_string_cat_printf(out, "num %d max %lu\n", num, file->settings.record.max); + break; + } + uint8_t* data = file->contents; + if(data) { + for(int rec = 0; rec < num; rec++) { + furi_string_cat_printf(out, "record %d\n", rec); + for(int ch = 0; ch < size; ch += 4) { + furi_string_cat_printf(out, "%03x|", ch); + for(int i = 0; i < 4; i++) { + if(ch + i < size) { + furi_string_cat_printf(out, "%02x ", data[rec * size + ch + i]); + } else { + furi_string_cat_printf(out, " "); + } + } + for(int i = 0; i < 4 && ch + i < size; i++) { + const size_t data_index = rec * size + ch + i; + if(isprint(data[data_index])) { + furi_string_cat_printf(out, "%c", data[data_index]); + } else { + furi_string_cat_printf(out, "."); + } + } + furi_string_cat_printf(out, "\n"); + } + furi_string_cat_printf(out, " \n"); + } + } +} + +bool mf_df_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { + return ATQA0 == 0x44 && ATQA1 == 0x03 && SAK == 0x20; +} + +uint16_t mf_df_prepare_get_version(uint8_t* dest) { + dest[0] = MF_DF_GET_VERSION; + return 1; +} + +bool mf_df_parse_get_version_response(uint8_t* buf, uint16_t len, MifareDesfireVersion* out) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + if(len < sizeof(MifareDesfireVersion)) { + return false; + } + memcpy(out, buf, sizeof(MifareDesfireVersion)); + return true; +} + +uint16_t mf_df_prepare_get_free_memory(uint8_t* dest) { + dest[0] = MF_DF_GET_FREE_MEMORY; + return 1; +} + +bool mf_df_parse_get_free_memory_response(uint8_t* buf, uint16_t len, MifareDesfireFreeMemory* out) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + if(len != 3) { + return false; + } + out->bytes = buf[0] | (buf[1] << 8) | (buf[2] << 16); + return true; +} + +uint16_t mf_df_prepare_get_key_settings(uint8_t* dest) { + dest[0] = MF_DF_GET_KEY_SETTINGS; + return 1; +} + +bool mf_df_parse_get_key_settings_response( + uint8_t* buf, + uint16_t len, + MifareDesfireKeySettings* out) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + if(len < 2) { + return false; + } + out->change_key_id = buf[0] >> 4; + out->config_changeable = (buf[0] & 0x8) != 0; + out->free_create_delete = (buf[0] & 0x4) != 0; + out->free_directory_list = (buf[0] & 0x2) != 0; + out->master_key_changeable = (buf[0] & 0x1) != 0; + out->flags = buf[1] >> 4; + out->max_keys = buf[1] & 0xF; + return true; +} + +uint16_t mf_df_prepare_get_key_version(uint8_t* dest, uint8_t key_id) { + dest[0] = MF_DF_GET_KEY_VERSION; + dest[1] = key_id; + return 2; +} + +bool mf_df_parse_get_key_version_response(uint8_t* buf, uint16_t len, MifareDesfireKeyVersion* out) { + if(len != 2 || *buf) { + return false; + } + out->version = buf[1]; + return true; +} + +uint16_t mf_df_prepare_get_application_ids(uint8_t* dest) { + dest[0] = MF_DF_GET_APPLICATION_IDS; + return 1; +} + +bool mf_df_parse_get_application_ids_response( + uint8_t* buf, + uint16_t len, + MifareDesfireApplication** app_head) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + if(len % 3 != 0) { + return false; + } + while(len) { + MifareDesfireApplication* app = malloc(sizeof(MifareDesfireApplication)); + memset(app, 0, sizeof(MifareDesfireApplication)); + memcpy(app->id, buf, 3); + len -= 3; + buf += 3; + *app_head = app; + app_head = &app->next; + } + return true; +} + +uint16_t mf_df_prepare_select_application(uint8_t* dest, uint8_t id[3]) { + dest[0] = MF_DF_SELECT_APPLICATION; + dest[1] = id[0]; + dest[2] = id[1]; + dest[3] = id[2]; + return 4; +} + +bool mf_df_parse_select_application_response(uint8_t* buf, uint16_t len) { + return len == 1 && !*buf; +} + +uint16_t mf_df_prepare_get_file_ids(uint8_t* dest) { + dest[0] = MF_DF_GET_FILE_IDS; + return 1; +} + +bool mf_df_parse_get_file_ids_response(uint8_t* buf, uint16_t len, MifareDesfireFile** file_head) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + while(len) { + MifareDesfireFile* file = malloc(sizeof(MifareDesfireFile)); + memset(file, 0, sizeof(MifareDesfireFile)); + file->id = *buf; + len--; + buf++; + *file_head = file; + file_head = &file->next; + } + return true; +} + +uint16_t mf_df_prepare_get_file_settings(uint8_t* dest, uint8_t file_id) { + dest[0] = MF_DF_GET_FILE_SETTINGS; + dest[1] = file_id; + return 2; +} + +bool mf_df_parse_get_file_settings_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out) { + if(len < 5 || *buf) { + return false; + } + len--; + buf++; + out->type = buf[0]; + out->comm = buf[1]; + out->access_rights = buf[2] | (buf[3] << 8); + switch(out->type) { + case MifareDesfireFileTypeStandard: + case MifareDesfireFileTypeBackup: + if(len != 7) { + return false; + } + out->settings.data.size = buf[4] | (buf[5] << 8) | (buf[6] << 16); + break; + case MifareDesfireFileTypeValue: + if(len != 17) { + return false; + } + out->settings.value.lo_limit = buf[4] | (buf[5] << 8) | (buf[6] << 16) | (buf[7] << 24); + out->settings.value.hi_limit = buf[8] | (buf[9] << 8) | (buf[10] << 16) | (buf[11] << 24); + out->settings.value.limited_credit_value = buf[12] | (buf[13] << 8) | (buf[14] << 16) | + (buf[15] << 24); + out->settings.value.limited_credit_enabled = buf[16]; + break; + case MifareDesfireFileTypeLinearRecord: + case MifareDesfireFileTypeCyclicRecord: + if(len != 13) { + return false; + } + out->settings.record.size = buf[4] | (buf[5] << 8) | (buf[6] << 16); + out->settings.record.max = buf[7] | (buf[8] << 8) | (buf[9] << 16); + out->settings.record.cur = buf[10] | (buf[11] << 8) | (buf[12] << 16); + break; + default: + return false; + } + return true; +} + +uint16_t mf_df_prepare_read_data(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len) { + dest[0] = MF_DF_READ_DATA; + dest[1] = file_id; + dest[2] = offset; + dest[3] = offset >> 8; + dest[4] = offset >> 16; + dest[5] = len; + dest[6] = len >> 8; + dest[7] = len >> 16; + return 8; +} + +uint16_t mf_df_prepare_get_value(uint8_t* dest, uint8_t file_id) { + dest[0] = MF_DF_GET_VALUE; + dest[1] = file_id; + return 2; +} + +uint16_t + mf_df_prepare_read_records(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len) { + dest[0] = MF_DF_READ_RECORDS; + dest[1] = file_id; + dest[2] = offset; + dest[3] = offset >> 8; + dest[4] = offset >> 16; + dest[5] = len; + dest[6] = len >> 8; + dest[7] = len >> 16; + return 8; +} + +bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out) { + if(len < 1 || *buf) { + return false; + } + len--; + buf++; + out->contents = malloc(len); + memcpy(out->contents, buf, len); + return true; +} + +bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data) { + furi_assert(tx_rx); + furi_assert(data); + + bool card_read = false; + do { + // Get version + tx_rx->tx_bits = 8 * mf_df_prepare_get_version(tx_rx->tx_data); + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting version"); + break; + } + if(!mf_df_parse_get_version_response(tx_rx->rx_data, tx_rx->rx_bits / 8, &data->version)) { + FURI_LOG_W(TAG, "Bad DESFire GET_VERSION responce"); + } + + // Get free memory + tx_rx->tx_bits = 8 * mf_df_prepare_get_free_memory(tx_rx->tx_data); + if(furi_hal_nfc_tx_rx_full(tx_rx)) { + data->free_memory = malloc(sizeof(MifareDesfireFreeMemory)); + if(!mf_df_parse_get_free_memory_response( + tx_rx->rx_data, tx_rx->rx_bits / 8, data->free_memory)) { + FURI_LOG_D(TAG, "Bad DESFire GET_FREE_MEMORY response (normal for pre-EV1 cards)"); + free(data->free_memory); + data->free_memory = NULL; + } + } + + // Get key settings + tx_rx->tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx->tx_data); + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_D(TAG, "Bad exchange getting key settings"); + } else { + data->master_key_settings = malloc(sizeof(MifareDesfireKeySettings)); + if(!mf_df_parse_get_key_settings_response( + tx_rx->rx_data, tx_rx->rx_bits / 8, data->master_key_settings)) { + FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response"); + free(data->master_key_settings); + data->master_key_settings = NULL; + } else { + MifareDesfireKeyVersion** key_version_head = + &data->master_key_settings->key_version_head; + for(uint8_t key_id = 0; key_id < data->master_key_settings->max_keys; key_id++) { + tx_rx->tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx->tx_data, key_id); + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting key version"); + continue; + } + MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion)); + memset(key_version, 0, sizeof(MifareDesfireKeyVersion)); + key_version->id = key_id; + if(!mf_df_parse_get_key_version_response( + tx_rx->rx_data, tx_rx->rx_bits / 8, key_version)) { + FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response"); + free(key_version); + continue; + } + *key_version_head = key_version; + key_version_head = &key_version->next; + } + } + } + + // Get application IDs + tx_rx->tx_bits = 8 * mf_df_prepare_get_application_ids(tx_rx->tx_data); + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting application IDs"); + break; + } else { + if(!mf_df_parse_get_application_ids_response( + tx_rx->rx_data, tx_rx->rx_bits / 8, &data->app_head)) { + FURI_LOG_W(TAG, "Bad DESFire GET_APPLICATION_IDS response"); + break; + } + } + + for(MifareDesfireApplication* app = data->app_head; app; app = app->next) { + tx_rx->tx_bits = 8 * mf_df_prepare_select_application(tx_rx->tx_data, app->id); + if(!furi_hal_nfc_tx_rx_full(tx_rx) || + !mf_df_parse_select_application_response( + tx_rx->rx_data, tx_rx->rx_bits / 8)) { //-V1051 + FURI_LOG_W(TAG, "Bad exchange selecting application"); + continue; + } + tx_rx->tx_bits = 8 * mf_df_prepare_get_key_settings(tx_rx->tx_data); + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting key settings"); + } else { + app->key_settings = malloc(sizeof(MifareDesfireKeySettings)); + memset(app->key_settings, 0, sizeof(MifareDesfireKeySettings)); + if(!mf_df_parse_get_key_settings_response( + tx_rx->rx_data, tx_rx->rx_bits / 8, app->key_settings)) { + FURI_LOG_W(TAG, "Bad DESFire GET_KEY_SETTINGS response"); + free(app->key_settings); + app->key_settings = NULL; + continue; + } + + MifareDesfireKeyVersion** key_version_head = &app->key_settings->key_version_head; + for(uint8_t key_id = 0; key_id < app->key_settings->max_keys; key_id++) { + tx_rx->tx_bits = 8 * mf_df_prepare_get_key_version(tx_rx->tx_data, key_id); + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting key version"); + continue; + } + MifareDesfireKeyVersion* key_version = malloc(sizeof(MifareDesfireKeyVersion)); + memset(key_version, 0, sizeof(MifareDesfireKeyVersion)); + key_version->id = key_id; + if(!mf_df_parse_get_key_version_response( + tx_rx->rx_data, tx_rx->rx_bits / 8, key_version)) { + FURI_LOG_W(TAG, "Bad DESFire GET_KEY_VERSION response"); + free(key_version); + continue; + } + *key_version_head = key_version; + key_version_head = &key_version->next; + } + } + + tx_rx->tx_bits = 8 * mf_df_prepare_get_file_ids(tx_rx->tx_data); + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting file IDs"); + } else { + if(!mf_df_parse_get_file_ids_response( + tx_rx->rx_data, tx_rx->rx_bits / 8, &app->file_head)) { + FURI_LOG_W(TAG, "Bad DESFire GET_FILE_IDS response"); + } + } + + for(MifareDesfireFile* file = app->file_head; file; file = file->next) { + tx_rx->tx_bits = 8 * mf_df_prepare_get_file_settings(tx_rx->tx_data, file->id); + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange getting file settings"); + continue; + } + if(!mf_df_parse_get_file_settings_response( + tx_rx->rx_data, tx_rx->rx_bits / 8, file)) { + FURI_LOG_W(TAG, "Bad DESFire GET_FILE_SETTINGS response"); + continue; + } + switch(file->type) { + case MifareDesfireFileTypeStandard: + case MifareDesfireFileTypeBackup: + tx_rx->tx_bits = 8 * mf_df_prepare_read_data(tx_rx->tx_data, file->id, 0, 0); + break; + case MifareDesfireFileTypeValue: + tx_rx->tx_bits = 8 * mf_df_prepare_get_value(tx_rx->tx_data, file->id); + break; + case MifareDesfireFileTypeLinearRecord: + case MifareDesfireFileTypeCyclicRecord: + tx_rx->tx_bits = + 8 * mf_df_prepare_read_records(tx_rx->tx_data, file->id, 0, 0); + break; + } + if(!furi_hal_nfc_tx_rx_full(tx_rx)) { + FURI_LOG_W(TAG, "Bad exchange reading file %d", file->id); + continue; + } + if(!mf_df_parse_read_data_response(tx_rx->rx_data, tx_rx->rx_bits / 8, file)) { + FURI_LOG_W(TAG, "Bad response reading file %d", file->id); + continue; + } + } + } + + card_read = true; + } while(false); + + return card_read; +} diff --git a/lib/nfc/protocols/mifare_desfire.h b/lib/nfc/protocols/mifare_desfire.h new file mode 100644 index 0000000..8faa98e --- /dev/null +++ b/lib/nfc/protocols/mifare_desfire.h @@ -0,0 +1,179 @@ +#pragma once + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MF_DF_GET_VERSION (0x60) +#define MF_DF_GET_FREE_MEMORY (0x6E) +#define MF_DF_GET_KEY_SETTINGS (0x45) +#define MF_DF_GET_KEY_VERSION (0x64) +#define MF_DF_GET_APPLICATION_IDS (0x6A) +#define MF_DF_SELECT_APPLICATION (0x5A) +#define MF_DF_GET_FILE_IDS (0x6F) +#define MF_DF_GET_FILE_SETTINGS (0xF5) + +#define MF_DF_READ_DATA (0xBD) +#define MF_DF_GET_VALUE (0x6C) +#define MF_DF_READ_RECORDS (0xBB) + +typedef struct { + uint8_t hw_vendor; + uint8_t hw_type; + uint8_t hw_subtype; + uint8_t hw_major; + uint8_t hw_minor; + uint8_t hw_storage; + uint8_t hw_proto; + + uint8_t sw_vendor; + uint8_t sw_type; + uint8_t sw_subtype; + uint8_t sw_major; + uint8_t sw_minor; + uint8_t sw_storage; + uint8_t sw_proto; + + uint8_t uid[7]; + uint8_t batch[5]; + uint8_t prod_week; + uint8_t prod_year; +} MifareDesfireVersion; + +typedef struct { + uint32_t bytes; +} MifareDesfireFreeMemory; // EV1+ only + +typedef struct MifareDesfireKeyVersion { + uint8_t id; + uint8_t version; + struct MifareDesfireKeyVersion* next; +} MifareDesfireKeyVersion; + +typedef struct { + uint8_t change_key_id; + bool config_changeable; + bool free_create_delete; + bool free_directory_list; + bool master_key_changeable; + uint8_t flags; + uint8_t max_keys; + MifareDesfireKeyVersion* key_version_head; +} MifareDesfireKeySettings; + +typedef enum { + MifareDesfireFileTypeStandard = 0, + MifareDesfireFileTypeBackup = 1, + MifareDesfireFileTypeValue = 2, + MifareDesfireFileTypeLinearRecord = 3, + MifareDesfireFileTypeCyclicRecord = 4, +} MifareDesfireFileType; + +typedef enum { + MifareDesfireFileCommunicationSettingsPlaintext = 0, + MifareDesfireFileCommunicationSettingsAuthenticated = 1, + MifareDesfireFileCommunicationSettingsEnciphered = 3, +} MifareDesfireFileCommunicationSettings; + +typedef struct MifareDesfireFile { + uint8_t id; + MifareDesfireFileType type; + MifareDesfireFileCommunicationSettings comm; + uint16_t access_rights; + union { + struct { + uint32_t size; + } data; + struct { + uint32_t lo_limit; + uint32_t hi_limit; + uint32_t limited_credit_value; + bool limited_credit_enabled; + } value; + struct { + uint32_t size; + uint32_t max; + uint32_t cur; + } record; + } settings; + uint8_t* contents; + + struct MifareDesfireFile* next; +} MifareDesfireFile; + +typedef struct MifareDesfireApplication { + uint8_t id[3]; + MifareDesfireKeySettings* key_settings; + MifareDesfireFile* file_head; + + struct MifareDesfireApplication* next; +} MifareDesfireApplication; + +typedef struct { + MifareDesfireVersion version; + MifareDesfireFreeMemory* free_memory; + MifareDesfireKeySettings* master_key_settings; + MifareDesfireApplication* app_head; +} MifareDesfireData; + +void mf_df_clear(MifareDesfireData* data); + +void mf_df_cat_data(MifareDesfireData* data, FuriString* out); +void mf_df_cat_card_info(MifareDesfireData* data, FuriString* out); +void mf_df_cat_version(MifareDesfireVersion* version, FuriString* out); +void mf_df_cat_free_mem(MifareDesfireFreeMemory* free_mem, FuriString* out); +void mf_df_cat_key_settings(MifareDesfireKeySettings* ks, FuriString* out); +void mf_df_cat_application_info(MifareDesfireApplication* app, FuriString* out); +void mf_df_cat_application(MifareDesfireApplication* app, FuriString* out); +void mf_df_cat_file(MifareDesfireFile* file, FuriString* out); + +bool mf_df_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); + +MifareDesfireApplication* mf_df_get_application(MifareDesfireData* data, const uint8_t (*aid)[3]); +MifareDesfireFile* mf_df_get_file(MifareDesfireApplication* app, uint8_t id); + +uint16_t mf_df_prepare_get_version(uint8_t* dest); +bool mf_df_parse_get_version_response(uint8_t* buf, uint16_t len, MifareDesfireVersion* out); + +uint16_t mf_df_prepare_get_free_memory(uint8_t* dest); +bool mf_df_parse_get_free_memory_response(uint8_t* buf, uint16_t len, MifareDesfireFreeMemory* out); + +uint16_t mf_df_prepare_get_key_settings(uint8_t* dest); +bool mf_df_parse_get_key_settings_response( + uint8_t* buf, + uint16_t len, + MifareDesfireKeySettings* out); + +uint16_t mf_df_prepare_get_key_version(uint8_t* dest, uint8_t key_id); +bool mf_df_parse_get_key_version_response(uint8_t* buf, uint16_t len, MifareDesfireKeyVersion* out); + +uint16_t mf_df_prepare_get_application_ids(uint8_t* dest); +bool mf_df_parse_get_application_ids_response( + uint8_t* buf, + uint16_t len, + MifareDesfireApplication** app_head); + +uint16_t mf_df_prepare_select_application(uint8_t* dest, uint8_t id[3]); +bool mf_df_parse_select_application_response(uint8_t* buf, uint16_t len); + +uint16_t mf_df_prepare_get_file_ids(uint8_t* dest); +bool mf_df_parse_get_file_ids_response(uint8_t* buf, uint16_t len, MifareDesfireFile** file_head); + +uint16_t mf_df_prepare_get_file_settings(uint8_t* dest, uint8_t file_id); +bool mf_df_parse_get_file_settings_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out); + +uint16_t mf_df_prepare_read_data(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len); +uint16_t mf_df_prepare_get_value(uint8_t* dest, uint8_t file_id); +uint16_t mf_df_prepare_read_records(uint8_t* dest, uint8_t file_id, uint32_t offset, uint32_t len); +bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFile* out); + +bool mf_df_read_card(FuriHalNfcTxRxContext* tx_rx, MifareDesfireData* data); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c new file mode 100644 index 0000000..266b6e2 --- /dev/null +++ b/lib/nfc/protocols/mifare_ultralight.c @@ -0,0 +1,1946 @@ +#include +#include +#include "mifare_ultralight.h" +#include "nfc_util.h" +#include +#include + +#define TAG "MfUltralight" + +// Algorithms from: https://github.com/RfidResearchGroup/proxmark3/blob/0f6061c16f072372b7d4d381911f1542afbc3a69/common/generator.c#L110 +uint32_t mf_ul_pwdgen_xiaomi(FuriHalNfcDevData* data) { + uint8_t hash[20]; + mbedtls_sha1(data->uid, data->uid_len, hash); + + uint32_t pwd = 0; + pwd |= (hash[hash[0] % 20]) << 24; + pwd |= (hash[(hash[0] + 5) % 20]) << 16; + pwd |= (hash[(hash[0] + 13) % 20]) << 8; + pwd |= (hash[(hash[0] + 17) % 20]); + + return pwd; +} + +uint32_t mf_ul_pwdgen_amiibo(FuriHalNfcDevData* data) { + uint8_t* uid = data->uid; + + uint32_t pwd = 0; + pwd |= (uid[1] ^ uid[3] ^ 0xAA) << 24; + pwd |= (uid[2] ^ uid[4] ^ 0x55) << 16; + pwd |= (uid[3] ^ uid[5] ^ 0xAA) << 8; + pwd |= uid[4] ^ uid[6] ^ 0x55; + + return pwd; +} + +bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { + if((ATQA0 == 0x44) && (ATQA1 == 0x00) && (SAK == 0x00)) { + return true; + } + return false; +} + +void mf_ul_reset(MfUltralightData* data) { + furi_assert(data); + data->type = MfUltralightTypeUnknown; + memset(&data->version, 0, sizeof(MfUltralightVersion)); + memset(data->signature, 0, sizeof(data->signature)); + memset(data->counter, 0, sizeof(data->counter)); + memset(data->tearing, 0, sizeof(data->tearing)); + memset(data->data, 0, sizeof(data->data)); + data->data_size = 0; + data->data_read = 0; + data->curr_authlim = 0; + data->auth_success = false; +} + +static MfUltralightFeatures mf_ul_get_features(MfUltralightType type) { + switch(type) { + case MfUltralightTypeUL11: + case MfUltralightTypeUL21: + return MfUltralightSupportFastRead | MfUltralightSupportCompatWrite | + MfUltralightSupportReadCounter | MfUltralightSupportIncrCounter | + MfUltralightSupportAuth | MfUltralightSupportSignature | + MfUltralightSupportTearingFlags | MfUltralightSupportVcsl; + case MfUltralightTypeNTAG213: + case MfUltralightTypeNTAG215: + case MfUltralightTypeNTAG216: + return MfUltralightSupportFastRead | MfUltralightSupportCompatWrite | + MfUltralightSupportReadCounter | MfUltralightSupportAuth | + MfUltralightSupportSignature | MfUltralightSupportSingleCounter | + MfUltralightSupportAsciiMirror; + case MfUltralightTypeNTAGI2C1K: + case MfUltralightTypeNTAGI2C2K: + return MfUltralightSupportFastRead | MfUltralightSupportSectorSelect; + case MfUltralightTypeNTAGI2CPlus1K: + case MfUltralightTypeNTAGI2CPlus2K: + return MfUltralightSupportFastRead | MfUltralightSupportAuth | + MfUltralightSupportFastWrite | MfUltralightSupportSignature | + MfUltralightSupportSectorSelect; + case MfUltralightTypeNTAG203: + return MfUltralightSupportCompatWrite | MfUltralightSupportCounterInMemory; + case MfUltralightTypeULC: + return MfUltralightSupportCompatWrite | MfUltralightSupport3DesAuth; + default: + // Assumed original MFUL 512-bit + return MfUltralightSupportCompatWrite; + } +} + +static void mf_ul_set_default_version(MfUltralightReader* reader, MfUltralightData* data) { + data->type = MfUltralightTypeUnknown; + reader->pages_to_read = 16; +} + +static void mf_ul_set_version_ntag203(MfUltralightReader* reader, MfUltralightData* data) { + data->type = MfUltralightTypeNTAG203; + reader->pages_to_read = 42; +} + +static void mf_ul_set_version_ulc(MfUltralightReader* reader, MfUltralightData* data) { + data->type = MfUltralightTypeULC; + reader->pages_to_read = 48; +} + +bool mf_ultralight_read_version( + FuriHalNfcTxRxContext* tx_rx, + MfUltralightReader* reader, + MfUltralightData* data) { + bool version_read = false; + + do { + FURI_LOG_D(TAG, "Reading version"); + tx_rx->tx_data[0] = MF_UL_GET_VERSION_CMD; + tx_rx->tx_bits = 8; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(!furi_hal_nfc_tx_rx(tx_rx, 50) || tx_rx->rx_bits != 64) { + FURI_LOG_D(TAG, "Failed reading version"); + mf_ul_set_default_version(reader, data); + furi_hal_nfc_sleep(); + furi_hal_nfc_activate_nfca(300, NULL); + break; + } + MfUltralightVersion* version = (MfUltralightVersion*)tx_rx->rx_data; + data->version = *version; + if(version->storage_size == 0x0B || version->storage_size == 0x00) { + data->type = MfUltralightTypeUL11; + reader->pages_to_read = 20; + } else if(version->storage_size == 0x0E) { + data->type = MfUltralightTypeUL21; + reader->pages_to_read = 41; + } else if(version->storage_size == 0x0F) { + data->type = MfUltralightTypeNTAG213; + reader->pages_to_read = 45; + } else if(version->storage_size == 0x11) { + data->type = MfUltralightTypeNTAG215; + reader->pages_to_read = 135; + } else if(version->prod_subtype == 5 && version->prod_ver_major == 2) { + // NTAG I2C + bool known = false; + if(version->prod_ver_minor == 1) { + if(version->storage_size == 0x13) { + data->type = MfUltralightTypeNTAGI2C1K; + reader->pages_to_read = 231; + known = true; + } else if(version->storage_size == 0x15) { + data->type = MfUltralightTypeNTAGI2C2K; + reader->pages_to_read = 485; + known = true; + } + } else if(version->prod_ver_minor == 2) { + if(version->storage_size == 0x13) { + data->type = MfUltralightTypeNTAGI2CPlus1K; + reader->pages_to_read = 236; + known = true; + } else if(version->storage_size == 0x15) { + data->type = MfUltralightTypeNTAGI2CPlus2K; + reader->pages_to_read = 492; + known = true; + } + } + + if(!known) { + mf_ul_set_default_version(reader, data); + } + } else if(version->storage_size == 0x13) { + data->type = MfUltralightTypeNTAG216; + reader->pages_to_read = 231; + } else { + mf_ul_set_default_version(reader, data); + break; + } + version_read = true; + } while(false); + + reader->supported_features = mf_ul_get_features(data->type); + return version_read; +} + +bool mf_ultralight_authenticate(FuriHalNfcTxRxContext* tx_rx, uint32_t key, uint16_t* pack) { + furi_assert(pack); + bool authenticated = false; + + do { + FURI_LOG_D(TAG, "Authenticating"); + tx_rx->tx_data[0] = MF_UL_PWD_AUTH; + nfc_util_num2bytes(key, 4, &tx_rx->tx_data[1]); + tx_rx->tx_bits = 40; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { + FURI_LOG_D(TAG, "Tag did not respond to authentication"); + break; + } + + // PACK + if(tx_rx->rx_bits < 2 * 8) { + FURI_LOG_D(TAG, "Authentication failed"); + break; + } + + *pack = (tx_rx->rx_data[1] << 8) | tx_rx->rx_data[0]; + + FURI_LOG_I(TAG, "Auth success. Password: %08lX. PACK: %04X", key, *pack); + authenticated = true; + } while(false); + + return authenticated; +} + +static int16_t mf_ultralight_page_addr_to_tag_addr(uint8_t sector, uint8_t page) { + return sector * 256 + page; +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_1k( + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + // 0 - 226: sector 0 + // 227 - 228: config registers + // 229 - 230: session registers + + if(linear_address > 230) { + *valid_pages = 0; + return -1; + } else if(linear_address >= 229) { + *sector = 3; + *valid_pages = 2 - (linear_address - 229); + return linear_address - 229 + 248; + } else if(linear_address >= 227) { + *sector = 0; + *valid_pages = 2 - (linear_address - 227); + return linear_address - 227 + 232; + } else { + *sector = 0; + *valid_pages = 227 - linear_address; + return linear_address; + } +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_2k( + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + // 0 - 255: sector 0 + // 256 - 480: sector 1 + // 481 - 482: config registers + // 483 - 484: session registers + + if(linear_address > 484) { + *valid_pages = 0; + return -1; + } else if(linear_address >= 483) { + *sector = 3; + *valid_pages = 2 - (linear_address - 483); + return linear_address - 483 + 248; + } else if(linear_address >= 481) { + *sector = 1; + *valid_pages = 2 - (linear_address - 481); + return linear_address - 481 + 232; + } else if(linear_address >= 256) { + *sector = 1; + *valid_pages = 225 - (linear_address - 256); + return linear_address - 256; + } else { + *sector = 0; + *valid_pages = 256 - linear_address; + return linear_address; + } +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_1k( + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + // 0 - 233: sector 0 + registers + // 234 - 235: session registers + + if(linear_address > 235) { + *valid_pages = 0; + return -1; + } else if(linear_address >= 234) { + *sector = 0; + *valid_pages = 2 - (linear_address - 234); + return linear_address - 234 + 236; + } else { + *sector = 0; + *valid_pages = 234 - linear_address; + return linear_address; + } +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_2k( + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + // 0 - 233: sector 0 + registers + // 234 - 235: session registers + // 236 - 491: sector 1 + + if(linear_address > 491) { + *valid_pages = 0; + return -1; + } else if(linear_address >= 236) { + *sector = 1; + *valid_pages = 256 - (linear_address - 236); + return linear_address - 236; + } else if(linear_address >= 234) { + *sector = 0; + *valid_pages = 2 - (linear_address - 234); + return linear_address - 234 + 236; + } else { + *sector = 0; + *valid_pages = 234 - linear_address; + return linear_address; + } +} + +static int16_t mf_ultralight_ntag_i2c_addr_lin_to_tag( + MfUltralightData* data, + MfUltralightReader* reader, + int16_t linear_address, + uint8_t* sector, + int16_t* valid_pages) { + switch(data->type) { + case MfUltralightTypeNTAGI2C1K: + return mf_ultralight_ntag_i2c_addr_lin_to_tag_1k(linear_address, sector, valid_pages); + + case MfUltralightTypeNTAGI2C2K: + return mf_ultralight_ntag_i2c_addr_lin_to_tag_2k(linear_address, sector, valid_pages); + + case MfUltralightTypeNTAGI2CPlus1K: + return mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_1k(linear_address, sector, valid_pages); + + case MfUltralightTypeNTAGI2CPlus2K: + return mf_ultralight_ntag_i2c_addr_lin_to_tag_plus_2k(linear_address, sector, valid_pages); + + default: + *sector = 0xff; + *valid_pages = reader->pages_to_read - linear_address; + return linear_address; + } +} + +static int16_t + mf_ultralight_ntag_i2c_addr_tag_to_lin_1k(uint8_t page, uint8_t sector, uint16_t* valid_pages) { + bool valid = false; + int16_t translated_page; + if(sector == 0) { + if(page <= 226) { + *valid_pages = 227 - page; + translated_page = page; + valid = true; + } else if(page >= 232 && page <= 233) { + *valid_pages = 2 - (page - 232); + translated_page = page - 232 + 227; + valid = true; + } + } else if(sector == 3) { + if(page >= 248 && page <= 249) { + *valid_pages = 2 - (page - 248); + translated_page = page - 248 + 229; + valid = true; + } + } + + if(!valid) { + *valid_pages = 0; + translated_page = -1; + } + return translated_page; +} + +static int16_t + mf_ultralight_ntag_i2c_addr_tag_to_lin_2k(uint8_t page, uint8_t sector, uint16_t* valid_pages) { + bool valid = false; + int16_t translated_page; + if(sector == 0) { + *valid_pages = 256 - page; + translated_page = page; + valid = true; + } else if(sector == 1) { + if(page <= 224) { + *valid_pages = 225 - page; + translated_page = 256 + page; + valid = true; + } else if(page >= 232 && page <= 233) { + *valid_pages = 2 - (page - 232); + translated_page = page - 232 + 481; + valid = true; + } + } else if(sector == 3) { + if(page >= 248 && page <= 249) { + *valid_pages = 2 - (page - 248); + translated_page = page - 248 + 483; + valid = true; + } + } + + if(!valid) { + *valid_pages = 0; + translated_page = -1; + } + return translated_page; +} + +static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_1k( + uint8_t page, + uint8_t sector, + uint16_t* valid_pages) { + bool valid = false; + int16_t translated_page; + if(sector == 0) { + if(page <= 233) { + *valid_pages = 234 - page; + translated_page = page; + valid = true; + } else if(page >= 236 && page <= 237) { + *valid_pages = 2 - (page - 236); + translated_page = page - 236 + 234; + valid = true; + } + } else if(sector == 3) { + if(page >= 248 && page <= 249) { + *valid_pages = 2 - (page - 248); + translated_page = page - 248 + 234; + valid = true; + } + } + + if(!valid) { + *valid_pages = 0; + translated_page = -1; + } + return translated_page; +} + +static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_2k( + uint8_t page, + uint8_t sector, + uint16_t* valid_pages) { + bool valid = false; + int16_t translated_page; + if(sector == 0) { + if(page <= 233) { + *valid_pages = 234 - page; + translated_page = page; + valid = true; + } else if(page >= 236 && page <= 237) { + *valid_pages = 2 - (page - 236); + translated_page = page - 236 + 234; + valid = true; + } + } else if(sector == 1) { + *valid_pages = 256 - page; + translated_page = page + 236; + valid = true; + } else if(sector == 3) { + if(page >= 248 && page <= 249) { + *valid_pages = 2 - (page - 248); + translated_page = page - 248 + 234; + valid = true; + } + } + + if(!valid) { + *valid_pages = 0; + translated_page = -1; + } + return translated_page; +} + +static int16_t mf_ultralight_ntag_i2c_addr_tag_to_lin( + MfUltralightData* data, + uint8_t page, + uint8_t sector, + uint16_t* valid_pages) { + switch(data->type) { + case MfUltralightTypeNTAGI2C1K: + return mf_ultralight_ntag_i2c_addr_tag_to_lin_1k(page, sector, valid_pages); + + case MfUltralightTypeNTAGI2C2K: + return mf_ultralight_ntag_i2c_addr_tag_to_lin_2k(page, sector, valid_pages); + + case MfUltralightTypeNTAGI2CPlus1K: + return mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_1k(page, sector, valid_pages); + + case MfUltralightTypeNTAGI2CPlus2K: + return mf_ultralight_ntag_i2c_addr_tag_to_lin_plus_2k(page, sector, valid_pages); + + default: + *valid_pages = data->data_size / 4 - page; + return page; + } +} + +MfUltralightConfigPages* mf_ultralight_get_config_pages(MfUltralightData* data) { + if(data->type >= MfUltralightTypeUL11 && data->type <= MfUltralightTypeNTAG216) { + return (MfUltralightConfigPages*)&data->data[data->data_size - 4 * 4]; + } else if( + data->type >= MfUltralightTypeNTAGI2CPlus1K && + data->type <= MfUltralightTypeNTAGI2CPlus2K) { + return (MfUltralightConfigPages*)&data->data[0xe3 * 4]; //-V641 + } else { + return NULL; + } +} + +static uint16_t mf_ultralight_calc_auth_count(MfUltralightData* data) { + if(mf_ul_get_features(data->type) & MfUltralightSupportAuth) { + MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); + uint16_t scaled_authlim = config->access.authlim; + // NTAG I2C Plus uses 2^AUTHLIM attempts rather than the direct number + if(scaled_authlim > 0 && data->type >= MfUltralightTypeNTAGI2CPlus1K && + data->type <= MfUltralightTypeNTAGI2CPlus2K) { + scaled_authlim = 1 << scaled_authlim; + } + return scaled_authlim; + } + + return 0; +} + +// NTAG21x will NAK if NFC_CNT_EN unset, so preempt +static bool mf_ultralight_should_read_counters(MfUltralightData* data) { + if(data->type < MfUltralightTypeNTAG213 || data->type > MfUltralightTypeNTAG216) return true; + + MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); + return config->access.nfc_cnt_en; +} + +static bool mf_ultralight_sector_select(FuriHalNfcTxRxContext* tx_rx, uint8_t sector) { + FURI_LOG_D(TAG, "Selecting sector %u", sector); + tx_rx->tx_data[0] = MF_UL_SECTOR_SELECT; + tx_rx->tx_data[1] = 0xff; + tx_rx->tx_bits = 16; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { + FURI_LOG_D(TAG, "Failed to issue sector select command"); + return false; + } + + tx_rx->tx_data[0] = sector; + tx_rx->tx_data[1] = 0x00; + tx_rx->tx_data[2] = 0x00; + tx_rx->tx_data[3] = 0x00; + tx_rx->tx_bits = 32; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + // This is NOT a typo! The tag ACKs by not sending a response within 1ms. + if(furi_hal_nfc_tx_rx(tx_rx, 20)) { + // TODO: what gets returned when an actual NAK is received? + FURI_LOG_D(TAG, "Sector %u select NAK'd", sector); + return false; + } + + return true; +} + +bool mf_ultralight_read_pages_direct( + FuriHalNfcTxRxContext* tx_rx, + uint8_t start_index, + uint8_t* data) { + FURI_LOG_D(TAG, "Reading pages %d - %d", start_index, start_index + 3); + tx_rx->tx_data[0] = MF_UL_READ_CMD; + tx_rx->tx_data[1] = start_index; + tx_rx->tx_bits = 16; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(!furi_hal_nfc_tx_rx(tx_rx, 50) || tx_rx->rx_bits < 16 * 8) { + FURI_LOG_D(TAG, "Failed to read pages %d - %d", start_index, start_index + 3); + return false; + } + memcpy(data, tx_rx->rx_data, 16); //-V1086 + return true; +} + +bool mf_ultralight_read_pages( + FuriHalNfcTxRxContext* tx_rx, + MfUltralightReader* reader, + MfUltralightData* data) { + uint8_t pages_read_cnt = 0; + uint8_t curr_sector_index = 0xff; + reader->pages_read = 0; + for(size_t i = 0; i < reader->pages_to_read; i += pages_read_cnt) { + uint8_t tag_sector; + int16_t valid_pages; + int16_t tag_page = mf_ultralight_ntag_i2c_addr_lin_to_tag( + data, reader, (int16_t)i, &tag_sector, &valid_pages); + + furi_assert(tag_page != -1); + if(curr_sector_index != tag_sector) { + if(!mf_ultralight_sector_select(tx_rx, tag_sector)) return false; + curr_sector_index = tag_sector; + } + + FURI_LOG_D( + TAG, "Reading pages %zu - %zu", i, i + (valid_pages > 4 ? 4 : valid_pages) - 1U); + tx_rx->tx_data[0] = MF_UL_READ_CMD; + tx_rx->tx_data[1] = tag_page; + tx_rx->tx_bits = 16; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + + if(!furi_hal_nfc_tx_rx(tx_rx, 50) || tx_rx->rx_bits < 16 * 8) { + FURI_LOG_D( + TAG, + "Failed to read pages %zu - %zu", + i, + i + (valid_pages > 4 ? 4 : valid_pages) - 1U); + break; + } + + if(valid_pages > 4) { + pages_read_cnt = 4; + } else { + pages_read_cnt = valid_pages; + } + reader->pages_read += pages_read_cnt; + memcpy(&data->data[i * 4], tx_rx->rx_data, pages_read_cnt * 4); + } + data->data_size = reader->pages_to_read * 4; + data->data_read = reader->pages_read * 4; + + return reader->pages_read > 0; +} + +bool mf_ultralight_fast_read_pages( + FuriHalNfcTxRxContext* tx_rx, + MfUltralightReader* reader, + MfUltralightData* data) { + uint8_t curr_sector_index = 0xff; + reader->pages_read = 0; + while(reader->pages_read < reader->pages_to_read) { + uint8_t tag_sector; + int16_t valid_pages; + int16_t tag_page = mf_ultralight_ntag_i2c_addr_lin_to_tag( + data, reader, reader->pages_read, &tag_sector, &valid_pages); + + furi_assert(tag_page != -1); + if(curr_sector_index != tag_sector) { + if(!mf_ultralight_sector_select(tx_rx, tag_sector)) return false; + curr_sector_index = tag_sector; + } + + FURI_LOG_D( + TAG, "Reading pages %d - %d", reader->pages_read, reader->pages_read + valid_pages - 1); + tx_rx->tx_data[0] = MF_UL_FAST_READ_CMD; + tx_rx->tx_data[1] = tag_page; + tx_rx->tx_data[2] = valid_pages - 1; + tx_rx->tx_bits = 24; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + memcpy(&data->data[reader->pages_read * 4], tx_rx->rx_data, valid_pages * 4); + reader->pages_read += valid_pages; + data->data_size = reader->pages_read * 4; + } else { + FURI_LOG_D( + TAG, + "Failed to read pages %d - %d", + reader->pages_read, + reader->pages_read + valid_pages - 1); + break; + } + } + + return reader->pages_read == reader->pages_to_read; +} + +bool mf_ultralight_read_signature(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data) { + bool signature_read = false; + + FURI_LOG_D(TAG, "Reading signature"); + tx_rx->tx_data[0] = MF_UL_READ_SIG; + tx_rx->tx_data[1] = 0; + tx_rx->tx_bits = 16; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(furi_hal_nfc_tx_rx(tx_rx, 50)) { + memcpy(data->signature, tx_rx->rx_data, sizeof(data->signature)); + signature_read = true; + } else { + FURI_LOG_D(TAG, "Failed redaing signature"); + } + + return signature_read; +} + +bool mf_ultralight_read_counters(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data) { + uint8_t counter_read = 0; + + FURI_LOG_D(TAG, "Reading counters"); + bool is_single_counter = (mf_ul_get_features(data->type) & MfUltralightSupportSingleCounter) != + 0; + for(size_t i = is_single_counter ? 2 : 0; i < 3; i++) { + tx_rx->tx_data[0] = MF_UL_READ_CNT; + tx_rx->tx_data[1] = i; + tx_rx->tx_bits = 16; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { + FURI_LOG_D(TAG, "Failed to read %d counter", i); + break; + } + data->counter[i] = (tx_rx->rx_data[2] << 16) | (tx_rx->rx_data[1] << 8) | + tx_rx->rx_data[0]; + counter_read++; + } + + return counter_read == (is_single_counter ? 1 : 3); +} + +bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data) { + uint8_t flag_read = 0; + + FURI_LOG_D(TAG, "Reading tearing flags"); + for(size_t i = 0; i < 3; i++) { + tx_rx->tx_data[0] = MF_UL_CHECK_TEARING; + tx_rx->tx_data[1] = i; + tx_rx->tx_bits = 16; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + if(!furi_hal_nfc_tx_rx(tx_rx, 50)) { + FURI_LOG_D(TAG, "Failed to read %d tearing flag", i); + break; + } + data->tearing[i] = tx_rx->rx_data[0]; + flag_read++; + } + + return flag_read == 2; +} + +static bool mf_ul_probe_3des_auth(FuriHalNfcTxRxContext* tx_rx) { + tx_rx->tx_data[0] = MF_UL_AUTHENTICATE_1; + tx_rx->tx_data[1] = 0; + tx_rx->tx_bits = 16; + tx_rx->tx_rx_type = FuriHalNfcTxRxTypeDefault; + bool rc = furi_hal_nfc_tx_rx(tx_rx, 50) && tx_rx->rx_bits == 9 * 8 && + tx_rx->rx_data[0] == 0xAF; + + // Reset just in case, we're not going to finish authenticating and need to if tag doesn't support auth + furi_hal_nfc_sleep(); + furi_hal_nfc_activate_nfca(300, NULL); + + return rc; +} + +bool mf_ul_read_card( + FuriHalNfcTxRxContext* tx_rx, + MfUltralightReader* reader, + MfUltralightData* data) { + furi_assert(tx_rx); + furi_assert(reader); + furi_assert(data); + + bool card_read = false; + + // Read Mifare Ultralight version + if(mf_ultralight_read_version(tx_rx, reader, data)) { + if(reader->supported_features & MfUltralightSupportSignature) { + // Read Signature + mf_ultralight_read_signature(tx_rx, data); + } + } else { + uint8_t dummy[16]; + // No GET_VERSION command, check if AUTHENTICATE command available (detect UL C). + if(mf_ul_probe_3des_auth(tx_rx)) { + mf_ul_set_version_ulc(reader, data); + } else if(mf_ultralight_read_pages_direct(tx_rx, 41, dummy)) { + // No AUTHENTICATE, check for NTAG203 by reading last page (41) + mf_ul_set_version_ntag203(reader, data); + } else { + // We're really an original Mifare Ultralight, reset tag for safety + furi_hal_nfc_sleep(); + furi_hal_nfc_activate_nfca(300, NULL); + } + + reader->supported_features = mf_ul_get_features(data->type); + } + + card_read = mf_ultralight_read_pages(tx_rx, reader, data); + + if(card_read) { + if(reader->supported_features & MfUltralightSupportReadCounter && + mf_ultralight_should_read_counters(data)) { + mf_ultralight_read_counters(tx_rx, data); + } + if(reader->supported_features & MfUltralightSupportTearingFlags) { + mf_ultralight_read_tearing_flags(tx_rx, data); + } + data->curr_authlim = 0; + + if(reader->pages_read == reader->pages_to_read && + reader->supported_features & MfUltralightSupportAuth && !data->auth_success) { + MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); + if(config->access.authlim == 0) { + // Attempt to auth with default PWD + uint16_t pack; + data->auth_success = mf_ultralight_authenticate(tx_rx, MF_UL_DEFAULT_PWD, &pack); + if(data->auth_success) { + config->auth_data.pwd.value = MF_UL_DEFAULT_PWD; + config->auth_data.pack.value = pack; + } else { + furi_hal_nfc_sleep(); + furi_hal_nfc_activate_nfca(300, NULL); + } + } + } + } + + if(reader->pages_read != reader->pages_to_read) { + if(reader->supported_features & MfUltralightSupportAuth) { + // Probably password protected, fix AUTH0 and PROT so before AUTH0 + // can be written and since AUTH0 won't be readable, like on the + // original card + MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); + config->auth0 = reader->pages_read; + config->access.prot = true; + } + } + + return card_read; +} + +static void mf_ul_protect_auth_data_on_read_command_i2c( + uint8_t* tx_buff, + uint8_t start_page, + uint8_t end_page, + MfUltralightEmulator* emulator) { + if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K) { + // Blank out PWD and PACK + if(start_page <= 229 && end_page >= 229) { + uint16_t offset = (229 - start_page) * 4; + uint8_t count = 4; + if(end_page >= 230) count += 2; + memset(&tx_buff[offset], 0, count); + } + + // Handle AUTH0 for sector 0 + if(!emulator->auth_success) { + if(emulator->config_cache.access.prot) { + uint8_t auth0 = emulator->config_cache.auth0; + if(auth0 < end_page) { + // start_page is always < auth0; otherwise is NAK'd already + uint8_t page_offset = auth0 - start_page; + uint8_t page_count = end_page - auth0; + memset(&tx_buff[page_offset * 4], 0, page_count * 4); + } + } + } + } +} + +static void mf_ul_ntag_i2c_fill_cross_area_read( + uint8_t* tx_buff, + uint8_t start_page, + uint8_t end_page, + MfUltralightEmulator* emulator) { + // For copying config or session registers in fast read + int16_t tx_page_offset; + int16_t data_page_offset; + uint8_t page_length; + bool apply = false; + MfUltralightType type = emulator->data.type; + if(emulator->curr_sector == 0) { + if(type == MfUltralightTypeNTAGI2C1K) { + if(start_page <= 233 && end_page >= 232) { + tx_page_offset = start_page - 232; + data_page_offset = 227; + page_length = 2; + apply = true; + } + } else if(type == MfUltralightTypeNTAGI2CPlus1K || type == MfUltralightTypeNTAGI2CPlus2K) { + if(start_page <= 237 && end_page >= 236) { + tx_page_offset = start_page - 236; + data_page_offset = 234; + page_length = 2; + apply = true; + } + } + } else if(emulator->curr_sector == 1) { + if(type == MfUltralightTypeNTAGI2C2K) { + if(start_page <= 233 && end_page >= 232) { + tx_page_offset = start_page - 232; + data_page_offset = 483; + page_length = 2; + apply = true; + } + } + } + + if(apply) { + while(tx_page_offset < 0 && page_length > 0) { //-V614 + ++tx_page_offset; + ++data_page_offset; + --page_length; + } + memcpy( + &tx_buff[tx_page_offset * 4], + &emulator->data.data[data_page_offset * 4], + page_length * 4); + } +} + +static bool mf_ul_check_auth(MfUltralightEmulator* emulator, uint8_t start_page, bool is_write) { + if(!emulator->auth_success) { + if(start_page >= emulator->config_cache.auth0 && + (emulator->config_cache.access.prot || is_write)) + return false; + } + + if(is_write && emulator->config_cache.access.cfglck) { + uint16_t config_start_page = emulator->page_num - 4; + if(start_page == config_start_page || start_page == config_start_page + 1) return false; + } + + return true; +} + +static bool mf_ul_ntag_i2c_plus_check_auth( + MfUltralightEmulator* emulator, + uint8_t start_page, + bool is_write) { + if(!emulator->auth_success) { + // Check NFC_PROT + if(emulator->curr_sector == 0 && (emulator->config_cache.access.prot || is_write)) { + if(start_page >= emulator->config_cache.auth0) return false; + } else if(emulator->curr_sector == 1) { + // We don't have to specifically check for type because this is done + // by address translator + uint8_t pt_i2c = emulator->data.data[231 * 4]; + // Check 2K_PROT + if(pt_i2c & 0x08) return false; + } + } + + if(emulator->curr_sector == 1) { + // Check NFC_DIS_SEC1 + if(emulator->config_cache.access.nfc_dis_sec1) return false; + } + + return true; +} + +static int16_t mf_ul_get_dynamic_lock_page_addr(MfUltralightData* data) { + switch(data->type) { + case MfUltralightTypeNTAG203: + return 0x28; + case MfUltralightTypeUL21: + case MfUltralightTypeNTAG213: + case MfUltralightTypeNTAG215: + case MfUltralightTypeNTAG216: + return data->data_size / 4 - 5; + case MfUltralightTypeNTAGI2C1K: + case MfUltralightTypeNTAGI2CPlus1K: + case MfUltralightTypeNTAGI2CPlus2K: + return 0xe2; + case MfUltralightTypeNTAGI2C2K: + return 0x1e0; + default: + return -1; // No dynamic lock bytes + } +} + +// Returns true if page not locked +// write_page is tag address +static bool mf_ul_check_lock(MfUltralightEmulator* emulator, int16_t write_page) { + if(write_page < 2) return false; // Page 0-1 is always locked + if(write_page == 2) return true; // Page 2 does not have a lock flag + + // Check static lock bytes + if(write_page <= 15) { + uint16_t static_lock_bytes = emulator->data.data[10] | (emulator->data.data[11] << 8); + return (static_lock_bytes & (1 << write_page)) == 0; + } + + // Check dynamic lock bytes + + // Check max page + switch(emulator->data.type) { + case MfUltralightTypeNTAG203: + // Counter page can be locked and is after dynamic locks + if(write_page == 40) return true; + break; + case MfUltralightTypeUL21: + case MfUltralightTypeNTAG213: + case MfUltralightTypeNTAG215: + case MfUltralightTypeNTAG216: + if(write_page >= emulator->page_num - 5) return true; + break; + case MfUltralightTypeNTAGI2C1K: + case MfUltralightTypeNTAGI2CPlus1K: + if(write_page > 225) return true; + break; + case MfUltralightTypeNTAGI2C2K: + if(write_page > 479) return true; + break; + case MfUltralightTypeNTAGI2CPlus2K: + if(write_page >= 226 && write_page <= 255) return true; + if(write_page >= 512) return true; + break; + default: + furi_crash("Unknown MFUL"); + return true; + } + + int16_t dynamic_lock_index = mf_ul_get_dynamic_lock_page_addr(&emulator->data); + if(dynamic_lock_index == -1) return true; + // Run address through converter because NTAG I2C 2K is special + uint16_t valid_pages; // unused + dynamic_lock_index = + mf_ultralight_ntag_i2c_addr_tag_to_lin( + &emulator->data, dynamic_lock_index & 0xff, dynamic_lock_index >> 8, &valid_pages) * + 4; + + uint16_t dynamic_lock_bytes = emulator->data.data[dynamic_lock_index] | + (emulator->data.data[dynamic_lock_index + 1] << 8); + uint8_t shift; + + switch(emulator->data.type) { + // low byte LSB range, MSB range + case MfUltralightTypeNTAG203: + if(write_page >= 16 && write_page <= 27) //-V560 + shift = (write_page - 16) / 4 + 1; + else if(write_page >= 28 && write_page <= 39) //-V560 + shift = (write_page - 28) / 4 + 5; + else if(write_page == 41) + shift = 12; + else { + furi_crash("Unknown MFUL"); + } + + break; + case MfUltralightTypeUL21: + case MfUltralightTypeNTAG213: + // 16-17, 30-31 + shift = (write_page - 16) / 2; + break; + case MfUltralightTypeNTAG215: + case MfUltralightTypeNTAG216: + case MfUltralightTypeNTAGI2C1K: + case MfUltralightTypeNTAGI2CPlus1K: + // 16-31, 128-129 + // 16-31, 128-143 + shift = (write_page - 16) / 16; + break; + case MfUltralightTypeNTAGI2C2K: + // 16-47, 240-271 + shift = (write_page - 16) / 32; + break; + case MfUltralightTypeNTAGI2CPlus2K: + // 16-47, 256-271 + if(write_page >= 208 && write_page <= 225) + shift = 6; + else if(write_page >= 256 && write_page <= 271) + shift = 7; + else + shift = (write_page - 16) / 32; + break; + default: + furi_crash("Unknown MFUL"); + break; + } + + return (dynamic_lock_bytes & (1 << shift)) == 0; +} + +static void mf_ul_make_ascii_mirror(MfUltralightEmulator* emulator, FuriString* str) { + // Locals to improve readability + uint8_t mirror_page = emulator->config->mirror_page; + uint8_t mirror_byte = emulator->config->mirror.mirror_byte; + MfUltralightMirrorConf mirror_conf = emulator->config_cache.mirror.mirror_conf; + uint16_t last_user_page_index = emulator->page_num - 6; + bool uid_printed = false; + + if(mirror_conf == MfUltralightMirrorUid || mirror_conf == MfUltralightMirrorUidCounter) { + // UID range check + if(mirror_page < 4 || mirror_page > last_user_page_index - 3 || + (mirror_page == last_user_page_index - 3 && mirror_byte > 2)) { + if(mirror_conf == MfUltralightMirrorUid) return; + // NTAG21x has the peculiar behavior when UID+counter selected, if UID does not fit but + // counter will fit, it will actually mirror the counter + furi_string_cat(str, " "); + } else { + for(int i = 0; i < 3; ++i) { + furi_string_cat_printf(str, "%02X", emulator->data.data[i]); + } + // Skip BCC0 + for(int i = 4; i < 8; ++i) { + furi_string_cat_printf(str, "%02X", emulator->data.data[i]); + } + uid_printed = true; + } + + uint16_t next_byte_offset = mirror_page * 4 + mirror_byte + 14; + if(mirror_conf == MfUltralightMirrorUidCounter) ++next_byte_offset; + mirror_page = next_byte_offset / 4; + mirror_byte = next_byte_offset % 4; + } + + if(mirror_conf == MfUltralightMirrorCounter || mirror_conf == MfUltralightMirrorUidCounter) { + // Counter is only printed if counter enabled + if(emulator->config_cache.access.nfc_cnt_en) { + // Counter protection check + if(emulator->config_cache.access.nfc_cnt_pwd_prot && !emulator->auth_success) return; + // Counter range check + if(mirror_page < 4) return; + if(mirror_page > last_user_page_index - 1) return; + if(mirror_page == last_user_page_index - 1 && mirror_byte > 2) return; + + if(mirror_conf == MfUltralightMirrorUidCounter) + furi_string_cat(str, uid_printed ? "x" : " "); + + furi_string_cat_printf(str, "%06lX", emulator->data.counter[2]); + } + } +} + +static void mf_ul_increment_single_counter(MfUltralightEmulator* emulator) { + if(!emulator->read_counter_incremented && emulator->config_cache.access.nfc_cnt_en) { + if(emulator->data.counter[2] < 0xFFFFFF) { + ++emulator->data.counter[2]; + emulator->data_changed = true; + } + emulator->read_counter_incremented = true; + } +} + +static bool + mf_ul_emulate_ntag203_counter_write(MfUltralightEmulator* emulator, uint8_t* page_buff) { + // We'll reuse the existing counters for other NTAGs as staging + // Counter 0 stores original value, data is new value + uint32_t counter_value; + if(emulator->data.tearing[0] == MF_UL_TEARING_FLAG_DEFAULT) { + counter_value = emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] | + (emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] << 8); + } else { + // We've had a reset here, so load from original value + counter_value = emulator->data.counter[0]; + } + // Although the datasheet says increment by 0 is always possible, this is not the case on + // an actual tag. If the counter is at 0xFFFF, any writes are locked out. + if(counter_value == 0xFFFF) return false; + uint32_t increment = page_buff[0] | (page_buff[1] << 8); + if(counter_value == 0) { + counter_value = increment; + } else { + // Per datasheet specifying > 0x000F is supposed to NAK, but actual tag doesn't + increment &= 0x000F; + if(counter_value + increment > 0xFFFF) return false; + counter_value += increment; + } + // Commit to new value counter + emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] = (uint8_t)counter_value; + emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] = (uint8_t)(counter_value >> 8); + emulator->data.tearing[0] = MF_UL_TEARING_FLAG_DEFAULT; + if(counter_value == 0xFFFF) { + // Tag will lock out counter if final number is 0xFFFF, even if you try to roll it back + emulator->data.counter[1] = 0xFFFF; + } + emulator->data_changed = true; + return true; +} + +static void mf_ul_emulate_write( + MfUltralightEmulator* emulator, + int16_t tag_addr, + int16_t write_page, + uint8_t* page_buff) { + // Assumption: all access checks have been completed + + if(tag_addr == 2) { + // Handle static locks + uint16_t orig_static_locks = emulator->data.data[write_page * 4 + 2] | + (emulator->data.data[write_page * 4 + 3] << 8); + uint16_t new_static_locks = page_buff[2] | (page_buff[3] << 8); + if(orig_static_locks & 1) new_static_locks &= ~0x08; + if(orig_static_locks & 2) new_static_locks &= ~0xF0; + if(orig_static_locks & 4) new_static_locks &= 0xFF; + new_static_locks |= orig_static_locks; + page_buff[0] = emulator->data.data[write_page * 4]; + page_buff[1] = emulator->data.data[write_page * 4 + 1]; + page_buff[2] = new_static_locks & 0xff; + page_buff[3] = new_static_locks >> 8; + } else if(tag_addr == 3) { + // Handle OTP/capability container + *(uint32_t*)page_buff |= *(uint32_t*)&emulator->data.data[write_page * 4]; + } else if(tag_addr == mf_ul_get_dynamic_lock_page_addr(&emulator->data)) { + // Handle dynamic locks + if(emulator->data.type == MfUltralightTypeNTAG203) { + // NTAG203 lock bytes are a bit different from the others + uint8_t orig_page_lock_byte = emulator->data.data[write_page * 4]; + uint8_t orig_cnt_lock_byte = emulator->data.data[write_page * 4 + 1]; + uint8_t new_page_lock_byte = page_buff[0]; + uint8_t new_cnt_lock_byte = page_buff[1]; + + if(orig_page_lock_byte & 0x01) // Block lock bits 1-3 + new_page_lock_byte &= ~0x0E; + if(orig_page_lock_byte & 0x10) // Block lock bits 5-7 + new_page_lock_byte &= ~0xE0; + for(uint8_t i = 0; i < 4; ++i) { + if(orig_cnt_lock_byte & (1 << i)) // Block lock counter bit + new_cnt_lock_byte &= ~(1 << (4 + i)); + } + + new_page_lock_byte |= orig_page_lock_byte; + new_cnt_lock_byte |= orig_cnt_lock_byte; + page_buff[0] = new_page_lock_byte; + page_buff[1] = new_cnt_lock_byte; + } else { + uint16_t orig_locks = emulator->data.data[write_page * 4] | + (emulator->data.data[write_page * 4 + 1] << 8); + uint8_t orig_block_locks = emulator->data.data[write_page * 4 + 2]; + uint16_t new_locks = page_buff[0] | (page_buff[1] << 8); + uint8_t new_block_locks = page_buff[2]; + + int block_lock_count; + switch(emulator->data.type) { + case MfUltralightTypeUL21: + block_lock_count = 5; + break; + case MfUltralightTypeNTAG213: + block_lock_count = 6; + break; + case MfUltralightTypeNTAG215: + block_lock_count = 4; + break; + case MfUltralightTypeNTAG216: + case MfUltralightTypeNTAGI2C1K: + case MfUltralightTypeNTAGI2CPlus1K: + block_lock_count = 7; + break; + case MfUltralightTypeNTAGI2C2K: + case MfUltralightTypeNTAGI2CPlus2K: + block_lock_count = 8; + break; + default: + furi_crash("Unknown MFUL"); + break; + } + + for(int i = 0; i < block_lock_count; ++i) { + if(orig_block_locks & (1 << i)) new_locks &= ~(3 << (2 * i)); + } + + new_locks |= orig_locks; + new_block_locks |= orig_block_locks; + + page_buff[0] = new_locks & 0xff; + page_buff[1] = new_locks >> 8; + page_buff[2] = new_block_locks; + if(emulator->data.type >= MfUltralightTypeUL21 && //-V1016 + emulator->data.type <= MfUltralightTypeNTAG216) + page_buff[3] = MF_UL_TEARING_FLAG_DEFAULT; + else + page_buff[3] = 0; + } + } + + memcpy(&emulator->data.data[write_page * 4], page_buff, 4); + emulator->data_changed = true; +} + +bool mf_ul_emulation_supported(MfUltralightData* data) { + return data->type != MfUltralightTypeULC; +} + +void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle) { + emulator->comp_write_cmd_started = false; + emulator->sector_select_cmd_started = false; + emulator->curr_sector = 0; + emulator->ntag_i2c_plus_sector3_lockout = false; + emulator->auth_success = false; + if(is_power_cycle) { + if(emulator->config != NULL) emulator->config_cache = *emulator->config; + + if(emulator->supported_features & MfUltralightSupportSingleCounter) { + emulator->read_counter_incremented = false; + } + + if(emulator->data.type == MfUltralightTypeNTAG203) { + // Apply lockout if counter ever reached 0xFFFF + if(emulator->data.counter[1] == 0xFFFF) { + emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] = 0xFF; + emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] = 0xFF; + } + // Copy original counter value from data + emulator->data.counter[0] = + emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4] | + (emulator->data.data[MF_UL_NTAG203_COUNTER_PAGE * 4 + 1] << 8); + } + } else { + if(emulator->config != NULL) { + // ACCESS (less CFGLCK) and AUTH0 are updated when reactivated + // MIRROR_CONF is not; don't know about STRG_MOD_EN, but we're not using that anyway + emulator->config_cache.access.value = (emulator->config->access.value & 0xBF) | + (emulator->config_cache.access.value & 0x40); + emulator->config_cache.auth0 = emulator->config->auth0; + } + } + if(emulator->data.type == MfUltralightTypeNTAG203) { + // Mark counter as dirty + emulator->data.tearing[0] = 0; + } +} + +void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* data) { + FURI_LOG_D(TAG, "Prepare emulation"); + emulator->data = *data; + emulator->supported_features = mf_ul_get_features(data->type); + emulator->config = mf_ultralight_get_config_pages(&emulator->data); + emulator->page_num = emulator->data.data_size / 4; + emulator->data_changed = false; + memset(&emulator->auth_attempt, 0, sizeof(MfUltralightAuth)); + mf_ul_reset_emulation(emulator, true); +} + +bool mf_ul_prepare_emulation_response( + uint8_t* buff_rx, + uint16_t buff_rx_len, + uint8_t* buff_tx, + uint16_t* buff_tx_len, + uint32_t* data_type, + void* context) { + furi_assert(context); + MfUltralightEmulator* emulator = context; + uint16_t tx_bytes = 0; + uint16_t tx_bits = 0; + bool command_parsed = false; + bool send_ack = false; + bool respond_nothing = false; + bool reset_idle = false; + +#ifdef FURI_DEBUG + FuriString* debug_buf; + debug_buf = furi_string_alloc(); + for(int i = 0; i < (buff_rx_len + 7) / 8; ++i) { + furi_string_cat_printf(debug_buf, "%02x ", buff_rx[i]); + } + furi_string_trim(debug_buf); + FURI_LOG_T(TAG, "Emu RX (%d): %s", buff_rx_len, furi_string_get_cstr(debug_buf)); + furi_string_reset(debug_buf); +#endif + + // Check composite commands + if(emulator->comp_write_cmd_started) { + if(buff_rx_len == 16 * 8) { + if(emulator->data.type == MfUltralightTypeNTAG203 && + emulator->comp_write_page_addr == MF_UL_NTAG203_COUNTER_PAGE) { + send_ack = mf_ul_emulate_ntag203_counter_write(emulator, buff_rx); + command_parsed = send_ack; + } else { + mf_ul_emulate_write( + emulator, + emulator->comp_write_page_addr, + emulator->comp_write_page_addr, + buff_rx); + send_ack = true; + command_parsed = true; + } + } + emulator->comp_write_cmd_started = false; + } else if(emulator->sector_select_cmd_started) { + if(buff_rx_len == 4 * 8) { + if(buff_rx[0] <= 0xFE) { + emulator->curr_sector = buff_rx[0] > 3 ? 0 : buff_rx[0]; + emulator->ntag_i2c_plus_sector3_lockout = false; + command_parsed = true; + respond_nothing = true; + FURI_LOG_D(TAG, "Changing sector to %d", emulator->curr_sector); + } + } + emulator->sector_select_cmd_started = false; + } else if(buff_rx_len >= 8) { + uint8_t cmd = buff_rx[0]; + if(cmd == MF_UL_GET_VERSION_CMD) { + if(emulator->data.type >= MfUltralightTypeUL11) { + if(buff_rx_len == 1 * 8) { + tx_bytes = sizeof(emulator->data.version); + memcpy(buff_tx, &emulator->data.version, tx_bytes); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } + } else if(cmd == MF_UL_READ_CMD) { + if(buff_rx_len == (1 + 1) * 8) { + int16_t start_page = buff_rx[1]; + tx_bytes = 16; + if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { + if(start_page < emulator->page_num) { + do { + uint8_t copied_pages = 0; + uint8_t src_page = start_page; + uint8_t last_page_plus_one = start_page + 4; + uint8_t pwd_page = emulator->page_num - 2; + FuriString* ascii_mirror = NULL; + size_t ascii_mirror_len = 0; + const char* ascii_mirror_cptr = NULL; + uint8_t ascii_mirror_curr_page = 0; + uint8_t ascii_mirror_curr_byte = 0; + if(last_page_plus_one > emulator->page_num) + last_page_plus_one = emulator->page_num; + if(emulator->supported_features & MfUltralightSupportAuth) { + if(!mf_ul_check_auth(emulator, start_page, false)) break; + if(!emulator->auth_success && emulator->config_cache.access.prot && + emulator->config_cache.auth0 < last_page_plus_one) + last_page_plus_one = emulator->config_cache.auth0; + } + if(emulator->supported_features & MfUltralightSupportSingleCounter) + mf_ul_increment_single_counter(emulator); + if(emulator->supported_features & MfUltralightSupportAsciiMirror && + emulator->config_cache.mirror.mirror_conf != + MfUltralightMirrorNone) { + ascii_mirror_curr_byte = emulator->config->mirror.mirror_byte; + ascii_mirror_curr_page = emulator->config->mirror_page; + // Try to avoid wasting time making mirror if we won't copy it + // Conservatively check with UID+counter mirror size + if(last_page_plus_one > ascii_mirror_curr_page && + start_page + 3 >= ascii_mirror_curr_page && + start_page <= ascii_mirror_curr_page + 6) { + ascii_mirror = furi_string_alloc(); + mf_ul_make_ascii_mirror(emulator, ascii_mirror); + ascii_mirror_len = furi_string_utf8_length(ascii_mirror); + ascii_mirror_cptr = furi_string_get_cstr(ascii_mirror); + // Move pointer to where it should be to start copying + if(ascii_mirror_len > 0 && + ascii_mirror_curr_page < start_page && + ascii_mirror_curr_byte != 0) { + uint8_t diff = 4 - ascii_mirror_curr_byte; + ascii_mirror_len -= diff; + ascii_mirror_cptr += diff; + ascii_mirror_curr_byte = 0; + ++ascii_mirror_curr_page; + } + while(ascii_mirror_len > 0 && + ascii_mirror_curr_page < start_page) { + uint8_t diff = ascii_mirror_len > 4 ? 4 : ascii_mirror_len; + ascii_mirror_len -= diff; + ascii_mirror_cptr += diff; + ++ascii_mirror_curr_page; + } + } + } + + uint8_t* dest_ptr = buff_tx; + while(copied_pages < 4) { + // Copy page + memcpy(dest_ptr, &emulator->data.data[src_page * 4], 4); + + // Note: don't have to worry about roll-over with ASCII mirror because + // lowest valid page for it is 4, while roll-over will at best read + // pages 0-2 + if(ascii_mirror_len > 0 && src_page == ascii_mirror_curr_page) { + // Copy ASCII mirror + size_t copy_len = 4 - ascii_mirror_curr_byte; + if(copy_len > ascii_mirror_len) copy_len = ascii_mirror_len; + for(size_t i = 0; i < copy_len; ++i) { + if(*ascii_mirror_cptr != ' ') + dest_ptr[ascii_mirror_curr_byte] = + (uint8_t)*ascii_mirror_cptr; + ++ascii_mirror_curr_byte; + ++ascii_mirror_cptr; + } + ascii_mirror_len -= copy_len; + // Don't care if this is inaccurate after ascii_mirror_len = 0 + ascii_mirror_curr_byte = 0; + ++ascii_mirror_curr_page; + } + + if(emulator->supported_features & MfUltralightSupportAuth) { + if(src_page == pwd_page || src_page == pwd_page + 1) { + // Blank out PWD and PACK pages + memset(dest_ptr, 0, 4); + } + } + + dest_ptr += 4; + ++copied_pages; + ++src_page; + if(src_page >= last_page_plus_one) src_page = 0; + } + if(ascii_mirror != NULL) { + furi_string_free(ascii_mirror); + } + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } while(false); + } + } else { + uint16_t valid_pages; + start_page = mf_ultralight_ntag_i2c_addr_tag_to_lin( + &emulator->data, start_page, emulator->curr_sector, &valid_pages); + if(start_page != -1) { + if(emulator->data.type < MfUltralightTypeNTAGI2CPlus1K || + mf_ul_ntag_i2c_plus_check_auth(emulator, buff_rx[1], false)) { + if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K && + emulator->curr_sector == 3 && valid_pages == 1) { + // Rewind back a sector to match behavior on a real tag + --start_page; + ++valid_pages; + } + + uint16_t copy_count = (valid_pages > 4 ? 4 : valid_pages) * 4; + FURI_LOG_D( + TAG, + "NTAG I2C Emu: page valid, %02x:%02x -> %d, %d", + emulator->curr_sector, + buff_rx[1], + start_page, + valid_pages); + memcpy(buff_tx, &emulator->data.data[start_page * 4], copy_count); + // For NTAG I2C, there's no roll-over; remainder is filled by null bytes + if(copy_count < tx_bytes) + memset(&buff_tx[copy_count], 0, tx_bytes - copy_count); + // Special case: NTAG I2C Plus sector 0 page 233 read crosses into page 236 + if(start_page == 233) + memcpy( + &buff_tx[12], &emulator->data.data[(start_page + 1) * 4], 4); + mf_ul_protect_auth_data_on_read_command_i2c( + buff_tx, start_page, start_page + copy_count / 4 - 1, emulator); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } else { + FURI_LOG_D( + TAG, + "NTAG I2C Emu: page invalid, %02x:%02x", + emulator->curr_sector, + buff_rx[1]); + if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K && + emulator->curr_sector == 3 && + !emulator->ntag_i2c_plus_sector3_lockout) { + // NTAG I2C Plus has a weird behavior where if you read sector 3 + // at an invalid address, it responds with zeroes then locks + // the read out, while if you read the mirrored session registers, + // it returns both session registers on either pages + memset(buff_tx, 0, tx_bytes); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + emulator->ntag_i2c_plus_sector3_lockout = true; + } + } + } + if(!command_parsed) tx_bytes = 0; + } + } else if(cmd == MF_UL_FAST_READ_CMD) { + if(emulator->supported_features & MfUltralightSupportFastRead) { + if(buff_rx_len == (1 + 2) * 8) { + int16_t start_page = buff_rx[1]; + uint8_t end_page = buff_rx[2]; + if(start_page <= end_page) { + tx_bytes = ((end_page + 1) - start_page) * 4; + if(emulator->data.type < MfUltralightTypeNTAGI2C1K) { + if((start_page < emulator->page_num) && + (end_page < emulator->page_num)) { + do { + if(emulator->supported_features & MfUltralightSupportAuth) { + // NAK if not authenticated and requested pages cross over AUTH0 + if(!emulator->auth_success && + emulator->config_cache.access.prot && + (start_page >= emulator->config_cache.auth0 || + end_page >= emulator->config_cache.auth0)) + break; + } + if(emulator->supported_features & + MfUltralightSupportSingleCounter) + mf_ul_increment_single_counter(emulator); + + // Copy requested pages + memcpy( + buff_tx, &emulator->data.data[start_page * 4], tx_bytes); + + if(emulator->supported_features & + MfUltralightSupportAsciiMirror && + emulator->config_cache.mirror.mirror_conf != + MfUltralightMirrorNone) { + // Copy ASCII mirror + // Less stringent check here, because expecting FAST_READ to + // only be issued once rather than repeatedly + FuriString* ascii_mirror; + ascii_mirror = furi_string_alloc(); + mf_ul_make_ascii_mirror(emulator, ascii_mirror); + size_t ascii_mirror_len = + furi_string_utf8_length(ascii_mirror); + const char* ascii_mirror_cptr = + furi_string_get_cstr(ascii_mirror); + int16_t mirror_start_offset = + (emulator->config->mirror_page - start_page) * 4 + + emulator->config->mirror.mirror_byte; + if(mirror_start_offset < 0) { + if(mirror_start_offset < -(int16_t)ascii_mirror_len) { + // Past ASCII mirror, don't copy + ascii_mirror_len = 0; + } else { + ascii_mirror_cptr += -mirror_start_offset; + ascii_mirror_len -= -mirror_start_offset; + mirror_start_offset = 0; + } + } + if(ascii_mirror_len > 0) { + int16_t mirror_end_offset = + mirror_start_offset + ascii_mirror_len; + if(mirror_end_offset > (end_page + 1) * 4) { + mirror_end_offset = (end_page + 1) * 4; + ascii_mirror_len = + mirror_end_offset - mirror_start_offset; + } + for(size_t i = 0; i < ascii_mirror_len; ++i) { + if(*ascii_mirror_cptr != ' ') + buff_tx[mirror_start_offset] = + (uint8_t)*ascii_mirror_cptr; + ++mirror_start_offset; + ++ascii_mirror_cptr; + } + } + furi_string_free(ascii_mirror); + } + + if(emulator->supported_features & MfUltralightSupportAuth) { + // Clear PWD and PACK pages + uint8_t pwd_page = emulator->page_num - 2; + int16_t pwd_page_offset = pwd_page - start_page; + // PWD page + if(pwd_page_offset >= 0 && pwd_page <= end_page) { + memset(&buff_tx[pwd_page_offset * 4], 0, 4); + // PACK page + if(pwd_page + 1 <= end_page) + memset(&buff_tx[(pwd_page_offset + 1) * 4], 0, 4); + } + } + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } while(false); + } + } else { + uint16_t valid_pages; + start_page = mf_ultralight_ntag_i2c_addr_tag_to_lin( + &emulator->data, start_page, emulator->curr_sector, &valid_pages); + if(start_page != -1) { + if(emulator->data.type < MfUltralightTypeNTAGI2CPlus1K || + mf_ul_ntag_i2c_plus_check_auth(emulator, buff_rx[1], false)) { + uint16_t copy_count = tx_bytes; + if(copy_count > valid_pages * 4) copy_count = valid_pages * 4; + memcpy( + buff_tx, &emulator->data.data[start_page * 4], copy_count); + if(copy_count < tx_bytes) + memset(&buff_tx[copy_count], 0, tx_bytes - copy_count); + mf_ul_ntag_i2c_fill_cross_area_read( + buff_tx, buff_rx[1], buff_rx[2], emulator); + mf_ul_protect_auth_data_on_read_command_i2c( + buff_tx, + start_page, + start_page + copy_count / 4 - 1, + emulator); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } + } + if(!command_parsed) tx_bytes = 0; + } + } + } + } else if(cmd == MF_UL_WRITE) { + if(buff_rx_len == (1 + 5) * 8) { + do { + uint8_t orig_write_page = buff_rx[1]; + int16_t write_page = orig_write_page; + uint16_t valid_pages; // unused + write_page = mf_ultralight_ntag_i2c_addr_tag_to_lin( + &emulator->data, write_page, emulator->curr_sector, &valid_pages); + if(write_page == -1) // NTAG I2C range check + break; + else if(write_page < 2 || write_page >= emulator->page_num) // Other MFUL/NTAG range check + break; + + if(emulator->supported_features & MfUltralightSupportAuth) { + if(emulator->data.type >= MfUltralightTypeNTAGI2CPlus1K) { + if(!mf_ul_ntag_i2c_plus_check_auth(emulator, orig_write_page, true)) + break; + } else { + if(!mf_ul_check_auth(emulator, orig_write_page, true)) break; + } + } + int16_t tag_addr = mf_ultralight_page_addr_to_tag_addr( + emulator->curr_sector, orig_write_page); + if(!mf_ul_check_lock(emulator, tag_addr)) break; + if(emulator->data.type == MfUltralightTypeNTAG203 && + orig_write_page == MF_UL_NTAG203_COUNTER_PAGE) { + send_ack = mf_ul_emulate_ntag203_counter_write(emulator, &buff_rx[2]); + command_parsed = send_ack; + } else { + mf_ul_emulate_write(emulator, tag_addr, write_page, &buff_rx[2]); + send_ack = true; + command_parsed = true; + } + } while(false); + } + } else if(cmd == MF_UL_FAST_WRITE) { + if(emulator->supported_features & MfUltralightSupportFastWrite) { + if(buff_rx_len == (1 + 66) * 8) { + if(buff_rx[1] == 0xF0 && buff_rx[2] == 0xFF) { + // TODO: update when SRAM emulation implemented + send_ack = true; + command_parsed = true; + } + } + } + } else if(cmd == MF_UL_COMP_WRITE) { + if(emulator->supported_features & MfUltralightSupportCompatWrite) { + if(buff_rx_len == (1 + 1) * 8) { + uint8_t write_page = buff_rx[1]; + do { + if(write_page < 2 || write_page >= emulator->page_num) break; + if(emulator->supported_features & MfUltralightSupportAuth && + !mf_ul_check_auth(emulator, write_page, true)) + break; + // Note we don't convert to tag addr here because there's only one sector + if(!mf_ul_check_lock(emulator, write_page)) break; + + emulator->comp_write_cmd_started = true; + emulator->comp_write_page_addr = write_page; + send_ack = true; + command_parsed = true; + } while(false); + } + } + } else if(cmd == MF_UL_READ_CNT) { + if(emulator->supported_features & MfUltralightSupportReadCounter) { + if(buff_rx_len == (1 + 1) * 8) { + do { + uint8_t cnt_num = buff_rx[1]; + + // NTAG21x checks + if(emulator->supported_features & MfUltralightSupportSingleCounter) { + if(cnt_num != 2) break; // Only counter 2 is available + if(!emulator->config_cache.access.nfc_cnt_en) + break; // NAK if counter not enabled + if(emulator->config_cache.access.nfc_cnt_pwd_prot && + !emulator->auth_success) + break; + } + + if(cnt_num < 3) { + buff_tx[0] = emulator->data.counter[cnt_num] & 0xFF; + buff_tx[1] = (emulator->data.counter[cnt_num] >> 8) & 0xFF; + buff_tx[2] = (emulator->data.counter[cnt_num] >> 16) & 0xFF; + tx_bytes = 3; + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } while(false); + } + } + } else if(cmd == MF_UL_INC_CNT) { + if(emulator->supported_features & MfUltralightSupportIncrCounter) { + if(buff_rx_len == (1 + 5) * 8) { + uint8_t cnt_num = buff_rx[1]; + uint32_t inc = (buff_rx[2] | (buff_rx[3] << 8) | (buff_rx[4] << 16)); + // TODO: can you increment by 0 when counter is at 0xffffff? + if((cnt_num < 3) && (emulator->data.counter[cnt_num] != 0x00FFFFFF) && + (emulator->data.counter[cnt_num] + inc <= 0x00FFFFFF)) { + emulator->data.counter[cnt_num] += inc; + // We're RAM-backed, so tearing never happens + emulator->data.tearing[cnt_num] = MF_UL_TEARING_FLAG_DEFAULT; + emulator->data_changed = true; + send_ack = true; + command_parsed = true; + } + } + } + } else if(cmd == MF_UL_PWD_AUTH) { + if(emulator->supported_features & MfUltralightSupportAuth) { + if(buff_rx_len == (1 + 4) * 8) { + // Record password sent by PCD + memcpy( + emulator->auth_attempt.pwd.raw, + &buff_rx[1], + sizeof(emulator->auth_attempt.pwd.raw)); + emulator->auth_attempted = true; + if(emulator->auth_received_callback) { + emulator->auth_received_callback( + emulator->auth_attempt, emulator->context); + } + + uint16_t scaled_authlim = mf_ultralight_calc_auth_count(&emulator->data); + if(scaled_authlim != 0 && emulator->data.curr_authlim >= scaled_authlim) { + if(emulator->data.curr_authlim != UINT16_MAX) { + // Handle case where AUTHLIM has been lowered or changed from 0 + emulator->data.curr_authlim = UINT16_MAX; + emulator->data_changed = true; + } + // AUTHLIM reached, always fail + buff_tx[0] = MF_UL_NAK_AUTHLIM_REACHED; + tx_bits = 4; + *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; + mf_ul_reset_emulation(emulator, false); + command_parsed = true; + } else { + if(memcmp(&buff_rx[1], emulator->config->auth_data.pwd.raw, 4) == 0) { + // Correct password + buff_tx[0] = emulator->config->auth_data.pack.raw[0]; + buff_tx[1] = emulator->config->auth_data.pack.raw[1]; + tx_bytes = 2; + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + emulator->auth_success = true; + command_parsed = true; + if(emulator->data.curr_authlim != 0) { + // Reset current AUTHLIM + emulator->data.curr_authlim = 0; + emulator->data_changed = true; + } + } else if(!emulator->config->auth_data.pwd.value) { + // Unknown password, pretend to be an Amiibo + buff_tx[0] = 0x80; + buff_tx[1] = 0x80; + tx_bytes = 2; + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + emulator->auth_success = true; + command_parsed = true; + } else { + // Wrong password, increase negative verification count + if(emulator->data.curr_authlim < UINT16_MAX) { + ++emulator->data.curr_authlim; + emulator->data_changed = true; + } + if(scaled_authlim != 0 && + emulator->data.curr_authlim >= scaled_authlim) { + emulator->data.curr_authlim = UINT16_MAX; + buff_tx[0] = MF_UL_NAK_AUTHLIM_REACHED; + tx_bits = 4; + *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; + mf_ul_reset_emulation(emulator, false); + command_parsed = true; + } else { + // Should delay here to slow brute forcing + } + } + } + } + } + } else if(cmd == MF_UL_READ_SIG) { + if(emulator->supported_features & MfUltralightSupportSignature) { + // Check 2nd byte = 0x00 - RFU + if(buff_rx_len == (1 + 1) * 8 && buff_rx[1] == 0x00) { + tx_bytes = sizeof(emulator->data.signature); + memcpy(buff_tx, emulator->data.signature, tx_bytes); + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } + } else if(cmd == MF_UL_CHECK_TEARING) { + if(emulator->supported_features & MfUltralightSupportTearingFlags) { + if(buff_rx_len == (1 + 1) * 8) { + uint8_t cnt_num = buff_rx[1]; + if(cnt_num < 3) { + buff_tx[0] = emulator->data.tearing[cnt_num]; + tx_bytes = 1; + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } + } + } else if(cmd == MF_UL_HALT_START) { + reset_idle = true; + FURI_LOG_D(TAG, "Received HLTA"); + } else if(cmd == MF_UL_SECTOR_SELECT) { + if(emulator->supported_features & MfUltralightSupportSectorSelect) { + if(buff_rx_len == (1 + 1) * 8 && buff_rx[1] == 0xFF) { + // Send ACK + emulator->sector_select_cmd_started = true; + send_ack = true; + command_parsed = true; + } + } + } else if(cmd == MF_UL_READ_VCSL) { + if(emulator->supported_features & MfUltralightSupportVcsl) { + if(buff_rx_len == (1 + 20) * 8) { + buff_tx[0] = emulator->config_cache.vctid; + tx_bytes = 1; + *data_type = FURI_HAL_NFC_TXRX_DEFAULT; + command_parsed = true; + } + } + } else { + // NTAG203 appears to NAK instead of just falling off on invalid commands + if(emulator->data.type != MfUltralightTypeNTAG203) reset_idle = true; + FURI_LOG_D(TAG, "Received invalid command"); + } + } else { + reset_idle = true; + FURI_LOG_D(TAG, "Received invalid buffer less than 8 bits in length"); + } + + if(reset_idle) { + mf_ul_reset_emulation(emulator, false); + tx_bits = 0; + command_parsed = true; + } + + if(!command_parsed) { + // Send NACK + buff_tx[0] = MF_UL_NAK_INVALID_ARGUMENT; + tx_bits = 4; + *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; + // Every NAK should cause reset to IDLE + mf_ul_reset_emulation(emulator, false); + } else if(send_ack) { + buff_tx[0] = MF_UL_ACK; + tx_bits = 4; + *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; + } + + if(respond_nothing) { + *buff_tx_len = UINT16_MAX; + *data_type = FURI_HAL_NFC_TX_RAW_RX_DEFAULT; + } else { + // Return tx buffer size in bits + if(tx_bytes) { + tx_bits = tx_bytes * 8; + } + *buff_tx_len = tx_bits; + } + +#ifdef FURI_DEBUG + if(*buff_tx_len == UINT16_MAX) { + FURI_LOG_T(TAG, "Emu TX: no reply"); + } else if(*buff_tx_len > 0) { + int count = (*buff_tx_len + 7) / 8; + for(int i = 0; i < count; ++i) { + furi_string_cat_printf(debug_buf, "%02x ", buff_tx[i]); + } + furi_string_trim(debug_buf); + FURI_LOG_T(TAG, "Emu TX (%d): %s", *buff_tx_len, furi_string_get_cstr(debug_buf)); + furi_string_free(debug_buf); + } else { + FURI_LOG_T(TAG, "Emu TX: HALT"); + } +#endif + + return tx_bits > 0; +} + +bool mf_ul_is_full_capture(MfUltralightData* data) { + if(data->data_read != data->data_size) return false; + + // Having read all the pages doesn't mean that we've got everything. + // By default PWD is 0xFFFFFFFF, but if read back it is always 0x00000000, + // so a default read on an auth-supported NTAG is never complete. + if(!(mf_ul_get_features(data->type) & MfUltralightSupportAuth)) return true; + MfUltralightConfigPages* config = mf_ultralight_get_config_pages(data); + return config->auth_data.pwd.value != 0 || config->auth_data.pack.value != 0; +} diff --git a/lib/nfc/protocols/mifare_ultralight.h b/lib/nfc/protocols/mifare_ultralight.h new file mode 100644 index 0000000..9cb7ca5 --- /dev/null +++ b/lib/nfc/protocols/mifare_ultralight.h @@ -0,0 +1,269 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Largest tag is NTAG I2C Plus 2K, both data sectors plus SRAM +#define MF_UL_MAX_DUMP_SIZE ((238 + 256 + 16) * 4) + +#define MF_UL_TEARING_FLAG_DEFAULT (0xBD) + +#define MF_UL_HALT_START (0x50) +#define MF_UL_GET_VERSION_CMD (0x60) +#define MF_UL_READ_CMD (0x30) +#define MF_UL_FAST_READ_CMD (0x3A) +#define MF_UL_WRITE (0xA2) +#define MF_UL_FAST_WRITE (0xA6) +#define MF_UL_COMP_WRITE (0xA0) +#define MF_UL_READ_CNT (0x39) +#define MF_UL_INC_CNT (0xA5) +#define MF_UL_AUTHENTICATE_1 (0x1A) +#define MF_UL_PWD_AUTH (0x1B) +#define MF_UL_READ_SIG (0x3C) +#define MF_UL_CHECK_TEARING (0x3E) +#define MF_UL_READ_VCSL (0x4B) +#define MF_UL_SECTOR_SELECT (0xC2) + +#define MF_UL_ACK (0xa) +#define MF_UL_NAK_INVALID_ARGUMENT (0x0) +#define MF_UL_NAK_AUTHLIM_REACHED (0x4) + +#define MF_UL_NTAG203_COUNTER_PAGE (41) + +#define MF_UL_DEFAULT_PWD (0xFFFFFFFF) + +typedef enum { + MfUltralightAuthMethodManual, + MfUltralightAuthMethodAmeebo, + MfUltralightAuthMethodXiaomi, + MfUltralightAuthMethodAuto, +} MfUltralightAuthMethod; + +// Important: order matters; some features are based on positioning in this enum +typedef enum { + MfUltralightTypeUnknown, + MfUltralightTypeNTAG203, + MfUltralightTypeULC, + // Below have config pages and GET_VERSION support + MfUltralightTypeUL11, + MfUltralightTypeUL21, + MfUltralightTypeNTAG213, + MfUltralightTypeNTAG215, + MfUltralightTypeNTAG216, + // Below also have sector select + // NTAG I2C's *does not* have regular config pages, so it's a bit of an odd duck + MfUltralightTypeNTAGI2C1K, + MfUltralightTypeNTAGI2C2K, + // NTAG I2C Plus has stucture expected from NTAG21x + MfUltralightTypeNTAGI2CPlus1K, + MfUltralightTypeNTAGI2CPlus2K, + + // Keep last for number of types calculation + MfUltralightTypeNum, +} MfUltralightType; + +typedef enum { + MfUltralightSupportNone = 0, + MfUltralightSupportFastRead = 1 << 0, + MfUltralightSupportTearingFlags = 1 << 1, + MfUltralightSupportReadCounter = 1 << 2, + MfUltralightSupportIncrCounter = 1 << 3, + MfUltralightSupportSignature = 1 << 4, + MfUltralightSupportFastWrite = 1 << 5, + MfUltralightSupportCompatWrite = 1 << 6, + MfUltralightSupportAuth = 1 << 7, + MfUltralightSupportVcsl = 1 << 8, + MfUltralightSupportSectorSelect = 1 << 9, + // NTAG21x only has counter 2 + MfUltralightSupportSingleCounter = 1 << 10, + // ASCII mirror is not a command, but handy to have as a flag + MfUltralightSupportAsciiMirror = 1 << 11, + // NTAG203 counter that's in memory rather than through a command + MfUltralightSupportCounterInMemory = 1 << 12, + MfUltralightSupport3DesAuth = 1 << 13, +} MfUltralightFeatures; + +typedef enum { + MfUltralightMirrorNone, + MfUltralightMirrorUid, + MfUltralightMirrorCounter, + MfUltralightMirrorUidCounter, +} MfUltralightMirrorConf; + +typedef struct { + uint8_t header; + uint8_t vendor_id; + uint8_t prod_type; + uint8_t prod_subtype; + uint8_t prod_ver_major; + uint8_t prod_ver_minor; + uint8_t storage_size; + uint8_t protocol_type; +} MfUltralightVersion; + +typedef struct { + uint8_t sn0[3]; + uint8_t btBCC0; + uint8_t sn1[4]; + uint8_t btBCC1; + uint8_t internal; + uint8_t lock[2]; + uint8_t otp[4]; +} MfUltralightManufacturerBlock; + +typedef struct { + MfUltralightType type; + MfUltralightVersion version; + uint8_t signature[32]; + uint32_t counter[3]; + uint8_t tearing[3]; + MfUltralightAuthMethod auth_method; + uint8_t auth_key[4]; + bool auth_success; + uint16_t curr_authlim; + uint16_t data_size; + uint8_t data[MF_UL_MAX_DUMP_SIZE]; + uint16_t data_read; +} MfUltralightData; + +typedef struct __attribute__((packed)) { + union { + uint8_t raw[4]; + uint32_t value; + } pwd; + union { + uint8_t raw[2]; + uint16_t value; + } pack; +} MfUltralightAuth; + +// Common configuration pages for MFUL EV1, NTAG21x, and NTAG I2C Plus +typedef struct __attribute__((packed)) { + union { + uint8_t value; + struct { + uint8_t rfui1 : 2; + bool strg_mod_en : 1; + bool rfui2 : 1; + uint8_t mirror_byte : 2; + MfUltralightMirrorConf mirror_conf : 2; + }; + } mirror; + uint8_t rfui1; + uint8_t mirror_page; + uint8_t auth0; + union { + uint8_t value; + struct { + uint8_t authlim : 3; + bool nfc_cnt_pwd_prot : 1; + bool nfc_cnt_en : 1; + bool nfc_dis_sec1 : 1; // NTAG I2C Plus only + bool cfglck : 1; + bool prot : 1; + }; + } access; + uint8_t vctid; + uint8_t rfui2[2]; + MfUltralightAuth auth_data; + uint8_t rfui3[2]; +} MfUltralightConfigPages; + +typedef struct { + uint16_t pages_to_read; + int16_t pages_read; + MfUltralightFeatures supported_features; +} MfUltralightReader; + +// TODO rework with reader analyzer +typedef void (*MfUltralightAuthReceivedCallback)(MfUltralightAuth auth, void* context); + +typedef struct { + MfUltralightData data; + MfUltralightConfigPages* config; + // Most config values don't apply until power cycle, so cache config pages + // for correct behavior + MfUltralightConfigPages config_cache; + MfUltralightFeatures supported_features; + uint16_t page_num; + bool data_changed; + bool comp_write_cmd_started; + uint8_t comp_write_page_addr; + bool auth_success; + uint8_t curr_sector; + bool sector_select_cmd_started; + bool ntag_i2c_plus_sector3_lockout; + bool read_counter_incremented; + bool auth_attempted; + MfUltralightAuth auth_attempt; + + // TODO rework with reader analyzer + MfUltralightAuthReceivedCallback auth_received_callback; + void* context; +} MfUltralightEmulator; + +void mf_ul_reset(MfUltralightData* data); + +bool mf_ul_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK); + +bool mf_ultralight_read_version( + FuriHalNfcTxRxContext* tx_rx, + MfUltralightReader* reader, + MfUltralightData* data); + +bool mf_ultralight_read_pages_direct( + FuriHalNfcTxRxContext* tx_rx, + uint8_t start_index, + uint8_t* data); + +bool mf_ultralight_read_pages( + FuriHalNfcTxRxContext* tx_rx, + MfUltralightReader* reader, + MfUltralightData* data); + +bool mf_ultralight_fast_read_pages( + FuriHalNfcTxRxContext* tx_rx, + MfUltralightReader* reader, + MfUltralightData* data); + +bool mf_ultralight_read_signature(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data); + +bool mf_ultralight_read_counters(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data); + +bool mf_ultralight_read_tearing_flags(FuriHalNfcTxRxContext* tx_rx, MfUltralightData* data); + +bool mf_ultralight_authenticate(FuriHalNfcTxRxContext* tx_rx, uint32_t key, uint16_t* pack); + +MfUltralightConfigPages* mf_ultralight_get_config_pages(MfUltralightData* data); + +bool mf_ul_read_card( + FuriHalNfcTxRxContext* tx_rx, + MfUltralightReader* reader, + MfUltralightData* data); + +bool mf_ul_emulation_supported(MfUltralightData* data); + +void mf_ul_reset_emulation(MfUltralightEmulator* emulator, bool is_power_cycle); + +void mf_ul_prepare_emulation(MfUltralightEmulator* emulator, MfUltralightData* data); + +bool mf_ul_prepare_emulation_response( + uint8_t* buff_rx, + uint16_t buff_rx_len, + uint8_t* buff_tx, + uint16_t* buff_tx_len, + uint32_t* data_type, + void* context); + +uint32_t mf_ul_pwdgen_amiibo(FuriHalNfcDevData* data); + +uint32_t mf_ul_pwdgen_xiaomi(FuriHalNfcDevData* data); + +bool mf_ul_is_full_capture(MfUltralightData* data); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/nfc_util.c b/lib/nfc/protocols/nfc_util.c new file mode 100644 index 0000000..8cb6d57 --- /dev/null +++ b/lib/nfc/protocols/nfc_util.c @@ -0,0 +1,70 @@ +#include "nfc_util.h" + +#include + +static const uint8_t nfc_util_odd_byte_parity[256] = { + 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, + 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, + 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, + 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, + 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, + 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, + 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, + 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, + 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1}; + +void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest) { + furi_assert(dest); + furi_assert(len <= 8); + + while(len--) { + dest[len] = (uint8_t)src; + src >>= 8; + } +} + +uint64_t nfc_util_bytes2num(const uint8_t* src, uint8_t len) { + furi_assert(src); + furi_assert(len <= 8); + + uint64_t res = 0; + while(len--) { + res = (res << 8) | (*src); + src++; + } + return res; +} + +uint8_t nfc_util_even_parity32(uint32_t data) { + // data ^= data >> 16; + // data ^= data >> 8; + // return !nfc_util_odd_byte_parity[data]; + return (__builtin_parity(data) & 0xFF); +} + +uint8_t nfc_util_odd_parity8(uint8_t data) { + return nfc_util_odd_byte_parity[data]; +} + +void nfc_util_odd_parity(const uint8_t* src, uint8_t* dst, uint8_t len) { + furi_assert(src); + furi_assert(dst); + + uint8_t parity = 0; + uint8_t bit = 0; + while(len--) { + parity |= nfc_util_odd_parity8(*src) << (7 - bit); // parity is MSB first + bit++; + if(bit == 8) { + *dst = parity; + dst++; + parity = 0; + bit = 0; + } + src++; + } + + if(bit) { + *dst = parity; + } +} \ No newline at end of file diff --git a/lib/nfc/protocols/nfc_util.h b/lib/nfc/protocols/nfc_util.h new file mode 100644 index 0000000..a9d5a3f --- /dev/null +++ b/lib/nfc/protocols/nfc_util.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest); + +uint64_t nfc_util_bytes2num(const uint8_t* src, uint8_t len); + +uint8_t nfc_util_even_parity32(uint32_t data); + +uint8_t nfc_util_odd_parity8(uint8_t data); + +void nfc_util_odd_parity(const uint8_t* src, uint8_t* dst, uint8_t len); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfca.c b/lib/nfc/protocols/nfca.c new file mode 100644 index 0000000..ab4f3f2 --- /dev/null +++ b/lib/nfc/protocols/nfca.c @@ -0,0 +1,140 @@ +#include "nfca.h" +#include +#include +#include + +#define NFCA_CRC_INIT (0x6363) + +#define NFCA_F_SIG (13560000.0) +#define T_SIG 7374 //73.746ns*100 +#define T_SIG_x8 58992 //T_SIG*8 +#define T_SIG_x8_x8 471936 //T_SIG*8*8 +#define T_SIG_x8_x9 530928 //T_SIG*8*9 + +#define NFCA_SIGNAL_MAX_EDGES (1350) + +typedef struct { + uint8_t cmd; + uint8_t param; +} nfca_cmd_rats; + +static uint8_t nfca_default_ats[] = {0x05, 0x78, 0x80, 0x80, 0x00}; + +static uint8_t nfca_halt_req[] = {NFCA_CMD_HALT, 0x00}; + +uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len) { + uint16_t crc = NFCA_CRC_INIT; + uint8_t byte = 0; + + for(uint8_t i = 0; i < len; i++) { + byte = buff[i]; + byte ^= (uint8_t)(crc & 0xff); + byte ^= byte << 4; + crc = (crc >> 8) ^ (((uint16_t)byte) << 8) ^ (((uint16_t)byte) << 3) ^ + (((uint16_t)byte) >> 4); + } + + return crc; +} + +void nfca_append_crc16(uint8_t* buff, uint16_t len) { + uint16_t crc = nfca_get_crc16(buff, len); + buff[len] = (uint8_t)crc; + buff[len + 1] = (uint8_t)(crc >> 8); +} + +bool nfca_emulation_handler( + uint8_t* buff_rx, + uint16_t buff_rx_len, + uint8_t* buff_tx, + uint16_t* buff_tx_len) { + bool halt = false; + uint8_t rx_bytes = buff_rx_len / 8; + + if(rx_bytes == sizeof(nfca_halt_req) && !memcmp(buff_rx, nfca_halt_req, rx_bytes)) { + halt = true; + } else if(rx_bytes == sizeof(nfca_cmd_rats) && buff_rx[0] == NFCA_CMD_RATS) { + memcpy(buff_tx, nfca_default_ats, sizeof(nfca_default_ats)); + *buff_tx_len = sizeof(nfca_default_ats) * 8; + } + + return halt; +} + +static void nfca_add_bit(DigitalSignal* signal, bool bit) { + if(bit) { + signal->start_level = true; + for(size_t i = 0; i < 7; i++) { + signal->edge_timings[i] = T_SIG_x8; + } + signal->edge_timings[7] = T_SIG_x8_x9; + signal->edge_cnt = 8; + } else { + signal->start_level = false; + signal->edge_timings[0] = T_SIG_x8_x8; + for(size_t i = 1; i < 9; i++) { + signal->edge_timings[i] = T_SIG_x8; + } + signal->edge_cnt = 9; + } +} + +static void nfca_add_byte(NfcaSignal* nfca_signal, uint8_t byte, bool parity) { + for(uint8_t i = 0; i < 8; i++) { + if(byte & (1 << i)) { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); + } else { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero); + } + } + if(parity) { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); + } else { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero); + } +} + +NfcaSignal* nfca_signal_alloc() { + NfcaSignal* nfca_signal = malloc(sizeof(NfcaSignal)); + nfca_signal->one = digital_signal_alloc(10); + nfca_signal->zero = digital_signal_alloc(10); + nfca_add_bit(nfca_signal->one, true); + nfca_add_bit(nfca_signal->zero, false); + nfca_signal->tx_signal = digital_signal_alloc(NFCA_SIGNAL_MAX_EDGES); + + return nfca_signal; +} + +void nfca_signal_free(NfcaSignal* nfca_signal) { + furi_assert(nfca_signal); + + digital_signal_free(nfca_signal->one); + digital_signal_free(nfca_signal->zero); + digital_signal_free(nfca_signal->tx_signal); + free(nfca_signal); +} + +void nfca_signal_encode(NfcaSignal* nfca_signal, uint8_t* data, uint16_t bits, uint8_t* parity) { + furi_assert(nfca_signal); + furi_assert(data); + furi_assert(parity); + + nfca_signal->tx_signal->edge_cnt = 0; + nfca_signal->tx_signal->start_level = true; + // Start of frame + digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); + + if(bits < 8) { + for(size_t i = 0; i < bits; i++) { + if(FURI_BIT(data[0], i)) { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->one); + } else { + digital_signal_append(nfca_signal->tx_signal, nfca_signal->zero); + } + } + } else { + for(size_t i = 0; i < bits / 8; i++) { + nfca_add_byte(nfca_signal, data[i], parity[i / 8] & (1 << (7 - (i & 0x07)))); + } + } +} diff --git a/lib/nfc/protocols/nfca.h b/lib/nfc/protocols/nfca.h new file mode 100644 index 0000000..e4978a3 --- /dev/null +++ b/lib/nfc/protocols/nfca.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include + +#define NFCA_CMD_RATS (0xE0U) +#define NFCA_CMD_HALT (0x50U) + +typedef struct { + DigitalSignal* one; + DigitalSignal* zero; + DigitalSignal* tx_signal; +} NfcaSignal; + +uint16_t nfca_get_crc16(uint8_t* buff, uint16_t len); + +void nfca_append_crc16(uint8_t* buff, uint16_t len); + +bool nfca_emulation_handler( + uint8_t* buff_rx, + uint16_t buff_rx_len, + uint8_t* buff_tx, + uint16_t* buff_tx_len); + +NfcaSignal* nfca_signal_alloc(); + +void nfca_signal_free(NfcaSignal* nfca_signal); + +void nfca_signal_encode(NfcaSignal* nfca_signal, uint8_t* data, uint16_t bits, uint8_t* parity); diff --git a/lib/nfc/protocols/nfcv.c b/lib/nfc/protocols/nfcv.c new file mode 100644 index 0000000..2814632 --- /dev/null +++ b/lib/nfc/protocols/nfcv.c @@ -0,0 +1,1438 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nfcv.h" +#include "nfc_util.h" +#include "slix.h" + +#define TAG "NfcV" + +/* macros to map "modulate field" flag to GPIO level */ +#define GPIO_LEVEL_MODULATED NFCV_LOAD_MODULATION_POLARITY +#define GPIO_LEVEL_UNMODULATED (!GPIO_LEVEL_MODULATED) + +/* timing macros */ +#define DIGITAL_SIGNAL_UNIT_S (100000000000.0f) +#define DIGITAL_SIGNAL_UNIT_US (100000.0f) + +ReturnCode nfcv_inventory(uint8_t* uid) { + uint16_t received = 0; + rfalNfcvInventoryRes res; + ReturnCode ret = ERR_NONE; + + for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { + /* TODO: needs proper abstraction via furi_hal(_ll)_* */ + ret = rfalNfcvPollerInventory(RFAL_NFCV_NUM_SLOTS_1, 0, NULL, &res, &received); + + if(ret == ERR_NONE) { + break; + } + } + + if(ret == ERR_NONE) { + if(uid != NULL) { + memcpy(uid, res.UID, NFCV_UID_LENGTH); + } + } + + return ret; +} + +ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* nfcv_data) { + UNUSED(reader); + + uint16_t received = 0; + for(size_t block = 0; block < nfcv_data->block_num; block++) { + uint8_t rxBuf[32]; + FURI_LOG_D(TAG, "Reading block %d/%d", block, (nfcv_data->block_num - 1)); + + ReturnCode ret = ERR_NONE; + for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { + ret = rfalNfcvPollerReadSingleBlock( + RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, block, rxBuf, sizeof(rxBuf), &received); + + if(ret == ERR_NONE) { + break; + } + } + if(ret != ERR_NONE) { + FURI_LOG_D(TAG, "failed to read: %d", ret); + return ret; + } + memcpy( + &(nfcv_data->data[block * nfcv_data->block_size]), &rxBuf[1], nfcv_data->block_size); + FURI_LOG_D( + TAG, + " %02X %02X %02X %02X", + nfcv_data->data[block * nfcv_data->block_size + 0], + nfcv_data->data[block * nfcv_data->block_size + 1], + nfcv_data->data[block * nfcv_data->block_size + 2], + nfcv_data->data[block * nfcv_data->block_size + 3]); + } + + return ERR_NONE; +} + +ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + uint8_t rxBuf[32]; + uint16_t received = 0; + ReturnCode ret = ERR_NONE; + + FURI_LOG_D(TAG, "Read SYSTEM INFORMATION..."); + + for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { + /* TODO: needs proper abstraction via furi_hal(_ll)_* */ + ret = rfalNfcvPollerGetSystemInformation( + RFAL_NFCV_REQ_FLAG_DEFAULT, NULL, rxBuf, sizeof(rxBuf), &received); + + if(ret == ERR_NONE) { + break; + } + } + + if(ret == ERR_NONE) { + nfc_data->type = FuriHalNfcTypeV; + nfc_data->uid_len = NFCV_UID_LENGTH; + /* UID is stored reversed in this response */ + for(int pos = 0; pos < nfc_data->uid_len; pos++) { + nfc_data->uid[pos] = rxBuf[2 + (NFCV_UID_LENGTH - 1 - pos)]; + } + nfcv_data->dsfid = rxBuf[NFCV_UID_LENGTH + 2]; + nfcv_data->afi = rxBuf[NFCV_UID_LENGTH + 3]; + nfcv_data->block_num = rxBuf[NFCV_UID_LENGTH + 4] + 1; + nfcv_data->block_size = rxBuf[NFCV_UID_LENGTH + 5] + 1; + nfcv_data->ic_ref = rxBuf[NFCV_UID_LENGTH + 6]; + FURI_LOG_D( + TAG, + " UID: %02X %02X %02X %02X %02X %02X %02X %02X", + nfc_data->uid[0], + nfc_data->uid[1], + nfc_data->uid[2], + nfc_data->uid[3], + nfc_data->uid[4], + nfc_data->uid[5], + nfc_data->uid[6], + nfc_data->uid[7]); + FURI_LOG_D( + TAG, + " DSFID %d, AFI %d, Blocks %d, Size %d, IC Ref %d", + nfcv_data->dsfid, + nfcv_data->afi, + nfcv_data->block_num, + nfcv_data->block_size, + nfcv_data->ic_ref); + return ret; + } + FURI_LOG_D(TAG, "Failed: %d", ret); + + return ret; +} + +bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + furi_assert(reader); + furi_assert(nfc_data); + furi_assert(nfcv_data); + + if(nfcv_read_sysinfo(nfc_data, nfcv_data) != ERR_NONE) { + return false; + } + + if(nfcv_read_blocks(reader, nfcv_data) != ERR_NONE) { + return false; + } + + /* clear all know sub type data before reading them */ + memset(&nfcv_data->sub_data, 0x00, sizeof(nfcv_data->sub_data)); + + if(slix_check_card_type(nfc_data)) { + FURI_LOG_I(TAG, "NXP SLIX detected"); + nfcv_data->sub_type = NfcVTypeSlix; + } else if(slix2_check_card_type(nfc_data)) { + FURI_LOG_I(TAG, "NXP SLIX2 detected"); + nfcv_data->sub_type = NfcVTypeSlix2; + if(slix2_read_custom(nfc_data, nfcv_data) != ERR_NONE) { + return false; + } + } else if(slix_s_check_card_type(nfc_data)) { + FURI_LOG_I(TAG, "NXP SLIX-S detected"); + nfcv_data->sub_type = NfcVTypeSlixS; + } else if(slix_l_check_card_type(nfc_data)) { + FURI_LOG_I(TAG, "NXP SLIX-L detected"); + nfcv_data->sub_type = NfcVTypeSlixL; + } else { + nfcv_data->sub_type = NfcVTypePlain; + } + + return true; +} + +void nfcv_crc(uint8_t* data, uint32_t length) { + uint32_t reg = 0xFFFF; + + for(size_t i = 0; i < length; i++) { + reg = reg ^ ((uint32_t)data[i]); + for(size_t j = 0; j < 8; j++) { + if(reg & 0x0001) { + reg = (reg >> 1) ^ 0x8408; + } else { + reg = (reg >> 1); + } + } + } + + uint16_t crc = ~(uint16_t)(reg & 0xffff); + + data[length + 0] = crc & 0xFF; + data[length + 1] = crc >> 8; +} + +void nfcv_emu_free_signals(NfcVEmuAirSignals* signals) { + furi_assert(signals); + + if(signals->nfcv_resp_one) { + digital_signal_free(signals->nfcv_resp_one); + } + if(signals->nfcv_resp_zero) { + digital_signal_free(signals->nfcv_resp_zero); + } + if(signals->nfcv_resp_sof) { + digital_signal_free(signals->nfcv_resp_sof); + } + if(signals->nfcv_resp_eof) { + digital_signal_free(signals->nfcv_resp_eof); + } + signals->nfcv_resp_one = NULL; + signals->nfcv_resp_zero = NULL; + signals->nfcv_resp_sof = NULL; + signals->nfcv_resp_eof = NULL; +} + +bool nfcv_emu_alloc_signals(NfcVEmuAir* air, NfcVEmuAirSignals* signals, uint32_t slowdown) { + furi_assert(air); + furi_assert(signals); + + bool success = true; + + if(!signals->nfcv_resp_one) { + /* logical one: unmodulated then 8 pulses */ + signals->nfcv_resp_one = digital_signal_alloc( + slowdown * (air->nfcv_resp_unmod->edge_cnt + 8 * air->nfcv_resp_pulse->edge_cnt)); + if(!signals->nfcv_resp_one) { + return false; + } + for(size_t i = 0; i < slowdown; i++) { + success &= digital_signal_append(signals->nfcv_resp_one, air->nfcv_resp_unmod); + } + for(size_t i = 0; i < slowdown * 8; i++) { + success &= digital_signal_append(signals->nfcv_resp_one, air->nfcv_resp_pulse); + } + if(!success) { + return false; + } + } + if(!signals->nfcv_resp_zero) { + /* logical zero: 8 pulses then unmodulated */ + signals->nfcv_resp_zero = digital_signal_alloc( + slowdown * (8 * air->nfcv_resp_pulse->edge_cnt + air->nfcv_resp_unmod->edge_cnt)); + if(!signals->nfcv_resp_zero) { + return false; + } + for(size_t i = 0; i < slowdown * 8; i++) { + success &= digital_signal_append(signals->nfcv_resp_zero, air->nfcv_resp_pulse); + } + for(size_t i = 0; i < slowdown; i++) { + success &= digital_signal_append(signals->nfcv_resp_zero, air->nfcv_resp_unmod); + } + if(!success) { + return false; + } + } + if(!signals->nfcv_resp_sof) { + /* SOF: unmodulated, 24 pulses, logic 1 */ + signals->nfcv_resp_sof = digital_signal_alloc( + slowdown * (3 * air->nfcv_resp_unmod->edge_cnt + 24 * air->nfcv_resp_pulse->edge_cnt) + + signals->nfcv_resp_one->edge_cnt); + if(!signals->nfcv_resp_sof) { + return false; + } + for(size_t i = 0; i < slowdown * 3; i++) { + success &= digital_signal_append(signals->nfcv_resp_sof, air->nfcv_resp_unmod); + } + for(size_t i = 0; i < slowdown * 24; i++) { + success &= digital_signal_append(signals->nfcv_resp_sof, air->nfcv_resp_pulse); + } + success &= digital_signal_append(signals->nfcv_resp_sof, signals->nfcv_resp_one); + if(!success) { + return false; + } + } + if(!signals->nfcv_resp_eof) { + /* EOF: logic 0, 24 pulses, unmodulated */ + signals->nfcv_resp_eof = digital_signal_alloc( + signals->nfcv_resp_zero->edge_cnt + + slowdown * (24 * air->nfcv_resp_pulse->edge_cnt + 3 * air->nfcv_resp_unmod->edge_cnt) + + air->nfcv_resp_unmod->edge_cnt); + if(!signals->nfcv_resp_eof) { + return false; + } + success &= digital_signal_append(signals->nfcv_resp_eof, signals->nfcv_resp_zero); + for(size_t i = 0; i < slowdown * 23; i++) { + success &= digital_signal_append(signals->nfcv_resp_eof, air->nfcv_resp_pulse); + } + /* we don't want to add the last level as we just want a transition to "unmodulated" again */ + for(size_t i = 0; i < slowdown; i++) { + success &= digital_signal_append(signals->nfcv_resp_eof, air->nfcv_resp_half_pulse); + } + } + return success; +} + +bool nfcv_emu_alloc(NfcVData* nfcv_data) { + furi_assert(nfcv_data); + + if(!nfcv_data->frame) { + nfcv_data->frame = malloc(NFCV_FRAMESIZE_MAX); + if(!nfcv_data->frame) { + return false; + } + } + + if(!nfcv_data->emu_air.nfcv_signal) { + /* assuming max frame length is 255 bytes */ + nfcv_data->emu_air.nfcv_signal = digital_sequence_alloc(8 * 255 + 2, &gpio_spi_r_mosi); + if(!nfcv_data->emu_air.nfcv_signal) { + return false; + } + } + if(!nfcv_data->emu_air.nfcv_resp_unmod) { + /* unmodulated 256/fc or 1024/fc signal as building block */ + nfcv_data->emu_air.nfcv_resp_unmod = digital_signal_alloc(4); + if(!nfcv_data->emu_air.nfcv_resp_unmod) { + return false; + } + nfcv_data->emu_air.nfcv_resp_unmod->start_level = GPIO_LEVEL_UNMODULATED; + nfcv_data->emu_air.nfcv_resp_unmod->edge_timings[0] = + (uint32_t)(NFCV_RESP_SUBC1_UNMOD_256 * DIGITAL_SIGNAL_UNIT_S); + nfcv_data->emu_air.nfcv_resp_unmod->edge_cnt = 1; + } + if(!nfcv_data->emu_air.nfcv_resp_pulse) { + /* modulated fc/32 or fc/8 pulse as building block */ + nfcv_data->emu_air.nfcv_resp_pulse = digital_signal_alloc(4); + if(!nfcv_data->emu_air.nfcv_resp_pulse) { + return false; + } + nfcv_data->emu_air.nfcv_resp_pulse->start_level = GPIO_LEVEL_MODULATED; + nfcv_data->emu_air.nfcv_resp_pulse->edge_timings[0] = + (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); + nfcv_data->emu_air.nfcv_resp_pulse->edge_timings[1] = + (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); + nfcv_data->emu_air.nfcv_resp_pulse->edge_cnt = 2; + } + + if(!nfcv_data->emu_air.nfcv_resp_half_pulse) { + /* modulated fc/32 or fc/8 pulse as building block */ + nfcv_data->emu_air.nfcv_resp_half_pulse = digital_signal_alloc(4); + if(!nfcv_data->emu_air.nfcv_resp_half_pulse) { + return false; + } + nfcv_data->emu_air.nfcv_resp_half_pulse->start_level = GPIO_LEVEL_MODULATED; + nfcv_data->emu_air.nfcv_resp_half_pulse->edge_timings[0] = + (uint32_t)(NFCV_RESP_SUBC1_PULSE_32 * DIGITAL_SIGNAL_UNIT_S); + nfcv_data->emu_air.nfcv_resp_half_pulse->edge_cnt = 1; + } + + bool success = true; + success &= nfcv_emu_alloc_signals(&nfcv_data->emu_air, &nfcv_data->emu_air.signals_high, 1); + success &= nfcv_emu_alloc_signals(&nfcv_data->emu_air, &nfcv_data->emu_air.signals_low, 4); + + if(!success) { + FURI_LOG_E(TAG, "Failed to allocate signals"); + return false; + } + + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_SOF, + nfcv_data->emu_air.signals_high.nfcv_resp_sof); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_BIT0, + nfcv_data->emu_air.signals_high.nfcv_resp_zero); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_BIT1, + nfcv_data->emu_air.signals_high.nfcv_resp_one); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_EOF, + nfcv_data->emu_air.signals_high.nfcv_resp_eof); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_LOW_SOF, + nfcv_data->emu_air.signals_low.nfcv_resp_sof); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_LOW_BIT0, + nfcv_data->emu_air.signals_low.nfcv_resp_zero); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_LOW_BIT1, + nfcv_data->emu_air.signals_low.nfcv_resp_one); + digital_sequence_set_signal( + nfcv_data->emu_air.nfcv_signal, + NFCV_SIG_LOW_EOF, + nfcv_data->emu_air.signals_low.nfcv_resp_eof); + + return true; +} + +void nfcv_emu_free(NfcVData* nfcv_data) { + furi_assert(nfcv_data); + + if(nfcv_data->frame) { + free(nfcv_data->frame); + } + if(nfcv_data->emu_protocol_ctx) { + free(nfcv_data->emu_protocol_ctx); + } + if(nfcv_data->emu_air.nfcv_resp_unmod) { + digital_signal_free(nfcv_data->emu_air.nfcv_resp_unmod); + } + if(nfcv_data->emu_air.nfcv_resp_pulse) { + digital_signal_free(nfcv_data->emu_air.nfcv_resp_pulse); + } + if(nfcv_data->emu_air.nfcv_resp_half_pulse) { + digital_signal_free(nfcv_data->emu_air.nfcv_resp_half_pulse); + } + if(nfcv_data->emu_air.nfcv_signal) { + digital_sequence_free(nfcv_data->emu_air.nfcv_signal); + } + if(nfcv_data->emu_air.reader_signal) { + // Stop pulse reader and disable bus before free + pulse_reader_stop(nfcv_data->emu_air.reader_signal); + // Free pulse reader + pulse_reader_free(nfcv_data->emu_air.reader_signal); + } + + nfcv_data->frame = NULL; + nfcv_data->emu_air.nfcv_resp_unmod = NULL; + nfcv_data->emu_air.nfcv_resp_pulse = NULL; + nfcv_data->emu_air.nfcv_resp_half_pulse = NULL; + nfcv_data->emu_air.nfcv_signal = NULL; + nfcv_data->emu_air.reader_signal = NULL; + + nfcv_emu_free_signals(&nfcv_data->emu_air.signals_high); + nfcv_emu_free_signals(&nfcv_data->emu_air.signals_low); +} + +void nfcv_emu_send( + FuriHalNfcTxRxContext* tx_rx, + NfcVData* nfcv, + uint8_t* data, + uint8_t length, + NfcVSendFlags flags, + uint32_t send_time) { + furi_assert(tx_rx); + furi_assert(nfcv); + + /* picked default value (0) to match the most common format */ + if(flags == NfcVSendFlagsNormal) { + flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof | + NfcVSendFlagsOneSubcarrier | NfcVSendFlagsHighRate; + } + + if(flags & NfcVSendFlagsCrc) { + nfcv_crc(data, length); + length += 2; + } + + /* depending on the request flags, send with high or low rate */ + uint32_t bit0 = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_BIT0 : NFCV_SIG_LOW_BIT0; + uint32_t bit1 = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_BIT1 : NFCV_SIG_LOW_BIT1; + uint32_t sof = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_SOF : NFCV_SIG_LOW_SOF; + uint32_t eof = (flags & NfcVSendFlagsHighRate) ? NFCV_SIG_EOF : NFCV_SIG_LOW_EOF; + + digital_sequence_clear(nfcv->emu_air.nfcv_signal); + + if(flags & NfcVSendFlagsSof) { + digital_sequence_add(nfcv->emu_air.nfcv_signal, sof); + } + + for(int bit_total = 0; bit_total < length * 8; bit_total++) { + uint32_t byte_pos = bit_total / 8; + uint32_t bit_pos = bit_total % 8; + uint8_t bit_val = 0x01 << bit_pos; + + digital_sequence_add(nfcv->emu_air.nfcv_signal, (data[byte_pos] & bit_val) ? bit1 : bit0); + } + + if(flags & NfcVSendFlagsEof) { + digital_sequence_add(nfcv->emu_air.nfcv_signal, eof); + } + + furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); + digital_sequence_set_sendtime(nfcv->emu_air.nfcv_signal, send_time); + digital_sequence_send(nfcv->emu_air.nfcv_signal); + furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); + + if(tx_rx->sniff_tx) { + tx_rx->sniff_tx(data, length * 8, false, tx_rx->sniff_context); + } +} + +static void nfcv_revuidcpy(uint8_t* dst, uint8_t* src) { + for(int pos = 0; pos < NFCV_UID_LENGTH; pos++) { + dst[pos] = src[NFCV_UID_LENGTH - 1 - pos]; + } +} + +static int nfcv_revuidcmp(uint8_t* dst, uint8_t* src) { + for(int pos = 0; pos < NFCV_UID_LENGTH; pos++) { + if(dst[pos] != src[NFCV_UID_LENGTH - 1 - pos]) { + return 1; + } + } + return 0; +} + +void nfcv_emu_handle_packet( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + + if(nfcv_data->frame_length < 2) { + return; + } + + if(nfcv_data->echo_mode) { + nfcv_emu_send( + tx_rx, + nfcv_data, + nfcv_data->frame, + nfcv_data->frame_length, + NfcVSendFlagsSof | NfcVSendFlagsHighRate | NfcVSendFlagsEof, + ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO data"); + return; + } + + /* parse the frame data for the upcoming part 3 handling */ + ctx->flags = nfcv_data->frame[0]; + ctx->command = nfcv_data->frame[1]; + ctx->selected = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && (ctx->flags & NFCV_REQ_FLAG_SELECT); + ctx->addressed = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && + (ctx->flags & NFCV_REQ_FLAG_ADDRESS); + ctx->advanced = (ctx->command >= NFCV_CMD_ADVANCED); + ctx->address_offset = 2 + (ctx->advanced ? 1 : 0); + ctx->payload_offset = ctx->address_offset + (ctx->addressed ? NFCV_UID_LENGTH : 0); + ctx->response_flags = NfcVSendFlagsSof | NfcVSendFlagsCrc | NfcVSendFlagsEof; + ctx->send_time = nfcv_data->eof_timestamp + NFCV_FDT_FC(4380); + + if(ctx->flags & NFCV_REQ_FLAG_DATA_RATE) { + ctx->response_flags |= NfcVSendFlagsHighRate; + } + if(ctx->flags & NFCV_REQ_FLAG_SUB_CARRIER) { + ctx->response_flags |= NfcVSendFlagsTwoSubcarrier; + } + + if(ctx->payload_offset + 2 > nfcv_data->frame_length) { +#ifdef NFCV_VERBOSE + FURI_LOG_D(TAG, "command 0x%02X, but packet is too short", ctx->command); +#endif + return; + } + + /* standard behavior is implemented */ + if(ctx->addressed) { + uint8_t* address = &nfcv_data->frame[ctx->address_offset]; + if(nfcv_revuidcmp(address, nfc_data->uid)) { +#ifdef NFCV_VERBOSE + FURI_LOG_D(TAG, "addressed command 0x%02X, but not for us:", ctx->command); + FURI_LOG_D( + TAG, + " dest: %02X%02X%02X%02X%02X%02X%02X%02X", + address[7], + address[6], + address[5], + address[4], + address[3], + address[2], + address[1], + address[0]); + FURI_LOG_D( + TAG, + " our UID: %02X%02X%02X%02X%02X%02X%02X%02X", + nfc_data->uid[0], + nfc_data->uid[1], + nfc_data->uid[2], + nfc_data->uid[3], + nfc_data->uid[4], + nfc_data->uid[5], + nfc_data->uid[6], + nfc_data->uid[7]); +#endif + return; + } + } + + if(ctx->selected && !nfcv_data->selected) { +#ifdef NFCV_VERBOSE + FURI_LOG_D( + TAG, + "selected card shall execute command 0x%02X, but we were not selected", + ctx->command); +#endif + return; + } + + /* then give control to the card subtype specific protocol filter */ + if(ctx->emu_protocol_filter != NULL) { + if(ctx->emu_protocol_filter(tx_rx, nfc_data, nfcv_data)) { + if(strlen(nfcv_data->last_command) > 0) { +#ifdef NFCV_VERBOSE + FURI_LOG_D( + TAG, "Received command %s (handled by filter)", nfcv_data->last_command); +#endif + } + return; + } + } + + switch(ctx->command) { + case NFCV_CMD_INVENTORY: { + bool respond = false; + + if(ctx->flags & NFCV_REQ_FLAG_AFI) { + uint8_t afi = nfcv_data->frame[ctx->payload_offset]; + + uint8_t family = (afi & 0xF0); + uint8_t subfamily = (afi & 0x0F); + + if(family) { + if(subfamily) { + /* selected family and subfamily only */ + if(afi == nfcv_data->afi) { + respond = true; + } + } else { + /* selected family, any subfamily */ + if(family == (nfcv_data->afi & 0xf0)) { + respond = true; + } + } + } else { + if(subfamily) { + /* proprietary subfamily only */ + if(afi == nfcv_data->afi) { + respond = true; + } + } else { + /* all families and subfamilies */ + respond = true; + } + } + + } else { + respond = true; + } + + if(!nfcv_data->quiet && respond) { + int buffer_pos = 0; + ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; + ctx->response_buffer[buffer_pos++] = nfcv_data->dsfid; + nfcv_revuidcpy(&ctx->response_buffer[buffer_pos], nfc_data->uid); + buffer_pos += NFCV_UID_LENGTH; + + nfcv_emu_send( + tx_rx, + nfcv_data, + ctx->response_buffer, + buffer_pos, + ctx->response_flags, + ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "INVENTORY"); + } else { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "INVENTORY (quiet)"); + } + break; + } + + case NFCV_CMD_STAY_QUIET: { + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "STAYQUIET"); + nfcv_data->quiet = true; + break; + } + + case NFCV_CMD_LOCK_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + nfcv_data->security_status[block] |= 0x01; + nfcv_data->modified = true; + + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK BLOCK %d", block); + break; + } + + case NFCV_CMD_WRITE_DSFID: { + uint8_t id = nfcv_data->frame[ctx->payload_offset]; + + if(!(nfcv_data->security_status[0] & NfcVLockBitDsfid)) { + nfcv_data->dsfid = id; + nfcv_data->modified = true; + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE DSFID %02X", id); + break; + } + + case NFCV_CMD_WRITE_AFI: { + uint8_t id = nfcv_data->frame[ctx->payload_offset]; + + if(!(nfcv_data->security_status[0] & NfcVLockBitAfi)) { + nfcv_data->afi = id; + nfcv_data->modified = true; + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE AFI %02X", id); + break; + } + + case NFCV_CMD_LOCK_DSFID: { + if(!(nfcv_data->security_status[0] & NfcVLockBitDsfid)) { + nfcv_data->security_status[0] |= NfcVLockBitDsfid; + nfcv_data->modified = true; + + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK DSFID"); + break; + } + + case NFCV_CMD_LOCK_AFI: { + if(!(nfcv_data->security_status[0] & NfcVLockBitAfi)) { + nfcv_data->security_status[0] |= NfcVLockBitAfi; + nfcv_data->modified = true; + + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } + + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "LOCK AFI"); + break; + } + + case NFCV_CMD_SELECT: { + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_data->selected = true; + nfcv_data->quiet = false; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SELECT"); + break; + } + + case NFCV_CMD_RESET_TO_READY: { + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_data->quiet = false; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "RESET_TO_READY"); + break; + } + + case NFCV_CMD_READ_MULTI_BLOCK: + case NFCV_CMD_READ_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + int blocks = 1; + + if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) { + blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; + } + + /* limit the maximum block count, underflow accepted */ + if(block + blocks > nfcv_data->block_num) { + blocks = nfcv_data->block_num - block; + } + + /* only respond with the valid blocks, if there are any */ + if(blocks > 0) { + uint8_t buffer_pos = 0; + + ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; + + for(int block_index = 0; block_index < blocks; block_index++) { + int block_current = block + block_index; + /* prepend security status */ + if(ctx->flags & NFCV_REQ_FLAG_OPTION) { + ctx->response_buffer[buffer_pos++] = + nfcv_data->security_status[1 + block_current]; + } + /* then the data block */ + memcpy( + &ctx->response_buffer[buffer_pos], + &nfcv_data->data[nfcv_data->block_size * block_current], + nfcv_data->block_size); + buffer_pos += nfcv_data->block_size; + } + nfcv_emu_send( + tx_rx, + nfcv_data, + ctx->response_buffer, + buffer_pos, + ctx->response_flags, + ctx->send_time); + } else { + /* reply with an error only in addressed or selected mode */ + if(ctx->addressed || ctx->selected) { + ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; + ctx->response_buffer[1] = NFCV_ERROR_GENERIC; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time); + } + } + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ BLOCK %d", block); + + break; + } + + case NFCV_CMD_WRITE_MULTI_BLOCK: + case NFCV_CMD_WRITE_BLOCK: { + uint8_t blocks = 1; + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t data_pos = ctx->payload_offset + 1; + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + blocks = nfcv_data->frame[data_pos] + 1; + data_pos++; + } + + uint8_t* data = &nfcv_data->frame[data_pos]; + uint32_t data_len = nfcv_data->block_size * blocks; + + if((block + blocks) <= nfcv_data->block_num && + (data_pos + data_len + 2) == nfcv_data->frame_length) { + ctx->response_buffer[0] = NFCV_NOERROR; + memcpy( + &nfcv_data->data[nfcv_data->block_size * block], + &nfcv_data->frame[data_pos], + data_len); + nfcv_data->modified = true; + + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + } else { + ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; + ctx->response_buffer[1] = NFCV_ERROR_GENERIC; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 2, ctx->response_flags, ctx->send_time); + } + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "WRITE MULTI BLOCK %d, %d blocks", + block, + blocks); + } else { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "WRITE BLOCK %d <- %02X %02X %02X %02X", + block, + data[0], + data[1], + data[2], + data[3]); + } + break; + } + + case NFCV_CMD_GET_SYSTEM_INFO: { + int buffer_pos = 0; + ctx->response_buffer[buffer_pos++] = NFCV_NOERROR; + ctx->response_buffer[buffer_pos++] = NFCV_SYSINFO_FLAG_DSFID | NFCV_SYSINFO_FLAG_AFI | + NFCV_SYSINFO_FLAG_MEMSIZE | NFCV_SYSINFO_FLAG_ICREF; + nfcv_revuidcpy(&ctx->response_buffer[buffer_pos], nfc_data->uid); + buffer_pos += NFCV_UID_LENGTH; + ctx->response_buffer[buffer_pos++] = nfcv_data->dsfid; /* DSFID */ + ctx->response_buffer[buffer_pos++] = nfcv_data->afi; /* AFI */ + ctx->response_buffer[buffer_pos++] = nfcv_data->block_num - 1; /* number of blocks */ + ctx->response_buffer[buffer_pos++] = nfcv_data->block_size - 1; /* block size */ + ctx->response_buffer[buffer_pos++] = nfcv_data->ic_ref; /* IC reference */ + + nfcv_emu_send( + tx_rx, + nfcv_data, + ctx->response_buffer, + buffer_pos, + ctx->response_flags, + ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "SYSTEMINFO"); + break; + } + + case NFCV_CMD_CUST_ECHO_MODE: { + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_data->echo_mode = true; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO mode"); + break; + } + + case NFCV_CMD_CUST_ECHO_DATA: { + nfcv_emu_send( + tx_rx, + nfcv_data, + &nfcv_data->frame[ctx->payload_offset], + nfcv_data->frame_length - ctx->payload_offset - 2, + NfcVSendFlagsSof | NfcVSendFlagsHighRate | NfcVSendFlagsEof, + ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "ECHO data"); + break; + } + + default: + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "unsupported: %02X", + ctx->command); + break; + } + + if(strlen(nfcv_data->last_command) > 0) { +#ifdef NFCV_VERBOSE + FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command); +#endif + } +} + +void nfcv_emu_sniff_packet( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + + if(nfcv_data->frame_length < 2) { + return; + } + + /* parse the frame data for the upcoming part 3 handling */ + ctx->flags = nfcv_data->frame[0]; + ctx->command = nfcv_data->frame[1]; + ctx->selected = (ctx->flags & NFCV_REQ_FLAG_SELECT); + ctx->addressed = !(ctx->flags & NFCV_REQ_FLAG_INVENTORY) && + (ctx->flags & NFCV_REQ_FLAG_ADDRESS); + ctx->advanced = (ctx->command >= NFCV_CMD_ADVANCED); + ctx->address_offset = 2 + (ctx->advanced ? 1 : 0); + ctx->payload_offset = ctx->address_offset + (ctx->addressed ? NFCV_UID_LENGTH : 0); + + char flags_string[5]; + + snprintf( + flags_string, + 5, + "%c%c%c%d", + (ctx->flags & NFCV_REQ_FLAG_INVENTORY) ? + 'I' : + (ctx->addressed ? 'A' : (ctx->selected ? 'S' : '*')), + ctx->advanced ? 'X' : ' ', + (ctx->flags & NFCV_REQ_FLAG_DATA_RATE) ? 'h' : 'l', + (ctx->flags & NFCV_REQ_FLAG_SUB_CARRIER) ? 2 : 1); + + switch(ctx->command) { + case NFCV_CMD_INVENTORY: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s INVENTORY", flags_string); + break; + } + + case NFCV_CMD_STAY_QUIET: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s STAYQUIET", flags_string); + nfcv_data->quiet = true; + break; + } + + case NFCV_CMD_LOCK_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s LOCK %d", + flags_string, + block); + break; + } + + case NFCV_CMD_WRITE_DSFID: { + uint8_t id = nfcv_data->frame[ctx->payload_offset]; + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s WR DSFID %d", + flags_string, + id); + break; + } + + case NFCV_CMD_WRITE_AFI: { + uint8_t id = nfcv_data->frame[ctx->payload_offset]; + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s WR AFI %d", + flags_string, + id); + break; + } + + case NFCV_CMD_LOCK_DSFID: { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s LOCK DSFID", + flags_string); + break; + } + + case NFCV_CMD_LOCK_AFI: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s LOCK AFI", flags_string); + break; + } + + case NFCV_CMD_SELECT: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s SELECT", flags_string); + break; + } + + case NFCV_CMD_RESET_TO_READY: { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "%s RESET", flags_string); + break; + } + + case NFCV_CMD_READ_MULTI_BLOCK: + case NFCV_CMD_READ_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t blocks = 1; + + if(ctx->command == NFCV_CMD_READ_MULTI_BLOCK) { + blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; + } + + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s READ %d cnt: %d", + flags_string, + block, + blocks); + + break; + } + + case NFCV_CMD_WRITE_MULTI_BLOCK: + case NFCV_CMD_WRITE_BLOCK: { + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t blocks = 1; + uint8_t data_pos = 1; + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + blocks = nfcv_data->frame[ctx->payload_offset + 1] + 1; + data_pos++; + } + + uint8_t* data = &nfcv_data->frame[ctx->payload_offset + data_pos]; + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s WRITE %d, cnd %d", + flags_string, + block, + blocks); + } else { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s WRITE %d %02X %02X %02X %02X", + flags_string, + block, + data[0], + data[1], + data[2], + data[3]); + } + break; + } + + case NFCV_CMD_GET_SYSTEM_INFO: { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s SYSTEMINFO", + flags_string); + break; + } + + default: + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "%s unsupported: %02X", + flags_string, + ctx->command); + break; + } + + if(strlen(nfcv_data->last_command) > 0) { + FURI_LOG_D(TAG, "Received command %s", nfcv_data->last_command); + } +} + +void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + furi_assert(nfc_data); + furi_assert(nfcv_data); + + if(!nfcv_emu_alloc(nfcv_data)) { + FURI_LOG_E(TAG, "Failed to allocate structures"); + nfcv_data->ready = false; + return; + } + + strcpy(nfcv_data->last_command, ""); + nfcv_data->quiet = false; + nfcv_data->selected = false; + nfcv_data->modified = false; + + /* everything is initialized */ + nfcv_data->ready = true; + + /* ensure the GPIO is already in unmodulated state */ + furi_hal_gpio_init(&gpio_spi_r_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh); + furi_hal_gpio_write(&gpio_spi_r_mosi, GPIO_LEVEL_UNMODULATED); + + rfal_platform_spi_acquire(); + /* stop operation to configure for transparent and passive mode */ + st25r3916ExecuteCommand(ST25R3916_CMD_STOP); + /* set enable, rx_enable and field detector enable */ + st25r3916WriteRegister( + ST25R3916_REG_OP_CONTROL, + ST25R3916_REG_OP_CONTROL_en | ST25R3916_REG_OP_CONTROL_rx_en | + ST25R3916_REG_OP_CONTROL_en_fd_auto_efd); + /* explicitely set the modulation resistor in case system config changes for some reason */ + st25r3916WriteRegister( + ST25R3916_REG_PT_MOD, + (0 << ST25R3916_REG_PT_MOD_ptm_res_shift) | (15 << ST25R3916_REG_PT_MOD_pt_res_shift)); + /* target mode: target, other fields do not have any effect as we use transparent mode */ + st25r3916WriteRegister(ST25R3916_REG_MODE, ST25R3916_REG_MODE_targ); + /* let us modulate the field using MOSI, read ASK modulation using IRQ */ + st25r3916ExecuteCommand(ST25R3916_CMD_TRANSPARENT_MODE); + + furi_hal_spi_bus_handle_deinit(&furi_hal_spi_bus_handle_nfc); + + /* if not set already, initialize the default protocol handler */ + if(!nfcv_data->emu_protocol_ctx) { + nfcv_data->emu_protocol_ctx = malloc(sizeof(NfcVEmuProtocolCtx)); + if(nfcv_data->sub_type == NfcVTypeSniff) { + nfcv_data->emu_protocol_handler = &nfcv_emu_sniff_packet; + } else { + nfcv_data->emu_protocol_handler = &nfcv_emu_handle_packet; + } + } + + FURI_LOG_D(TAG, "Starting NfcV emulation"); + FURI_LOG_D( + TAG, + " UID: %02X %02X %02X %02X %02X %02X %02X %02X", + nfc_data->uid[0], + nfc_data->uid[1], + nfc_data->uid[2], + nfc_data->uid[3], + nfc_data->uid[4], + nfc_data->uid[5], + nfc_data->uid[6], + nfc_data->uid[7]); + + switch(nfcv_data->sub_type) { + case NfcVTypeSlixL: + FURI_LOG_D(TAG, " Card type: SLIX-L"); + slix_l_prepare(nfcv_data); + break; + case NfcVTypeSlixS: + FURI_LOG_D(TAG, " Card type: SLIX-S"); + slix_s_prepare(nfcv_data); + break; + case NfcVTypeSlix2: + FURI_LOG_D(TAG, " Card type: SLIX2"); + slix2_prepare(nfcv_data); + break; + case NfcVTypeSlix: + FURI_LOG_D(TAG, " Card type: SLIX"); + slix_prepare(nfcv_data); + break; + case NfcVTypePlain: + FURI_LOG_D(TAG, " Card type: Plain"); + break; + case NfcVTypeSniff: + FURI_LOG_D(TAG, " Card type: Sniffing"); + break; + } + + /* allocate a 512 edge buffer, more than enough */ + nfcv_data->emu_air.reader_signal = + pulse_reader_alloc(&gpio_nfc_irq_rfid_pull, NFCV_PULSE_BUFFER); + /* timebase shall be 1 ns */ + pulse_reader_set_timebase(nfcv_data->emu_air.reader_signal, PulseReaderUnitNanosecond); + /* and configure to already calculate the number of bits */ + pulse_reader_set_bittime(nfcv_data->emu_air.reader_signal, NFCV_PULSE_DURATION_NS); + /* this IO is fed into the µC via a diode, so we need a pulldown */ + pulse_reader_set_pull(nfcv_data->emu_air.reader_signal, GpioPullDown); + + /* start sampling */ + pulse_reader_start(nfcv_data->emu_air.reader_signal); +} + +void nfcv_emu_deinit(NfcVData* nfcv_data) { + furi_assert(nfcv_data); + + furi_hal_spi_bus_handle_init(&furi_hal_spi_bus_handle_nfc); + nfcv_emu_free(nfcv_data); + + if(nfcv_data->emu_protocol_ctx) { + free(nfcv_data->emu_protocol_ctx); + nfcv_data->emu_protocol_ctx = NULL; + } + + /* set registers back to how we found them */ + st25r3916WriteRegister(ST25R3916_REG_OP_CONTROL, 0x00); + st25r3916WriteRegister(ST25R3916_REG_MODE, 0x08); + rfal_platform_spi_release(); +} + +bool nfcv_emu_loop( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + NfcVData* nfcv_data, + uint32_t timeout_ms) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data); + + bool ret = false; + uint32_t frame_state = NFCV_FRAME_STATE_SOF1; + uint32_t periods_previous = 0; + uint32_t frame_pos = 0; + uint32_t byte_value = 0; + uint32_t bits_received = 0; + uint32_t timeout = timeout_ms * 1000; + bool wait_for_pulse = false; + + if(!nfcv_data->ready) { + return false; + } + +#ifdef NFCV_DIAGNOSTIC_DUMPS + uint8_t period_buffer[NFCV_DIAGNOSTIC_DUMP_SIZE]; + uint32_t period_buffer_pos = 0; +#endif + + while(true) { + uint32_t periods = pulse_reader_receive(nfcv_data->emu_air.reader_signal, timeout); + uint32_t timestamp = DWT->CYCCNT; + + /* when timed out, reset to SOF state */ + if(periods == PULSE_READER_NO_EDGE || periods == PULSE_READER_LOST_EDGE) { + break; + } + +#ifdef NFCV_DIAGNOSTIC_DUMPS + if(period_buffer_pos < sizeof(period_buffer)) { + period_buffer[period_buffer_pos++] = periods; + } +#endif + + /* short helper for detecting a pulse position */ + if(wait_for_pulse) { + wait_for_pulse = false; + if(periods != 1) { + frame_state = NFCV_FRAME_STATE_RESET; + } + continue; + } + + switch(frame_state) { + case NFCV_FRAME_STATE_SOF1: + if(periods == 1) { + frame_state = NFCV_FRAME_STATE_SOF2; + } else { + frame_state = NFCV_FRAME_STATE_SOF1; + break; + } + break; + + case NFCV_FRAME_STATE_SOF2: + /* waiting for the second low period, telling us about coding */ + if(periods == 6) { + frame_state = NFCV_FRAME_STATE_CODING_256; + periods_previous = 0; + wait_for_pulse = true; + } else if(periods == 4) { + frame_state = NFCV_FRAME_STATE_CODING_4; + periods_previous = 2; + wait_for_pulse = true; + } else { + frame_state = NFCV_FRAME_STATE_RESET; + } + break; + + case NFCV_FRAME_STATE_CODING_256: + if(periods_previous > periods) { + frame_state = NFCV_FRAME_STATE_RESET; + break; + } + + /* previous symbol left us with some pulse periods */ + periods -= periods_previous; + + if(periods > 512) { + frame_state = NFCV_FRAME_STATE_RESET; + break; + } else if(periods == 2) { + frame_state = NFCV_FRAME_STATE_EOF; + break; + } + + periods_previous = 512 - (periods + 1); + byte_value = (periods - 1) / 2; + if(frame_pos < NFCV_FRAMESIZE_MAX) { + nfcv_data->frame[frame_pos++] = (uint8_t)byte_value; + } + + wait_for_pulse = true; + + break; + + case NFCV_FRAME_STATE_CODING_4: + if(periods_previous > periods) { + frame_state = NFCV_FRAME_STATE_RESET; + break; + } + + /* previous symbol left us with some pulse periods */ + periods -= periods_previous; + periods_previous = 0; + + byte_value >>= 2; + bits_received += 2; + + if(periods == 1) { + byte_value |= 0x00 << 6; // -V684 + periods_previous = 6; + } else if(periods == 3) { + byte_value |= 0x01 << 6; + periods_previous = 4; + } else if(periods == 5) { + byte_value |= 0x02 << 6; + periods_previous = 2; + } else if(periods == 7) { + byte_value |= 0x03 << 6; + periods_previous = 0; + } else if(periods == 2) { + frame_state = NFCV_FRAME_STATE_EOF; + break; + } else { + frame_state = NFCV_FRAME_STATE_RESET; + break; + } + + if(bits_received >= 8) { + if(frame_pos < NFCV_FRAMESIZE_MAX) { + nfcv_data->frame[frame_pos++] = (uint8_t)byte_value; + } + bits_received = 0; + } + wait_for_pulse = true; + break; + } + + /* post-state-machine cleanup and reset */ + if(frame_state == NFCV_FRAME_STATE_RESET) { + frame_state = NFCV_FRAME_STATE_SOF1; + } else if(frame_state == NFCV_FRAME_STATE_EOF) { + nfcv_data->frame_length = frame_pos; + nfcv_data->eof_timestamp = timestamp; + break; + } + } + + if(frame_state == NFCV_FRAME_STATE_EOF) { + /* we know that this code uses TIM2, so stop pulse reader */ + pulse_reader_stop(nfcv_data->emu_air.reader_signal); + if(tx_rx->sniff_rx) { + tx_rx->sniff_rx(nfcv_data->frame, frame_pos * 8, false, tx_rx->sniff_context); + } + nfcv_data->emu_protocol_handler(tx_rx, nfc_data, nfcv_data); + + pulse_reader_start(nfcv_data->emu_air.reader_signal); + ret = true; + + } +#ifdef NFCV_VERBOSE + else { + if(frame_state != NFCV_FRAME_STATE_SOF1) { + FURI_LOG_T(TAG, "leaving while in state: %lu", frame_state); + } + } +#endif + +#ifdef NFCV_DIAGNOSTIC_DUMPS + if(period_buffer_pos) { + FURI_LOG_T(TAG, "pulses:"); + for(uint32_t pos = 0; pos < period_buffer_pos; pos++) { + FURI_LOG_T(TAG, " #%lu: %u", pos, period_buffer[pos]); + } + } +#endif + + return ret; +} diff --git a/lib/nfc/protocols/nfcv.h b/lib/nfc/protocols/nfcv.h new file mode 100644 index 0000000..e4139de --- /dev/null +++ b/lib/nfc/protocols/nfcv.h @@ -0,0 +1,334 @@ +#pragma once + +#include +#include + +#include +#include +#include "nfc_util.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* true: modulating releases load, false: modulating adds load resistor to field coil */ +#define NFCV_LOAD_MODULATION_POLARITY (false) + +#define NFCV_FC (13560000.0f) /* MHz */ +#define NFCV_RESP_SUBC1_PULSE_32 (1.0f / (NFCV_FC / 32) / 2.0f) /* 1.1799 µs */ +#define NFCV_RESP_SUBC1_UNMOD_256 (256.0f / NFCV_FC) /* 18.8791 µs */ +#define NFCV_PULSE_DURATION_NS (128.0f * 1000000000.0f / NFCV_FC) + +/* ISO/IEC 15693-3:2019(E) 10.4.12: maximum number of blocks is defined as 256 */ +#define NFCV_BLOCKS_MAX 256 +/* ISO/IEC 15693-3:2019(E) 10.4.12: maximum size of blocks is defined as 32 */ +#define NFCV_BLOCKSIZE_MAX 32 +/* the resulting memory size a card can have */ +#define NFCV_MEMSIZE_MAX (NFCV_BLOCKS_MAX * NFCV_BLOCKSIZE_MAX) +/* ISO/IEC 15693-3:2019(E) 7.1b: standard allows up to 8192, the maxium frame length that we are expected to receive/send is less */ +#define NFCV_FRAMESIZE_MAX (1 + NFCV_MEMSIZE_MAX + NFCV_BLOCKS_MAX) + +/* maximum string length for log messages */ +#define NFCV_LOG_STR_LEN 128 +/* maximum of pulses to be buffered by pulse reader */ +#define NFCV_PULSE_BUFFER 512 + +//#define NFCV_DIAGNOSTIC_DUMPS +//#define NFCV_DIAGNOSTIC_DUMP_SIZE 256 +//#define NFCV_VERBOSE + +/* helpers to calculate the send time based on DWT->CYCCNT */ +#define NFCV_FDT_USEC(usec) ((usec)*64) +#define NFCV_FDT_FC(ticks) ((ticks)*6400 / 1356) + +/* state machine when receiving frame bits */ +#define NFCV_FRAME_STATE_SOF1 0 +#define NFCV_FRAME_STATE_SOF2 1 +#define NFCV_FRAME_STATE_CODING_4 2 +#define NFCV_FRAME_STATE_CODING_256 3 +#define NFCV_FRAME_STATE_EOF 4 +#define NFCV_FRAME_STATE_RESET 5 + +/* sequences for every section of a frame */ +#define NFCV_SIG_SOF 0 +#define NFCV_SIG_BIT0 1 +#define NFCV_SIG_BIT1 2 +#define NFCV_SIG_EOF 3 +#define NFCV_SIG_LOW_SOF 4 +#define NFCV_SIG_LOW_BIT0 5 +#define NFCV_SIG_LOW_BIT1 6 +#define NFCV_SIG_LOW_EOF 7 + +/* various constants */ +#define NFCV_COMMAND_RETRIES 5 +#define NFCV_UID_LENGTH 8 + +/* ISO15693 protocol flags */ +typedef enum { + /* ISO15693 protocol flags when INVENTORY is NOT set */ + NFCV_REQ_FLAG_SUB_CARRIER = (1 << 0), + NFCV_REQ_FLAG_DATA_RATE = (1 << 1), + NFCV_REQ_FLAG_INVENTORY = (1 << 2), + NFCV_REQ_FLAG_PROTOCOL_EXT = (1 << 3), + NFCV_REQ_FLAG_SELECT = (1 << 4), + NFCV_REQ_FLAG_ADDRESS = (1 << 5), + NFCV_REQ_FLAG_OPTION = (1 << 6), + /* ISO15693 protocol flags when INVENTORY flag is set */ + NFCV_REQ_FLAG_AFI = (1 << 4), + NFCV_REQ_FLAG_NB_SLOTS = (1 << 5) +} NfcVRequestFlags; + +/* ISO15693 protocol flags */ +typedef enum { + NFCV_RES_FLAG_ERROR = (1 << 0), + NFCV_RES_FLAG_VALIDITY = (1 << 1), + NFCV_RES_FLAG_FINAL = (1 << 2), + NFCV_RES_FLAG_PROTOCOL_EXT = (1 << 3), + NFCV_RES_FLAG_SEC_LEN1 = (1 << 4), + NFCV_RES_FLAG_SEC_LEN2 = (1 << 5), + NFCV_RES_FLAG_WAIT_EXT = (1 << 6), +} NfcVRsponseFlags; + +/* flags for SYSINFO response */ +typedef enum { + NFCV_SYSINFO_FLAG_DSFID = (1 << 0), + NFCV_SYSINFO_FLAG_AFI = (1 << 1), + NFCV_SYSINFO_FLAG_MEMSIZE = (1 << 2), + NFCV_SYSINFO_FLAG_ICREF = (1 << 3) +} NfcVSysinfoFlags; + +/* ISO15693 command codes */ +typedef enum { + /* mandatory command codes */ + NFCV_CMD_INVENTORY = 0x01, + NFCV_CMD_STAY_QUIET = 0x02, + /* optional command codes */ + NFCV_CMD_READ_BLOCK = 0x20, + NFCV_CMD_WRITE_BLOCK = 0x21, + NFCV_CMD_LOCK_BLOCK = 0x22, + NFCV_CMD_READ_MULTI_BLOCK = 0x23, + NFCV_CMD_WRITE_MULTI_BLOCK = 0x24, + NFCV_CMD_SELECT = 0x25, + NFCV_CMD_RESET_TO_READY = 0x26, + NFCV_CMD_WRITE_AFI = 0x27, + NFCV_CMD_LOCK_AFI = 0x28, + NFCV_CMD_WRITE_DSFID = 0x29, + NFCV_CMD_LOCK_DSFID = 0x2A, + NFCV_CMD_GET_SYSTEM_INFO = 0x2B, + NFCV_CMD_READ_MULTI_SECSTATUS = 0x2C, + /* advanced command codes */ + NFCV_CMD_ADVANCED = 0xA0, + /* flipper zero custom command codes */ + NFCV_CMD_CUST_ECHO_MODE = 0xDE, + NFCV_CMD_CUST_ECHO_DATA = 0xDF +} NfcVCommands; + +/* ISO15693 Response error codes */ +typedef enum { + NFCV_NOERROR = 0x00, + NFCV_ERROR_CMD_NOT_SUP = 0x01, // Command not supported + NFCV_ERROR_CMD_NOT_REC = 0x02, // Command not recognized (eg. parameter error) + NFCV_ERROR_CMD_OPTION = 0x03, // Command option not supported + NFCV_ERROR_GENERIC = 0x0F, // No additional Info about this error + NFCV_ERROR_BLOCK_UNAVAILABLE = 0x10, + NFCV_ERROR_BLOCK_LOCKED_ALREADY = 0x11, // cannot lock again + NFCV_ERROR_BLOCK_LOCKED = 0x12, // cannot be changed + NFCV_ERROR_BLOCK_WRITE = 0x13, // Writing was unsuccessful + NFCV_ERROR_BLOCL_WRITELOCK = 0x14 // Locking was unsuccessful +} NfcVErrorcodes; + +typedef enum { + NfcVLockBitDsfid = 1 << 0, + NfcVLockBitAfi = 1 << 1, + NfcVLockBitEas = 1 << 2, + NfcVLockBitPpl = 1 << 3, +} NfcVLockBits; + +typedef enum { + NfcVAuthMethodManual, + NfcVAuthMethodTonieBox, +} NfcVAuthMethod; + +typedef enum { + NfcVTypePlain = 0, + NfcVTypeSlix = 1, + NfcVTypeSlixS = 2, + NfcVTypeSlixL = 3, + NfcVTypeSlix2 = 4, + NfcVTypeSniff = 255, +} NfcVSubtype; + +typedef enum { + NfcVSendFlagsNormal = 0, + NfcVSendFlagsSof = 1 << 0, + NfcVSendFlagsCrc = 1 << 1, + NfcVSendFlagsEof = 1 << 2, + NfcVSendFlagsOneSubcarrier = 0, + NfcVSendFlagsTwoSubcarrier = 1 << 3, + NfcVSendFlagsLowRate = 0, + NfcVSendFlagsHighRate = 1 << 4 +} NfcVSendFlags; + +/* SLIX specific config flags */ +typedef enum { + NfcVSlixDataFlagsNone = 0, + NfcVSlixDataFlagsHasKeyRead = 1 << 0, + NfcVSlixDataFlagsHasKeyWrite = 1 << 1, + NfcVSlixDataFlagsHasKeyPrivacy = 1 << 2, + NfcVSlixDataFlagsHasKeyDestroy = 1 << 3, + NfcVSlixDataFlagsHasKeyEas = 1 << 4, + NfcVSlixDataFlagsValidKeyRead = 1 << 8, + NfcVSlixDataFlagsValidKeyWrite = 1 << 9, + NfcVSlixDataFlagsValidKeyPrivacy = 1 << 10, + NfcVSlixDataFlagsValidKeyDestroy = 1 << 11, + NfcVSlixDataFlagsValidKeyEas = 1 << 12, + NfcVSlixDataFlagsPrivacy = 1 << 16, + NfcVSlixDataFlagsDestroyed = 1 << 17 +} NfcVSlixDataFlags; + +/* abstract the file read/write operations for all SLIX types to reduce duplicated code */ +typedef enum { + SlixFeatureRead = 1 << 0, + SlixFeatureWrite = 1 << 1, + SlixFeaturePrivacy = 1 << 2, + SlixFeatureDestroy = 1 << 3, + SlixFeatureEas = 1 << 4, + SlixFeatureSignature = 1 << 5, + SlixFeatureProtection = 1 << 6, + + SlixFeatureSlix = SlixFeatureEas, + SlixFeatureSlixS = + (SlixFeatureRead | SlixFeatureWrite | SlixFeaturePrivacy | SlixFeatureDestroy | + SlixFeatureEas), + SlixFeatureSlixL = (SlixFeaturePrivacy | SlixFeatureDestroy | SlixFeatureEas), + SlixFeatureSlix2 = + (SlixFeatureRead | SlixFeatureWrite | SlixFeaturePrivacy | SlixFeatureDestroy | + SlixFeatureEas | SlixFeatureSignature | SlixFeatureProtection), +} SlixTypeFeatures; + +typedef struct { + uint32_t flags; + uint8_t key_read[4]; + uint8_t key_write[4]; + uint8_t key_privacy[4]; + uint8_t key_destroy[4]; + uint8_t key_eas[4]; + uint8_t rand[2]; + uint8_t signature[32]; + /* SLIX2 options */ + uint8_t pp_pointer; + uint8_t pp_condition; +} NfcVSlixData; + +typedef union { + NfcVSlixData slix; +} NfcVSubtypeData; + +typedef struct { + DigitalSignal* nfcv_resp_sof; + DigitalSignal* nfcv_resp_one; + DigitalSignal* nfcv_resp_zero; + DigitalSignal* nfcv_resp_eof; +} NfcVEmuAirSignals; + +typedef struct { + PulseReader* reader_signal; + DigitalSignal* nfcv_resp_pulse; /* pulse length, fc/32 */ + DigitalSignal* nfcv_resp_half_pulse; /* half pulse length, fc/32 */ + DigitalSignal* nfcv_resp_unmod; /* unmodulated length 256/fc */ + NfcVEmuAirSignals signals_high; + NfcVEmuAirSignals signals_low; + DigitalSequence* nfcv_signal; +} NfcVEmuAir; + +typedef void (*NfcVEmuProtocolHandler)( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data); +typedef bool (*NfcVEmuProtocolFilter)( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data); + +/* the default ISO15693 handler context */ +typedef struct { + uint8_t flags; /* ISO15693-3 flags of the header as specified */ + uint8_t command; /* ISO15693-3 command at offset 1 as specified */ + bool selected; /* ISO15693-3 flags: selected frame */ + bool addressed; /* ISO15693-3 flags: addressed frame */ + bool advanced; /* ISO15693-3 command: advanced command */ + uint8_t address_offset; /* ISO15693-3 offset of the address in frame, if addressed is set */ + uint8_t payload_offset; /* ISO15693-3 offset of the payload in frame */ + + uint8_t response_buffer[NFCV_FRAMESIZE_MAX]; /* pre-allocated response buffer */ + NfcVSendFlags response_flags; /* flags to use when sending response */ + uint32_t send_time; /* timestamp when to send the response */ + + NfcVEmuProtocolFilter emu_protocol_filter; +} NfcVEmuProtocolCtx; + +typedef struct { + /* common ISO15693 fields, being specified in ISO15693-3 */ + uint8_t dsfid; + uint8_t afi; + uint8_t ic_ref; + uint16_t block_num; + uint8_t block_size; + uint8_t data[NFCV_MEMSIZE_MAX]; + uint8_t security_status[1 + NFCV_BLOCKS_MAX]; + bool selected; + bool quiet; + + bool modified; + bool ready; + bool echo_mode; + + /* specfic variant infos */ + NfcVSubtype sub_type; + NfcVSubtypeData sub_data; + NfcVAuthMethod auth_method; + + /* precalced air level data */ + NfcVEmuAir emu_air; + + uint8_t* frame; /* [NFCV_FRAMESIZE_MAX] ISO15693-2 incoming raw data from air layer */ + uint8_t frame_length; /* ISO15693-2 length of incoming data */ + uint32_t eof_timestamp; /* ISO15693-2 EOF timestamp, read from DWT->CYCCNT */ + + /* handler for the protocol layer as specified in ISO15693-3 */ + NfcVEmuProtocolHandler emu_protocol_handler; + void* emu_protocol_ctx; + /* runtime data */ + char last_command[NFCV_LOG_STR_LEN]; + char error[NFCV_LOG_STR_LEN]; +} NfcVData; + +typedef struct { + uint16_t blocks_to_read; + int16_t blocks_read; +} NfcVReader; + +ReturnCode nfcv_read_blocks(NfcVReader* reader, NfcVData* data); +ReturnCode nfcv_read_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* data); +ReturnCode nfcv_inventory(uint8_t* uid); +bool nfcv_read_card(NfcVReader* reader, FuriHalNfcDevData* nfc_data, NfcVData* data); + +void nfcv_emu_init(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); +void nfcv_emu_deinit(NfcVData* nfcv_data); +bool nfcv_emu_loop( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + NfcVData* nfcv_data, + uint32_t timeout_ms); +void nfcv_emu_send( + FuriHalNfcTxRxContext* tx_rx, + NfcVData* nfcv, + uint8_t* data, + uint8_t length, + NfcVSendFlags flags, + uint32_t send_time); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/slix.c b/lib/nfc/protocols/slix.c new file mode 100644 index 0000000..dbff2f2 --- /dev/null +++ b/lib/nfc/protocols/slix.c @@ -0,0 +1,784 @@ + +#include +#include "nfcv.h" +#include "slix.h" +#include "nfc_util.h" +#include +#include "furi_hal_nfc.h" +#include + +#define TAG "Slix" + +ReturnCode slix2_read_nxp_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + furi_assert(nfc_data); + furi_assert(nfcv_data); + + uint8_t rxBuf[32]; + uint16_t received = 0; + ReturnCode ret = ERR_NONE; + + FURI_LOG_D(TAG, "Read NXP SYSTEM INFORMATION..."); + + for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { + uint8_t cmd[] = {}; + uint8_t uid[NFCV_UID_LENGTH]; + + /* UID is stored reversed in requests */ + for(int pos = 0; pos < nfc_data->uid_len; pos++) { + uid[pos] = nfc_data->uid[nfc_data->uid_len - 1 - pos]; + } + + ReturnCode ret = rfalNfcvPollerTransceiveReq( + NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION, + RFAL_NFCV_REQ_FLAG_DEFAULT, + NFCV_MANUFACTURER_NXP, + uid, + cmd, + sizeof(cmd), + rxBuf, + sizeof(rxBuf), + &received); + + if(ret == ERR_NONE) { + break; + } + } + + if(ret != ERR_NONE || received != 8) { //-V560 + FURI_LOG_D(TAG, "Failed: %d, %d", ret, received); + return ret; + } + FURI_LOG_D(TAG, "Success..."); + + NfcVSlixData* slix = &nfcv_data->sub_data.slix; + slix->pp_pointer = rxBuf[1]; + slix->pp_condition = rxBuf[2]; + + /* convert NXP's to our internal lock bits format */ + nfcv_data->security_status[0] = 0; + nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitDsfid) ? NfcVLockBitDsfid : 0; + nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitAfi) ? NfcVLockBitAfi : 0; + nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitEas) ? NfcVLockBitEas : 0; + nfcv_data->security_status[0] |= (rxBuf[3] & SlixLockBitPpl) ? NfcVLockBitPpl : 0; + + return ERR_NONE; +} + +ReturnCode slix2_read_signature(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + furi_assert(nfc_data); + furi_assert(nfcv_data); + + uint8_t rxBuf[64]; + uint16_t received = 0; + ReturnCode ret = ERR_NONE; + + FURI_LOG_D(TAG, "Read SIGNATURE..."); + + for(int tries = 0; tries < NFCV_COMMAND_RETRIES; tries++) { + uint8_t cmd[] = {}; + uint8_t uid[NFCV_UID_LENGTH]; + + /* UID is stored reversed in requests */ + for(int pos = 0; pos < nfc_data->uid_len; pos++) { + uid[pos] = nfc_data->uid[nfc_data->uid_len - 1 - pos]; + } + + ReturnCode ret = rfalNfcvPollerTransceiveReq( + NFCV_CMD_NXP_READ_SIGNATURE, + RFAL_NFCV_REQ_FLAG_DEFAULT, + NFCV_MANUFACTURER_NXP, + uid, + cmd, + sizeof(cmd), + rxBuf, + sizeof(rxBuf), + &received); + + if(ret == ERR_NONE) { + break; + } + } + + if(ret != ERR_NONE || received != 33) { //-V560 + FURI_LOG_D(TAG, "Failed: %d, %d", ret, received); + return ret; + } + FURI_LOG_D(TAG, "Success..."); + + NfcVSlixData* slix = &nfcv_data->sub_data.slix; + memcpy(slix->signature, &rxBuf[1], 32); + + return ERR_NONE; +} + +ReturnCode slix2_read_custom(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data) { + ReturnCode ret = ERR_NONE; + + ret = slix2_read_nxp_sysinfo(nfc_data, nfcv_data); + if(ret != ERR_NONE) { + return ret; + } + ret = slix2_read_signature(nfc_data, nfcv_data); + + return ret; +} + +static uint32_t slix_read_be(uint8_t* data, uint32_t length) { + uint32_t value = 0; + + for(uint32_t pos = 0; pos < length; pos++) { + value <<= 8; + value |= data[pos]; + } + + return value; +} + +uint8_t slix_get_ti(FuriHalNfcDevData* nfc_data) { + return (nfc_data->uid[3] >> 3) & 3; +} + +bool slix_check_card_type(FuriHalNfcDevData* nfc_data) { + if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x01) && + slix_get_ti(nfc_data) == 2) { + return true; + } + return false; +} + +bool slix2_check_card_type(FuriHalNfcDevData* nfc_data) { + if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x01) && + slix_get_ti(nfc_data) == 1) { + return true; + } + return false; +} + +bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data) { + if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x02)) { + return true; + } + return false; +} + +bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data) { + if((nfc_data->uid[0] == 0xE0) && (nfc_data->uid[1] == 0x04) && (nfc_data->uid[2] == 0x03)) { + return true; + } + return false; +} + +ReturnCode slix_get_random(NfcVData* data) { + uint16_t received = 0; + uint8_t rxBuf[32]; + + ReturnCode ret = rfalNfcvPollerTransceiveReq( + NFCV_CMD_NXP_GET_RANDOM_NUMBER, + RFAL_NFCV_REQ_FLAG_DEFAULT, + NFCV_MANUFACTURER_NXP, + NULL, + NULL, + 0, + rxBuf, + sizeof(rxBuf), + &received); + + if(ret == ERR_NONE) { + if(received != 3) { + return ERR_PROTO; + } + if(data != NULL) { + data->sub_data.slix.rand[0] = rxBuf[2]; + data->sub_data.slix.rand[1] = rxBuf[1]; + } + } + + return ret; +} + +ReturnCode slix_unlock(NfcVData* data, uint32_t password_id) { + furi_assert(rand); + + uint16_t received = 0; + uint8_t rxBuf[32]; + uint8_t cmd_set_pass[] = { + password_id, + data->sub_data.slix.rand[1], + data->sub_data.slix.rand[0], + data->sub_data.slix.rand[1], + data->sub_data.slix.rand[0]}; + uint8_t* password = NULL; + + switch(password_id) { + case SLIX_PASS_READ: + password = data->sub_data.slix.key_read; + break; + case SLIX_PASS_WRITE: + password = data->sub_data.slix.key_write; + break; + case SLIX_PASS_PRIVACY: + password = data->sub_data.slix.key_privacy; + break; + case SLIX_PASS_DESTROY: + password = data->sub_data.slix.key_destroy; + break; + case SLIX_PASS_EASAFI: + password = data->sub_data.slix.key_eas; + break; + default: + break; + } + + if(!password) { + return ERR_NOTSUPP; + } + + for(int pos = 0; pos < 4; pos++) { + cmd_set_pass[1 + pos] ^= password[3 - pos]; + } + + ReturnCode ret = rfalNfcvPollerTransceiveReq( + NFCV_CMD_NXP_SET_PASSWORD, + RFAL_NFCV_REQ_FLAG_DATA_RATE, + NFCV_MANUFACTURER_NXP, + NULL, + cmd_set_pass, + sizeof(cmd_set_pass), + rxBuf, + sizeof(rxBuf), + &received); + + return ret; +} + +static void slix_generic_pass_infos( + uint8_t password_id, + NfcVSlixData* slix, + uint8_t** password, + uint32_t* flag_valid, + uint32_t* flag_set) { + switch(password_id) { + case SLIX_PASS_READ: + *password = slix->key_read; + *flag_valid = NfcVSlixDataFlagsValidKeyRead; + *flag_set = NfcVSlixDataFlagsHasKeyRead; + break; + case SLIX_PASS_WRITE: + *password = slix->key_write; + *flag_valid = NfcVSlixDataFlagsValidKeyWrite; + *flag_set = NfcVSlixDataFlagsHasKeyWrite; + break; + case SLIX_PASS_PRIVACY: + *password = slix->key_privacy; + *flag_valid = NfcVSlixDataFlagsValidKeyPrivacy; + *flag_set = NfcVSlixDataFlagsHasKeyPrivacy; + break; + case SLIX_PASS_DESTROY: + *password = slix->key_destroy; + *flag_valid = NfcVSlixDataFlagsValidKeyDestroy; + *flag_set = NfcVSlixDataFlagsHasKeyDestroy; + break; + case SLIX_PASS_EASAFI: + *password = slix->key_eas; + *flag_valid = NfcVSlixDataFlagsValidKeyEas; + *flag_set = NfcVSlixDataFlagsHasKeyEas; + break; + default: + break; + } +} + +bool slix_generic_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in, + uint32_t password_supported) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + NfcVSlixData* slix = &nfcv_data->sub_data.slix; + + if((slix->flags & NfcVSlixDataFlagsPrivacy) && + ctx->command != NFCV_CMD_NXP_GET_RANDOM_NUMBER && + ctx->command != NFCV_CMD_NXP_SET_PASSWORD) { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "command 0x%02X ignored, privacy mode", + ctx->command); + FURI_LOG_D(TAG, "%s", nfcv_data->last_command); + return true; + } + + bool handled = false; + + switch(ctx->command) { + case NFCV_CMD_NXP_GET_RANDOM_NUMBER: { + slix->rand[0] = furi_hal_random_get(); + slix->rand[1] = furi_hal_random_get(); + + ctx->response_buffer[0] = NFCV_NOERROR; + ctx->response_buffer[1] = slix->rand[1]; + ctx->response_buffer[2] = slix->rand[0]; + + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 3, ctx->response_flags, ctx->send_time); + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "GET_RANDOM_NUMBER -> 0x%02X%02X", + slix->rand[0], + slix->rand[1]); + + handled = true; + break; + } + + case NFCV_CMD_NXP_SET_PASSWORD: { + /* the password to be set is the first parameter */ + uint8_t password_id = nfcv_data->frame[ctx->payload_offset]; + /* right after that is the XORed password */ + uint8_t* password_xored = &nfcv_data->frame[ctx->payload_offset + 1]; + + /* only handle if the password type is supported */ + if(!(password_id & password_supported)) { + break; + } + + /* fetch the last RAND value */ + uint8_t* rand = slix->rand; + + /* first calc the password that has been sent */ + uint8_t password_rcv[4]; + for(int pos = 0; pos < 4; pos++) { + password_rcv[pos] = password_xored[3 - pos] ^ rand[pos % 2]; + } + uint32_t pass_received = slix_read_be(password_rcv, 4); + + /* then determine the password type (or even update if not set yet) */ + uint8_t* password = NULL; + uint32_t flag_valid = 0; + uint32_t flag_set = 0; + + slix_generic_pass_infos(password_id, slix, &password, &flag_valid, &flag_set); + + /* when the password is not supported, return silently */ + if(!password) { + break; + } + + /* check if the password is known */ + bool pass_valid = false; + uint32_t pass_expect = 0; + + if(slix->flags & flag_set) { + /* if so, fetch the stored password and compare */ + pass_expect = slix_read_be(password, 4); + pass_valid = (pass_expect == pass_received); + } else { + /* if not known, just accept it and store that password */ + memcpy(password, password_rcv, 4); + nfcv_data->modified = true; + slix->flags |= flag_set; + + pass_valid = true; + } + + /* if the pass was valid or accepted for other reasons, continue */ + if(pass_valid) { + slix->flags |= flag_valid; + + /* handle actions when a correct password was given, aside of setting the flag */ + switch(password_id) { + case SLIX_PASS_PRIVACY: + slix->flags &= ~NfcVSlixDataFlagsPrivacy; + nfcv_data->modified = true; + break; + case SLIX_PASS_DESTROY: + slix->flags |= NfcVSlixDataFlagsDestroyed; + FURI_LOG_D(TAG, "Pooof! Got destroyed"); + break; + default: + break; + } + + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "SET_PASSWORD #%02X 0x%08lX OK", + password_id, + pass_received); + } else { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "SET_PASSWORD #%02X 0x%08lX/%08lX FAIL", + password_id, + pass_received, + pass_expect); + } + handled = true; + break; + } + + case NFCV_CMD_NXP_WRITE_PASSWORD: { + uint8_t password_id = nfcv_data->frame[ctx->payload_offset]; + + if(!(password_id & password_supported)) { + break; + } + + uint8_t* new_password = &nfcv_data->frame[ctx->payload_offset + 1]; + uint8_t* password = NULL; + uint32_t flag_valid = 0; + uint32_t flag_set = 0; + + slix_generic_pass_infos(password_id, slix, &password, &flag_valid, &flag_set); + + /* when the password is not supported, return silently */ + if(!password) { + break; + } + + bool pass_valid = (slix->flags & flag_valid); + if(!(slix->flags & flag_set)) { + pass_valid = true; + } + + if(pass_valid) { + slix->flags |= flag_valid; + slix->flags |= flag_set; + + memcpy(password, new_password, 4); + + ctx->response_buffer[0] = NFCV_NOERROR; + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE_PASSWORD OK"); + } else { + snprintf( + nfcv_data->last_command, sizeof(nfcv_data->last_command), "WRITE_PASSWORD FAIL"); + } + handled = true; + break; + } + + case NFCV_CMD_NXP_ENABLE_PRIVACY: { + ctx->response_buffer[0] = NFCV_NOERROR; + + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, 1, ctx->response_flags, ctx->send_time); + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "NFCV_CMD_NXP_ENABLE_PRIVACY"); + + slix->flags |= NfcVSlixDataFlagsPrivacy; + handled = true; + break; + } + } + + return handled; +} + +bool slix_l_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + bool handled = false; + + /* many SLIX share some of the functions, place that in a generic handler */ + if(slix_generic_protocol_filter( + tx_rx, + nfc_data, + nfcv_data_in, + SLIX_PASS_PRIVACY | SLIX_PASS_DESTROY | SLIX_PASS_EASAFI)) { + return true; + } + + return handled; +} + +void slix_l_prepare(NfcVData* nfcv_data) { + FURI_LOG_D( + TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); + FURI_LOG_D( + TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); + FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); + FURI_LOG_D( + TAG, + " Privacy mode: %s", + (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF"); + + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + ctx->emu_protocol_filter = &slix_l_protocol_filter; +} + +bool slix_s_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + bool handled = false; + + /* many SLIX share some of the functions, place that in a generic handler */ + if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_ALL)) { + return true; + } + + return handled; +} + +void slix_s_prepare(NfcVData* nfcv_data) { + FURI_LOG_D( + TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); + FURI_LOG_D( + TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); + FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); + FURI_LOG_D( + TAG, + " Privacy mode: %s", + (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF"); + + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + ctx->emu_protocol_filter = &slix_s_protocol_filter; +} + +bool slix_protocol_filter( + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + bool handled = false; + + /* many SLIX share some of the functions, place that in a generic handler */ + if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_EASAFI)) { + return true; + } + + return handled; +} + +void slix_prepare(NfcVData* nfcv_data) { + FURI_LOG_D( + TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); + FURI_LOG_D( + TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); + FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); + FURI_LOG_D( + TAG, + " Privacy mode: %s", + (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF"); + + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + ctx->emu_protocol_filter = &slix_protocol_filter; +} + +bool slix2_protocol_filter( // -V524 + FuriHalNfcTxRxContext* tx_rx, + FuriHalNfcDevData* nfc_data, + void* nfcv_data_in) { + furi_assert(tx_rx); + furi_assert(nfc_data); + furi_assert(nfcv_data_in); + + NfcVData* nfcv_data = (NfcVData*)nfcv_data_in; + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + NfcVSlixData* slix = &nfcv_data->sub_data.slix; + + bool handled = false; + + /* many SLIX share some of the functions, place that in a generic handler */ + if(slix_generic_protocol_filter(tx_rx, nfc_data, nfcv_data_in, SLIX_PASS_ALL)) { + return true; + } + + switch(ctx->command) { + /* override WRITE BLOCK for block 79 (16 bit counter) */ + case NFCV_CMD_WRITE_BLOCK: + case NFCV_CMD_WRITE_MULTI_BLOCK: { + uint8_t resp_len = 1; + uint8_t blocks = 1; + uint8_t block = nfcv_data->frame[ctx->payload_offset]; + uint8_t data_pos = ctx->payload_offset + 1; + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + blocks = nfcv_data->frame[data_pos] + 1; + data_pos++; + } + + uint8_t* data = &nfcv_data->frame[data_pos]; + uint32_t data_len = nfcv_data->block_size * blocks; + + if((block + blocks) <= nfcv_data->block_num && + (data_pos + data_len + 2) == nfcv_data->frame_length) { + ctx->response_buffer[0] = NFCV_NOERROR; + + for(int block_num = block; block_num < block + blocks; block_num++) { + /* special case, 16-bit counter */ + if(block_num == 79) { + uint32_t dest; + uint32_t ctr_old; + + memcpy(&dest, &nfcv_data->frame[data_pos], 4); + memcpy(&ctr_old, &nfcv_data->data[nfcv_data->block_size * block_num], 4); + + uint32_t ctr_new = ctr_old; + bool allowed = true; + + /* increment counter */ + if(dest == 1) { + ctr_new = (ctr_old & 0xFFFF0000) | ((ctr_old + 1) & 0xFFFF); + + /* protection flag set? */ + if(ctr_old & 0x01000000) { //-V1051 + allowed = nfcv_data->sub_data.slix.flags & + NfcVSlixDataFlagsValidKeyRead; + } + } else { + ctr_new = dest; + allowed = nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsValidKeyWrite; + } + + if(allowed) { + memcpy( //-V1086 + &nfcv_data->data[nfcv_data->block_size * block_num], + &ctr_new, + 4); + } else { + /* incorrect read or write password */ + ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; + ctx->response_buffer[1] = NFCV_ERROR_GENERIC; + resp_len = 2; + } + } else { + memcpy( + &nfcv_data->data[nfcv_data->block_size * block_num], + &nfcv_data->frame[data_pos], + nfcv_data->block_size); + } + data_pos += nfcv_data->block_size; + } + nfcv_data->modified = true; + + } else { + ctx->response_buffer[0] = NFCV_RES_FLAG_ERROR; + ctx->response_buffer[1] = NFCV_ERROR_GENERIC; + resp_len = 2; + } + + bool respond = (ctx->response_buffer[0] == NFCV_NOERROR) || + (ctx->addressed || ctx->selected); + + if(respond) { + nfcv_emu_send( + tx_rx, + nfcv_data, + ctx->response_buffer, + resp_len, + ctx->response_flags, + ctx->send_time); + } + + if(ctx->command == NFCV_CMD_WRITE_MULTI_BLOCK) { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "WRITE MULTI BLOCK %d, %d blocks", + block, + blocks); + } else { + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "WRITE BLOCK %d <- %02X %02X %02X %02X", + block, + data[0], + data[1], + data[2], + data[3]); + } + handled = true; + break; + } + + case NFCV_CMD_NXP_READ_SIGNATURE: { + uint32_t len = 0; + ctx->response_buffer[len++] = NFCV_NOERROR; + memcpy(&ctx->response_buffer[len], slix->signature, sizeof(slix->signature)); + len += sizeof(slix->signature); + + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, len, ctx->response_flags, ctx->send_time); + snprintf(nfcv_data->last_command, sizeof(nfcv_data->last_command), "READ_SIGNATURE"); + + handled = true; + break; + } + + case NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION: { + uint32_t len = 0; + uint8_t lock_bits = 0; + + /* convert our internal lock bits format into NXP's */ + lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitDsfid) ? SlixLockBitDsfid : 0; + lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitAfi) ? SlixLockBitAfi : 0; + lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitEas) ? SlixLockBitEas : 0; + lock_bits |= (nfcv_data->security_status[0] & NfcVLockBitPpl) ? SlixLockBitPpl : 0; + + ctx->response_buffer[len++] = NFCV_NOERROR; + ctx->response_buffer[len++] = nfcv_data->sub_data.slix.pp_pointer; + ctx->response_buffer[len++] = nfcv_data->sub_data.slix.pp_condition; + ctx->response_buffer[len++] = lock_bits; + ctx->response_buffer[len++] = 0x7F; /* features LSB */ + ctx->response_buffer[len++] = 0x35; /* features */ + ctx->response_buffer[len++] = 0; /* features */ + ctx->response_buffer[len++] = 0; /* features MSB */ + + nfcv_emu_send( + tx_rx, nfcv_data, ctx->response_buffer, len, ctx->response_flags, ctx->send_time); + snprintf( + nfcv_data->last_command, + sizeof(nfcv_data->last_command), + "GET_NXP_SYSTEM_INFORMATION"); + + handled = true; + break; + } + } + + return handled; +} + +void slix2_prepare(NfcVData* nfcv_data) { + FURI_LOG_D( + TAG, " Privacy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_privacy, 4)); + FURI_LOG_D( + TAG, " Destroy pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_destroy, 4)); + FURI_LOG_D(TAG, " EAS pass: 0x%08lX", slix_read_be(nfcv_data->sub_data.slix.key_eas, 4)); + FURI_LOG_D( + TAG, + " Privacy mode: %s", + (nfcv_data->sub_data.slix.flags & NfcVSlixDataFlagsPrivacy) ? "ON" : "OFF"); + + NfcVEmuProtocolCtx* ctx = nfcv_data->emu_protocol_ctx; + ctx->emu_protocol_filter = &slix2_protocol_filter; +} diff --git a/lib/nfc/protocols/slix.h b/lib/nfc/protocols/slix.h new file mode 100644 index 0000000..67f09e4 --- /dev/null +++ b/lib/nfc/protocols/slix.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include "nfc_util.h" +#include + +#define NFCV_MANUFACTURER_NXP 0x04 + +/* ISO15693-3 CUSTOM NXP COMMANDS */ +typedef enum { + NFCV_CMD_NXP_SET_EAS = 0xA2, + NFCV_CMD_NXP_RESET_EAS = 0xA3, + NFCV_CMD_NXP_LOCK_EAS = 0xA4, + NFCV_CMD_NXP_EAS_ALARM = 0xA5, + NFCV_CMD_NXP_PASSWORD_PROTECT_EAS_AFI = 0xA6, + NFCV_CMD_NXP_WRITE_EAS_ID = 0xA7, + NFCV_CMD_NXP_GET_NXP_SYSTEM_INFORMATION = 0xAB, + NFCV_CMD_NXP_INVENTORY_PAGE_READ = 0xB0, + NFCV_CMD_NXP_INVENTORY_PAGE_READ_FAST = 0xB1, + NFCV_CMD_NXP_GET_RANDOM_NUMBER = 0xB2, + NFCV_CMD_NXP_SET_PASSWORD = 0xB3, + NFCV_CMD_NXP_WRITE_PASSWORD = 0xB4, + NFCV_CMD_NXP_64_BIT_PASSWORD_PROTECTION = 0xB5, + NFCV_CMD_NXP_PROTECT_PAGE = 0xB6, + NFCV_CMD_NXP_LOCK_PAGE_PROTECTION_CONDITION = 0xB7, + NFCV_CMD_NXP_DESTROY = 0xB9, + NFCV_CMD_NXP_ENABLE_PRIVACY = 0xBA, + NFCV_CMD_NXP_STAY_QUIET_PERSISTENT = 0xBC, + NFCV_CMD_NXP_READ_SIGNATURE = 0xBD +} SlixCommands; + +/* lock bit bits used in SLIX's NXP SYSTEM INFORMATION response */ +typedef enum { + SlixLockBitAfi = 1 << 0, + SlixLockBitEas = 1 << 1, + SlixLockBitDsfid = 1 << 2, + SlixLockBitPpl = 1 << 3, +} SlixLockBits; + +/* available passwords */ +#define SLIX_PASS_READ 0x01 +#define SLIX_PASS_WRITE 0x02 +#define SLIX_PASS_PRIVACY 0x04 +#define SLIX_PASS_DESTROY 0x08 +#define SLIX_PASS_EASAFI 0x10 + +#define SLIX_PASS_ALL \ + (SLIX_PASS_READ | SLIX_PASS_WRITE | SLIX_PASS_PRIVACY | SLIX_PASS_DESTROY | SLIX_PASS_EASAFI) + +bool slix_check_card_type(FuriHalNfcDevData* nfc_data); +bool slix2_check_card_type(FuriHalNfcDevData* nfc_data); +bool slix_s_check_card_type(FuriHalNfcDevData* nfc_data); +bool slix_l_check_card_type(FuriHalNfcDevData* nfc_data); + +ReturnCode slix2_read_custom(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); +ReturnCode slix2_read_signature(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); +ReturnCode slix2_read_nxp_sysinfo(FuriHalNfcDevData* nfc_data, NfcVData* nfcv_data); + +ReturnCode slix_get_random(NfcVData* data); +ReturnCode slix_unlock(NfcVData* data, uint32_t password_id); + +void slix_prepare(NfcVData* nfcv_data); +void slix_s_prepare(NfcVData* nfcv_data); +void slix_l_prepare(NfcVData* nfcv_data); +void slix2_prepare(NfcVData* nfcv_data); diff --git a/mifare_fuzzer.c b/mifare_fuzzer.c index 9fa5ab7..f9ac29d 100644 --- a/mifare_fuzzer.c +++ b/mifare_fuzzer.c @@ -1,9 +1,9 @@ #include "mifare_fuzzer_i.h" /// @brief mifare_fuzzer_custom_event_callback() -/// @param context -/// @param event -/// @return +/// @param context +/// @param event +/// @return static bool mifare_fuzzer_custom_event_callback(void* context, uint32_t event) { furi_assert(context); MifareFuzzerApp* app = context; @@ -11,8 +11,8 @@ static bool mifare_fuzzer_custom_event_callback(void* context, uint32_t event) { } /// @brief mifare_fuzzer_back_event_callback() -/// @param context -/// @return +/// @param context +/// @return static bool mifare_fuzzer_back_event_callback(void* context) { furi_assert(context); MifareFuzzerApp* app = context; @@ -20,15 +20,15 @@ static bool mifare_fuzzer_back_event_callback(void* context) { } /// @brief mifare_fuzzer_tick_event_callback() -/// @param context -static void mifare_fuzzer_tick_event_callback(void* context){ +/// @param context +static void mifare_fuzzer_tick_event_callback(void* context) { furi_assert(context); MifareFuzzerApp* app = context; scene_manager_handle_tick_event(app->scene_manager); } /// @brief mifare_fuzzer_alloc() -/// @return +/// @return MifareFuzzerApp* mifare_fuzzer_alloc() { MifareFuzzerApp* app = malloc(sizeof(MifareFuzzerApp)); @@ -36,43 +36,38 @@ MifareFuzzerApp* mifare_fuzzer_alloc() { app->scene_manager = scene_manager_alloc(&mifare_fuzzer_scene_handlers, app); view_dispatcher_enable_queue(app->view_dispatcher); view_dispatcher_set_event_callback_context(app->view_dispatcher, app); - view_dispatcher_set_custom_event_callback(app->view_dispatcher, mifare_fuzzer_custom_event_callback); - view_dispatcher_set_navigation_event_callback(app->view_dispatcher, mifare_fuzzer_back_event_callback); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, mifare_fuzzer_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, mifare_fuzzer_back_event_callback); + + // Setup NFC + app->dev = nfc_device_alloc(); // 1000 ticks are about 1 sec - view_dispatcher_set_tick_event_callback(app->view_dispatcher, mifare_fuzzer_tick_event_callback, MIFARE_FUZZER_TICK_PERIOD); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, mifare_fuzzer_tick_event_callback, MIFARE_FUZZER_TICK_PERIOD); // Open GUI record app->gui = furi_record_open(RECORD_GUI); - view_dispatcher_attach_to_gui( - app->view_dispatcher, - app->gui, - ViewDispatcherTypeFullscreen - ); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); // view: select card type app->submenu_card = submenu_alloc(); view_dispatcher_add_view( - app->view_dispatcher, - MifareFuzzerViewSelectCard, - submenu_get_view(app->submenu_card) - ); + app->view_dispatcher, MifareFuzzerViewSelectCard, submenu_get_view(app->submenu_card)); // view: select attack type app->submenu_attack = submenu_alloc(); view_dispatcher_add_view( - app->view_dispatcher, - MifareFuzzerViewSelectAttack, - submenu_get_view(app->submenu_attack) - ); + app->view_dispatcher, MifareFuzzerViewSelectAttack, submenu_get_view(app->submenu_attack)); // view: emulator app->emulator_view = mifare_fuzzer_emulator_alloc(); view_dispatcher_add_view( app->view_dispatcher, MifareFuzzerViewEmulator, - mifare_fuzzer_emulator_get_view(app->emulator_view) - ); + mifare_fuzzer_emulator_get_view(app->emulator_view)); // worker app->worker = mifare_fuzzer_worker_alloc(); @@ -95,7 +90,7 @@ MifareFuzzerApp* mifare_fuzzer_alloc() { } /// @brief mifare_fuzzer_free() -/// @param app +/// @param app void mifare_fuzzer_free(MifareFuzzerApp* app) { furi_assert(app); @@ -134,6 +129,9 @@ void mifare_fuzzer_free(MifareFuzzerApp* app) { furi_record_close(RECORD_DIALOGS); app->dialogs = NULL; + // nfc + app->dev = NULL; + // furi strings furi_string_free(app->uid_str); furi_string_free(app->file_path); @@ -145,8 +143,8 @@ void mifare_fuzzer_free(MifareFuzzerApp* app) { } /// @brief mifare_fuzzer_app (ENTRYPOINT) -/// @param p -/// @return +/// @param p +/// @return int32_t mifare_fuzzer_app(void* p) { UNUSED(p); //FURI_LOG_D(TAG, "mifare_fuzzer_app()"); diff --git a/mifare_fuzzer_i.h b/mifare_fuzzer_i.h index b6cc9bb..82f881e 100644 --- a/mifare_fuzzer_i.h +++ b/mifare_fuzzer_i.h @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -48,7 +49,6 @@ typedef enum { } MifareFuzzerView; struct MifareFuzzerApp { - Gui* gui; ViewDispatcher* view_dispatcher; @@ -75,4 +75,5 @@ struct MifareFuzzerApp { FuriString* uid_str; Stream* uids_stream; + NfcDevice* dev; }; diff --git a/mifare_fuzzer_worker.c b/mifare_fuzzer_worker.c index 2448831..9d5c885 100644 --- a/mifare_fuzzer_worker.c +++ b/mifare_fuzzer_worker.c @@ -1,91 +1,109 @@ +#include #include "mifare_fuzzer_worker.h" +#include +#include /// @brief mifare_fuzzer_worker_alloc() -/// @return +/// @return MifareFuzzerWorker* mifare_fuzzer_worker_alloc() { MifareFuzzerWorker* mifare_fuzzer_worker = malloc(sizeof(MifareFuzzerWorker)); // Worker thread attributes - mifare_fuzzer_worker->thread = furi_thread_alloc_ex("MifareFuzzerWorker", 8192, mifare_fuzzer_worker_task, mifare_fuzzer_worker); + mifare_fuzzer_worker->thread = furi_thread_alloc_ex( + "MifareFuzzerWorker", 8192, mifare_fuzzer_worker_task, mifare_fuzzer_worker); + mifare_fuzzer_worker->nfc_worker = nfc_worker_alloc(); mifare_fuzzer_worker->state = MifareFuzzerWorkerStateStop; return mifare_fuzzer_worker; } /// @brief mifare_fuzzer_worker_free() -/// @param mifare_fuzzer_worker +/// @param mifare_fuzzer_worker void mifare_fuzzer_worker_free(MifareFuzzerWorker* mifare_fuzzer_worker) { furi_assert(mifare_fuzzer_worker); furi_thread_free(mifare_fuzzer_worker->thread); + nfc_worker_free(mifare_fuzzer_worker->nfc_worker); free(mifare_fuzzer_worker); } +static bool nfc_mf_classic_emulate_worker_callback(NfcWorkerEvent event, void* context) { + MifareFuzzerWorker* mifare_fuzzer_worker = context; + if(event == NfcWorkerEventSuccess) { + mifare_fuzzer_worker_stop(mifare_fuzzer_worker); + } + return true; +} + /// @brief mifare_fuzzer_worker_stop() -/// @param mifare_fuzzer_worker +/// @param mifare_fuzzer_worker void mifare_fuzzer_worker_stop(MifareFuzzerWorker* mifare_fuzzer_worker) { furi_assert(mifare_fuzzer_worker); - if (mifare_fuzzer_worker->state != MifareFuzzerWorkerStateStop) { + if(mifare_fuzzer_worker->state != MifareFuzzerWorkerStateStop) { mifare_fuzzer_worker->state = MifareFuzzerWorkerStateStop; furi_thread_join(mifare_fuzzer_worker->thread); + nfc_worker_stop(mifare_fuzzer_worker->nfc_worker); } } /// @brief mifare_fuzzer_worker_start() -/// @param mifare_fuzzer_worker +/// @param mifare_fuzzer_worker void mifare_fuzzer_worker_start(MifareFuzzerWorker* mifare_fuzzer_worker) { furi_assert(mifare_fuzzer_worker); mifare_fuzzer_worker->state = MifareFuzzerWorkerStateEmulate; + nfc_worker_start( + mifare_fuzzer_worker->nfc_worker, + NfcWorkerStateMfClassicEmulate, + &mifare_fuzzer_worker->dev->dev_data, + nfc_mf_classic_emulate_worker_callback, + NULL); furi_thread_start(mifare_fuzzer_worker->thread); } /// @brief mifare_fuzzer_worker_task() -/// @param context -/// @return +/// @param context +/// @return int32_t mifare_fuzzer_worker_task(void* context) { - MifareFuzzerWorker* mifare_fuzzer_worker = context; - - if(mifare_fuzzer_worker->state == MifareFuzzerWorkerStateEmulate) { - - FuriHalNfcDevData params = mifare_fuzzer_worker->nfc_dev_data; - - furi_hal_nfc_exit_sleep(); - while(mifare_fuzzer_worker->state == MifareFuzzerWorkerStateEmulate) { - furi_hal_nfc_listen( - params.uid, - params.uid_len, - params.atqa, - params.sak, false, 500 - ); - furi_delay_ms(50); - } - furi_hal_nfc_sleep(); - - } - - mifare_fuzzer_worker->state = MifareFuzzerWorkerStateStop; - + UNUSED(context); return 0; } /// @brief mifare_fuzzer_worker_is_emulating() -/// @param mifare_fuzzer_worker -/// @return +/// @param mifare_fuzzer_worker +/// @return bool mifare_fuzzer_worker_is_emulating(MifareFuzzerWorker* mifare_fuzzer_worker) { - if (mifare_fuzzer_worker->state == MifareFuzzerWorkerStateEmulate) { + if(mifare_fuzzer_worker->state == MifareFuzzerWorkerStateEmulate) { return true; } return false; } /// @brief mifare_fuzzer_worker_set_nfc_dev_data() -/// @param mifare_fuzzer_worker -/// @param nfc_dev_data -void mifare_fuzzer_worker_set_nfc_dev_data(MifareFuzzerWorker* mifare_fuzzer_worker, FuriHalNfcDevData nfc_dev_data) { +/// @param mifare_fuzzer_worker +/// @param nfc_dev_data +void mifare_fuzzer_worker_set_nfc_dev_data( + MifareFuzzerWorker* mifare_fuzzer_worker, + FuriHalNfcDevData nfc_dev_data) { mifare_fuzzer_worker->nfc_dev_data = nfc_dev_data; } /// @brief mifare_fuzzer_worker_get_nfc_dev_data() -/// @param mifare_fuzzer_worker -/// @return +/// @param mifare_fuzzer_worker +/// @return FuriHalNfcDevData mifare_fuzzer_worker_get_nfc_dev_data(MifareFuzzerWorker* mifare_fuzzer_worker) { return mifare_fuzzer_worker->nfc_dev_data; } + +/// @brief mifare_fuzzer_worker_set_nfc_dev_data() +/// @param mifare_fuzzer_worker +/// @param nfc_dev_data +void mifare_fuzzer_worker_set_nfc_device( + MifareFuzzerWorker* mifare_fuzzer_worker, + NfcDevice* nfc_device) { + mifare_fuzzer_worker->dev = nfc_device; +} + +/// @brief mifare_fuzzer_worker_get_nfc_dev_data() +/// @param mifare_fuzzer_worker +/// @return +NfcDevice* mifare_fuzzer_worker_get_nfc_device(MifareFuzzerWorker* mifare_fuzzer_worker) { + return mifare_fuzzer_worker->dev; +} diff --git a/mifare_fuzzer_worker.h b/mifare_fuzzer_worker.h index 1fa7042..7cd90ca 100644 --- a/mifare_fuzzer_worker.h +++ b/mifare_fuzzer_worker.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include typedef enum MifareFuzzerWorkerState { MifareFuzzerWorkerStateEmulate, @@ -12,6 +13,8 @@ typedef enum MifareFuzzerWorkerState { typedef struct MifareFuzzerWorker { FuriThread* thread; + NfcWorker* nfc_worker; + NfcDevice* dev; MifareFuzzerWorkerState state; FuriHalNfcDevData nfc_dev_data; } MifareFuzzerWorker; @@ -23,7 +26,11 @@ void mifare_fuzzer_worker_stop(MifareFuzzerWorker* mifare_fuzzer_worker); void mifare_fuzzer_worker_start(MifareFuzzerWorker* mifare_fuzzer_worker); // task int32_t mifare_fuzzer_worker_task(void* context); -// +// bool mifare_fuzzer_worker_is_emulating(MifareFuzzerWorker* mifare_fuzzer_worker); + void mifare_fuzzer_worker_set_nfc_dev_data(MifareFuzzerWorker* mifare_fuzzer_worker, FuriHalNfcDevData nfc_dev_data); FuriHalNfcDevData mifare_fuzzer_worker_get_nfc_dev_data(MifareFuzzerWorker* mifare_fuzzer_worker); + +void mifare_fuzzer_worker_set_nfc_device(MifareFuzzerWorker* mifare_fuzzer_worker, NfcDevice* nfc_device); +NfcDevice* mifare_fuzzer_worker_get_nfc_device(MifareFuzzerWorker* mifare_fuzzer_worker); \ No newline at end of file diff --git a/scenes/mifare_fuzzer_scene_emulator.c b/scenes/mifare_fuzzer_scene_emulator.c index 6232ff0..6967c3e 100644 --- a/scenes/mifare_fuzzer_scene_emulator.c +++ b/scenes/mifare_fuzzer_scene_emulator.c @@ -16,8 +16,8 @@ uint8_t id_uid_test[9][7] = { }; /// @brief mifare_fuzzer_scene_emulator_callback() -/// @param event -/// @param context +/// @param event +/// @param context static void mifare_fuzzer_scene_emulator_callback(MifareFuzzerEvent event, void* context) { //FURI_LOG_D(TAG, "mifare_fuzzer_scene_emulator_callback()"); furi_assert(context); @@ -26,7 +26,7 @@ static void mifare_fuzzer_scene_emulator_callback(MifareFuzzerEvent event, void* } /// @brief mifare_fuzzer_scene_emulator_on_enter() -/// @param context +/// @param context void mifare_fuzzer_scene_emulator_on_enter(void* context) { //FURI_LOG_D(TAG, "mifare_fuzzer_scene_emulator_on_enter()"); MifareFuzzerApp* app = context; @@ -38,13 +38,14 @@ void mifare_fuzzer_scene_emulator_on_enter(void* context) { tick_counter = 0; mifare_fuzzer_emulator_set_tick_num(app->emulator_view, tick_counter); emulator->ticks_between_cards = MIFARE_FUZZER_DEFAULT_TICKS_BETWEEN_CARDS; - mifare_fuzzer_emulator_set_ticks_between_cards(app->emulator_view, emulator->ticks_between_cards); + mifare_fuzzer_emulator_set_ticks_between_cards( + app->emulator_view, emulator->ticks_between_cards); // init default card data FuriHalNfcDevData nfc_dev_data; nfc_dev_data.atqa[0] = 0x00; nfc_dev_data.atqa[1] = 0x00; nfc_dev_data.sak = 0x00; - if (app->card == MifareCardUltralight) { + if(app->card == MifareCardUltralight) { nfc_dev_data.uid_len = 0x07; } else { nfc_dev_data.uid_len = 0x04; @@ -57,27 +58,28 @@ void mifare_fuzzer_scene_emulator_on_enter(void* context) { attack_step = 0; // switch to view - view_dispatcher_switch_to_view( - app->view_dispatcher, - MifareFuzzerViewEmulator - ); + view_dispatcher_switch_to_view(app->view_dispatcher, MifareFuzzerViewEmulator); } /// @brief mifare_fuzzer_scene_emulator_on_event() -/// @param context -/// @param event -/// @return +/// @param context +/// @param event +/// @return bool mifare_fuzzer_scene_emulator_on_event(void* context, SceneManagerEvent event) { //FURI_LOG_D(TAG, "mifare_fuzzer_scene_emulator_on_event()"); FuriHalNfcDevData nfc_dev_data; MifareFuzzerApp* app = context; MifareFuzzerEmulator* emulator = app->emulator_view; + if(!nfc_device_load(app->dev, "/ext/nfc/file.nfc", true)) { + return false; + } + NfcDevice* nfc_device = app->dev; bool consumed = false; - if (event.type == SceneManagerEventTypeCustom) { - if (event.event == MifareFuzzerEventStartAttack) { + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == MifareFuzzerEventStartAttack) { //FURI_LOG_D(TAG, "mifare_fuzzer_scene_emulator_on_event() :: MifareFuzzerEventStartAttack"); // Stop worker @@ -85,17 +87,17 @@ bool mifare_fuzzer_scene_emulator_on_event(void* context, SceneManagerEvent even // Set card type // TODO: Move somewhere else, I do not like this to be there - if (app->card == MifareCardClassic1k) { + if(app->card == MifareCardClassic1k) { nfc_dev_data.atqa[0] = 0x04; nfc_dev_data.atqa[1] = 0x00; nfc_dev_data.sak = 0x08; nfc_dev_data.uid_len = 0x04; - } else if (app->card == MifareCardClassic4k) { + } else if(app->card == MifareCardClassic4k) { nfc_dev_data.atqa[0] = 0x02; nfc_dev_data.atqa[1] = 0x00; nfc_dev_data.sak = 0x18; nfc_dev_data.uid_len = 0x04; - } else if (app->card == MifareCardUltralight) { + } else if(app->card == MifareCardUltralight) { nfc_dev_data.atqa[0] = 0x44; nfc_dev_data.atqa[1] = 0x00; nfc_dev_data.sak = 0x00; @@ -103,19 +105,19 @@ bool mifare_fuzzer_scene_emulator_on_event(void* context, SceneManagerEvent even } // Set UIDs - if (app->attack == MifareFuzzerAttackTestValues) { + if(app->attack == MifareFuzzerAttackTestValues) { // Load test UIDs for(uint8_t i = 0; i < nfc_dev_data.uid_len; i++) { nfc_dev_data.uid[i] = id_uid_test[attack_step][i]; } // Next UIDs on next loop - if (attack_step >= 8) { + if(attack_step >= 8) { attack_step = 0; } else { attack_step++; } - } else if (app->attack == MifareFuzzerAttackRandomValues) { - if (app->card == MifareCardUltralight) { + } else if(app->attack == MifareFuzzerAttackRandomValues) { + if(app->card == MifareCardUltralight) { // First byte of a 7 byte UID is the manufacturer-code // https://github.com/Proxmark/proxmark3/blob/master/client/taginfo.c // https://stackoverflow.com/questions/37837730/mifare-cards-distinguish-between-4-byte-and-7-byte-uids @@ -132,10 +134,10 @@ bool mifare_fuzzer_scene_emulator_on_event(void* context, SceneManagerEvent even nfc_dev_data.uid[i] = (furi_hal_random_get() & 0xFF); } } - } else if (app->attack == MifareFuzzerAttackLoadUidsFromFile) { + } else if(app->attack == MifareFuzzerAttackLoadUidsFromFile) { //bool end_of_list = false; // read stream - while(true){ + while(true) { furi_string_reset(app->uid_str); if(!stream_read_line(app->uids_stream, app->uid_str)) { // restart from beginning on empty line @@ -146,7 +148,9 @@ bool mifare_fuzzer_scene_emulator_on_event(void* context, SceneManagerEvent even // Skip comments if(furi_string_get_char(app->uid_str, 0) == '#') continue; // Skip lines with invalid length - if((furi_string_size(app->uid_str) != 9) && (furi_string_size(app->uid_str) != 15)) continue; + if((furi_string_size(app->uid_str) != 9) && + (furi_string_size(app->uid_str) != 15)) + continue; break; } @@ -156,7 +160,7 @@ bool mifare_fuzzer_scene_emulator_on_event(void* context, SceneManagerEvent even // parse string to UID // TODO: a better validation on input? for(uint8_t i = 0; i < nfc_dev_data.uid_len; i++) { - if (i <= ((furi_string_size(app->uid_str) - 1) / 2)) { + if(i <= ((furi_string_size(app->uid_str) - 1) / 2)) { char temp_str[3]; temp_str[0] = furi_string_get_cstr(app->uid_str)[i * 2]; temp_str[1] = furi_string_get_cstr(app->uid_str)[i * 2 + 1]; @@ -166,10 +170,10 @@ bool mifare_fuzzer_scene_emulator_on_event(void* context, SceneManagerEvent even nfc_dev_data.uid[i] = 0x00; } } - } - mifare_fuzzer_worker_set_nfc_dev_data(app->worker, nfc_dev_data); + nfc_device->dev_data.nfc_data = nfc_dev_data; + mifare_fuzzer_worker_set_nfc_device(app->worker, nfc_device); mifare_fuzzer_emulator_set_nfc_dev_data(app->emulator_view, nfc_dev_data); // Reset tick_counter @@ -179,27 +183,29 @@ bool mifare_fuzzer_scene_emulator_on_event(void* context, SceneManagerEvent even // Start worker mifare_fuzzer_worker_start(app->worker); - } else if (event.event == MifareFuzzerEventStopAttack) { + } else if(event.event == MifareFuzzerEventStopAttack) { //FURI_LOG_D(TAG, "mifare_fuzzer_scene_emulator_on_event() :: MifareFuzzerEventStopAttack"); // Stop worker mifare_fuzzer_worker_stop(app->worker); - } else if (event.event == MifareFuzzerEventIncrementTicks) { - if (!emulator->is_attacking) { - if (emulator->ticks_between_cards < MIFARE_FUZZER_MAX_TICKS_BETWEEN_CARDS) { + } else if(event.event == MifareFuzzerEventIncrementTicks) { + if(!emulator->is_attacking) { + if(emulator->ticks_between_cards < MIFARE_FUZZER_MAX_TICKS_BETWEEN_CARDS) { emulator->ticks_between_cards++; - mifare_fuzzer_emulator_set_ticks_between_cards(app->emulator_view, emulator->ticks_between_cards); + mifare_fuzzer_emulator_set_ticks_between_cards( + app->emulator_view, emulator->ticks_between_cards); }; }; - } else if (event.event == MifareFuzzerEventDecrementTicks) { - if (!emulator->is_attacking) { - if (emulator->ticks_between_cards > MIFARE_FUZZER_MIN_TICKS_BETWEEN_CARDS) { + } else if(event.event == MifareFuzzerEventDecrementTicks) { + if(!emulator->is_attacking) { + if(emulator->ticks_between_cards > MIFARE_FUZZER_MIN_TICKS_BETWEEN_CARDS) { emulator->ticks_between_cards--; - mifare_fuzzer_emulator_set_ticks_between_cards(app->emulator_view, emulator->ticks_between_cards); + mifare_fuzzer_emulator_set_ticks_between_cards( + app->emulator_view, emulator->ticks_between_cards); }; }; } consumed = true; - } else if (event.type == SceneManagerEventTypeTick) { + } else if(event.type == SceneManagerEventTypeTick) { //FURI_LOG_D(TAG, "mifare_fuzzer_scene_emulator_on_event() :: SceneManagerEventTypeTick"); // Used to check tick length (not perfect but enough) @@ -208,14 +214,15 @@ bool mifare_fuzzer_scene_emulator_on_event(void* context, SceneManagerEvent even //FURI_LOG_D(TAG, "Time is: %.2d:%.2d:%.2d", curr_dt.hour, curr_dt.minute, curr_dt.second); // If emulator is attacking - if (emulator->is_attacking) { + if(emulator->is_attacking) { // increment tick_counter tick_counter++; mifare_fuzzer_emulator_set_tick_num(app->emulator_view, tick_counter); //FURI_LOG_D(TAG, "tick_counter is: %.2d", tick_counter); - if (tick_counter >= emulator->ticks_between_cards) { + if(tick_counter >= emulator->ticks_between_cards) { // Queue event for changing UID - view_dispatcher_send_custom_event(app->view_dispatcher, MifareFuzzerEventStartAttack); + view_dispatcher_send_custom_event( + app->view_dispatcher, MifareFuzzerEventStartAttack); } } @@ -226,7 +233,7 @@ bool mifare_fuzzer_scene_emulator_on_event(void* context, SceneManagerEvent even } /// @brief mifare_fuzzer_scene_emulator_on_exit() -/// @param context +/// @param context void mifare_fuzzer_scene_emulator_on_exit(void* context) { //FURI_LOG_D(TAG, "mifare_fuzzer_scene_emulator_on_exit()"); MifareFuzzerApp* app = context; @@ -237,5 +244,4 @@ void mifare_fuzzer_scene_emulator_on_exit(void* context) { stream_rewind(app->uids_stream); buffered_file_stream_close(app->uids_stream); } - }