From b11b797abe3e59f9980bd5841de08a91dcb01966 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 11 Dec 2023 00:48:28 +0200 Subject: [PATCH 01/13] info command draft --- client/src/cmdhfmf.c | 97 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 85ecd8033..6284c7766 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -8795,10 +8795,107 @@ static int CmdHFMFHidEncode(const char *Cmd) { return PM3_SUCCESS; } +static int CmdHF14AMfInfo(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf mf info", + "Information and check vulnerabilities in the mfc card\n" + "To check some of them need to specify key and/or specific keys in the copmmand line", + "hf mf info -k ffffffff -nv\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0(NULL, "bin", "", "Binary string i.e 0001001001"), + arg_lit0("v", "verbose", "verbose output"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + bool do_nack_test = false; + bool verbose = arg_get_lit(ctx, 2); + CLIParserFree(ctx); + + clearCommandBuffer(); + SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT, 0, 0, NULL, 0); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { + PrintAndLogEx(DEBUG, "iso14443a card select timeout"); + return 0; + } + + iso14a_card_select_t card; + memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t)); + + /* + 0: couldn't read + 1: OK, with ATS + 2: OK, no ATS + 3: proprietary Anticollision + */ + uint64_t select_status = resp.oldarg[0]; + + if (select_status == 0) { + PrintAndLogEx(DEBUG, "iso14443a card select failed"); + return select_status; + } + + if (select_status == 3) { + PrintAndLogEx(INFO, "Card doesn't support standard iso14443-3 anticollision"); + + if (verbose) { + PrintAndLogEx(SUCCESS, "ATQA: %02X %02X", card.atqa[1], card.atqa[0]); + } + + return select_status; + } + + if (verbose) { + PrintAndLogEx(INFO, "--- " _CYAN_("ISO14443-a Information") "---------------------"); + } + + PrintAndLogEx(SUCCESS, " UID: " _GREEN_("%s"), sprint_hex(card.uid, card.uidlen)); + PrintAndLogEx(SUCCESS, "ATQA: " _GREEN_("%02X %02X"), card.atqa[1], card.atqa[0]); + PrintAndLogEx(SUCCESS, " SAK: " _GREEN_("%02X [%" PRIu64 "]"), card.sak, resp.oldarg[0]); + + int res = detect_classic_static_nonce(); + if (res == NONCE_STATIC) + PrintAndLogEx(SUCCESS, "Static nonce: " _YELLOW_("yes")); + + if (res == NONCE_FAIL && verbose) + PrintAndLogEx(SUCCESS, "Static nonce: " _RED_("read failed")); + + if (res == NONCE_NORMAL) { + + // not static + res = detect_classic_prng(); + if (res == 1) + PrintAndLogEx(SUCCESS, "Prng detection: " _GREEN_("weak")); + else if (res == 0) + PrintAndLogEx(SUCCESS, "Prng detection: " _YELLOW_("hard")); + else + PrintAndLogEx(FAILED, "Prng detection: " _RED_("fail")); + + if (do_nack_test) + detect_classic_nackbug(verbose); + } + + uint8_t signature[32] = {0}; + res = read_mfc_ev1_signature(signature); + if (res == PM3_SUCCESS) { + mfc_ev1_print_signature(card.uid, card.uidlen, signature, sizeof(signature)); + } + + + + PrintAndLogEx(NORMAL, "done..."); + return PM3_SUCCESS; +} + static command_t CommandTable[] = { {"help", CmdHelp, AlwaysAvailable, "This help"}, {"list", CmdHF14AMfList, AlwaysAvailable, "List MIFARE history"}, {"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("recovery") " -----------------------"}, + {"info", CmdHF14AMfInfo, IfPm3Iso14443a, "mfc card Info"}, {"darkside", CmdHF14AMfDarkside, IfPm3Iso14443a, "Darkside attack"}, {"nested", CmdHF14AMfNested, IfPm3Iso14443a, "Nested attack"}, {"hardnested", CmdHF14AMfNestedHard, AlwaysAvailable, "Nested attack for hardened MIFARE Classic cards"}, From ea467414f4248a19a887d27ebd00717a9654b340 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 11 Dec 2023 00:58:48 +0200 Subject: [PATCH 02/13] add magic card detect --- client/src/cmdhfmf.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 6284c7766..f70340aa0 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -8879,6 +8879,8 @@ static int CmdHF14AMfInfo(const char *Cmd) { detect_classic_nackbug(verbose); } + detect_mf_magic(true); + uint8_t signature[32] = {0}; res = read_mfc_ev1_signature(signature); if (res == PM3_SUCCESS) { From 6066e7e924f53e9721e99823d76cfa0ad55bedaa Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 11 Dec 2023 14:22:19 +0200 Subject: [PATCH 03/13] rearrange some keys and add one from the bottom --- client/src/mifare/mifaredefault.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/client/src/mifare/mifaredefault.h b/client/src/mifare/mifaredefault.h index 0a631a765..d94aae269 100644 --- a/client/src/mifare/mifaredefault.h +++ b/client/src/mifare/mifaredefault.h @@ -55,7 +55,9 @@ static const uint64_t g_mifare_default_keys[] = { 0xffffffffffff, // Default key (first key used by program if no user defined key) - 0xa0a1a2a3a4a5, // NFCForum MAD key + 0xa0a1a2a3a4a5, // NFCForum MAD key A + 0xb0b1b2b3b4b5, // NFCForum MAD key B + 0x89ECA97F8C2A, // NFCForum MAD key B 0xd3f7d3f7d3f7, // NDEF public key 0x4b791bea7bcc, // MFC EV1 Signature 17 B 0x5C8FF9990DA2, // MFC EV1 Signature 16 A @@ -95,13 +97,12 @@ static const uint64_t g_mifare_default_keys[] = { 0x11496F97752A, // HID 0x3E65E4FB65B3, // Gym 0x000000000000, // Blank key - 0xb0b1b2b3b4b5, - 0xaabbccddeeff, + 0x010203040506, 0x1a2b3c4d5e6f, 0x123456789abc, - 0x010203040506, 0x123456abcdef, 0xabcdef123456, + 0xaabbccddeeff, 0x4d3a99c351dd, 0x1a982c7e459a, 0x714c5c886e97, From fb51bf4fa1698a42d9f5d8587b895c40f329cc50 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 11 Dec 2023 14:23:26 +0200 Subject: [PATCH 04/13] add backdoor info and check keys for sector 0 --- client/src/cmdhfmf.c | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index f70340aa0..bd9eead19 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -8806,13 +8806,14 @@ static int CmdHF14AMfInfo(const char *Cmd) { void *argtable[] = { arg_param_begin, arg_str0(NULL, "bin", "", "Binary string i.e 0001001001"), + arg_lit0("n", "nack", "do nack test"), arg_lit0("v", "verbose", "verbose output"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); - bool do_nack_test = false; - bool verbose = arg_get_lit(ctx, 2); + bool do_nack_test = arg_get_lit(ctx, 2); + bool verbose = arg_get_lit(ctx, 3); CLIParserFree(ctx); clearCommandBuffer(); @@ -8849,14 +8850,13 @@ static int CmdHF14AMfInfo(const char *Cmd) { return select_status; } - if (verbose) { - PrintAndLogEx(INFO, "--- " _CYAN_("ISO14443-a Information") "---------------------"); - } - + PrintAndLogEx(INFO, "--- " _CYAN_("ISO14443-a Information") "---------------------"); PrintAndLogEx(SUCCESS, " UID: " _GREEN_("%s"), sprint_hex(card.uid, card.uidlen)); PrintAndLogEx(SUCCESS, "ATQA: " _GREEN_("%02X %02X"), card.atqa[1], card.atqa[0]); PrintAndLogEx(SUCCESS, " SAK: " _GREEN_("%02X [%" PRIu64 "]"), card.sak, resp.oldarg[0]); + PrintAndLogEx(INFO, "--- " _CYAN_("RNG Information") "---------------------"); + int res = detect_classic_static_nonce(); if (res == NONCE_STATIC) PrintAndLogEx(SUCCESS, "Static nonce: " _YELLOW_("yes")); @@ -8879,11 +8879,37 @@ static int CmdHF14AMfInfo(const char *Cmd) { detect_classic_nackbug(verbose); } - detect_mf_magic(true); + PrintAndLogEx(INFO, "--- " _CYAN_("Backdoors Information") "---------------------"); + if (detect_mf_magic(true) == 0) + PrintAndLogEx(INFO, ""); + + PrintAndLogEx(INFO, "--- " _CYAN_("Keys Information") "---------------------"); + int sectorsCnt = 1; + uint8_t *keyBlock = NULL; + uint32_t keycnt = 0; + int ret = mfLoadKeys(&keyBlock, &keycnt, NULL, 0, NULL, 0); + if (ret != PM3_SUCCESS) { + return ret; + } + + // create/initialize key storage structure + sector_t *e_sector = NULL; + if (initSectorTable(&e_sector, sectorsCnt) != PM3_SUCCESS) { + free(keyBlock); + return PM3_EMALLOC; + } + res = mfCheckKeys_fast(sectorsCnt, true, true, 1, keycnt, keyBlock, e_sector, false); + + PrintAndLogEx(FAILED, "res: %d, %d %d", res, e_sector[0].foundKey[0], e_sector[0].foundKey[1]); + + free(keyBlock); + free(e_sector); + uint8_t signature[32] = {0}; res = read_mfc_ev1_signature(signature); - if (res == PM3_SUCCESS) { + if (res == PM3_SUCCESS) { + PrintAndLogEx(INFO, "--- " _CYAN_("Signature Information") "---------------------"); mfc_ev1_print_signature(card.uid, card.uidlen, signature, sizeof(signature)); } From af0e25b51935e3c17769591daab07dd8bd4a9310 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 11 Dec 2023 15:44:43 +0200 Subject: [PATCH 05/13] read block0 and check keys --- client/src/cmdhfmf.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index bd9eead19..f258c9ac3 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -8884,6 +8884,9 @@ static int CmdHF14AMfInfo(const char *Cmd) { PrintAndLogEx(INFO, ""); PrintAndLogEx(INFO, "--- " _CYAN_("Keys Information") "---------------------"); + uint8_t key[MIFARE_KEY_SIZE] = {0}; + uint8_t keyType = 0xff; + int sectorsCnt = 1; uint8_t *keyBlock = NULL; uint32_t keycnt = 0; @@ -8899,8 +8902,30 @@ static int CmdHF14AMfInfo(const char *Cmd) { return PM3_EMALLOC; } res = mfCheckKeys_fast(sectorsCnt, true, true, 1, keycnt, keyBlock, e_sector, false); + if (res == PM3_SUCCESS) { + uint8_t blockdata[MFBLOCK_SIZE] = {0}; - PrintAndLogEx(FAILED, "res: %d, %d %d", res, e_sector[0].foundKey[0], e_sector[0].foundKey[1]); + if (e_sector[0].foundKey[0]) { + PrintAndLogEx(SUCCESS, "Sector 0 key A... %12llx", e_sector[0].Key[0]); + + num_to_bytes(e_sector[0].Key[0], MIFARE_KEY_SIZE, key); + if (mfReadBlock(0, MF_KEY_A, key, blockdata) == PM3_SUCCESS) + keyType = MF_KEY_A; + } + + if (e_sector[0].foundKey[1]) { + PrintAndLogEx(SUCCESS, "Sector 0 key B... %12llx", e_sector[0].Key[1]); + + if (keyType == 0xff) { + num_to_bytes(e_sector[0].Key[1], MIFARE_KEY_SIZE, key); + if (mfReadBlock(0, MF_KEY_B, key, blockdata) == PM3_SUCCESS) + keyType = MF_KEY_B; + } + } + + if (keyType != 0xff) + PrintAndLogEx(SUCCESS, "Block 0 ......... %s", sprint_hex(blockdata, MFBLOCK_SIZE)); + } free(keyBlock); free(e_sector); From ebe9d72cc20df221ffa213bbd04326bdf73fb6f5 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:46:25 +0200 Subject: [PATCH 06/13] set debug log level --- armsrc/appmain.c | 3 ++- client/src/cmdhw.c | 16 ++++++---------- client/src/preferences.c | 38 ++++++++++++++++++++++++++++++++++++++ client/src/preferences.h | 3 +++ 4 files changed, 49 insertions(+), 11 deletions(-) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index fc07d66f8..ce6d79dbb 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -799,7 +799,8 @@ static void PacketReceived(PacketCommandNG *packet) { // emulator case CMD_SET_DBGMODE: { g_dbglevel = packet->data.asBytes[0]; - print_debug_level(); + if (packet->length == 1 || packet->data.asBytes[1] != 0) + print_debug_level(); reply_ng(CMD_SET_DBGMODE, PM3_SUCCESS, NULL, 0); break; } diff --git a/client/src/cmdhw.c b/client/src/cmdhw.c index b355e66cb..184aa58e8 100644 --- a/client/src/cmdhw.c +++ b/client/src/cmdhw.c @@ -31,6 +31,7 @@ #include "cmdhw.h" #include "cmddata.h" #include "commonutil.h" +#include "preferences.h" #include "pm3_cmd.h" #include "pmflash.h" // rdv40validation_t #include "cmdflashmem.h" // get_signature.. @@ -476,14 +477,9 @@ static int CmdDbg(const char *Cmd) { return PM3_EINVARG; } - clearCommandBuffer(); - SendCommandNG(CMD_GET_DBGMODE, NULL, 0); - PacketResponseNG resp; - if (WaitForResponseTimeout(CMD_GET_DBGMODE, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "Failed to get current device debug level"); - return PM3_ETIMEOUT; - } - uint8_t curr = resp.data.asBytes[0]; + uint8_t curr = DBG_NONE; + if (getDeviceDebugLevel(&curr) != PM3_SUCCESS) + return PM3_EFAILED; const char *dbglvlstr; switch (curr) { @@ -522,8 +518,8 @@ static int CmdDbg(const char *Cmd) { else if (lv4) dbg = 4; - clearCommandBuffer(); - SendCommandNG(CMD_SET_DBGMODE, &dbg, sizeof(dbg)); + if (setDeviceDebugLevel(dbg, true) != PM3_SUCCESS) + return PM3_EFAILED; } return PM3_SUCCESS; } diff --git a/client/src/preferences.c b/client/src/preferences.c index dd697da74..4effb1bef 100644 --- a/client/src/preferences.c +++ b/client/src/preferences.c @@ -762,6 +762,44 @@ static int setCmdDeviceDebug (const char *Cmd) } */ +int getDeviceDebugLevel (uint8_t *debug_level) { + if (!g_session.pm3_present) + return PM3_EFAILED; + + clearCommandBuffer(); + SendCommandNG(CMD_GET_DBGMODE, NULL, 0); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_GET_DBGMODE, &resp, 2000) == false) { + PrintAndLogEx(WARNING, "Failed to get current device debug level"); + return PM3_ETIMEOUT; + } + + if (debug_level) + *debug_level = resp.data.asBytes[0]; + + return PM3_SUCCESS; +} + +int setDeviceDebugLevel (uint8_t debug_level, bool verbose) { + if (!g_session.pm3_present) + return PM3_EFAILED; + + if (verbose) + PrintAndLogEx (INFO,"setting device debug loglevel to %u", debug_level); + + uint8_t cdata[] = {debug_level, verbose}; + clearCommandBuffer(); + SendCommandNG(CMD_SET_DBGMODE, cdata, sizeof(cdata)); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_SET_DBGMODE, &resp, 2000) == false) { + PrintAndLogEx (WARNING,"failed to set device debug loglevel"); + return PM3_EFAILED; + } + + return PM3_SUCCESS; +} + + static int setCmdOutput(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "prefs set output", diff --git a/client/src/preferences.h b/client/src/preferences.h index 56ba72fa3..74e3bc2a8 100644 --- a/client/src/preferences.h +++ b/client/src/preferences.h @@ -31,4 +31,7 @@ int preferences_save(void); void preferences_save_callback(json_t *root); void preferences_load_callback(json_t *root); +int getDeviceDebugLevel (uint8_t *debug_level); +int setDeviceDebugLevel (uint8_t debug_level, bool verbose); + #endif From 784e11021e939712f3a784dcfe39f701e044e374 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:00:09 +0200 Subject: [PATCH 07/13] Remove debug messages from the info command --- client/src/cmdhfmf.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index f258c9ac3..89dec7c6e 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -39,6 +39,7 @@ #include "cmdhw.h" // set_fpga_mode #include "loclass/cipherutils.h" // BitstreamOut_t #include "proxendian.h" +#include "preferences.h" #include "mifare/gen4.h" static int CmdHelp(const char *Cmd); @@ -8816,6 +8817,10 @@ static int CmdHF14AMfInfo(const char *Cmd) { bool verbose = arg_get_lit(ctx, 3); CLIParserFree(ctx); + uint8_t dbg_curr = DBG_NONE; + if (getDeviceDebugLevel(&dbg_curr) != PM3_SUCCESS) + return PM3_EFAILED; + clearCommandBuffer(); SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT, 0, 0, NULL, 0); PacketResponseNG resp; @@ -8855,6 +8860,9 @@ static int CmdHF14AMfInfo(const char *Cmd) { PrintAndLogEx(SUCCESS, "ATQA: " _GREEN_("%02X %02X"), card.atqa[1], card.atqa[0]); PrintAndLogEx(SUCCESS, " SAK: " _GREEN_("%02X [%" PRIu64 "]"), card.sak, resp.oldarg[0]); + if (setDeviceDebugLevel(DBG_NONE, false) != PM3_SUCCESS) + return PM3_EFAILED; + PrintAndLogEx(INFO, "--- " _CYAN_("RNG Information") "---------------------"); int res = detect_classic_static_nonce(); @@ -8938,7 +8946,8 @@ static int CmdHF14AMfInfo(const char *Cmd) { mfc_ev1_print_signature(card.uid, card.uidlen, signature, sizeof(signature)); } - + if (setDeviceDebugLevel(dbg_curr, false) != PM3_SUCCESS) + return PM3_EFAILED; PrintAndLogEx(NORMAL, "done..."); return PM3_SUCCESS; From edd004adc5dc9310b681abc6b64cec080358d425 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:17:03 +0200 Subject: [PATCH 08/13] move rng info and add static encrypted nonce detect placeholder --- client/src/cmdhfmf.c | 59 +++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 89dec7c6e..7324ea195 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -8863,30 +8863,6 @@ static int CmdHF14AMfInfo(const char *Cmd) { if (setDeviceDebugLevel(DBG_NONE, false) != PM3_SUCCESS) return PM3_EFAILED; - PrintAndLogEx(INFO, "--- " _CYAN_("RNG Information") "---------------------"); - - int res = detect_classic_static_nonce(); - if (res == NONCE_STATIC) - PrintAndLogEx(SUCCESS, "Static nonce: " _YELLOW_("yes")); - - if (res == NONCE_FAIL && verbose) - PrintAndLogEx(SUCCESS, "Static nonce: " _RED_("read failed")); - - if (res == NONCE_NORMAL) { - - // not static - res = detect_classic_prng(); - if (res == 1) - PrintAndLogEx(SUCCESS, "Prng detection: " _GREEN_("weak")); - else if (res == 0) - PrintAndLogEx(SUCCESS, "Prng detection: " _YELLOW_("hard")); - else - PrintAndLogEx(FAILED, "Prng detection: " _RED_("fail")); - - if (do_nack_test) - detect_classic_nackbug(verbose); - } - PrintAndLogEx(INFO, "--- " _CYAN_("Backdoors Information") "---------------------"); if (detect_mf_magic(true) == 0) PrintAndLogEx(INFO, ""); @@ -8898,9 +8874,9 @@ static int CmdHF14AMfInfo(const char *Cmd) { int sectorsCnt = 1; uint8_t *keyBlock = NULL; uint32_t keycnt = 0; - int ret = mfLoadKeys(&keyBlock, &keycnt, NULL, 0, NULL, 0); - if (ret != PM3_SUCCESS) { - return ret; + int res = mfLoadKeys(&keyBlock, &keycnt, NULL, 0, NULL, 0); + if (res != PM3_SUCCESS) { + return res; } // create/initialize key storage structure @@ -8938,6 +8914,34 @@ static int CmdHF14AMfInfo(const char *Cmd) { free(keyBlock); free(e_sector); + PrintAndLogEx(INFO, "--- " _CYAN_("RNG Information") "---------------------"); + + res = detect_classic_static_nonce(); + if (res == NONCE_STATIC) + PrintAndLogEx(SUCCESS, "Static nonce: " _YELLOW_("yes")); + + if (res == NONCE_FAIL && verbose) + PrintAndLogEx(SUCCESS, "Static nonce: " _RED_("read failed")); + + if (res == NONCE_NORMAL) { + // not static + res = detect_classic_prng(); + if (res == 1) + PrintAndLogEx(SUCCESS, "Prng detection: " _GREEN_("weak")); + else if (res == 0) + PrintAndLogEx(SUCCESS, "Prng detection: " _YELLOW_("hard")); + else + PrintAndLogEx(FAILED, "Prng detection: " _RED_("fail")); + + + // detect static encrypted nonce + if (keyType != 0xff) { + + } + + if (do_nack_test) + detect_classic_nackbug(verbose); + } uint8_t signature[32] = {0}; res = read_mfc_ev1_signature(signature); @@ -8949,7 +8953,6 @@ static int CmdHF14AMfInfo(const char *Cmd) { if (setDeviceDebugLevel(dbg_curr, false) != PM3_SUCCESS) return PM3_EFAILED; - PrintAndLogEx(NORMAL, "done..."); return PM3_SUCCESS; } From d352f9d44ec06384572bdbba6cbf37b8a2d52ff0 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 11 Dec 2023 19:21:25 +0200 Subject: [PATCH 09/13] add key placeholder --- client/src/cmdhfmf.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 7324ea195..3a400899c 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -8806,15 +8806,30 @@ static int CmdHF14AMfInfo(const char *Cmd) { void *argtable[] = { arg_param_begin, - arg_str0(NULL, "bin", "", "Binary string i.e 0001001001"), + arg_str0("k", "key", "", "key, 6 hex bytes"), + arg_lit0("a", NULL, "input key type is key A (def)"), + arg_lit0("b", NULL, "input key type is key B"), arg_lit0("n", "nack", "do nack test"), arg_lit0("v", "verbose", "verbose output"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); - bool do_nack_test = arg_get_lit(ctx, 2); - bool verbose = arg_get_lit(ctx, 3); + /*uint8_t keytype = MF_KEY_A; + if (arg_get_lit(ctx, 2) && arg_get_lit(ctx, 3)) { + CLIParserFree(ctx); + PrintAndLogEx(WARNING, "Input key type must be A or B"); + return PM3_EINVARG; + } else if (arg_get_lit(ctx, 3)) { + keytype = MF_KEY_B; + } + + int keylen = 0; + uint8_t key[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + CLIGetHexWithReturn(ctx, 4, key, &keylen); +*/ + bool do_nack_test = arg_get_lit(ctx, 4); + bool verbose = arg_get_lit(ctx, 5); CLIParserFree(ctx); uint8_t dbg_curr = DBG_NONE; From 91892bc1bf1a23369afb57ce525b805e87098416 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 11 Dec 2023 21:24:26 +0200 Subject: [PATCH 10/13] add static encrypted nonce detection --- armsrc/appmain.c | 11 ++++++ armsrc/mifarecmd.c | 71 +++++++++++++++++++++++++++++++++- armsrc/mifarecmd.h | 1 + armsrc/mifareutil.c | 6 ++- armsrc/mifareutil.h | 2 +- client/src/cmdhfmf.c | 8 +++- client/src/mifare/mifarehost.c | 27 +++++++++++++ client/src/mifare/mifarehost.h | 1 + include/pm3_cmd.h | 8 ++-- 9 files changed, 125 insertions(+), 10 deletions(-) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index ce6d79dbb..849bfcb2b 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -1866,6 +1866,17 @@ static void PacketReceived(PacketCommandNG *packet) { MifareHasStaticNonce(); break; } + case CMD_HF_MIFARE_STATIC_ENCRYPTED_NONCE: { + struct p { + uint8_t block_no; + uint8_t key_type; + uint8_t key[6]; + } PACKED; + struct p *payload = (struct p *) packet->data.asBytes; + + MifareHasStaticEncryptedNonce(payload->block_no, payload->key_type, payload->key); + break; + } #endif #ifdef WITH_NFCBARCODE diff --git a/armsrc/mifarecmd.c b/armsrc/mifarecmd.c index 32e90d56a..1f0683599 100644 --- a/armsrc/mifarecmd.c +++ b/armsrc/mifarecmd.c @@ -90,7 +90,7 @@ int16_t mifare_cmd_readblocks(uint8_t key_auth_cmd, uint8_t *key, uint8_t read_c goto OUT; } - if (mifare_classic_authex_cmd(pcs, cuid, block_no, key_auth_cmd, ui64key, AUTH_FIRST, NULL, NULL)) { + if (mifare_classic_authex_cmd(pcs, cuid, block_no, key_auth_cmd, ui64key, AUTH_FIRST, NULL, NULL, NULL)) { if (g_dbglevel >= DBG_ERROR) Dbprintf("Auth error"); retval = PM3_ESOFT; goto OUT; @@ -158,7 +158,7 @@ int16_t mifare_cmd_writeblocks(uint8_t key_auth_cmd, uint8_t *key, uint8_t write goto OUT; }; - if (mifare_classic_authex_cmd(pcs, cuid, block_no, key_auth_cmd, ui64key, AUTH_FIRST, NULL, NULL)) { + if (mifare_classic_authex_cmd(pcs, cuid, block_no, key_auth_cmd, ui64key, AUTH_FIRST, NULL, NULL, NULL)) { if (g_dbglevel >= DBG_ERROR) Dbprintf("Auth error"); retval = PM3_ESOFT; goto OUT; @@ -2681,6 +2681,73 @@ OUT: // 2B F9 1C 1B D5 08 48 48 03 A4 B1 B1 75 FF 2D 90 // ^^ ^^ +void MifareHasStaticEncryptedNonce(uint8_t block_no, uint8_t key_type, uint8_t *key) { + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + + clear_trace(); + set_tracing(true); + + int retval = PM3_SUCCESS; + uint8_t *uid = BigBuf_malloc(10); + memset(uid, 0x00, 10); + + uint8_t data[1] = { NONCE_FAIL }; + struct Crypto1State mpcs = {0, 0}; + struct Crypto1State *pcs; + pcs = &mpcs; + uint64_t ui64key = bytes_to_num(key, 6); + + iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN); + + iso14a_card_select_t card_info; + uint32_t cuid = 0; + if (!iso14443a_select_card(uid, &card_info, &cuid, true, 0, true)) { + retval = PM3_ESOFT; + goto OUT; + } + + uint8_t key_auth_cmd = MIFARE_AUTH_KEYA + (key_type & 1); + if (mifare_classic_authex_cmd(pcs, cuid, block_no, key_auth_cmd, ui64key, AUTH_FIRST, NULL, NULL, NULL)) { + if (g_dbglevel >= DBG_ERROR) Dbprintf("Auth error"); + retval = PM3_ESOFT; + goto OUT; + }; + + uint32_t nt = 0; + uint8_t enc_counter = 0; + uint32_t ntenc = 0; + uint32_t oldntenc = 0; + for (uint8_t i = 0; i < 3; i++) { + if (mifare_classic_authex_cmd(pcs, cuid, block_no, key_auth_cmd, ui64key, AUTH_NESTED, &nt, &ntenc, NULL)) { + if (g_dbglevel >= DBG_ERROR) Dbprintf("Auth error"); + retval = PM3_ESOFT; + goto OUT; + }; + + if (g_dbglevel >= DBG_INFO) + Dbprintf("nt: %x, nt encoded: %x", nt, ntenc); + + if (oldntenc == 0) + oldntenc = ntenc; + else if (ntenc == oldntenc) + enc_counter++; + } + + if (enc_counter) { + data[0] = NONCE_STATIC_ENC; + } else { + data[0] = NONCE_NORMAL; + } + +OUT: + crypto1_deinit(pcs); + + reply_ng(CMD_HF_MIFARE_STATIC_ENCRYPTED_NONCE, retval, data, sizeof(data)); + // turns off + OnSuccessMagic(); + BigBuf_free(); +} + void OnSuccessMagic(void) { FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); LEDsoff(); diff --git a/armsrc/mifarecmd.h b/armsrc/mifarecmd.h index 52bc37f35..64df74f97 100644 --- a/armsrc/mifarecmd.h +++ b/armsrc/mifarecmd.h @@ -49,6 +49,7 @@ void MifareCSetBlock(uint32_t arg0, uint32_t arg1, uint8_t *datain); // Work wi void MifareCGetBlock(uint32_t arg0, uint32_t arg1, uint8_t *datain); void MifareCIdent(bool is_mfc); // is "magic chinese" card? void MifareHasStaticNonce(void); // Has the tag a static nonce? +void MifareHasStaticEncryptedNonce(uint8_t block_no, uint8_t key_type, uint8_t *key); // Has the tag a static encrypted nonce? // MFC GEN3 int DoGen3Cmd(uint8_t *cmd, uint8_t cmd_len); diff --git a/armsrc/mifareutil.c b/armsrc/mifareutil.c index 581efda8f..3258007e3 100644 --- a/armsrc/mifareutil.c +++ b/armsrc/mifareutil.c @@ -142,9 +142,9 @@ int mifare_classic_auth(struct Crypto1State *pcs, uint32_t uid, uint8_t blockNo, return mifare_classic_authex(pcs, uid, blockNo, keyType, ui64Key, isNested, NULL, NULL); } int mifare_classic_authex(struct Crypto1State *pcs, uint32_t uid, uint8_t blockNo, uint8_t keyType, uint64_t ui64Key, uint8_t isNested, uint32_t *ntptr, uint32_t *timing) { - return mifare_classic_authex_cmd(pcs, uid, blockNo, (keyType & 1) ? MIFARE_AUTH_KEYB : MIFARE_AUTH_KEYA, ui64Key, isNested, ntptr, timing); + return mifare_classic_authex_cmd(pcs, uid, blockNo, (keyType & 1) ? MIFARE_AUTH_KEYB : MIFARE_AUTH_KEYA, ui64Key, isNested, ntptr, NULL, timing); } -int mifare_classic_authex_cmd(struct Crypto1State *pcs, uint32_t uid, uint8_t blockNo, uint8_t cmd, uint64_t ui64Key, uint8_t isNested, uint32_t *ntptr, uint32_t *timing) { +int mifare_classic_authex_cmd(struct Crypto1State *pcs, uint32_t uid, uint8_t blockNo, uint8_t cmd, uint64_t ui64Key, uint8_t isNested, uint32_t *ntptr, uint32_t *ntencptr, uint32_t *timing) { // "random" reader nonce: uint8_t nr[4]; @@ -159,6 +159,8 @@ int mifare_classic_authex_cmd(struct Crypto1State *pcs, uint32_t uid, uint8_t bl // Save the tag nonce (nt) uint32_t nt = bytes_to_num(receivedAnswer, 4); + if (ntencptr) + *ntencptr = nt; // ----------------------------- crypto1 create if (isNested) diff --git a/armsrc/mifareutil.h b/armsrc/mifareutil.h index 1e45d50be..e7ee0f7f9 100644 --- a/armsrc/mifareutil.h +++ b/armsrc/mifareutil.h @@ -72,7 +72,7 @@ uint16_t mifare_sendcmd_short(struct Crypto1State *pcs, uint8_t crypted, uint8_t // mifare classic int mifare_classic_auth(struct Crypto1State *pcs, uint32_t uid, uint8_t blockNo, uint8_t keyType, uint64_t ui64Key, uint8_t isNested); int mifare_classic_authex(struct Crypto1State *pcs, uint32_t uid, uint8_t blockNo, uint8_t keyType, uint64_t ui64Key, uint8_t isNested, uint32_t *ntptr, uint32_t *timing); -int mifare_classic_authex_cmd(struct Crypto1State *pcs, uint32_t uid, uint8_t blockNo, uint8_t cmd, uint64_t ui64Key, uint8_t isNested, uint32_t *ntptr, uint32_t *timing); +int mifare_classic_authex_cmd(struct Crypto1State *pcs, uint32_t uid, uint8_t blockNo, uint8_t cmd, uint64_t ui64Key, uint8_t isNested, uint32_t *ntptr, uint32_t *ntencptr, uint32_t *timing); int mifare_classic_readblock(struct Crypto1State *pcs, uint8_t blockNo, uint8_t *blockData); int mifare_classic_readblock_ex(struct Crypto1State *pcs, uint8_t blockNo, uint8_t *blockData, uint8_t iso_byte); diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 3a400899c..357e1d45e 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -8875,7 +8875,7 @@ static int CmdHF14AMfInfo(const char *Cmd) { PrintAndLogEx(SUCCESS, "ATQA: " _GREEN_("%02X %02X"), card.atqa[1], card.atqa[0]); PrintAndLogEx(SUCCESS, " SAK: " _GREEN_("%02X [%" PRIu64 "]"), card.sak, resp.oldarg[0]); - if (setDeviceDebugLevel(DBG_NONE, false) != PM3_SUCCESS) + if (setDeviceDebugLevel(verbose ? DBG_INFO : DBG_NONE, false) != PM3_SUCCESS) return PM3_EFAILED; PrintAndLogEx(INFO, "--- " _CYAN_("Backdoors Information") "---------------------"); @@ -8951,7 +8951,11 @@ static int CmdHF14AMfInfo(const char *Cmd) { // detect static encrypted nonce if (keyType != 0xff) { - + res = detect_classic_static_encrypted_nonce(0, keyType, key); // TODO: add block number to the config + if (res == NONCE_STATIC) + PrintAndLogEx(SUCCESS, "Static nested nonce: " _YELLOW_("yes")); + if (res == NONCE_STATIC_ENC) + PrintAndLogEx(SUCCESS, "Static encrypted nonce: " _YELLOW_("yes")); } if (do_nack_test) diff --git a/client/src/mifare/mifarehost.c b/client/src/mifare/mifarehost.c index f28f38aaa..8c129b8d9 100644 --- a/client/src/mifare/mifarehost.c +++ b/client/src/mifare/mifarehost.c @@ -1349,6 +1349,33 @@ int detect_classic_static_nonce(void) { return NONCE_FAIL; } +/* Detect Mifare Classic static encrypted nonce +detects special magic cards that has a static / fixed nonce +returns: +0 = nonce ok +1 = has static/fixed nonce +2 = cmd failed +3 = has encrypted nonce +*/ +int detect_classic_static_encrypted_nonce(uint8_t block_no, uint8_t key_type, uint8_t *key) { + + clearCommandBuffer(); + uint8_t cdata[1 + 1 + MIFARE_KEY_SIZE] = {0}; + cdata[0] = block_no; + cdata[1] = key_type; + memcpy(&cdata[2], key, MIFARE_KEY_SIZE); + SendCommandNG(CMD_HF_MIFARE_STATIC_ENCRYPTED_NONCE, cdata, sizeof(cdata)); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_MIFARE_STATIC_ENCRYPTED_NONCE, &resp, 1000)) { + + if (resp.status == PM3_ESOFT) + return NONCE_FAIL; + + return resp.data.asBytes[0]; + } + return NONCE_FAIL; +} + /* try to see if card responses to "Chinese magic backdoor" commands. */ int detect_mf_magic(bool is_mfc) { diff --git a/client/src/mifare/mifarehost.h b/client/src/mifare/mifarehost.h index 6f131f924..5eacabda3 100644 --- a/client/src/mifare/mifarehost.h +++ b/client/src/mifare/mifarehost.h @@ -102,6 +102,7 @@ int detect_classic_prng(void); int detect_classic_nackbug(bool verbose); int detect_mf_magic(bool is_mfc); int detect_classic_static_nonce(void); +int detect_classic_static_encrypted_nonce(uint8_t block_no, uint8_t key_type, uint8_t *key); bool detect_mfc_ev1_signature(void); int read_mfc_ev1_signature(uint8_t *signature); diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index 9189704e4..33803857d 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -675,6 +675,7 @@ typedef struct { #define CMD_HF_MIFARE_NACK_DETECT 0x0730 #define CMD_HF_MIFARE_STATIC_NONCE 0x0731 +#define CMD_HF_MIFARE_STATIC_ENCRYPTED_NONCE 0x0732 // MFU OTP TearOff #define CMD_HF_MFU_OTP_TEAROFF 0x0740 @@ -740,9 +741,10 @@ typedef struct { #define MODE_FULLSIM 2 // Static Nonce detection -#define NONCE_FAIL 0x01 -#define NONCE_NORMAL 0x02 -#define NONCE_STATIC 0x03 +#define NONCE_FAIL 0x01 +#define NONCE_NORMAL 0x02 +#define NONCE_STATIC 0x03 +#define NONCE_STATIC_ENC 0x04 // Dbprintf flags #define FLAG_RAWPRINT 0x00 From 749ba504f8a93dbd54df41e156dfb36c1fa9eac0 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 11 Dec 2023 21:25:31 +0200 Subject: [PATCH 11/13] text --- client/src/cmdhfmf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 357e1d45e..e027a0d77 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -8923,7 +8923,7 @@ static int CmdHF14AMfInfo(const char *Cmd) { } if (keyType != 0xff) - PrintAndLogEx(SUCCESS, "Block 0 ......... %s", sprint_hex(blockdata, MFBLOCK_SIZE)); + PrintAndLogEx(SUCCESS, "Block 0.......... %s", sprint_hex(blockdata, MFBLOCK_SIZE)); } free(keyBlock); From 84cc6d6c79dde7ba2f8082bacbedf6018af6ddcd Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 11 Dec 2023 23:12:56 +0200 Subject: [PATCH 12/13] add: specify key and block for nested static encrypted checks --- client/src/cmdhfmf.c | 49 ++++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index e027a0d77..d65e48040 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -8806,16 +8806,19 @@ static int CmdHF14AMfInfo(const char *Cmd) { void *argtable[] = { arg_param_begin, - arg_str0("k", "key", "", "key, 6 hex bytes"), + arg_int0(NULL, "blk", "", "block number"), arg_lit0("a", NULL, "input key type is key A (def)"), arg_lit0("b", NULL, "input key type is key B"), + arg_str0("k", "key", "", "key, 6 hex bytes"), arg_lit0("n", "nack", "do nack test"), arg_lit0("v", "verbose", "verbose output"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); - /*uint8_t keytype = MF_KEY_A; + int blockn = arg_get_int_def(ctx, 1, 0); + + uint8_t keytype = MF_KEY_A; if (arg_get_lit(ctx, 2) && arg_get_lit(ctx, 3)) { CLIParserFree(ctx); PrintAndLogEx(WARNING, "Input key type must be A or B"); @@ -8827,15 +8830,20 @@ static int CmdHF14AMfInfo(const char *Cmd) { int keylen = 0; uint8_t key[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; CLIGetHexWithReturn(ctx, 4, key, &keylen); -*/ - bool do_nack_test = arg_get_lit(ctx, 4); - bool verbose = arg_get_lit(ctx, 5); + + bool do_nack_test = arg_get_lit(ctx, 5); + bool verbose = arg_get_lit(ctx, 6); CLIParserFree(ctx); uint8_t dbg_curr = DBG_NONE; if (getDeviceDebugLevel(&dbg_curr) != PM3_SUCCESS) return PM3_EFAILED; + if (keylen != 0 && keylen != 6) { + PrintAndLogEx(ERR, "Key length must be 6 bytes"); + return PM3_EINVARG; + } + clearCommandBuffer(); SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT, 0, 0, NULL, 0); PacketResponseNG resp; @@ -8883,8 +8891,8 @@ static int CmdHF14AMfInfo(const char *Cmd) { PrintAndLogEx(INFO, ""); PrintAndLogEx(INFO, "--- " _CYAN_("Keys Information") "---------------------"); - uint8_t key[MIFARE_KEY_SIZE] = {0}; - uint8_t keyType = 0xff; + uint8_t fkey[MIFARE_KEY_SIZE] = {0}; + uint8_t fKeyType = 0xff; int sectorsCnt = 1; uint8_t *keyBlock = NULL; @@ -8907,22 +8915,22 @@ static int CmdHF14AMfInfo(const char *Cmd) { if (e_sector[0].foundKey[0]) { PrintAndLogEx(SUCCESS, "Sector 0 key A... %12llx", e_sector[0].Key[0]); - num_to_bytes(e_sector[0].Key[0], MIFARE_KEY_SIZE, key); + num_to_bytes(e_sector[0].Key[0], MIFARE_KEY_SIZE, fkey); if (mfReadBlock(0, MF_KEY_A, key, blockdata) == PM3_SUCCESS) - keyType = MF_KEY_A; + fKeyType = MF_KEY_A; } if (e_sector[0].foundKey[1]) { PrintAndLogEx(SUCCESS, "Sector 0 key B... %12llx", e_sector[0].Key[1]); - if (keyType == 0xff) { - num_to_bytes(e_sector[0].Key[1], MIFARE_KEY_SIZE, key); + if (fKeyType == 0xff) { + num_to_bytes(e_sector[0].Key[1], MIFARE_KEY_SIZE, fkey); if (mfReadBlock(0, MF_KEY_B, key, blockdata) == PM3_SUCCESS) - keyType = MF_KEY_B; + fKeyType = MF_KEY_B; } } - if (keyType != 0xff) + if (fKeyType != 0xff) PrintAndLogEx(SUCCESS, "Block 0.......... %s", sprint_hex(blockdata, MFBLOCK_SIZE)); } @@ -8950,8 +8958,19 @@ static int CmdHF14AMfInfo(const char *Cmd) { // detect static encrypted nonce - if (keyType != 0xff) { - res = detect_classic_static_encrypted_nonce(0, keyType, key); // TODO: add block number to the config + if (keylen == 6) { + res = detect_classic_static_encrypted_nonce(blockn, keytype, key); + if (res == NONCE_STATIC) { + PrintAndLogEx(SUCCESS, "Static nested nonce: " _YELLOW_("yes")); + fKeyType = 0xff; // dont detect twice + } + if (res == NONCE_STATIC_ENC) { + PrintAndLogEx(SUCCESS, "Static encrypted nonce: " _YELLOW_("yes")); + fKeyType = 0xff; // dont detect twice + } + } + if (fKeyType != 0xff) { + res = detect_classic_static_encrypted_nonce(0, fKeyType, fkey); if (res == NONCE_STATIC) PrintAndLogEx(SUCCESS, "Static nested nonce: " _YELLOW_("yes")); if (res == NONCE_STATIC_ENC) From e77ed68775f465e15107d476c69b48a542655802 Mon Sep 17 00:00:00 2001 From: merlokk <807634+merlokk@users.noreply.github.com> Date: Mon, 11 Dec 2023 23:16:53 +0200 Subject: [PATCH 13/13] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15a2f4dca..92fb6c858 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Added `hf mf info` command and static encrypted nonce detection (@merlokk) - Changed `lf fdx demod` - now raw bytes shows all data (@iceman1001) - Changed `data num` - now can print reversed and inverse (@iceman1001) - Fixed `hf mf sim -ix` never returning console (@datatags)