diff options
author | gornekich <n.gorbadey@gmail.com> | 2022-07-26 18:30:49 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-26 18:30:49 +0300 |
commit | 9c59bcd7763cbe9b0132b0f2698543e2dfb11623 (patch) | |
tree | 95e88240a98270ee1960f3371c89e3a7b2341598 /lib | |
parent | ec19c11dbe4a81147947cc3a8dd2d182374ed67a (diff) |
[FL-2605] NFC new design (#1364)
* nfc: add new read scene
* lib: refactore nfc library
* mifare desfire: add read card fuction
* lib nfc: add auto read worker
* nfc: add supported cards
* nfc: add mifare classic read success scene
* nfc: add troyka support
* submodule: update protobuf
* nfc: mifare classic keys cache
* nfc: rework mifare classic key cache
* Correct spelling
* nfc: add user dictionary
* nfc: introduce block read map in fff
* nfc: rework dict attack
* nfc: improve dict attack
* nfc: rework mifare classic format
* nfc: rework MFC read with Reader
* nfc: add gui for MFC read success scene
* nfc: fix dict attack view gui
* nfc: add retry and exit confirm scenes
* nfc: add retry and exit scenes navigation
* nfc: check user dictionary
* nfc: remove unused scenes
* nfc: rename functions in nfc worker
* nfc: rename mf_classic_dict_attack -> dict_attack
* nfc: change scenes names
* nfc: remove scene tick events
* nfc: rework dict calls with buffer streams
* nfc: fix notifications
* nfc: fix mf desfire navigation
* nfc: remove notification from mf classic read success
* nfc: fix read sectors calculation
* nfc: add fallback for unknown card
* nfc: show file name while emulating
* nfc: fix build
* nfc: fix memory leak
* nfc: fix desfire read
* nfc: add no dict found navigation
* nfc: add read views
* nfc: update card fix
* nfc: fix access bytes save
* nfc: add exit and retry confirm to mf ultralight read success
* nfc: introduce detect reader
* nfc: change record open arg to macros
* nfc: fix start from archive
Co-authored-by: Astra <astra@astrra.space>
Co-authored-by: あく <alleteam@gmail.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/SConscript | 1 | ||||
-rw-r--r-- | lib/misc.scons | 2 | ||||
-rw-r--r-- | lib/nfc/SConscript | 16 | ||||
-rw-r--r-- | lib/nfc/helpers/mf_classic_dict.c | 148 | ||||
-rw-r--r-- | lib/nfc/helpers/mf_classic_dict.h | 28 | ||||
-rw-r--r-- | lib/nfc/helpers/nfc_debug_pcap.c | 166 | ||||
-rw-r--r-- | lib/nfc/helpers/nfc_debug_pcap.h | 21 | ||||
-rw-r--r-- | lib/nfc/nfc_device.c | 1278 | ||||
-rw-r--r-- | lib/nfc/nfc_device.h | 94 | ||||
-rw-r--r-- | lib/nfc/nfc_types.c | 65 | ||||
-rw-r--r-- | lib/nfc/nfc_types.h | 11 | ||||
-rw-r--r-- | lib/nfc/nfc_worker.c | 510 | ||||
-rwxr-xr-x | lib/nfc/nfc_worker.h | 71 | ||||
-rw-r--r-- | lib/nfc/nfc_worker_i.h | 48 | ||||
-rw-r--r-- | lib/nfc/parsers/nfc_supported_card.c | 12 | ||||
-rw-r--r-- | lib/nfc/parsers/nfc_supported_card.h | 27 | ||||
-rw-r--r-- | lib/nfc/parsers/troyka_parser.c | 70 | ||||
-rw-r--r-- | lib/nfc/parsers/troyka_parser.h | 9 | ||||
-rw-r--r-- | lib/nfc/protocols/crypto1.c (renamed from lib/nfc_protocols/crypto1.c) | 0 | ||||
-rw-r--r-- | lib/nfc/protocols/crypto1.h (renamed from lib/nfc_protocols/crypto1.h) | 0 | ||||
-rw-r--r-- | lib/nfc/protocols/emv.c (renamed from lib/nfc_protocols/emv.c) | 0 | ||||
-rwxr-xr-x | lib/nfc/protocols/emv.h (renamed from lib/nfc_protocols/emv.h) | 0 | ||||
-rw-r--r-- | lib/nfc/protocols/mifare_classic.c (renamed from lib/nfc_protocols/mifare_classic.c) | 325 | ||||
-rw-r--r-- | lib/nfc/protocols/mifare_classic.h (renamed from lib/nfc_protocols/mifare_classic.h) | 71 | ||||
-rw-r--r-- | lib/nfc/protocols/mifare_common.c (renamed from lib/nfc_protocols/mifare_common.c) | 0 | ||||
-rw-r--r-- | lib/nfc/protocols/mifare_common.h (renamed from lib/nfc_protocols/mifare_common.h) | 0 | ||||
-rw-r--r-- | lib/nfc/protocols/mifare_desfire.c (renamed from lib/nfc_protocols/mifare_desfire.c) | 172 | ||||
-rw-r--r-- | lib/nfc/protocols/mifare_desfire.h (renamed from lib/nfc_protocols/mifare_desfire.h) | 4 | ||||
-rw-r--r-- | lib/nfc/protocols/mifare_ultralight.c (renamed from lib/nfc_protocols/mifare_ultralight.c) | 0 | ||||
-rw-r--r-- | lib/nfc/protocols/mifare_ultralight.h (renamed from lib/nfc_protocols/mifare_ultralight.h) | 0 | ||||
-rw-r--r-- | lib/nfc/protocols/nfc_util.c (renamed from lib/nfc_protocols/nfc_util.c) | 0 | ||||
-rw-r--r-- | lib/nfc/protocols/nfc_util.h (renamed from lib/nfc_protocols/nfc_util.h) | 0 | ||||
-rwxr-xr-x | lib/nfc/protocols/nfca.c (renamed from lib/nfc_protocols/nfca.c) | 0 | ||||
-rw-r--r-- | lib/nfc/protocols/nfca.h (renamed from lib/nfc_protocols/nfca.h) | 0 |
34 files changed, 3071 insertions, 78 deletions
diff --git a/lib/SConscript b/lib/SConscript index a3617c5d..c5bc3947 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -70,6 +70,7 @@ libs = env.BuildModules( "infrared", "littlefs", "subghz", + "nfc", "appframe", "misc", "mbedtls", diff --git a/lib/misc.scons b/lib/misc.scons index 91a11ff6..5a826b18 100644 --- a/lib/misc.scons +++ b/lib/misc.scons @@ -7,7 +7,6 @@ env.Append( "#/lib/heatshrink", "#/lib/micro-ecc", "#/lib/nanopb", - "#/lib/nfc_protocols", "#/lib/u8g2", ], CPPDEFINES=[ @@ -24,7 +23,6 @@ sources = [] libs_recurse = [ "digital_signal", "micro-ecc", - "nfc_protocols", "one_wire", "u8g2", "update_util", diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript new file mode 100644 index 00000000..657f3a9e --- /dev/null +++ b/lib/nfc/SConscript @@ -0,0 +1,16 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/nfc", + ], +) + +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 00000000..410ddbd8 --- /dev/null +++ b/lib/nfc/helpers/mf_classic_dict.c @@ -0,0 +1,148 @@ +#include "mf_classic_dict.h" + +#include <lib/toolbox/args.h> +#include <lib/flipper_format/flipper_format.h> + +#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 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 == MfClassicDictTypeFlipper) { + 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; + } + + 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 == MfClassicDictTypeFlipper) { + if(!buffered_file_stream_open( + dict->stream, MF_CLASSIC_DICT_FLIPPER_PATH, FSAM_READ, 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; + } + } + + // Read total amount of keys + string_t next_line; + string_init(next_line); + while(true) { + if(!stream_read_line(dict->stream, next_line)) break; + if(string_get_char(next_line, 0) == '#') continue; + if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + dict->total_keys++; + } + string_clear(next_line); + stream_rewind(dict->stream); + + dict_loaded = true; + FURI_LOG_I(TAG, "Loaded dictionary with %d 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); +} + +uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict) { + furi_assert(dict); + + return dict->total_keys; +} + +bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) { + furi_assert(dict); + furi_assert(dict->stream); + + uint8_t key_byte_tmp = 0; + string_t next_line; + string_init(next_line); + + bool key_read = false; + *key = 0ULL; + while(!key_read) { + if(!stream_read_line(dict->stream, next_line)) break; + if(string_get_char(next_line, 0) == '#') continue; + if(string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; + for(uint8_t i = 0; i < 12; i += 2) { + args_char_to_hex( + string_get_char(next_line, i), string_get_char(next_line, i + 1), &key_byte_tmp); + *key |= (uint64_t)key_byte_tmp << 8 * (5 - i / 2); + } + key_read = true; + } + + string_clear(next_line); + return key_read; +} + +bool mf_classic_dict_rewind(MfClassicDict* dict) { + furi_assert(dict); + furi_assert(dict->stream); + + return stream_rewind(dict->stream); +} + +bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key) { + furi_assert(dict); + furi_assert(dict->stream); + + string_t key_str; + string_init(key_str); + for(size_t i = 0; i < 6; i++) { + string_cat_printf(key_str, "%02X", key[i]); + } + string_cat_printf(key_str, "\n"); + + bool key_added = false; + do { + if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break; + if(!stream_insert_string(dict->stream, key_str)) break; + key_added = true; + } while(false); + + string_clear(key_str); + return key_added; +} diff --git a/lib/nfc/helpers/mf_classic_dict.h b/lib/nfc/helpers/mf_classic_dict.h new file mode 100644 index 00000000..2654e668 --- /dev/null +++ b/lib/nfc/helpers/mf_classic_dict.h @@ -0,0 +1,28 @@ +#pragma once + +#include <stdbool.h> +#include <storage/storage.h> +#include <lib/flipper_format/flipper_format.h> +#include <lib/toolbox/stream/file_stream.h> +#include <lib/toolbox/stream/buffered_file_stream.h> + +typedef enum { + MfClassicDictTypeUser, + MfClassicDictTypeFlipper, +} MfClassicDictType; + +typedef struct MfClassicDict MfClassicDict; + +bool mf_classic_dict_check_presence(MfClassicDictType dict_type); + +MfClassicDict* mf_classic_dict_alloc(MfClassicDictType dict_type); + +void mf_classic_dict_free(MfClassicDict* dict); + +uint32_t mf_classic_dict_get_total_keys(MfClassicDict* dict); + +bool mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key); + +bool mf_classic_dict_rewind(MfClassicDict* dict); + +bool mf_classic_dict_add_key(MfClassicDict* dict, uint8_t* key); diff --git a/lib/nfc/helpers/nfc_debug_pcap.c b/lib/nfc/helpers/nfc_debug_pcap.c new file mode 100644 index 00000000..48d72bfb --- /dev/null +++ b/lib/nfc/helpers/nfc_debug_pcap.c @@ -0,0 +1,166 @@ +#include "nfc_debug_pcap.h" + +#include <furi_hal_rtc.h> +#include <stream_buffer.h> + +#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") +#define NFC_DEBUG_PCAP_BUFFER_SIZE 64 + +struct NfcDebugPcapWorker { + bool alive; + Storage* storage; + File* file; + StreamBufferHandle_t stream; + FuriThread* thread; +}; + +static File* nfc_debug_pcap_open(Storage* storage) { + File* file = storage_file_alloc(storage); + if(!storage_file_open(file, NFC_DEBUG_PCAP_FILENAME, FSAM_WRITE, FSOM_OPEN_APPEND)) { + storage_file_free(file); + return NULL; + } + if(!storage_file_tell(file)) { + 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(storage_file_write(file, &pcap_hdr, sizeof(pcap_hdr)) != sizeof(pcap_hdr)) { + FURI_LOG_E(TAG, "Failed to write pcap header"); + } + } + return file; +} + +static void + nfc_debug_pcap_write(NfcDebugPcapWorker* instance, uint8_t event, uint8_t* data, uint16_t len) { + FuriHalRtcDateTime datetime; + furi_hal_rtc_get_datetime(&datetime); + + 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, + }; + xStreamBufferSend(instance->stream, &pkt_hdr, sizeof(pkt_hdr), FuriWaitForever); + xStreamBufferSend(instance->stream, data, len, FuriWaitForever); +} + +static void + nfc_debug_pcap_write_tx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) { + NfcDebugPcapWorker* instance = context; + uint8_t event = crc_dropped ? DATA_PCD_TO_PICC_CRC_DROPPED : DATA_PCD_TO_PICC; + nfc_debug_pcap_write(instance, event, data, bits / 8); +} + +static void + nfc_debug_pcap_write_rx(uint8_t* data, uint16_t bits, bool crc_dropped, void* context) { + NfcDebugPcapWorker* instance = context; + uint8_t event = crc_dropped ? DATA_PICC_TO_PCD_CRC_DROPPED : DATA_PICC_TO_PCD; + nfc_debug_pcap_write(instance, event, data, bits / 8); +} + +int32_t nfc_debug_pcap_thread(void* context) { + NfcDebugPcapWorker* instance = context; + uint8_t buffer[NFC_DEBUG_PCAP_BUFFER_SIZE]; + + while(instance->alive) { + size_t ret = + xStreamBufferReceive(instance->stream, buffer, NFC_DEBUG_PCAP_BUFFER_SIZE, 50); + if(storage_file_write(instance->file, buffer, ret) != ret) { + FURI_LOG_E(TAG, "Failed to write pcap data"); + } + } + + return 0; +} + +NfcDebugPcapWorker* nfc_debug_pcap_alloc(Storage* storage) { + NfcDebugPcapWorker* instance = malloc(sizeof(NfcDebugPcapWorker)); + + instance->alive = true; + + instance->storage = storage; + + instance->file = nfc_debug_pcap_open(storage); + + instance->stream = xStreamBufferCreate(4096, 1); + + instance->thread = furi_thread_alloc(); + furi_thread_set_name(instance->thread, "PcapWorker"); + furi_thread_set_stack_size(instance->thread, 1024); + furi_thread_set_callback(instance->thread, nfc_debug_pcap_thread); + furi_thread_set_context(instance->thread, instance); + furi_thread_start(instance->thread); + + return instance; +} + +void nfc_debug_pcap_free(NfcDebugPcapWorker* instance) { + furi_assert(instance); + + instance->alive = false; + + furi_thread_join(instance->thread); + furi_thread_free(instance->thread); + + vStreamBufferDelete(instance->stream); + + if(instance->file) storage_file_free(instance->file); + + instance->storage = NULL; + + free(instance); +} + +void nfc_debug_pcap_prepare_tx_rx( + NfcDebugPcapWorker* instance, + FuriHalNfcTxRxContext* tx_rx, + bool is_picc) { + if(!instance || !instance->file) return; + + if(is_picc) { + tx_rx->sniff_tx = nfc_debug_pcap_write_rx; + tx_rx->sniff_rx = nfc_debug_pcap_write_tx; + } else { + tx_rx->sniff_tx = nfc_debug_pcap_write_tx; + tx_rx->sniff_rx = nfc_debug_pcap_write_rx; + } + + tx_rx->sniff_context = instance; +} diff --git a/lib/nfc/helpers/nfc_debug_pcap.h b/lib/nfc/helpers/nfc_debug_pcap.h new file mode 100644 index 00000000..6d2a449a --- /dev/null +++ b/lib/nfc/helpers/nfc_debug_pcap.h @@ -0,0 +1,21 @@ +#pragma once + +#include <furi_hal_nfc.h> +#include <storage/storage.h> + +typedef struct NfcDebugPcapWorker NfcDebugPcapWorker; + +NfcDebugPcapWorker* nfc_debug_pcap_alloc(Storage* storage); + +void nfc_debug_pcap_free(NfcDebugPcapWorker* instance); + +/** Prepare tx/rx context for debug pcap logging, if enabled. + * + * @param instance NfcDebugPcapWorker* instance, can be NULL + * @param tx_rx TX/RX context to log + * @param is_picc if true, record Flipper as PICC, else PCD. + */ +void nfc_debug_pcap_prepare_tx_rx( + NfcDebugPcapWorker* 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 00000000..649a2c5f --- /dev/null +++ b/lib/nfc/nfc_device.c @@ -0,0 +1,1278 @@ +#include "nfc_device.h" +#include "assets_icons.h" +#include "m-string.h" +#include "nfc_types.h" + +#include <lib/toolbox/path.h> +#include <lib/toolbox/hex.h> +#include <lib/nfc/protocols/nfc_util.h> +#include <flipper_format/flipper_format.h> + +#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 = 2; + +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; + +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); + string_init(nfc_dev->load_path); + string_init(nfc_dev->dev_data.parsed_data); + 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); + string_clear(nfc_dev->load_path); + string_clear(nfc_dev->dev_data.parsed_data); + free(nfc_dev); +} + +static void nfc_device_prepare_format_string(NfcDevice* dev, string_t format_string) { + if(dev->format == NfcDeviceSaveFormatUid) { + string_set_str(format_string, "UID"); + } else if(dev->format == NfcDeviceSaveFormatBankCard) { + string_set_str(format_string, "Bank card"); + } else if(dev->format == NfcDeviceSaveFormatMifareUl) { + string_set_str(format_string, nfc_mf_ul_type(dev->dev_data.mf_ul_data.type, true)); + } else if(dev->format == NfcDeviceSaveFormatMifareClassic) { + string_set_str(format_string, "Mifare Classic"); + } else if(dev->format == NfcDeviceSaveFormatMifareDesfire) { + string_set_str(format_string, "Mifare DESFire"); + } else { + string_set_str(format_string, "Unknown"); + } +} + +static bool nfc_device_parse_format_string(NfcDevice* dev, string_t format_string) { + if(string_start_with_str_p(format_string, "UID")) { + dev->format = NfcDeviceSaveFormatUid; + dev->dev_data.protocol = NfcDeviceProtocolUnknown; + return true; + } + if(string_start_with_str_p(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(string_equal_str_p(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(string_start_with_str_p(format_string, "Mifare Classic")) { + dev->format = NfcDeviceSaveFormatMifareClassic; + dev->dev_data.protocol = NfcDeviceProtocolMifareClassic; + return true; + } + if(string_start_with_str_p(format_string, "Mifare DESFire")) { + dev->format = NfcDeviceSaveFormatMifareDesfire; + dev->dev_data.protocol = NfcDeviceProtocolMifareDesfire; + 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; + string_t temp_str; + string_init(temp_str); + + // Save Mifare Ultralight specific data + do { + if(!flipper_format_write_comment_cstr(file, "Mifare Ultralight specific data")) 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++) { + string_printf(temp_str, "Counter %d", i); + if(!flipper_format_write_uint32( + file, string_get_cstr(temp_str), &data->counter[i], 1)) { + counters_saved = false; + break; + } + string_printf(temp_str, "Tearing %d", i); + if(!flipper_format_write_hex(file, 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; + bool pages_saved = true; + for(uint16_t i = 0; i < data->data_size; i += 4) { + string_printf(temp_str, "Page %d", i / 4); + if(!flipper_format_write_hex(file, 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); + + string_clear(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; + string_t temp_str; + string_init(temp_str); + + do { + // 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++) { + string_printf(temp_str, "Counter %d", i); + if(!flipper_format_read_uint32(file, string_get_cstr(temp_str), &data->counter[i], 1)) { + counters_parsed = false; + break; + } + string_printf(temp_str, "Tearing %d", i); + if(!flipper_format_read_hex(file, string_get_cstr(temp_str), &data->tearing[i], 1)) { + counters_parsed = false; + break; + } + } + if(!counters_parsed) break; + // Read pages + uint32_t pages = 0; + if(!flipper_format_read_uint32(file, "Pages total", &pages, 1)) break; + data->data_size = pages * 4; + bool pages_parsed = true; + for(uint16_t i = 0; i < pages; i++) { + string_printf(temp_str, "Page %d", i); + if(!flipper_format_read_hex(file, 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; + + parsed = true; + } while(false); + + string_clear(temp_str); + return parsed; +} + +static bool nfc_device_save_mifare_df_key_settings( + FlipperFormat* file, + MifareDesfireKeySettings* ks, + const char* prefix) { + bool saved = false; + string_t key; + string_init(key); + + do { + string_printf(key, "%s Change Key ID", prefix); + if(!flipper_format_write_hex(file, string_get_cstr(key), &ks->change_key_id, 1)) break; + string_printf(key, "%s Config Changeable", prefix); + if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->config_changeable, 1)) + break; + string_printf(key, "%s Free Create Delete", prefix); + if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->free_create_delete, 1)) + break; + string_printf(key, "%s Free Directory List", prefix); + if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->free_directory_list, 1)) + break; + string_printf(key, "%s Key Changeable", prefix); + if(!flipper_format_write_bool(file, string_get_cstr(key), &ks->master_key_changeable, 1)) + break; + if(ks->flags) { + string_printf(key, "%s Flags", prefix); + if(!flipper_format_write_hex(file, string_get_cstr(key), &ks->flags, 1)) break; + } + string_printf(key, "%s Max Keys", prefix); + if(!flipper_format_write_hex(file, string_get_cstr(key), &ks->max_keys, 1)) break; + for(MifareDesfireKeyVersion* kv = ks->key_version_head; kv; kv = kv->next) { + string_printf(key, "%s Key %d Version", prefix, kv->id); + if(!flipper_format_write_hex(file, string_get_cstr(key), &kv->version, 1)) break; + } + saved = true; + } while(false); + + string_clear(key); + return saved; +} + +bool nfc_device_load_mifare_df_key_settings( + FlipperFormat* file, + MifareDesfireKeySettings* ks, + const char* prefix) { + bool parsed = false; + string_t key; + string_init(key); + + do { + string_printf(key, "%s Change Key ID", prefix); + if(!flipper_format_read_hex(file, string_get_cstr(key), &ks->change_key_id, 1)) break; + string_printf(key, "%s Config Changeable", prefix); + if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->config_changeable, 1)) break; + string_printf(key, "%s Free Create Delete", prefix); + if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->free_create_delete, 1)) + break; + string_printf(key, "%s Free Directory List", prefix); + if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->free_directory_list, 1)) + break; + string_printf(key, "%s Key Changeable", prefix); + if(!flipper_format_read_bool(file, string_get_cstr(key), &ks->master_key_changeable, 1)) + break; + string_printf(key, "%s Flags", prefix); + if(flipper_format_key_exist(file, string_get_cstr(key))) { + if(!flipper_format_read_hex(file, string_get_cstr(key), &ks->flags, 1)) break; + } + string_printf(key, "%s Max Keys", prefix); + if(!flipper_format_read_hex(file, 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++) { + string_printf(key, "%s Key %d Version", prefix, key_id); + uint8_t version; + if(flipper_format_read_hex(file, 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); + + string_clear(key); + return parsed; +} + +static bool nfc_device_save_mifare_df_app(FlipperFormat* file, MifareDesfireApplication* app) { + bool saved = false; + string_t prefix, key; + string_init_printf(prefix, "Application %02x%02x%02x", app->id[0], app->id[1], app->id[2]); + string_init(key); + uint8_t* tmp = NULL; + + do { + if(app->key_settings) { + if(!nfc_device_save_mifare_df_key_settings( + file, app->key_settings, 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; + } + string_printf(key, "%s File IDs", string_get_cstr(prefix)); + if(!flipper_format_write_hex(file, 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; + string_printf(key, "%s File %d Type", string_get_cstr(prefix), f->id); + if(!flipper_format_write_hex(file, string_get_cstr(key), &f->type, 1)) break; + string_printf( + key, "%s File %d Communication Settings", string_get_cstr(prefix), f->id); + if(!flipper_format_write_hex(file, string_get_cstr(key), &f->comm, 1)) break; + string_printf(key, "%s File %d Access Rights", string_get_cstr(prefix), f->id); + if(!flipper_format_write_hex( + file, 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; + string_printf(key, "%s File %d Size", string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, string_get_cstr(key), &f->settings.data.size, 1)) + break; + } else if(f->type == MifareDesfireFileTypeValue) { + string_printf(key, "%s File %d Hi Limit", string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, string_get_cstr(key), &f->settings.value.hi_limit, 1)) + break; + string_printf(key, "%s File %d Lo Limit", string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, string_get_cstr(key), &f->settings.value.lo_limit, 1)) + break; + string_printf( + key, "%s File %d Limited Credit Value", string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, string_get_cstr(key), &f->settings.value.limited_credit_value, 1)) + break; + string_printf( + key, "%s File %d Limited Credit Enabled", string_get_cstr(prefix), f->id); + if(!flipper_format_write_bool( + file, string_get_cstr(key), &f->settings.value.limited_credit_enabled, 1)) + break; + size = 4; + } else if( + f->type == MifareDesfireFileTypeLinearRecord || + f->type == MifareDesfireFileTypeCyclicRecord) { + string_printf(key, "%s File %d Size", string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, string_get_cstr(key), &f->settings.record.size, 1)) + break; + string_printf(key, "%s File %d Max", string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, string_get_cstr(key), &f->settings.record.max, 1)) + break; + string_printf(key, "%s File %d Cur", string_get_cstr(prefix), f->id); + if(!flipper_format_write_uint32( + file, string_get_cstr(key), &f->settings.record.cur, 1)) + break; + size = f->settings.record.size * f->settings.record.cur; + } + if(f->contents) { + string_printf(key, "%s File %d", string_get_cstr(prefix), f->id); + if(!flipper_format_write_hex(file, string_get_cstr(key), f->contents, size)) break; + } + saved_files = true; + } + if(!saved_files) { + break; + } + saved = true; + } while(false); + + free(tmp); + string_clear(prefix); + string_clear(key); + return saved; +} + +bool nfc_device_load_mifare_df_app(FlipperFormat* file, MifareDesfireApplication* app) { + bool parsed = false; + string_t prefix, key; + string_init_printf(prefix, "Application %02x%02x%02x", app->id[0], app->id[1], app->id[2]); + string_init(key); + 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, string_get_cstr(prefix))) { + free(app->key_settings); + app->key_settings = NULL; + break; + } + string_printf(key, "%s File IDs", string_get_cstr(prefix)); + uint32_t n_files; + if(!flipper_format_get_value_count(file, string_get_cstr(key), &n_files)) break; + tmp = malloc(n_files); + if(!flipper_format_read_hex(file, 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]; + string_printf(key, "%s File %d Type", string_get_cstr(prefix), f->id); + if(!flipper_format_read_hex(file, string_get_cstr(key), &f->type, 1)) break; + string_printf( + key, "%s File %d Communication Settings", string_get_cstr(prefix), f->id); + if(!flipper_format_read_hex(file, string_get_cstr(key), &f->comm, 1)) break; + string_printf(key, "%s File %d Access Rights", string_get_cstr(prefix), f->id); + if(!flipper_format_read_hex(file, string_get_cstr(key), (uint8_t*)&f->access_rights, 2)) + break; + if(f->type == MifareDesfireFileTypeStandard || + f->type == MifareDesfireFileTypeBackup) { + string_printf(key, "%s File %d Size", string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, string_get_cstr(key), &f->settings.data.size, 1)) + break; + } else if(f->type == MifareDesfireFileTypeValue) { + string_printf(key, "%s File %d Hi Limit", string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, string_get_cstr(key), &f->settings.value.hi_limit, 1)) + break; + string_printf(key, "%s File %d Lo Limit", string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, string_get_cstr(key), &f->settings.value.lo_limit, 1)) + break; + string_printf( + key, "%s File %d Limited Credit Value", string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, string_get_cstr(key), &f->settings.value.limited_credit_value, 1)) + break; + string_printf( + key, "%s File %d Limited Credit Enabled", string_get_cstr(prefix), f->id); + if(!flipper_format_read_bool( + file, string_get_cstr(key), &f->settings.value.limited_credit_enabled, 1)) + break; + } else if( + f->type == MifareDesfireFileTypeLinearRecord || + f->type == MifareDesfireFileTypeCyclicRecord) { + string_printf(key, "%s File %d Size", string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, string_get_cstr(key), &f->settings.record.size, 1)) + break; + string_printf(key, "%s File %d Max", string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, string_get_cstr(key), &f->settings.record.max, 1)) + break; + string_printf(key, "%s File %d Cur", string_get_cstr(prefix), f->id); + if(!flipper_format_read_uint32( + file, string_get_cstr(key), &f->settings.record.cur, 1)) + break; + } + string_printf(key, "%s File %d", string_get_cstr(prefix), f->id); + if(flipper_format_key_exist(file, string_get_cstr(key))) { + uint32_t size; + if(!flipper_format_get_value_count(file, string_get_cstr(key), &size)) break; + f->contents = malloc(size); + if(!flipper_format_read_hex(file, 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); + string_clear(prefix); + string_clear(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); + 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) break; + } + parsed = true; + } while(false); + + free(tmp); + 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; + string_t temp_str; + string_init(temp_str); + + 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, 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); + + string_clear(temp_str); + return parsed; +} + +static void nfc_device_write_mifare_classic_block( + string_t block_str, + MfClassicData* data, + uint8_t block_num) { + 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)) { + string_cat_printf(block_str, "%02X ", sec_tr->key_a[i]); + } else { + 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)) { + string_cat_printf(block_str, "%02X ", sec_tr->access_bits[i]); + } else { + 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)) { + string_cat_printf(block_str, "%02X ", sec_tr->key_b[i]); + } else { + 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)) { + string_cat_printf(block_str, "%02X ", data->block[block_num].value[i]); + } else { + string_cat_printf(block_str, "?? "); + } + } + } + string_strim(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; + string_t temp_str; + string_init(temp_str); + 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 == 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; + string_t block_str; + string_init(block_str); + for(size_t i = 0; i < blocks; i++) { + string_printf(temp_str, "Block %d", i); + nfc_device_write_mifare_classic_block(block_str, data, i); + if(!flipper_format_write_string(file, string_get_cstr(temp_str), block_str)) { + block_saved = false; + break; + } + } + string_clear(block_str); + if(!block_saved) break; + saved = true; + } while(false); + + string_clear(temp_str); + return saved; +} + +static void nfc_device_load_mifare_classic_block( + string_t block_str, + MfClassicData* data, + uint8_t block_num) { + string_strim(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; + + string_strim(block_str); + for(size_t i = 0; i < MF_CLASSIC_BLOCK_SIZE; i++) { + char hi = string_get_char(block_str, 3 * i); + char low = string_get_char(block_str, 3 * i + 1); + uint8_t byte = 0; + if(hex_chars_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; + string_t temp_str; + uint32_t data_format_version = 0; + string_init(temp_str); + 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(!string_cmp_str(temp_str, "1K")) { + data->type = MfClassicType1k; + data_blocks = 64; + } else if(!string_cmp_str(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 compatability + 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; + string_t block_str; + string_init(block_str); + for(size_t i = 0; i < data_blocks; i++) { + string_printf(temp_str, "Block %d", i); + if(!flipper_format_read_string(file, string_get_cstr(temp_str), block_str)) { + block_read = false; + break; + } + nfc_device_load_mifare_classic_block(block_str, data, i); + } + string_clear(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); + + string_clear(temp_str); + return parsed; +} + +static void nfc_device_get_key_cache_file_path(NfcDevice* dev, string_t file_path) { + uint8_t* uid = dev->dev_data.nfc_data.uid; + uint8_t uid_len = dev->dev_data.nfc_data.uid_len; + string_set_str(file_path, NFC_DEVICE_KEYS_FOLDER "/"); + for(size_t i = 0; i < uid_len; i++) { + string_cat_printf(file_path, "%02X", uid[i]); + } + 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; + string_t temp_str; + string_init(temp_str); + + 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, string_get_cstr(temp_str))) break; + if(!flipper_format_file_open_always(file, 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 == 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)) { + string_printf(temp_str, "Key A sector %d", i); + key_save_success = + flipper_format_write_hex(file, string_get_cstr(temp_str), sec_tr->key_a, 6); + } + if(!key_save_success) break; + if(FURI_BIT(data->key_a_mask, i)) { + string_printf(temp_str, "Key B sector %d", i); + key_save_success = + flipper_format_write_hex(file, string_get_cstr(temp_str), sec_tr->key_b, 6); + } + } + save_success = key_save_success; + } while(false); + + flipper_format_free(file); + string_clear(temp_str); + return save_success; +} + +bool nfc_device_load_key_cache(NfcDevice* dev) { + furi_assert(dev); + string_t temp_str; + string_init(temp_str); + + 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, string_get_cstr(temp_str), NULL) != FSE_OK) break; + if(!flipper_format_file_open_existing(file, string_get_cstr(temp_str))) break; + uint32_t version = 0; + if(!flipper_format_read_header(file, temp_str, &version)) break; + if(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(!string_cmp_str(temp_str, "1K")) { + data->type = MfClassicType1k; + } else if(!string_cmp_str(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)) { + string_printf(temp_str, "Key A sector %d", i); + key_read_success = + flipper_format_read_hex(file, string_get_cstr(temp_str), sec_tr->key_a, 6); + } + if(!key_read_success) break; + if(FURI_BIT(data->key_b_mask, i)) { + string_printf(temp_str, "Key B sector %d", i); + key_read_success = + flipper_format_read_hex(file, string_get_cstr(temp_str), sec_tr->key_b, 6); + } + } + load_success = key_read_success; + } while(false); + + string_clear(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(string_t orig_path, string_t shadow_path) { + // TODO: this won't work if there is ".nfc" anywhere in the path other than + // at the end + size_t ext_start = string_search_str(orig_path, NFC_APP_EXTENSION); + string_set_n(shadow_path, orig_path, 0, ext_start); +} + +static void nfc_device_get_shadow_path(string_t orig_path, string_t shadow_path) { + nfc_device_get_path_without_ext(orig_path, shadow_path); + string_cat_printf(shadow_path, "%s", NFC_APP_SHADOW_EXTENSION); +} + +static bool nfc_device_save_file( + NfcDevice* dev, + const char* dev_name, + const char* folder, + const char* extension, + bool use_load_path) { + furi_assert(dev); + + bool saved = false; + FlipperFormat* file = flipper_format_file_alloc(dev->storage); + FuriHalNfcDevData* data = &dev->dev_data.nfc_data; + string_t temp_str; + string_init(temp_str); + + do { + if(use_load_path && !string_empty_p(dev->load_path)) { + // Get directory name + path_extract_dirname(string_get_cstr(dev->load_path), temp_str); + // Create nfc directory if necessary + if(!storage_simply_mkdir(dev->storage, string_get_cstr(temp_str))) break; + // Make path to file to save + string_cat_printf(temp_str, "/%s%s", dev_name, extension); + } else { + // Create nfc directory if necessary + if(!storage_simply_mkdir(dev->storage, NFC_APP_FOLDER)) break; + // First remove nfc device file if it was saved + string_printf(temp_str, "%s/%s%s", folder, dev_name, extension); + } + // Open file + if(!flipper_format_file_open_always(file, 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, Bank card")) + break; + nfc_device_prepare_format_string(dev, temp_str); + if(!flipper_format_write_string(file, "Device type", temp_str)) break; + // Write UID, ATQA, SAK + if(!flipper_format_write_comment_cstr(file, "UID, ATQA and SAK are common for all formats")) + break; + if(!flipper_format_write_hex(file, "UID", data->uid, data->uid_len)) break; + if(!flipper_format_write_hex(file, "ATQA", data->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 == 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) { + dialog_message_show_storage_error(dev->dialogs, "Can not save\nkey file"); + } + string_clear(temp_str); + flipper_format_free(file); + return saved; +} + +bool nfc_device_save(NfcDevice* dev, const char* dev_name) { + return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_EXTENSION, true); +} + +bool nfc_device_save_shadow(NfcDevice* dev, const char* dev_name) { + dev->shadow_file_exist = true; + return nfc_device_save_file(dev, dev_name, NFC_APP_FOLDER, NFC_APP_SHADOW_EXTENSION, true); +} + +static bool nfc_device_load_data(NfcDevice* dev, string_t 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; + string_t temp_str; + string_init(temp_str); + bool deprecated_version = false; + + if(dev->loading_cb) { + dev->loading_cb(dev->loading_cb_ctx, true); + } + + do { + // Check existance of shadow file + nfc_device_get_shadow_path(path, temp_str); + dev->shadow_file_exist = + storage_common_stat(dev->storage, 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, string_get_cstr(temp_str))) break; + } else { + if(!flipper_format_file_open_existing(file, 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(string_cmp_str(temp_str, nfc_file_header) || (version != nfc_file_version)) { + 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)) break; + data->uid_len = data_cnt; + if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break; + if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break; + if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break; + // 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 == 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"); + } + } + + string_clear(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 + string_set_str(dev->load_path, file_path); + bool dev_load = nfc_device_load_data(dev, dev->load_path, show_dialog); + if(dev_load) { + // Set device name + string_t filename; + string_init(filename); + path_extract_filename_no_ext(file_path, filename); + nfc_device_set_name(dev, string_get_cstr(filename)); + string_clear(filename); + } + + return dev_load; +} + +bool nfc_file_select(NfcDevice* dev) { + furi_assert(dev); + + // Input events and views are managed by file_browser + string_t nfc_app_folder; + string_init_set_str(nfc_app_folder, NFC_APP_FOLDER); + bool res = dialog_file_browser_show( + dev->dialogs, dev->load_path, nfc_app_folder, NFC_APP_EXTENSION, true, &I_Nfc_10px, true); + string_clear(nfc_app_folder); + if(res) { + string_t filename; + string_init(filename); + path_extract_filename(dev->load_path, filename, true); + strncpy(dev->dev_name, 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); + } + string_clear(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) { + memset(&dev_data->mf_ul_data, 0, sizeof(MfUltralightData)); + } else if(dev_data->protocol == NfcDeviceProtocolEMV) { + memset(&dev_data->emv_data, 0, sizeof(EmvData)); + } + memset(&dev_data->nfc_data, 0, sizeof(FuriHalNfcDevData)); + dev_data->protocol = NfcDeviceProtocolUnknown; + string_reset(dev_data->parsed_data); +} + +void nfc_device_clear(NfcDevice* dev) { + furi_assert(dev); + + nfc_device_data_clear(&dev->dev_data); + dev->format = NfcDeviceSaveFormatUid; + string_reset(dev->load_path); +} + +bool nfc_device_delete(NfcDevice* dev, bool use_load_path) { + furi_assert(dev); + + bool deleted = false; + string_t file_path; + string_init(file_path); + + do { + // Delete original file + if(use_load_path && !string_empty_p(dev->load_path)) { + string_set(file_path, dev->load_path); + } else { + string_printf(file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + } + if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; + // Delete shadow file if it exists + if(dev->shadow_file_exist) { + if(use_load_path && !string_empty_p(dev->load_path)) { + nfc_device_get_shadow_path(dev->load_path, file_path); + } else { + string_printf( + file_path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + } + if(!storage_simply_remove(dev->storage, string_get_cstr(file_path))) break; + } + deleted = true; + } while(0); + + if(!deleted) { + dialog_message_show_storage_error(dev->dialogs, "Can not remove file"); + } + + string_clear(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; + string_t path; + + string_init(path); + + do { + if(use_load_path && !string_empty_p(dev->load_path)) { + nfc_device_get_shadow_path(dev->load_path, path); + } else { + string_printf( + path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_SHADOW_EXTENSION); + } + if(!storage_simply_remove(dev->storage, string_get_cstr(path))) break; + dev->shadow_file_exist = false; + if(use_load_path && !string_empty_p(dev->load_path)) { + string_set(path, dev->load_path); + } else { + string_printf(path, "%s/%s%s", NFC_APP_FOLDER, dev->dev_name, NFC_APP_EXTENSION); + } + if(!nfc_device_load_data(dev, path, true)) break; + restored = true; + } while(0); + + string_clear(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 00000000..e1ff6d42 --- /dev/null +++ b/lib/nfc/nfc_device.h @@ -0,0 +1,94 @@ +#pragma once + +#include <stdint.h> +#include <stdbool.h> +#include <storage/storage.h> +#include <dialogs/dialogs.h> + +#include <furi_hal_nfc.h> +#include <lib/nfc/protocols/emv.h> +#include <lib/nfc/protocols/mifare_ultralight.h> +#include <lib/nfc/protocols/mifare_classic.h> +#include <lib/nfc/protocols/mifare_desfire.h> + +#define NFC_DEV_NAME_MAX_LEN 22 +#define NFC_READER_DATA_MAX_SIZE 64 + +#define NFC_APP_FOLDER ANY_PATH("nfc") +#define NFC_APP_EXTENSION ".nfc" +#define NFC_APP_SHADOW_EXTENSION ".shd" + +typedef void (*NfcLoadingCallback)(void* context, bool state); + +typedef enum { + NfcDeviceProtocolUnknown, + NfcDeviceProtocolEMV, + NfcDeviceProtocolMifareUl, + NfcDeviceProtocolMifareClassic, + NfcDeviceProtocolMifareDesfire, +} NfcProtocol; + +typedef enum { + NfcDeviceSaveFormatUid, + NfcDeviceSaveFormatBankCard, + NfcDeviceSaveFormatMifareUl, + NfcDeviceSaveFormatMifareClassic, + NfcDeviceSaveFormatMifareDesfire, +} NfcDeviceSaveFormat; + +typedef struct { + uint8_t data[NFC_READER_DATA_MAX_SIZE]; + uint16_t size; +} NfcReaderRequestData; + +typedef struct { + FuriHalNfcDevData nfc_data; + NfcProtocol protocol; + NfcReaderRequestData reader_data; + union { + EmvData emv_data; + MfUltralightData mf_ul_data; + MfClassicData mf_classic_data; + MifareDesfireData mf_df_data; + }; + string_t parsed_data; +} NfcDeviceData; + +typedef struct { + Storage* storage; + DialogsApp* dialogs; + NfcDeviceData dev_data; + char dev_name[NFC_DEV_NAME_MAX_LEN + 1]; + string_t load_path; + 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); diff --git a/lib/nfc/nfc_types.c b/lib/nfc/nfc_types.c new file mode 100644 index 00000000..42762876 --- /dev/null +++ b/lib/nfc/nfc_types.c @@ -0,0 +1,65 @@ +#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 == 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 == 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 00000000..fb53ce7c --- /dev/null +++ b/lib/nfc/nfc_types.h @@ -0,0 +1,11 @@ +#pragma once + +#include "nfc_device.h" + +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); diff --git a/lib/nfc/nfc_worker.c b/lib/nfc/nfc_worker.c new file mode 100644 index 00000000..4a3176ff --- /dev/null +++ b/lib/nfc/nfc_worker.c @@ -0,0 +1,510 @@ +#include "nfc_worker_i.h" +#include <furi_hal.h> + +#include <platform.h> +#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(); + furi_thread_set_name(nfc_worker->thread, "NfcWorker"); + furi_thread_set_stack_size(nfc_worker->thread, 8192); + furi_thread_set_callback(nfc_worker->thread, nfc_worker_task); + furi_thread_set_context(nfc_worker->thread, 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); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + nfc_worker->debug_pcap_worker = nfc_debug_pcap_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); + + if(nfc_worker->debug_pcap_worker) nfc_debug_pcap_free(nfc_worker->debug_pcap_worker); + + 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); + } + + 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); + if(nfc_worker->state == NfcWorkerStateBroken || nfc_worker->state == NfcWorkerStateReady) { + return; + } + 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) { + nfc_worker_read(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 == NfcWorkerStateMfClassicUserDictAttack) { + nfc_worker_mf_classic_dict_attack(nfc_worker, MfClassicDictTypeUser); + } else if(nfc_worker->state == NfcWorkerStateMfClassicFlipperDictAttack) { + nfc_worker_mf_classic_dict_attack(nfc_worker, MfClassicDictTypeFlipper); + } + furi_hal_nfc_sleep(); + nfc_worker_change_state(nfc_worker, NfcWorkerStateReady); + + return 0; +} + +static bool nfc_worker_read_mf_ultralight(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + bool read_success = false; + MfUltralightReader reader = {}; + MfUltralightData data = {}; + + nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, tx_rx, false); + do { + // Read card + 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); + + 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; + + nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, tx_rx, false); + do { + // Try to read supported card + FURI_LOG_I(TAG, "Try read 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); + } + } + } + } + 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 = (sectors_read == sectors_total); + } + } while(false); + + 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; + + nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, tx_rx, false); + do { + if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break; + if(!mf_df_read_card(tx_rx, data)) break; + read_success = true; + } while(false); + + return read_success; +} + +static bool nfc_worker_read_bank_card(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + bool read_success = false; + EmvApplication emv_app = {}; + EmvData* result = &nfc_worker->dev_data->emv_data; + + nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, tx_rx, false); + do { + // Read card + if(!furi_hal_nfc_detect(&nfc_worker->dev_data->nfc_data, 300)) break; + if(!emv_read_bank_card(tx_rx, &emv_app)) break; + // Copy data + // TODO Set EmvData to reader or like in mifare ultralight! + result->number_len = emv_app.card_number_len; + memcpy(result->number, emv_app.card_number, result->number_len); + result->aid_len = emv_app.aid_len; + memcpy(result->aid, emv_app.aid, result->aid_len); + if(emv_app.name_found) { + memcpy(result->name, emv_app.name, sizeof(emv_app.name)); + } + if(emv_app.exp_month) { + result->exp_mon = emv_app.exp_month; + result->exp_year = emv_app.exp_year; + } + if(emv_app.country_code) { + result->country_code = emv_app.country_code; + } + if(emv_app.currency_code) { + result->currency_code = emv_app.currency_code; + } + read_success = true; + } while(false); + + 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 if(nfc_data->interface == FuriHalNfcInterfaceIsoDep) { + FURI_LOG_I(TAG, "ISO14443-4 card detected"); + nfc_worker->dev_data->protocol = NfcDeviceProtocolEMV; + if(!nfc_worker_read_bank_card(nfc_worker, tx_rx)) { + FURI_LOG_I(TAG, "Unknown card. Save UID"); + 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 == NfcDeviceProtocolEMV) { + event = NfcWorkerEventReadBankCard; + 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) { + event = NfcWorkerEventReadUidNfcV; + 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 = {}; + nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true); + FuriHalNfcDevData* data = &nfc_worker->dev_data->nfc_data; + NfcReaderRequestData* reader_data = &nfc_worker->dev_data->reader_data; + + while(nfc_worker->state == NfcWorkerStateUidEmulate) { + if(furi_hal_nfc_listen(data->uid, data->uid_len, data->atqa, data->sak, true, 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 = {}; + nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true); + FuriHalNfcDevData params = { + .uid = {0xCF, 0x72, 0xd4, 0x40}, + .uid_len = 4, + .atqa = {0x00, 0x04}, + .sak = 0x20, + .type = FuriHalNfcTypeA, + }; + + while(nfc_worker->state == NfcWorkerStateEmulateApdu) { + 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); + } +} + +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); + 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; + } + } +} + +void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker, MfClassicDictType type) { + furi_assert(nfc_worker); + furi_assert(nfc_worker->callback); + + MfClassicData* data = &nfc_worker->dev_data->mf_classic_data; + uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type); + uint64_t key = 0; + FuriHalNfcTxRxContext tx_rx = {}; + bool card_found_notified = true; + bool card_removed_notified = false; + + // Load dictionary + MfClassicDict* dict = mf_classic_dict_alloc(type); + if(!dict) { + FURI_LOG_E(TAG, "Dictionary not found"); + nfc_worker->callback(NfcWorkerEventNoDictFound, nfc_worker->context); + mf_classic_dict_free(dict); + return; + } + + FURI_LOG_D(TAG, "Start Dictionary attack"); + 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; + bool is_key_a_found = mf_classic_is_key_found(data, i, MfClassicKeyA); + bool is_key_b_found = mf_classic_is_key_found(data, i, MfClassicKeyB); + while(mf_classic_dict_get_next_key(dict, &key)) { + 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; + } + FURI_LOG_D( + TAG, + "Try to auth to sector %d with key %04lx%08lx", + i, + (uint32_t)(key >> 32), + (uint32_t)key); + if(!is_key_a_found) { + is_key_a_found = mf_classic_is_key_found(data, i, MfClassicKeyA); + if(mf_classic_authenticate(&tx_rx, block_num, key, MfClassicKeyA)) { + mf_classic_set_key_found(data, i, MfClassicKeyA, key); + nfc_worker->callback(NfcWorkerEventFoundKeyA, nfc_worker->context); + } + furi_hal_nfc_sleep(); + } + if(!is_key_b_found) { + is_key_b_found = mf_classic_is_key_found(data, i, MfClassicKeyB); + if(mf_classic_authenticate(&tx_rx, block_num, key, MfClassicKeyB)) { + mf_classic_set_key_found(data, i, MfClassicKeyB, key); + nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); + } + } + if(is_key_a_found && is_key_b_found) break; + if(!((nfc_worker->state == NfcWorkerStateMfClassicUserDictAttack) || + (nfc_worker->state == NfcWorkerStateMfClassicFlipperDictAttack))) + 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 == NfcWorkerStateMfClassicUserDictAttack) || + (nfc_worker->state == NfcWorkerStateMfClassicFlipperDictAttack))) + break; + } + } + if(!((nfc_worker->state == NfcWorkerStateMfClassicUserDictAttack) || + (nfc_worker->state == NfcWorkerStateMfClassicFlipperDictAttack))) + break; + mf_classic_read_sector(&tx_rx, data, i); + mf_classic_dict_rewind(dict); + } + mf_classic_dict_free(dict); + if((nfc_worker->state == NfcWorkerStateMfClassicUserDictAttack) || + (nfc_worker->state == NfcWorkerStateMfClassicFlipperDictAttack)) { + 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 = {}; + nfc_debug_pcap_prepare_tx_rx(nfc_worker->debug_pcap_worker, &tx_rx, true); + 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) { + if(furi_hal_nfc_listen_rx(&tx_rx, 300)) { + mf_classic_emulator(&emulator, &tx_rx); + } + } + 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(); +} diff --git a/lib/nfc/nfc_worker.h b/lib/nfc/nfc_worker.h new file mode 100755 index 00000000..f6df406b --- /dev/null +++ b/lib/nfc/nfc_worker.h @@ -0,0 +1,71 @@ +#pragma once + +#include "nfc_device.h" + +typedef struct NfcWorker NfcWorker; + +typedef enum { + // Init states + NfcWorkerStateNone, + NfcWorkerStateBroken, + NfcWorkerStateReady, + // Main worker states + NfcWorkerStateRead, + NfcWorkerStateUidEmulate, + NfcWorkerStateMfUltralightEmulate, + NfcWorkerStateMfClassicEmulate, + NfcWorkerStateMfClassicUserDictAttack, + NfcWorkerStateMfClassicFlipperDictAttack, + // 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, + NfcWorkerEventReadBankCard, + + // Nfc worker common events + NfcWorkerEventSuccess, + NfcWorkerEventFail, + NfcWorkerEventAborted, + NfcWorkerEventCardDetected, + NfcWorkerEventNoCardDetected, + + // Mifare Classic events + NfcWorkerEventNoDictFound, + NfcWorkerEventNewSector, + NfcWorkerEventFoundKeyA, + NfcWorkerEventFoundKeyB, +} 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); diff --git a/lib/nfc/nfc_worker_i.h b/lib/nfc/nfc_worker_i.h new file mode 100644 index 00000000..f19f58d5 --- /dev/null +++ b/lib/nfc/nfc_worker_i.h @@ -0,0 +1,48 @@ +#pragma once + +#include "nfc_worker.h" + +#include <furi.h> +#include <lib/toolbox/stream/file_stream.h> + +#include <lib/nfc/protocols/nfc_util.h> +#include <lib/nfc/protocols/emv.h> +#include <lib/nfc/protocols/mifare_common.h> +#include <lib/nfc/protocols/mifare_ultralight.h> +#include <lib/nfc/protocols/mifare_classic.h> +#include <lib/nfc/protocols/mifare_desfire.h> +#include <lib/nfc/protocols/nfca.h> + +#include "helpers/mf_classic_dict.h" +#include "helpers/nfc_debug_pcap.h" + +struct NfcWorker { + FuriThread* thread; + Storage* storage; + Stream* dict_stream; + + NfcDeviceData* dev_data; + + NfcWorkerCallback callback; + void* context; + + NfcWorkerState state; + + NfcDebugPcapWorker* debug_pcap_worker; +}; + +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_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_mf_classic_dict_attack(NfcWorker* nfc_worker, MfClassicDictType type); + +void nfc_worker_emulate_apdu(NfcWorker* nfc_worker); diff --git a/lib/nfc/parsers/nfc_supported_card.c b/lib/nfc/parsers/nfc_supported_card.c new file mode 100644 index 00000000..44eee838 --- /dev/null +++ b/lib/nfc/parsers/nfc_supported_card.c @@ -0,0 +1,12 @@ +#include "nfc_supported_card.h" + +#include "troyka_parser.h" + +NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd] = { + [NfcSupportedCardTypeTroyka] = { + .protocol = NfcDeviceProtocolMifareClassic, + .verify = troyka_parser_verify, + .read = troyka_parser_read, + .parse = troyka_parser_parse, + }, +}; diff --git a/lib/nfc/parsers/nfc_supported_card.h b/lib/nfc/parsers/nfc_supported_card.h new file mode 100644 index 00000000..5c94c78c --- /dev/null +++ b/lib/nfc/parsers/nfc_supported_card.h @@ -0,0 +1,27 @@ +#pragma once + +#include <furi_hal_nfc.h> +#include "../nfc_worker.h" + +#include <m-string.h> + +typedef enum { + NfcSupportedCardTypeTroyka, + + NfcSupportedCardTypeEnd, +} NfcSupportedCardType; + +typedef bool (*NfcSupportedCardVerify)(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +typedef bool (*NfcSupportedCardRead)(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +typedef bool (*NfcSupportedCardParse)(NfcWorker* nfc_worker); + +typedef struct { + NfcProtocol protocol; + NfcSupportedCardVerify verify; + NfcSupportedCardRead read; + NfcSupportedCardParse parse; +} NfcSupportedCard; + +extern NfcSupportedCard nfc_supported_card[NfcSupportedCardTypeEnd]; diff --git a/lib/nfc/parsers/troyka_parser.c b/lib/nfc/parsers/troyka_parser.c new file mode 100644 index 00000000..653887cb --- /dev/null +++ b/lib/nfc/parsers/troyka_parser.c @@ -0,0 +1,70 @@ +#include "nfc_supported_card.h" + +#include <gui/modules/widget.h> +#include <nfc_worker_i.h> + +static const MfClassicAuthContext troyka_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 troyka_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + UNUSED(nfc_worker); + + MfClassicAuthContext auth_ctx = { + .key_a = MF_CLASSIC_NO_KEY, + .key_b = MF_CLASSIC_NO_KEY, + .sector = 8, + }; + return mf_classic_auth_attempt(tx_rx, &auth_ctx, 0xa73f5dc1d333); +} + +bool troyka_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx) { + furi_assert(nfc_worker); + + MfClassicReader reader = {}; + FuriHalNfcDevData* nfc_data = &nfc_worker->dev_data->nfc_data; + mf_classic_get_type(nfc_data->atqa[0], nfc_data->atqa[1], nfc_data->sak, &reader); + for(size_t i = 0; i < COUNT_OF(troyka_keys); i++) { + mf_classic_reader_add_sector( + &reader, troyka_keys[i].sector, troyka_keys[i].key_a, troyka_keys[i].key_b); + } + + return mf_classic_read_card(tx_rx, &reader, &nfc_worker->dev_data->mf_classic_data) == 16; +} + +bool troyka_parser_parse(NfcWorker* nfc_worker) { + MfClassicData* data = &nfc_worker->dev_data->mf_classic_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[3]; + uint32_t number = 0; + for(size_t i = 0; i < 4; i++) { + number <<= 8; + number |= temp_ptr[i]; + } + number >>= 4; + + string_printf( + nfc_worker->dev_data->parsed_data, + "Troyka Transport card\nNumber: %ld\nBalance: %d rub", + number, + balance); + + return true; +} diff --git a/lib/nfc/parsers/troyka_parser.h b/lib/nfc/parsers/troyka_parser.h new file mode 100644 index 00000000..0d5cee23 --- /dev/null +++ b/lib/nfc/parsers/troyka_parser.h @@ -0,0 +1,9 @@ +#pragma once + +#include "nfc_supported_card.h" + +bool troyka_parser_verify(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool troyka_parser_read(NfcWorker* nfc_worker, FuriHalNfcTxRxContext* tx_rx); + +bool troyka_parser_parse(NfcWorker* nfc_worker); diff --git a/lib/nfc_protocols/crypto1.c b/lib/nfc/protocols/crypto1.c index f08164ba..f08164ba 100644 --- a/lib/nfc_protocols/crypto1.c +++ b/lib/nfc/protocols/crypto1.c diff --git a/lib/nfc_protocols/crypto1.h b/lib/nfc/protocols/crypto1.h index 07b39c22..07b39c22 100644 --- a/lib/nfc_protocols/crypto1.h +++ b/lib/nfc/protocols/crypto1.h diff --git a/lib/nfc_protocols/emv.c b/lib/nfc/protocols/emv.c index 935c9f63..935c9f63 100644 --- a/lib/nfc_protocols/emv.c +++ b/lib/nfc/protocols/emv.c diff --git a/lib/nfc_protocols/emv.h b/lib/nfc/protocols/emv.h index b5a0c574..b5a0c574 100755 --- a/lib/nfc_protocols/emv.h +++ b/lib/nfc/protocols/emv.h diff --git a/lib/nfc_protocols/mifare_classic.c b/lib/nfc/protocols/mifare_classic.c index e35a1d6c..93fe6f69 100644 --- a/lib/nfc_protocols/mifare_classic.c +++ b/lib/nfc/protocols/mifare_classic.c @@ -25,6 +25,16 @@ typedef enum { MfClassicActionACWrite, } MfClassicAction; +const char* mf_classic_get_type_str(MfClassicType type) { + 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) { @@ -34,7 +44,16 @@ static uint8_t mf_classic_get_first_block_num_of_sector(uint8_t sector) { } } -static uint8_t mf_classic_get_sector_by_block(uint8_t block) { +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 { @@ -47,7 +66,7 @@ static uint8_t mf_classic_get_blocks_num_in_sector(uint8_t sector) { return sector < 32 ? 4 : 16; } -static uint8_t mf_classic_get_sector_trailer(uint8_t block) { +uint8_t mf_classic_get_sector_trailer_num_by_block(uint8_t block) { if(block < 128) { return block | 0x03; } else { @@ -55,15 +74,21 @@ static uint8_t mf_classic_get_sector_trailer(uint8_t block) { } } -static bool mf_classic_is_sector_trailer(uint8_t block) { - return block == mf_classic_get_sector_trailer(block); +bool mf_classic_is_sector_trailer(uint8_t block) { + return block == mf_classic_get_sector_trailer_num_by_block(block); } -uint8_t mf_classic_get_total_sectors_num(MfClassicReader* reader) { - furi_assert(reader); - if(reader->type == MfClassicType1k) { +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 == MfClassicType1k) { return MF_CLASSIC_1K_TOTAL_SECTORS_NUM; - } else if(reader->type == MfClassicType4k) { + } else if(type == MfClassicType4k) { return MF_CLASSIC_4K_TOTAL_SECTORS_NUM; } else { return 0; @@ -80,6 +105,104 @@ static uint16_t mf_classic_get_total_block_num(MfClassicType type) { } } +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_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); + } +} + +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); + *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 i = first_block; i < first_block + total_blocks_in_sec; i++) { + blocks_read = mf_classic_is_block_read(data, i); + if(!blocks_read) break; + } + if(blocks_read) { + *sectors_read += 1; + } + } +} + static bool mf_classic_is_allowed_access_sector_trailer( MfClassicEmulator* emulator, uint8_t block_num, @@ -126,7 +249,8 @@ static bool mf_classic_is_allowed_access_data_block( uint8_t block_num, MfClassicKey key, MfClassicAction action) { - uint8_t* sector_trailer = emulator->data.block[mf_classic_get_sector_trailer(block_num)].value; + uint8_t* sector_trailer = + emulator->data.block[mf_classic_get_sector_trailer_num_by_block(block_num)].value; uint8_t sector_block; if(block_num <= 128) { @@ -207,15 +331,18 @@ bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { } } -bool mf_classic_get_type( - uint8_t* uid, - uint8_t uid_len, - uint8_t ATQA0, - uint8_t ATQA1, - uint8_t SAK, - MfClassicReader* reader) { +MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK) { + UNUSED(ATQA1); + if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) { + return MfClassicType1k; + } else if((ATQA0 == 0x42 || ATQA0 == 0x02) && (SAK == 0x18)) { + return MfClassicType4k; + } + return MfClassicType1k; +} + +bool mf_classic_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK, MfClassicReader* reader) { UNUSED(ATQA1); - furi_assert(uid); furi_assert(reader); memset(reader, 0, sizeof(MfClassicReader)); @@ -226,14 +353,6 @@ bool mf_classic_get_type( } else { return false; } - - uint8_t* cuid_start = uid; - if(uid_len == 7) { - cuid_start = &uid[3]; - } - reader->cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) | - (cuid_start[3]); - return true; } @@ -246,7 +365,7 @@ void mf_classic_reader_add_sector( 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 - 1) { + 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; @@ -254,9 +373,8 @@ void mf_classic_reader_add_sector( } } -void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint32_t cuid, uint8_t sector) { +void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint8_t sector) { furi_assert(auth_ctx); - auth_ctx->cuid = cuid; auth_ctx->sector = sector; auth_ctx->key_a = MF_CLASSIC_NO_KEY; auth_ctx->key_b = MF_CLASSIC_NO_KEY; @@ -264,17 +382,18 @@ void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint32_t cuid, static bool mf_classic_auth( FuriHalNfcTxRxContext* tx_rx, - uint32_t cuid, uint32_t block, uint64_t key, MfClassicKey key_type, Crypto1* crypto) { bool auth_success = false; + uint32_t cuid = 0; 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(!furi_hal_nfc_activate_nfca(200, &cuid)) break; if(key_type == MfClassicKeyA) { tx_rx->tx_data[0] = MF_CLASSIC_AUTH_KEY_A_CMD; } else { @@ -315,6 +434,19 @@ static bool mf_classic_auth( 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); + furi_hal_nfc_sleep(); + return key_found; +} + bool mf_classic_auth_attempt( FuriHalNfcTxRxContext* tx_rx, MfClassicAuthContext* auth_ctx, @@ -330,7 +462,6 @@ bool mf_classic_auth_attempt( // Try AUTH with key A if(mf_classic_auth( tx_rx, - auth_ctx->cuid, mf_classic_get_first_block_num_of_sector(auth_ctx->sector), key, MfClassicKeyA, @@ -342,14 +473,12 @@ bool mf_classic_auth_attempt( if(need_halt) { furi_hal_nfc_sleep(); - furi_hal_nfc_activate_nfca(300, &auth_ctx->cuid); } if(auth_ctx->key_b == MF_CLASSIC_NO_KEY) { // Try AUTH with key B if(mf_classic_auth( tx_rx, - auth_ctx->cuid, mf_classic_get_first_block_num_of_sector(auth_ctx->sector), key, MfClassicKeyB, @@ -410,7 +539,60 @@ bool mf_classic_read_block( return read_block_success; } -bool mf_classic_read_sector( +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)) 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 { + 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; + FURI_LOG_D(TAG, "Try to read blocks with key B"); + key = nfc_util_bytes2num(sec_tr->key_b, sizeof(sec_tr->key_b)); + furi_hal_nfc_sleep(); + if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto)) 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 { + 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, @@ -419,7 +601,6 @@ bool mf_classic_read_sector( furi_assert(sector_reader); furi_assert(sector); - uint32_t cuid = 0; uint64_t key; MfClassicKey key_type; uint8_t first_block; @@ -428,7 +609,6 @@ bool mf_classic_read_sector( furi_hal_nfc_sleep(); do { // Activate card - if(!furi_hal_nfc_activate_nfca(200, &cuid)) break; 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; @@ -441,7 +621,7 @@ bool mf_classic_read_sector( } // Auth to first block in sector - if(!mf_classic_auth(tx_rx, cuid, first_block, key, key_type, crypto)) break; + if(!mf_classic_auth(tx_rx, first_block, key, key_type, crypto)) break; sector->total_blocks = mf_classic_get_blocks_num_in_sector(sector_reader->sector_num); // Read blocks @@ -478,18 +658,26 @@ uint8_t mf_classic_read_card( data->key_b_mask = 0; MfClassicSector temp_sector = {}; for(uint8_t i = 0; i < reader->sectors_to_read; i++) { - if(mf_classic_read_sector( + 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++) { - data->block[first_block + j] = temp_sector.block[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) { - data->key_a_mask |= 1 << reader->sector_reader[i].sector_num; + 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) { - data->key_b_mask |= 1 << reader->sector_reader[i].sector_num; + mf_classic_set_key_found( + data, + reader->sector_reader[i].sector_num, + MfClassicKeyB, + reader->sector_reader[i].key_b); } sectors_read++; } @@ -498,6 +686,46 @@ uint8_t mf_classic_read_card( return sectors_read; } +uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data) { + furi_assert(tx_rx); + furi_assert(data); + + uint8_t sectors_read = 0; + Crypto1 crypto = {}; + uint8_t total_sectors = mf_classic_get_total_sectors_num(data->type); + uint64_t key_a = 0; + uint64_t key_b = 0; + MfClassicSectorReader sec_reader = {}; + MfClassicSector temp_sector = {}; + + for(size_t i = 0; i < total_sectors; i++) { + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i); + // Load key A + if(mf_classic_is_key_found(data, i, MfClassicKeyA)) { + sec_reader.key_a = nfc_util_bytes2num(sec_tr->key_a, 6); + } else { + sec_reader.key_a = MF_CLASSIC_NO_KEY; + } + // Load key B + if(mf_classic_is_key_found(data, i, MfClassicKeyB)) { + sec_reader.key_b = nfc_util_bytes2num(sec_tr->key_b, 6); + } else { + sec_reader.key_b = MF_CLASSIC_NO_KEY; + } + if((key_a != MF_CLASSIC_NO_KEY) || (key_b != MF_CLASSIC_NO_KEY)) { + sec_reader.sector_num = i; + if(mf_classic_read_sector_with_reader(tx_rx, &crypto, &sec_reader, &temp_sector)) { + uint8_t first_block = mf_classic_get_first_block_num_of_sector(i); + for(uint8_t j = 0; j < temp_sector.total_blocks; j++) { + mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]); + } + sectors_read++; + } + } + } + return sectors_read; +} + void mf_crypto1_decrypt( Crypto1* crypto, uint8_t* encrypted_data, @@ -573,7 +801,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ } else if(plain_data[0] == 0x60 || plain_data[0] == 0x61) { uint8_t block = plain_data[1]; uint64_t key = 0; - uint8_t sector_trailer_block = mf_classic_get_sector_trailer(block); + uint8_t sector_trailer_block = mf_classic_get_sector_trailer_num_by_block(block); MfClassicSectorTrailer* sector_trailer = (MfClassicSectorTrailer*)emulator->data.block[sector_trailer_block].value; if(plain_data[0] == 0x60) { @@ -635,21 +863,6 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ nr, ar); - // Check if we store valid key - if(access_key == MfClassicKeyA) { - if(FURI_BIT(emulator->data.key_a_mask, mf_classic_get_sector_by_block(block)) == - 0) { - FURI_LOG_D(TAG, "Unsupported sector key A for block %d", sector_trailer_block); - break; - } - } else if(access_key == MfClassicKeyB) { - if(FURI_BIT(emulator->data.key_b_mask, mf_classic_get_sector_by_block(block)) == - 0) { - FURI_LOG_D(TAG, "Unsupported sector key B for block %d", sector_trailer_block); - break; - } - } - crypto1_word(&emulator->crypto, nr, 1); uint32_t cardRr = ar ^ crypto1_word(&emulator->crypto, 0, 0); if(cardRr != prng_successor(nonce, 64)) { diff --git a/lib/nfc_protocols/mifare_classic.h b/lib/nfc/protocols/mifare_classic.h index bbf34b2d..85f67b11 100644 --- a/lib/nfc_protocols/mifare_classic.h +++ b/lib/nfc/protocols/mifare_classic.h @@ -14,6 +14,8 @@ #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, @@ -30,9 +32,9 @@ typedef struct { } MfClassicBlock; typedef struct { - uint8_t key_a[6]; - uint8_t access_bits[4]; - uint8_t key_b[6]; + 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 { @@ -42,13 +44,13 @@ typedef struct { 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 { - uint32_t cuid; uint8_t sector; uint64_t key_a; uint64_t key_b; @@ -62,9 +64,8 @@ typedef struct { typedef struct { MfClassicType type; - uint32_t cuid; - uint8_t sectors_to_read; Crypto1 crypto; + uint8_t sectors_to_read; MfClassicSectorReader sector_reader[MF_CLASSIC_SECTORS_MAX]; } MfClassicReader; @@ -75,19 +76,51 @@ typedef struct { 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); -bool mf_classic_get_type( - uint8_t* uid, - uint8_t uid_len, - uint8_t ATQA0, - uint8_t ATQA1, - uint8_t SAK, - MfClassicReader* reader); +MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK); + +bool mf_classic_get_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK, MfClassicReader* reader); -uint8_t mf_classic_get_total_sectors_num(MfClassicReader* reader); +uint8_t mf_classic_get_total_sectors_num(MfClassicType type); -void mf_classic_auth_init_context(MfClassicAuthContext* auth_ctx, uint32_t cuid, uint8_t sector); +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_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); + +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_read(MfClassicData* data, uint8_t sector_num); + +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_auth_attempt( FuriHalNfcTxRxContext* tx_rx, @@ -100,15 +133,13 @@ void mf_classic_reader_add_sector( uint64_t key_a, uint64_t key_b); -bool mf_classic_read_sector( - FuriHalNfcTxRxContext* tx_rx, - Crypto1* crypto, - MfClassicSectorReader* sector_reader, - MfClassicSector* sector); +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); diff --git a/lib/nfc_protocols/mifare_common.c b/lib/nfc/protocols/mifare_common.c index fd622765..fd622765 100644 --- a/lib/nfc_protocols/mifare_common.c +++ b/lib/nfc/protocols/mifare_common.c diff --git a/lib/nfc_protocols/mifare_common.h b/lib/nfc/protocols/mifare_common.h index 2b694d90..2b694d90 100644 --- a/lib/nfc_protocols/mifare_common.h +++ b/lib/nfc/protocols/mifare_common.h diff --git a/lib/nfc_protocols/mifare_desfire.c b/lib/nfc/protocols/mifare_desfire.c index 6f28dc5d..1822d5c1 100644 --- a/lib/nfc_protocols/mifare_desfire.c +++ b/lib/nfc/protocols/mifare_desfire.c @@ -2,6 +2,8 @@ #include <furi.h> #include <furi_hal_nfc.h> +#define TAG "MifareDESFire" + void mf_df_clear(MifareDesfireData* data) { free(data->free_memory); if(data->master_key_settings) { @@ -449,3 +451,173 @@ bool mf_df_parse_read_data_response(uint8_t* buf, uint16_t len, MifareDesfireFil 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)) { + 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 index dbe0802e..e59743a2 100644 --- a/lib/nfc_protocols/mifare_desfire.h +++ b/lib/nfc/protocols/mifare_desfire.h @@ -4,6 +4,8 @@ #include <stdint.h> #include <stdbool.h> +#include <furi_hal_nfc.h> + #define MF_DF_GET_VERSION (0x60) #define MF_DF_GET_FREE_MEMORY (0x6E) #define MF_DF_GET_KEY_SETTINGS (0x45) @@ -163,3 +165,5 @@ uint16_t mf_df_prepare_read_data(uint8_t* dest, uint8_t file_id, uint32_t offset 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); diff --git a/lib/nfc_protocols/mifare_ultralight.c b/lib/nfc/protocols/mifare_ultralight.c index 9dcd1d6a..9dcd1d6a 100644 --- a/lib/nfc_protocols/mifare_ultralight.c +++ b/lib/nfc/protocols/mifare_ultralight.c diff --git a/lib/nfc_protocols/mifare_ultralight.h b/lib/nfc/protocols/mifare_ultralight.h index 77dbd1e4..77dbd1e4 100644 --- a/lib/nfc_protocols/mifare_ultralight.h +++ b/lib/nfc/protocols/mifare_ultralight.h diff --git a/lib/nfc_protocols/nfc_util.c b/lib/nfc/protocols/nfc_util.c index 9de6f982..9de6f982 100644 --- a/lib/nfc_protocols/nfc_util.c +++ b/lib/nfc/protocols/nfc_util.c diff --git a/lib/nfc_protocols/nfc_util.h b/lib/nfc/protocols/nfc_util.h index d530badc..d530badc 100644 --- a/lib/nfc_protocols/nfc_util.h +++ b/lib/nfc/protocols/nfc_util.h diff --git a/lib/nfc_protocols/nfca.c b/lib/nfc/protocols/nfca.c index c401f8cc..c401f8cc 100755 --- a/lib/nfc_protocols/nfca.c +++ b/lib/nfc/protocols/nfca.c diff --git a/lib/nfc_protocols/nfca.h b/lib/nfc/protocols/nfca.h index 498ef284..498ef284 100644 --- a/lib/nfc_protocols/nfca.h +++ b/lib/nfc/protocols/nfca.h |