From 5f2edb9bb897b1e4d7ccd83cade318935219abda Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Mon, 9 Jun 2025 15:36:44 +0200 Subject: [PATCH] reworked and improved the `hf mfp chk` key handling. reworked the nxp_detect_card technology function to enable other parts of the client to benefit from detecting card types. Like `hf mf info` or `hf mf autopwn` - two most common used commands. Now less waiting and more easily to know what next steps to do. --- CHANGELOG.md | 4 + client/src/cmdhf14a.c | 399 ++++++++++++++++++------- client/src/cmdhf14a.h | 20 +- client/src/cmdhfmf.c | 244 ++++++++++++++-- client/src/cmdhfmf.h | 4 + client/src/cmdhfmfdes.c | 32 +- client/src/cmdhfmfdes.h | 59 ++-- client/src/cmdhfmfp.c | 629 +++++++++++++++++++++++++--------------- client/src/cmdhfmfp.h | 17 +- client/src/fileutils.c | 24 +- doc/commands.json | 7 +- 11 files changed, 996 insertions(+), 443 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 876e8b886..a5b665025 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ 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] +- Changed `hf 14a info` - refactored code to be able to detect card technology across the client easier (@iceman1001) +- Changed `hf mf info` - now informs better if a different card technology is detected (@iceman1001) +- Changed `hf mf autopwn` - now exits if desfire is detected and limit attacks if mifare plus is detected (@iceman1001) +- Changed `hf mfp chk` - improved key handling and output (@iceman1001) - Fix `hf mf dump` - added a check for keyfile to contain enough keys for card (@iceman1001) - Fix `hf mf eview` - now viewing 2k, 4k cards doesn't get wrong background color (@iceman1001) - Changed `hf mf info` - skip checking if it detects a MIFARE Ultralight family card (@iceman1001) diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index 479bef132..b83716cb6 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -246,6 +246,49 @@ static uint16_t gs_frame_len = 0; static uint8_t gs_frames_num = 0; static uint16_t atsFSC[] = {16, 24, 32, 40, 48, 64, 96, 128, 256}; +int hf14a_getversion_data(iso14a_card_select_t *card, uint64_t select_status, version_hw_t *hw) { + + // field on, card selected if select_status is 1 or 4, not selected if 2 + int res = PM3_EFAILED; + + // if 4b UID or NXP, try to get version + if ((card->uidlen == 4) || ((card->uidlen == 7) && (card->uid[0] == 0x04))) { + // GetVersion + if ((select_status == 1) || (select_status == 4)) { // L4 + + uint8_t response[PM3_CMD_DATA_SIZE] = {0}; + int resp_len = 0; + uint8_t getVersion[5] = {0x90, 0x60, 0x00, 0x00, 0x00}; + res = ExchangeAPDU14a(getVersion, sizeof(getVersion), false, false, response, sizeof(response), &resp_len); + DropField(); + + if (res == PM3_ETIMEOUT) { + PrintAndLogEx(DEBUG, "iso14443a card select timeout"); + return PM3_ETIMEOUT; + } + + if (resp_len == 9) { + memcpy(hw, response, sizeof(version_hw_t)); + return PM3_SUCCESS; + } + return PM3_EFAILED; + + } + + // select_status = 2, L3 + uint8_t version[8] = {0}; + uint8_t uid[7] = {0}; + res = mfu_get_version_uid(version, uid); + DropField(); + if (res == PM3_SUCCESS) { + memcpy(hw, version + 1, sizeof(version_hw_t)); + } + } + + DropField(); + return res; +} + static int CmdHF14AList(const char *Cmd) { return CmdTraceListAlias(Cmd, "hf 14a", "14a -c"); } @@ -1735,94 +1778,236 @@ static void printTag(const char *tag) { PrintAndLogEx(SUCCESS, " " _YELLOW_("%s"), tag); } -int detect_nxp_card(uint8_t sak, uint16_t atqa, uint64_t select_status) { - +// Based on NXP AN10833 Rev 3.8 and NXP AN10834 Rev 4.2 +int detect_nxp_card(uint8_t sak, uint16_t atqa, uint64_t select_status, + uint8_t ats_hist_len, uint8_t *ats_hist, + bool version_hw_available, version_hw_t *version_hw) { int type = MTNONE; - if ((sak & 0x02) != 0x02) { - if ((sak & 0x19) == 0x19) { - type |= MTCLASSIC; - } else if ((sak & 0x40) == 0x40) { - type |= MTISO18092; - } else if ((sak & 0x38) == 0x38) { - type |= MTCLASSIC; - } else if ((sak & 0x18) == 0x18) { - if (select_status == 1) { - type |= MTPLUS; - } else { - type |= MTCLASSIC; - } - } else if ((sak & 0x09) == 0x09) { - type |= MTMINI; - } else if ((sak & 0x28) == 0x28) { - type |= MTCLASSIC; - } else if ((sak & 0x08) == 0x08) { - if (select_status == 1) { - type |= MTPLUS; - } else { - type |= MTCLASSIC; - } - } else if ((sak & 0x11) == 0x11) { - type |= MTPLUS; - } else if ((sak & 0x10) == 0x10) { - type |= MTPLUS; - } else if ((sak & 0x01) == 0x01) { - type |= MTCLASSIC; - } else if ((sak & 0x24) == 0x24) { - type |= MTDESFIRE; - } else if ((sak & 0x20) == 0x20) { - if (select_status == 1) { - if ((atqa & 0x0040) == 0x0040) { - if ((atqa & 0x0300) == 0x0300) { - type |= MTDESFIRE; - } else { - type |= MTPLUS; - } - } else { + if (version_hw_available) { - if ((atqa & 0x0001) == 0x0001) { - type |= HID_SEOS; - } else { - type |= MTPLUS; - } + switch (version_hw->product_type & 0x0F) { + case 0x1: { + type |= MTDESFIRE; - if ((atqa & 0x0004) == 0x0004) { + // special cases, override major_product_version_str when needed + switch (version_hw->major_product_version) { + case 0x42: type |= MTEMV; + break; + case 0xA0: + type |= MTDUOX; + break; + } + break; + } + case 0x2: { + type |= MTPLUS; + break; + } + case 0x3: { + type |= MTULTRALIGHT; + break; + } + case 0x4: { + type |= MTNTAG; + break; + } + case 0x7: { + type |= MTNTAG; + break; + } + case 0x8: { + type |= MTDESFIRE; + break; + } + case 0x9: { + break; + } + default: { + break; + } + } + + } + + if ((sak & 0x44) == 0x40) { + // ISO18092 Table 15: Target compliant with NFC transport protocol + type |= MTISO18092; + } + + if ((sak & 0x02) == 0x00) { // SAK b2=0 + + if ((sak & 0x08) == 0x08) { // SAK b2=0 b4=1 + + if ((sak & 0x10) == 0x10) { // SAK b2=0 b4=1 b5=1 + + if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=1 b5=1 b1=1, SAK=0x19 + type |= MTCLASSIC; + + } else { // SAK b2=0 b4=1 b5=1 b1=0 + + if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=1 b5=1 b1=0 b6=1, SAK=0x38 + type |= MTCLASSIC; + + } else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 + + if (select_status == 4) { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS + + if (version_hw_available) { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS GetVersion + type |= MTPLUS; + + } else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS No_GetVersion + + if (ats_hist_len > 0) { + + if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 0)) { + type |= MTPLUS; + } else { + type |= MTCLASSIC; + } + } + } + + } else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 no_ATS, SAK=0x18 + type |= MTCLASSIC; + } } } - type |= (MTDESFIRE | MT424); - } - } else if ((sak & 0x04) == 0x04) { - type |= MTDESFIRE; - } else { - type |= MTULTRALIGHT; - } - } else if ((sak & 0x0A) == 0x0A) { - if ((atqa & 0x0003) == 0x0003) { - type |= MTFUDAN; - } else if ((atqa & 0x0005) == 0x0005) { + } else { // SAK b2=0 b4=1 b5=0 + + if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=1 b5=0 b1=1, SAK=0x09 + type |= MTMINI; + + } else { // SAK b2=0 b4=1 b5=0 b1=0 + + if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=1 b5=0 b1=0 b6=1, SAK=0x28 + type |= MTCLASSIC; + + } else { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 + + if (select_status == 4) { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS + + if (version_hw_available) { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS GetVersion + type |= MTPLUS; + + } else { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS No_GetVersion + + if (ats_hist_len > 0) { + + if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 0)) { + type |= MTPLUS; + + } else if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x21\x30", 4) == 0)) { + type |= MTPLUS; + + } else { + type |= MTCLASSIC; + } + } + } + } else { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 no_ATS, SAK=0x08 + type |= MTCLASSIC; + } + } + } + } + + } else { // SAK b2=0 b4=0 + + if ((sak & 0x10) == 0x10) { // SAK b2=0 b4=0 b5=1 + + if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=0 b5=1 b1=1, SAK=0x11 + type |= MTPLUS; + } else { // SAK b2=0 b4=0 b5=1 b1=0, SAK=0x10 + type |= MTPLUS; + } + + } else { // SAK b2=0 b4=0 b5=0 + + if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=0 b5=0 b1=1 + type |= MTCLASSIC; + + } else { // SAK b2=0 b4=0 b5=0 b1=0 + + if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=0 b5=0 b1=0 b6=1, SAK=0x20 + + if (select_status == 1) { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS + + if (version_hw_available) { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS GetVersion + + if ((version_hw->product_type & 0x7F) == 0x02) { + type |= MTPLUS; + + } else if (((version_hw->product_type & 0x7F) == 0x01) || + (version_hw->product_type == 0x08) || + (version_hw->product_type == 0x91)) { + type |= MTDESFIRE; + + } else if (version_hw->product_type == 0x04) { + type |= (MTDESFIRE | MT424); + } + + } else { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS No GetVersion + + if (ats_hist_len > 0) { + + if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 0)) { + type |= MTPLUS; + + } else { + + if ((atqa == 0x0001) || (atqa == 0x0004)) { + type |= HID_SEOS; + } + + if (atqa == 0x0004) { + type |= MTEMV; + } + } + } + } + } + + } else { // SAK b2=0 b4=0 b5=0 b1=0 b6=0, SAK=0x00 + + if (version_hw_available == false) { + // SAK b2=0 b4=0 b5=0 b1=0 b6=0 No_GetVersion + int status = mfuc_test_authentication_support(); + if (status == PM3_SUCCESS) { + type |= MTULTRALIGHT_C; + } + } + type |= MTULTRALIGHT; + } + } + } + } + } else { // SAK b2=1 + + if (sak == 0x0A) { + + if (atqa == 0x0003) { + // Uses Shanghai algo + type |= MTFUDAN; + + } else if (atqa == 0x0005) { + type |= MTFUDAN; + } + + } else if (sak == 0x53) { type |= MTFUDAN; } - } else if ((sak & 0x53) == 0x53) { - type |= MTFUDAN; } return type; } -typedef struct { - uint8_t vendor_id; - uint8_t product_type; - uint8_t product_subtype; - uint8_t major_product_version; - uint8_t minor_product_version; - uint8_t storage_size; - uint8_t protocol_type; -} version_hw_t; - // Based on NXP AN10833 Rev 3.8 and NXP AN10834 Rev 4.2 -static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_status, uint8_t ats_hist_len, uint8_t *ats_hist, bool version_hw_available, version_hw_t *version_hw) { +static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_status, + uint8_t ats_hist_len, uint8_t *ats_hist, + bool version_hw_available, version_hw_t *version_hw) { int type = MTNONE; const char *product_type_str = ""; const char *major_product_version_str = ""; @@ -2237,6 +2422,8 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta // TODO: read page 2/3, then ?? printTag("MIFARE Ultralight C"); printTag("MIFARE Hospitality"); + type |= MTULTRALIGHT_C; + } else { printTag("MIFARE Ultralight"); } @@ -2450,36 +2637,12 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { ats_hist_pos += (card.ats[1] & 0x40) == 0x40; } - // field on, card selected if select_status is 1 or 4, not selected if 2 - bool version_hw_available = false; + version_hw_t version_hw = {0}; // if 4b UID or NXP, try to get version - if ((card.uidlen == 4) || ((card.uidlen == 7) && (card.uid[0] == 0x04))) { - // GetVersion - if ((select_status == 1) || (select_status == 4)) { // L4 - uint8_t response[PM3_CMD_DATA_SIZE] = {0}; - int resp_len = 0; - uint8_t getVersion[5] = {0x90, 0x60, 0x00, 0x00, 0x00}; - int res = ExchangeAPDU14a(getVersion, sizeof(getVersion), false, false, response, sizeof(response), &resp_len); - if (res == PM3_ETIMEOUT) { - PrintAndLogEx(DEBUG, "iso14443a card select timeout"); - DropField(); - return PM3_ETIMEOUT; - } - if (resp_len == 9) { - memcpy(&version_hw, response, sizeof(version_hw)); - version_hw_available = true; - } - } else { // select_status = 2, L3 - uint8_t version[8] = {0}; - uint8_t uid[7] = {0}; - if (mfu_get_version_uid(version, uid) == PM3_SUCCESS) { - memcpy(&version_hw, version + 1, sizeof(version_hw)); - version_hw_available = true; - } - } - } - DropField(); + int res = hf14a_getversion_data(&card, select_status, &version_hw); + bool version_hw_available = (res == PM3_SUCCESS); + PrintAndLogEx(INFO, "---------- " _CYAN_("ISO14443-A Information") " ----------"); PrintAndLogEx(SUCCESS, " UID: " _GREEN_("%s") " %s", sprint_hex(card.uid, card.uidlen), get_uid_type(&card)); PrintAndLogEx(SUCCESS, "ATQA: " _GREEN_("%02X %02X"), card.atqa[1], card.atqa[0]); @@ -2501,9 +2664,13 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { bool isSEOS = false; int nxptype = MTNONE; - PrintAndLogEx(SUCCESS, "Possible types:"); if (card.uidlen <= 4) { - nxptype = detect_nxp_card_print(card.sak, ((card.atqa[1] << 8) + card.atqa[0]), select_status, card.ats_len - ats_hist_pos, card.ats + ats_hist_pos, version_hw_available, &version_hw); + + PrintAndLogEx(SUCCESS, "Possible types:"); + nxptype = detect_nxp_card_print(card.sak, ((card.atqa[1] << 8) + card.atqa[0]), + select_status, card.ats_len - ats_hist_pos, card.ats + ats_hist_pos, + version_hw_available, &version_hw + ); isMifareMini = ((nxptype & MTMINI) == MTMINI); isMifareClassic = ((nxptype & MTCLASSIC) == MTCLASSIC); @@ -2522,7 +2689,8 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { } else { // Double & triple sized UID, can be mapped to a manufacturer. - PrintAndLogEx(SUCCESS, "MANUFACTURER: " _YELLOW_("%s"), getTagInfo(card.uid[0])); + PrintAndLogEx(SUCCESS, " " _YELLOW_("%s"), getTagInfo(card.uid[0])); + PrintAndLogEx(SUCCESS, "Possible types:"); switch (card.uid[0]) { case 0x02: { // ST @@ -2531,7 +2699,10 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { break; } case 0x04: { // NXP - nxptype = detect_nxp_card_print(card.sak, ((card.atqa[1] << 8) + card.atqa[0]), select_status, card.ats_len - ats_hist_pos, card.ats + ats_hist_pos, version_hw_available, &version_hw); + nxptype = detect_nxp_card_print(card.sak, ((card.atqa[1] << 8) + card.atqa[0]), + select_status, card.ats_len - ats_hist_pos, card.ats + ats_hist_pos, + version_hw_available, &version_hw + ); isMifareMini = ((nxptype & MTMINI) == MTMINI); isMifareClassic = ((nxptype & MTCLASSIC) == MTCLASSIC); @@ -2595,6 +2766,9 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { case 0x00: { isMifareClassic = false; + // ******** is card of the MFU type (UL/ULC/NTAG/ etc etc) + DropField(); + uint64_t tagT = GetHF14AMfU_Type(); if (tagT != MFU_TT_UL_ERROR) { ul_print_type(tagT, 0); @@ -2604,6 +2778,15 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { printTag("Possible AZTEK (iso14443a compliant)"); } + // reconnect for further tests + clearCommandBuffer(); + SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_NO_DISCONNECT, 0, 0, NULL, 0); + if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply"); + DropField(); + return PM3_ETIMEOUT; + } + memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t)); select_status = resp.oldarg[0]; // 0: couldn't read, 1: OK, with ATS, 2: OK, no ATS @@ -2656,7 +2839,7 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { if (card.ats_len >= 3) { // a valid ATS consists of at least the length byte (TL) and 2 CRC bytes - PrintAndLogEx(INFO, "-------------------------- " _CYAN_("ATS") " --------------------------"); + PrintAndLogEx(INFO, "-------------------------- " _CYAN_("ATS") " ----------------------------------"); bool ta1 = 0, tb1 = 0, tc1 = 0; if (select_status == 2) { @@ -2751,7 +2934,7 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { if ((card.ats[0] > pos) && (card.ats_len >= card.ats[0] + 2)) { uint8_t calen = card.ats[0] - pos; PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(INFO, "-------------------- " _CYAN_("Historical bytes") " --------------------"); + PrintAndLogEx(INFO, "-------------------- " _CYAN_("Historical bytes") " ----------------------------"); if (card.ats[pos] == 0xC1) { PrintAndLogEx(INFO, " %s", sprint_hex(card.ats + pos, calen)); @@ -2856,7 +3039,7 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { uint16_t sw = 0; uint8_t result[1024] = {0}; size_t resultlen = 0; - int res = Iso7816Select(CC_CONTACTLESS, ActivateField, true, vaid, vaidlen, result, sizeof(result), &resultlen, &sw); + res = Iso7816Select(CC_CONTACTLESS, ActivateField, true, vaid, vaidlen, result, sizeof(result), &resultlen, &sw); ActivateField = false; if (res) continue; @@ -2950,7 +3133,7 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { } if (isMifareClassic || isMifareMini) { - int res = detect_classic_static_nonce(); + res = detect_classic_static_nonce(); if (res == NONCE_STATIC) { PrintAndLogEx(SUCCESS, "Static nonce......... " _YELLOW_("yes")); } diff --git a/client/src/cmdhf14a.h b/client/src/cmdhf14a.h index 4a37a0425..2e438a2d6 100644 --- a/client/src/cmdhf14a.h +++ b/client/src/cmdhf14a.h @@ -36,6 +36,16 @@ typedef struct { const char *hint; } hintAIDList_t; +typedef struct { + uint8_t vendor_id; + uint8_t product_type; + uint8_t product_subtype; + uint8_t major_product_version; + uint8_t minor_product_version; + uint8_t storage_size; + uint8_t protocol_type; +} version_hw_t; + typedef enum { MTNONE = 0, MTCLASSIC = 1, @@ -49,6 +59,9 @@ typedef enum { MTFUDAN = 256, MTISO18092 = 512, MT424 = 1024, + MTULTRALIGHT_C = 2048, + MTDUOX = 4096, + MTNTAG = 8192, } nxp_mifare_type_t; int CmdHF14A(const char *Cmd); @@ -59,7 +72,10 @@ int CmdHF14ANdefRead(const char *Cmd); // used by cmdnfc.c int CmdHF14ANdefFormat(const char *Cmd); // used by cmdnfc.c int CmdHF14ANdefWrite(const char *Cmd); // used by cmdnfc.c -int detect_nxp_card(uint8_t sak, uint16_t atqa, uint64_t select_status); + +int detect_nxp_card(uint8_t sak, uint16_t atqa, uint64_t select_status, + uint8_t ats_hist_len, uint8_t *ats_hist, + bool version_hw_available, version_hw_t *version_hw); int hf14a_getconfig(hf14a_config_t *config); int hf14a_setconfig(hf14a_config_t *config, bool verbose); @@ -75,4 +91,6 @@ int SelectCard14443A_4_WithParameters(bool disconnect, bool verbose, iso14a_card bool Get_apdu_in_framing(void); void Set_apdu_in_framing(bool v); +int hf14a_getversion_data(iso14a_card_select_t *card, uint64_t select_status, version_hw_t *hw); + #endif diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 7a4efcf1e..7fba40787 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -372,7 +372,7 @@ int mfc_ev1_print_signature(uint8_t *uid, uint8_t uidlen, uint8_t *signature, in static int mf_read_uid(uint8_t *uid, int *uidlen, int *nxptype) { clearCommandBuffer(); - SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT, 0, 0, NULL, 0); + SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_NO_DISCONNECT, 0, 0, NULL, 0); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { PrintAndLogEx(DEBUG, "iso14443a card select failed"); @@ -383,13 +383,55 @@ static int mf_read_uid(uint8_t *uid, int *uidlen, int *nxptype) { iso14a_card_select_t card; memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t)); + uint64_t select_status = resp.oldarg[0]; + + // try to request ATS even if tag claims not to support it. If yes => 4 + if (select_status == 2) { + uint8_t rats[] = { 0xE0, 0x80 }; // FSDI=8 (FSD=256), CID=0 + clearCommandBuffer(); + SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_DISCONNECT, 2, 0, rats, sizeof(rats)); + if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply"); + return PM3_ETIMEOUT; + } + + memcpy(card.ats, resp.data.asBytes, resp.oldarg[0]); + card.ats_len = resp.oldarg[0]; // note: ats_len includes CRC Bytes + if (card.ats_len > 3) { + select_status = 4; + } + } + + uint8_t ats_hist_pos = 0; + if ((card.ats_len > 3) && (card.ats[0] > 1)) { + ats_hist_pos = 2; + ats_hist_pos += (card.ats[1] & 0x10) == 0x10; + ats_hist_pos += (card.ats[1] & 0x20) == 0x20; + ats_hist_pos += (card.ats[1] & 0x40) == 0x40; + } + + version_hw_t version_hw = {0}; + // if 4b UID or NXP, try to get version + int res = hf14a_getversion_data(&card, select_status, &version_hw); + DropField(); + + bool version_hw_available = (res == PM3_SUCCESS); + if (nxptype) { - uint64_t select_status = resp.oldarg[0]; - *nxptype = detect_nxp_card(card.sak, ((card.atqa[1] << 8) + card.atqa[0]), select_status); + + *nxptype = detect_nxp_card(card.sak + , ((card.atqa[1] << 8) + card.atqa[0]) + , select_status + , card.ats_len - ats_hist_pos + , card.ats + ats_hist_pos + , version_hw_available + , &version_hw + ); } memcpy(uid, card.uid, card.uidlen * sizeof(uint8_t)); *uidlen = card.uidlen; + return PM3_SUCCESS; } @@ -636,7 +678,7 @@ static void mf_print_block(uint16_t maxblocks, uint8_t blockno, uint8_t *d, bool } } -static void mf_print_blocks(uint16_t n, uint8_t *d, bool verbose) { +void mf_print_blocks(uint16_t n, uint8_t *d, bool verbose) { PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "-----+-----+-------------------------------------------------+-----------------"); PrintAndLogEx(INFO, " sec | blk | data | ascii"); @@ -660,7 +702,7 @@ static void mf_print_blocks(uint16_t n, uint8_t *d, bool verbose) { } // assumes n is in number of blocks 0..255 -static int mf_print_keys(uint16_t n, uint8_t *d) { +int mf_print_keys(uint16_t n, uint8_t *d) { uint8_t sectors = 0; switch (n) { case MIFARE_MINI_MAXBLOCK: @@ -2967,7 +3009,9 @@ static int CmdHF14AMfAutoPWN(const char *Cmd) { // Settings int prng_type = PM3_EUNDEF; int isOK = 0; + // ------------------------------ + PrintAndLogEx(NORMAL, ""); uint64_t tagT = GetHF14AMfU_Type(); if (tagT != MFU_TT_UL_ERROR) { @@ -2978,10 +3022,11 @@ static int CmdHF14AMfAutoPWN(const char *Cmd) { // Select card to get UID/UIDLEN/ATQA/SAK information clearCommandBuffer(); - SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT, 0, 0, NULL, 0); + SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_NO_DISCONNECT, 0, 0, NULL, 0); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_ACK, &resp, 1500) == false) { PrintAndLogEx(DEBUG, "iso14443a card select timeout"); + DropField(); return PM3_ETIMEOUT; } @@ -2997,6 +3042,64 @@ static int CmdHF14AMfAutoPWN(const char *Cmd) { iso14a_card_select_t card; memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t)); + // try to request ATS even if tag claims not to support it. If yes => 4 + if (select_status == 2) { + uint8_t rats[] = { 0xE0, 0x80 }; // FSDI=8 (FSD=256), CID=0 + clearCommandBuffer(); + SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_DISCONNECT, 2, 0, rats, sizeof(rats)); + if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply"); + return PM3_ETIMEOUT; + } + + memcpy(card.ats, resp.data.asBytes, resp.oldarg[0]); + card.ats_len = resp.oldarg[0]; // note: ats_len includes CRC Bytes + if (card.ats_len > 3) { + select_status = 4; + } + } + + uint8_t ats_hist_pos = 0; + if ((card.ats_len > 3) && (card.ats[0] > 1)) { + ats_hist_pos = 2; + ats_hist_pos += (card.ats[1] & 0x10) == 0x10; + ats_hist_pos += (card.ats[1] & 0x20) == 0x20; + ats_hist_pos += (card.ats[1] & 0x40) == 0x40; + } + + version_hw_t version_hw = {0}; + // if 4b UID or NXP, try to get version + int res = hf14a_getversion_data(&card, select_status, &version_hw); + DropField(); + + bool version_hw_available = (res == PM3_SUCCESS); + + int nxptype = detect_nxp_card(card.sak + , ((card.atqa[1] << 8) + card.atqa[0]) + , select_status + , card.ats_len - ats_hist_pos + , card.ats + ats_hist_pos + , version_hw_available + , &version_hw + ); + + if ((nxptype & MTDESFIRE) == MTDESFIRE) { + PrintAndLogEx(WARNING, "MIFARE DESFire card detected. Quitting..."); + return PM3_ESOFT; + } + + bool isMifareMini = ((nxptype & MTMINI) == MTMINI); + bool isMifarePlus = ((nxptype & MTPLUS) == MTPLUS); + + if (isMifarePlus) { + PrintAndLogEx(INFO, "MIFARE Plus card detected. Using limited set of attacks"); + } + + if (isMifareMini && sector_cnt != MIFARE_MINI_MAXSECTOR) { + PrintAndLogEx(WARNING, "MIFARE Mini S20 card detected. Changing sector count to %u", MIFARE_MINI_MAXSECTOR); + sector_cnt = MIFARE_MINI_MAXSECTOR; + } + bool known_key = (in_keys_len > 5); uint8_t key[MIFARE_KEY_SIZE] = {0}; if (known_key) { @@ -3134,7 +3237,7 @@ static int CmdHF14AMfAutoPWN(const char *Cmd) { return ret; } - int32_t res = PM3_SUCCESS; + res = PM3_SUCCESS; // Use the dictionary to find sector keys on the card if (verbose) { @@ -3465,6 +3568,20 @@ tryNested: } else { tryHardnested: // If the nested attack fails then we try the hardnested attack + + // skip this + if (isMifarePlus) { + + // Show the results to the user + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, _GREEN_("found keys:")); + printKeyTable(sector_cnt, e_sector); + PrintAndLogEx(NORMAL, ""); + free(e_sector); + free(fptr); + return PM3_ESOFT; + } + if (verbose) { PrintAndLogEx(INFO, "======================= " _YELLOW_("START HARDNESTED ATTACK") " ======================="); PrintAndLogEx(INFO, "sector no %3d, target key type %c, Slow %s", @@ -9578,7 +9695,9 @@ static int CmdHF14AMfValue(const char *Cmd) { }; CLIExecWithReturn(ctx, Cmd, argtable, false); - uint8_t blockno = (uint8_t)arg_get_int_def(ctx, 13, 1); + int keylen = 0; + uint8_t key[6] = {0}; + CLIGetHexWithReturn(ctx, 1, key, &keylen); uint8_t keytype = MF_KEY_A; if (arg_get_lit(ctx, 2) && arg_get_lit(ctx, 3)) { @@ -9589,22 +9708,6 @@ static int CmdHF14AMfValue(const char *Cmd) { keytype = MF_KEY_B; } - uint8_t transferkeytype = MF_KEY_A; - if (arg_get_lit(ctx, 9) && arg_get_lit(ctx, 10)) { - CLIParserFree(ctx); - PrintAndLogEx(WARNING, "Choose one single transfer key type"); - return PM3_EINVARG; - } else if (arg_get_lit(ctx, 10)) { - transferkeytype = MF_KEY_B; - } - - int keylen = 0; - uint8_t key[6] = {0}; - CLIGetHexWithReturn(ctx, 1, key, &keylen); - - int transferkeylen = 0; - uint8_t transferkey[6] = {0}; - CLIGetHexWithReturn(ctx, 8, transferkey, &transferkeylen); /* Value /Value Value BLK /BLK BLK /BLK @@ -9619,11 +9722,29 @@ static int CmdHF14AMfValue(const char *Cmd) { int64_t decval = (int64_t)arg_get_u64_def(ctx, 5, -1); // Dec by -1 is invalid, so not set. int64_t setval = (int64_t)arg_get_u64_def(ctx, 6, 0x7FFFFFFFFFFFFFFF); // out of bounds (for int32) so not set int64_t trnval = (int64_t)arg_get_u64_def(ctx, 7, -1); // block to transfer to + + int transferkeylen = 0; + uint8_t transferkey[6] = {0}; + CLIGetHexWithReturn(ctx, 8, transferkey, &transferkeylen); + + uint8_t transferkeytype = MF_KEY_A; + if (arg_get_lit(ctx, 9) && arg_get_lit(ctx, 10)) { + CLIParserFree(ctx); + PrintAndLogEx(WARNING, "Choose one single transfer key type"); + return PM3_EINVARG; + } else if (arg_get_lit(ctx, 10)) { + transferkeytype = MF_KEY_B; + } + bool getval = arg_get_lit(ctx, 11); bool resval = arg_get_lit(ctx, 12); + + uint8_t blockno = (uint8_t)arg_get_int_def(ctx, 13, 1); + int dlen = 0; uint8_t data[16] = {0}; CLIGetHexWithReturn(ctx, 14, data, &dlen); + CLIParserFree(ctx); // sanity checks @@ -10012,10 +10133,11 @@ static int CmdHF14AMfInfo(const char *Cmd) { } clearCommandBuffer(); - SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT, 0, 0, NULL, 0); + SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_NO_DISCONNECT, 0, 0, NULL, 0); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { PrintAndLogEx(DEBUG, "iso14443a card select timeout"); + DropField(); return PM3_ETIMEOUT; } @@ -10030,6 +10152,23 @@ static int CmdHF14AMfInfo(const char *Cmd) { */ uint64_t select_status = resp.oldarg[0]; + // try to request ATS even if tag claims not to support it. If yes => 4 + if (select_status == 2) { + uint8_t rats[] = { 0xE0, 0x80 }; // FSDI=8 (FSD=256), CID=0 + clearCommandBuffer(); + SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_RAW | ISO14A_APPEND_CRC | ISO14A_NO_DISCONNECT, 2, 0, rats, sizeof(rats)); + if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply"); + return PM3_ETIMEOUT; + } + + memcpy(card.ats, resp.data.asBytes, resp.oldarg[0]); + card.ats_len = resp.oldarg[0]; // note: ats_len includes CRC Bytes + if (card.ats_len > 3) { + select_status = 4; + } + } + if (select_status == 0) { PrintAndLogEx(DEBUG, "iso14443a card select failed"); return select_status; @@ -10050,19 +10189,62 @@ 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(verbose ? MAX(dbg_curr, DBG_INFO) : DBG_NONE, false) != PM3_SUCCESS) { - return PM3_EFAILED; + + uint8_t ats_hist_pos = 0; + if ((card.ats_len > 3) && (card.ats[0] > 1)) { + ats_hist_pos = 2; + ats_hist_pos += (card.ats[1] & 0x10) == 0x10; + ats_hist_pos += (card.ats[1] & 0x20) == 0x20; + ats_hist_pos += (card.ats[1] & 0x40) == 0x40; } - uint64_t tagtype = GetHF14AMfU_Type(); - if (tagtype != MFU_TT_UL_ERROR) { - PrintAndLogEx(INFO, "This is not MIFARE Classic based card"); + version_hw_t version_hw = {0}; + // if 4b UID or NXP, try to get version + int res = hf14a_getversion_data(&card, select_status, &version_hw); + DropField(); + bool version_hw_available = (res == PM3_SUCCESS); + + int card_type = detect_nxp_card(card.sak + , ((card.atqa[1] << 8) + card.atqa[0]) + , select_status + , card.ats_len - ats_hist_pos + , card.ats + ats_hist_pos + , version_hw_available + , &version_hw + ); + + if ((card_type & MTDESFIRE) == MTDESFIRE) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "MIFARE DESFire detected"); + PrintAndLogEx(HINT, "Hint: try `" _YELLOW_("hf mfdes info") "`"); + goto out; + } + + if ((card_type & MTULTRALIGHT) == MTULTRALIGHT) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "MIFARE Ultralight / NTAG detected"); PrintAndLogEx(HINT, "Hint: try `" _YELLOW_("hf mfu info") "`"); goto out; } + if ((card_type & MTPLUS) == MTPLUS) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "MIFARE Plus detected"); + PrintAndLogEx(HINT, "Hint: try `" _YELLOW_("hf mfp info") "`"); + } + + if ((card_type & MTEMV) == MTEMV) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "EMV detected"); + PrintAndLogEx(HINT, "Hint: try `" _YELLOW_("emv info") "`"); + } + + if (setDeviceDebugLevel(verbose ? MAX(dbg_curr, DBG_INFO) : DBG_NONE, false) != PM3_SUCCESS) { + return PM3_EFAILED; + } + uint8_t signature[32] = {0}; - int res = read_mfc_ev1_signature(signature); + res = read_mfc_ev1_signature(signature); if (res == PM3_SUCCESS) { mfc_ev1_print_signature(card.uid, card.uidlen, signature, sizeof(signature)); } diff --git a/client/src/cmdhfmf.h b/client/src/cmdhfmf.h index f39d63ff9..ff1695341 100644 --- a/client/src/cmdhfmf.h +++ b/client/src/cmdhfmf.h @@ -35,9 +35,13 @@ void printKeyTable(size_t sectorscnt, sector_t *e_sector); void printKeyTableEx(size_t sectorscnt, sector_t *e_sector, uint8_t start_sector); // void printKeyTableEx(size_t sectorscnt, sector_t *e_sector, uint8_t start_sector, bool singel_sector); + bool mfc_value(const uint8_t *d, int32_t *val); void mf_print_sector_hdr(uint8_t sector); void mf_print_block_one(uint8_t blockno, uint8_t *d, bool verbose); +int mf_print_keys(uint16_t n, uint8_t *d); +void mf_print_blocks(uint16_t n, uint8_t *d, bool verbose); + int mfc_ev1_print_signature(uint8_t *uid, uint8_t uidlen, uint8_t *signature, int signature_len); #endif diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index 1315869db..a6f4f934c 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -89,15 +89,6 @@ typedef struct mfdes_data { uint8_t *data; } PACKED mfdes_data_t; -typedef struct mfdes_info_res { - uint8_t isOK; - uint8_t uid[7]; - uint8_t uidlen; - uint8_t versionHW[7]; - uint8_t versionSW[7]; - uint8_t details[14]; -} PACKED mfdes_info_res_t; - typedef struct mfdes_value { uint8_t fileno; //01 uint8_t value[16]; @@ -136,21 +127,6 @@ typedef enum { MFDES_VALUE_FILE } MFDES_FILE_TYPE_T; -typedef enum { - DESFIRE_UNKNOWN = 0, - DESFIRE_MF3ICD40, - DESFIRE_EV1, - DESFIRE_EV2, - DESFIRE_EV2_XL, - DESFIRE_EV3, - DESFIRE_LIGHT, - PLUS_EV1, - PLUS_EV2, - NTAG413DNA, - NTAG424, - DUOX, -} nxp_cardtype_t; - typedef enum { DESFIRE_UNKNOWN_PROD = 0, DESFIRE_PHYSICAL, @@ -332,7 +308,7 @@ static const char *getAidCommentStr(uint32_t aid) { return ""; } -static nxp_cardtype_t getCardType(uint8_t type, uint8_t major, uint8_t minor) { +nxp_cardtype_t getCardType(uint8_t type, uint8_t major, uint8_t minor) { // DESFire MF3ICD40 if (type == 0x01 && major == 0x00 && minor == 0x02) @@ -385,7 +361,7 @@ static nxp_cardtype_t getCardType(uint8_t type, uint8_t major, uint8_t minor) { if (type == 0x04 && major == 0x30 && minor == 0x00) return NTAG424; - return DESFIRE_UNKNOWN; + return NXP_UNKNOWN; } // ref: https://www.nxp.com/docs/en/application-note/AN12343.pdf p7 @@ -423,7 +399,7 @@ static const char *getProductTypeStr(const uint8_t *versionhw) { return "UNKNOWN PROD"; } -static int mfdes_get_info(mfdes_info_res_t *info) { +int mfdes_get_info(mfdes_info_res_t *info) { PacketResponseNG resp; SendCommandNG(CMD_HF_DESFIRE_INFO, NULL, 0); @@ -710,7 +686,7 @@ static int CmdHF14ADesInfo(const char *Cmd) { return PM3_SUCCESS; } - if (cardtype == DESFIRE_UNKNOWN) { + if (cardtype == NXP_UNKNOWN) { PrintAndLogEx(INFO, "HW Version.. %s", sprint_hex_inrow(info.versionHW, sizeof(info.versionHW))); PrintAndLogEx(INFO, "SW Version.. %s", sprint_hex_inrow(info.versionSW, sizeof(info.versionSW))); PrintAndLogEx(INFO, "Version data identification failed. Report to Iceman!"); diff --git a/client/src/cmdhfmfdes.h b/client/src/cmdhfmfdes.h index 80b3e8c93..0eddcf6cf 100644 --- a/client/src/cmdhfmfdes.h +++ b/client/src/cmdhfmfdes.h @@ -20,6 +20,44 @@ #include "common.h" +// Ev1 card limits +#define MAX_NUM_KEYS 0x0F +#define MAX_APPLICATION_COUNT 28 +#define MAX_FILE_COUNT 32 +#define MAX_FRAME_SIZE 60 +#define FRAME_PAYLOAD_SIZE (MAX_FRAME_SIZE - 5) + +// Ev2 card limits +// Ev3 card limits +// Light card limits +// Light Ev1 card limits + +#define NOT_YET_AUTHENTICATED 0xFF + +typedef enum { + NXP_UNKNOWN = 0, + DESFIRE_MF3ICD40, + DESFIRE_EV1, + DESFIRE_EV2, + DESFIRE_EV2_XL, + DESFIRE_EV3, + DESFIRE_LIGHT, + PLUS_EV1, + PLUS_EV2, + NTAG413DNA, + NTAG424, + DUOX, +} nxp_cardtype_t; + +typedef struct { + uint8_t isOK; + uint8_t uid[7]; + uint8_t uidlen; + uint8_t versionHW[7]; + uint8_t versionSW[7]; + uint8_t details[14]; +} PACKED mfdes_info_res_t; + int CmdHFMFDes(const char *Cmd); /* @@ -29,24 +67,7 @@ int getKeySettings(uint8_t *aid); */ int desfire_print_signature(uint8_t *uid, uint8_t uidlen, uint8_t *signature, size_t signature_len); - -// Ev1 card limits -#define MAX_NUM_KEYS 0x0F -#define MAX_APPLICATION_COUNT 28 -#define MAX_FILE_COUNT 32 -#define MAX_FRAME_SIZE 60 -#define FRAME_PAYLOAD_SIZE (MAX_FRAME_SIZE - 5) - -// Ev2 card limits - -// Ev3 card limits - -// Light card limits - -// Light Ev1 card limits - -#define NOT_YET_AUTHENTICATED 0xFF - - +nxp_cardtype_t getCardType(uint8_t type, uint8_t major, uint8_t minor); +int mfdes_get_info(mfdes_info_res_t *info); #endif diff --git a/client/src/cmdhfmfp.c b/client/src/cmdhfmfp.c index 25f7f464e..d33286f46 100644 --- a/client/src/cmdhfmfp.c +++ b/client/src/cmdhfmfp.c @@ -17,6 +17,7 @@ //----------------------------------------------------------------------------- #include "cmdhfmfp.h" +#include "cmdhfmfdes.h" #include #include "cmdparser.h" // command_t #include "commonutil.h" // ARRAYLEN @@ -41,6 +42,7 @@ static const uint8_t mfp_default_key[16] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, static uint16_t mfp_card_adresses[] = {0x9000, 0x9001, 0x9002, 0x9003, 0x9004, 0x9006, 0x9007, 0xA000, 0xA001, 0xA080, 0xA081, 0xC000, 0xC001}; #define MFP_KEY_FILE_SIZE 14 + (2 * 64 * (AES_KEY_LEN + 1)) +#define MFP_CHK_KEY_TRIES (2) static int CmdHelp(const char *Cmd); @@ -141,54 +143,6 @@ static char *getTypeStr(uint8_t type) { return buf; } -static nxp_cardtype_t getCardType(uint8_t type, uint8_t major, uint8_t minor) { - - // DESFire MF3ICD40 - if (type == 0x01 && major == 0x00 && minor == 0x02) - return DESFIRE_MF3ICD40; - - // DESFire EV1 - if (type == 0x01 && major == 0x01 && minor == 0x00) - return DESFIRE_EV1; - - // DESFire EV2 - if (type == 0x01 && major == 0x12 && minor == 0x00) - return DESFIRE_EV2; - - if (type == 0x01 && major == 0x22 && minor == 0x00) - return DESFIRE_EV2_XL; - - // DESFire EV3 - if (type == 0x01 && major == 0x33 && minor == 0x00) - return DESFIRE_EV3; - - // DESFire Light - if (type == 0x08 && major == 0x30 && minor == 0x00) - return DESFIRE_LIGHT; - - // combo card DESFire / EMV - if (type == 0x81 && major == 0x42 && minor == 0x00) - return DESFIRE_EV2; - - // Plus EV1 - if (type == 0x02 && major == 0x11 && minor == 0x00) - return PLUS_EV1; - - // Plus Ev2 - if (type == 0x02 && major == 0x22 && minor == 0x00) - return PLUS_EV2; - - // NTAG 413 DNA - if (type == 0x04 && major == 0x10 && minor == 0x00) - return NTAG413DNA; - - // NTAG 424 - if (type == 0x04 && major == 0x30 && minor == 0x00) - return NTAG424; - - return MFP_UNKNOWN; -} - // --- GET SIGNATURE static int plus_print_signature(uint8_t *uid, uint8_t uidlen, uint8_t *signature, int signature_len) { int index = originality_check_verify(uid, uidlen, signature, signature_len, PK_MFP); @@ -259,6 +213,149 @@ static int get_plus_version(uint8_t *version, int *version_len) { return retval; } +static int mfp_read_card_id(iso14a_card_select_t *card, int *nxptype) { + + if (card == NULL) { + return PM3_EINVARG; + } + + clearCommandBuffer(); + SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT | ISO14A_NO_DISCONNECT, 0, 0, NULL, 0); + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { + PrintAndLogEx(DEBUG, "iso14443a card select failed"); + DropField(); + return PM3_ERFTRANS; + } + + memcpy(card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t)); + + if (nxptype) { + uint64_t select_status = resp.oldarg[0]; + + uint8_t ats_hist_pos = 0; + if ((card->ats_len > 3) && (card->ats[0] > 1)) { + ats_hist_pos = 2; + ats_hist_pos += (card->ats[1] & 0x10) == 0x10; + ats_hist_pos += (card->ats[1] & 0x20) == 0x20; + ats_hist_pos += (card->ats[1] & 0x40) == 0x40; + } + + version_hw_t version_hw = {0}; + // if 4b UID or NXP, try to get version + int res = hf14a_getversion_data(card, select_status, &version_hw); + DropField(); + + bool version_hw_available = (res == PM3_SUCCESS); + + *nxptype = detect_nxp_card(card->sak + , ((card->atqa[1] << 8) + card->atqa[0]) + , select_status + , card->ats_len - ats_hist_pos + , card->ats + ats_hist_pos + , version_hw_available + , &version_hw + ); + } + return PM3_SUCCESS; +} + +static int mfp_load_keygen_keys(uint8_t **pkeyBlock, uint32_t *pkeycnt, uint8_t *uid) { + + // Handle dymanica keys + + return PM3_SUCCESS; +} + +static int mfp_load_keys(uint8_t **pkeyBlock, uint32_t *pkeycnt, uint8_t *userkey, int userkeylen, const char *filename, int fnlen, uint8_t *uid, bool load_default) { + // Handle Keys + *pkeycnt = 0; + *pkeyBlock = NULL; + uint8_t *p; + + // Handle KDF uid based keys + if (uid) { + mfp_load_keygen_keys(pkeyBlock, pkeycnt, uid); + } + + // Handle user supplied key + // (it considers *pkeycnt and *pkeyBlock as possibly non-null so logic can be easily reordered) + if (userkeylen >= AES_KEY_LEN) { + int numKeys = userkeylen / AES_KEY_LEN; + p = realloc(*pkeyBlock, (*pkeycnt + numKeys) * AES_KEY_LEN); + if (p == NULL) { + PrintAndLogEx(WARNING, "Failed to allocate memory"); + free(*pkeyBlock); + return PM3_EMALLOC; + } + *pkeyBlock = p; + + memcpy(*pkeyBlock, userkey, numKeys * AES_KEY_LEN); + + for (int i = 0; i < numKeys; i++) { + PrintAndLogEx(DEBUG, _YELLOW_("%2d") " - %s", i, sprint_hex_inrow(*pkeyBlock + i * AES_KEY_LEN, AES_KEY_LEN)); + } + *pkeycnt += numKeys; + PrintAndLogEx(SUCCESS, "loaded " _GREEN_("%d") " user keys", numKeys); + } + + if (load_default) { + // Handle default keys + p = realloc(*pkeyBlock, (*pkeycnt + g_mifare_plus_default_keys_len) * AES_KEY_LEN); + if (p == NULL) { + PrintAndLogEx(WARNING, "Failed to allocate memory"); + free(*pkeyBlock); + return PM3_EMALLOC; + } + *pkeyBlock = p; + + // Copy default keys to list + int cnt = 0; + for (cnt = 0; cnt < g_mifare_plus_default_keys_len; cnt++) { + + int len = hex_to_bytes(g_mifare_plus_default_keys[cnt], (uint8_t *)(*pkeyBlock + (*pkeycnt + cnt) * AES_KEY_LEN), AES_KEY_LEN); + + PrintAndLogEx(DEBUG, _YELLOW_("%2d") " - %s", *pkeycnt + cnt, sprint_hex_inrow(*pkeyBlock + (*pkeycnt + cnt) * AES_KEY_LEN, AES_KEY_LEN)); + if (len != AES_KEY_LEN) { + break; + } + } + *pkeycnt += cnt; + PrintAndLogEx(SUCCESS, "loaded " _GREEN_("%zu") " hardcoded keys", cnt); + } + + // Handle user supplied dictionary file + if (fnlen > 0) { + + uint32_t loaded_numKeys = 0; + uint8_t *dict_keys = NULL; + + int res = loadFileDICTIONARY_safe(filename, (void **) &dict_keys, AES_KEY_LEN, &loaded_numKeys); + + if (res != PM3_SUCCESS || loaded_numKeys == 0 || dict_keys == NULL) { + PrintAndLogEx(FAILED, "An error occurred while loading the dictionary!"); + free(dict_keys); + free(*pkeyBlock); + return PM3_EFILE; + + } else { + + p = realloc(*pkeyBlock, (*pkeycnt + loaded_numKeys) * AES_KEY_LEN); + if (p == NULL) { + PrintAndLogEx(WARNING, "Failed to allocate memory"); + free(dict_keys); + free(*pkeyBlock); + return PM3_EMALLOC; + } + *pkeyBlock = p; + memcpy(*pkeyBlock + *pkeycnt * AES_KEY_LEN, dict_keys, loaded_numKeys * AES_KEY_LEN); + *pkeycnt += loaded_numKeys; + free(dict_keys); + } + } + return PM3_SUCCESS; +} + static int CmdHFMFPInfo(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf mfp info", @@ -730,29 +827,47 @@ static int CmdHFMFPAuth(const char *Cmd) { return MifareAuth4(NULL, keyn, key, true, false, true, verbose, false); } -static int data_crypt(mf4Session_t *mf4session, uint8_t *dati, uint8_t *dato, bool rev) { - uint8_t kenc[16]; - memcpy(kenc, mf4session->Kenc, 16); + +int mfp_data_crypt(mf4Session_t *mf4session, uint8_t *dati, uint8_t *dato, bool rev) { + uint8_t kenc[MFBLOCK_SIZE]; + memcpy(kenc, mf4session->Kenc, MFBLOCK_SIZE); + uint8_t ti[4]; memcpy(ti, mf4session->TI, 4); + uint8_t ctr[1]; - uint8_t IV[16] = {0, 0, 0x00, 0x00, 0x00, 0, 0x00, 0x00, 0x00, 0}; + uint8_t IV[MFBLOCK_SIZE] = { 0 }; + if (rev) { - ctr[0] = (uint8_t)(mf4session->R_Ctr & 0xff); - for (int i = 0; i < 9; i += 4) {memcpy(&IV[i], ctr, 1);} + + ctr[0] = (uint8_t)(mf4session->R_Ctr & 0xFF); + + for (int i = 0; i < 9; i += 4) { + memcpy(&IV[i], ctr, 1); + } + memcpy(&IV[12], ti, 4); // For reads TI is LS + } else { - ctr[0] = (uint8_t)(mf4session->W_Ctr & 0xff); - for (int i = 3; i < 16; i += 4) {memcpy(&IV[i], ctr, 1);} + + ctr[0] = (uint8_t)(mf4session->W_Ctr & 0xFF); + + for (int i = 3; i < MFBLOCK_SIZE; i += 4) { + memcpy(&IV[i], ctr, 1); + } + memcpy(&IV[0], ti, 4); // For writes TI is MS } + if (rev) { - aes_decode(IV, kenc, dati, dato, 16); + aes_decode(IV, kenc, dati, dato, MFBLOCK_SIZE); } else { - aes_encode(IV, kenc, dati, dato, 16); + aes_encode(IV, kenc, dati, dato, MFBLOCK_SIZE); } - return 0; + + return PM3_SUCCESS; } + static int CmdHFMFPRdbl(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf mfp rdbl", @@ -791,7 +906,7 @@ static int CmdHFMFPRdbl(const char *Cmd) { mfpSetVerboseMode(verbose); - if (!keylen) { + if (keylen == 0) { memmove(key, mfp_default_key, 16); keylen = 16; } @@ -820,8 +935,9 @@ static int CmdHFMFPRdbl(const char *Cmd) { uint16_t uKeyNum = 0x4000 + sectorNum * 2 + (keyB ? 1 : 0); keyn[0] = uKeyNum >> 8; keyn[1] = uKeyNum & 0xff; - if (verbose) + if (verbose) { PrintAndLogEx(INFO, "--block:%d sector[%u]:%02x key:%04x", blockn, mfNumBlocksPerSector(sectorNum), sectorNum, uKeyNum); + } mf4Session_t mf4session; int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false); @@ -849,7 +965,10 @@ static int CmdHFMFPRdbl(const char *Cmd) { return PM3_ESOFT; } - if (!plain) data_crypt(&mf4session, &data[1], &data[1], true); + if (plain == false) { + mfp_data_crypt(&mf4session, &data[1], &data[1], true); + } + uint8_t sector = mfSectorNum(blockn); mf_print_sector_hdr(sector); @@ -907,7 +1026,7 @@ static int CmdHFMFPRdsc(const char *Cmd) { mfpSetVerboseMode(verbose); - if (!keylen) { + if (keylen == 0) { memmove(key, mfp_default_key, 16); keylen = 16; } @@ -925,8 +1044,9 @@ static int CmdHFMFPRdsc(const char *Cmd) { uint16_t uKeyNum = 0x4000 + sectorNum * 2 + (keyB ? 1 : 0); keyn[0] = uKeyNum >> 8; keyn[1] = uKeyNum & 0xff; - if (verbose) + if (verbose) { PrintAndLogEx(INFO, "--sector[%u]:%02x key:%04x", mfNumBlocksPerSector(sectorNum), sectorNum, uKeyNum); + } mf4Session_t mf4session; int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false); @@ -961,7 +1081,11 @@ static int CmdHFMFPRdsc(const char *Cmd) { DropField(); return PM3_ESOFT; } - if (!plain) data_crypt(&mf4session, &data[1], &data[1], true); + + if (plain == false) { + mfp_data_crypt(&mf4session, &data[1], &data[1], true); + } + mf_print_block_one(blockno, data + 1, verbose); if (memcmp(&data[1 + 16], mac, 8) && !nomacres) { @@ -1043,8 +1167,9 @@ static int CmdHFMFPWrbl(const char *Cmd) { uint16_t uKeyNum = 0x4000 + sectorNum * 2 + (keyB ? 1 : 0); keyn[0] = uKeyNum >> 8; keyn[1] = uKeyNum & 0xff; - if (verbose) + if (verbose) { PrintAndLogEx(INFO, "--block:%d sector[%u]:%02x key:%04x", blockNum & 0xff, mfNumBlocksPerSector(sectorNum), sectorNum, uKeyNum); + } mf4Session_t mf4session; int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false); @@ -1052,7 +1177,11 @@ static int CmdHFMFPWrbl(const char *Cmd) { PrintAndLogEx(ERR, "Authentication error: %d", res); return res; } - if (!plain) data_crypt(&mf4session, &datain[0], &datain[0], false); + + if (plain == false) { + mfp_data_crypt(&mf4session, &datain[0], &datain[0], false); + } + uint8_t data[250] = {0}; int datalen = 0; uint8_t mac[8] = {0}; @@ -1146,24 +1275,35 @@ static int CmdHFMFPChKey(const char *Cmd) { PrintAndLogEx(ERR, " must be 16 bytes. Got %d", datainlen); return PM3_EINVARG; } + mf4Session_t mf4session; + keyn[0] = ki[0]; + if (ki[0] == 0x40) { // Only if we are working with sector keys + if (usekeyb) { keyn[1] = (ki[1] % 2 == 0) ? ki[1] + 1 : ki[1]; // If we change using key B, check if KI is key A } else { keyn[1] = (ki[1] % 2 == 0) ? ki[1] : ki[1] - 1; // If we change using key A, check if KI is key A } - } else {keyn[1] = ki[1];} + + } else { + keyn[1] = ki[1]; + } + if (verbose) { PrintAndLogEx(INFO, "--key index:", sprint_hex(keyn, 2)); } + int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false); if (res) { PrintAndLogEx(ERR, "Authentication error: %d", res); return res; } - data_crypt(&mf4session, &datain[0], &datain[0], false); + + mfp_data_crypt(&mf4session, &datain[0], &datain[0], false); + uint8_t data[250] = {0}; int datalen = 0; uint8_t mac[8] = {0}; @@ -1191,8 +1331,9 @@ static int CmdHFMFPChKey(const char *Cmd) { PrintAndLogEx(WARNING, "MAC card: %s", sprint_hex(&data[1], 8)); PrintAndLogEx(WARNING, "MAC reader: %s", sprint_hex(mac, 8)); } else if (!nomacres) { - if (verbose) + if (verbose) { PrintAndLogEx(INFO, "MAC: %s", sprint_hex(&data[1], 8)); + } } DropField(); @@ -1245,7 +1386,7 @@ static int CmdHFMFPChConf(const char *Cmd) { mfpSetVerboseMode(verbose); - if (!keylen) { + if (keylen == 0) { memmove(key, mfp_default_key, 16); keylen = 16; } @@ -1264,18 +1405,23 @@ static int CmdHFMFPChConf(const char *Cmd) { PrintAndLogEx(ERR, " must be in range [0..3]. Got %d", blockNum); return PM3_EINVARG; } + mf4Session_t mf4session; keyn[0] = 0x90; keyn[1] = usecck ? 0x01 : 0x00; + if (verbose) { PrintAndLogEx(INFO, "--key index:", sprint_hex(keyn, 2)); } + int res = MifareAuth4(&mf4session, keyn, key, true, true, true, verbose, false); if (res) { PrintAndLogEx(ERR, "Authentication error: %d", res); return res; } - data_crypt(&mf4session, &datain[0], &datain[0], false); + + mfp_data_crypt(&mf4session, &datain[0], &datain[0], false); + uint8_t data[250] = {0}; int datalen = 0; uint8_t mac[8] = {0}; @@ -1302,9 +1448,10 @@ static int CmdHFMFPChConf(const char *Cmd) { PrintAndLogEx(WARNING, "WARNING: mac not equal..."); PrintAndLogEx(WARNING, "MAC card: %s", sprint_hex(&data[1], 8)); PrintAndLogEx(WARNING, "MAC reader: %s", sprint_hex(mac, 8)); - } else if (!nomacres) { - if (verbose) + } else if (nomacres == false) { + if (verbose) { PrintAndLogEx(INFO, "MAC: %s", sprint_hex(&data[1], 8)); + } } DropField(); @@ -1312,19 +1459,33 @@ static int CmdHFMFPChConf(const char *Cmd) { return PM3_SUCCESS; } -static int plus_key_check(uint8_t startSector, uint8_t endSector, uint8_t startKeyAB, uint8_t endKeyAB, - uint8_t keyList[MAX_AES_KEYS_LIST_LEN][AES_KEY_LEN], size_t keyListLen, uint8_t foundKeys[2][64][AES_KEY_LEN + 1], - bool verbose) { - int res; - bool selectCard = true; - uint8_t keyn[2] = {0}; +static int plus_key_check(uint8_t start_sector, uint8_t end_sector, uint8_t startKeyAB, uint8_t endKeyAB, + uint8_t *keys, size_t keycount, uint8_t foundKeys[2][64][AES_KEY_LEN + 1], + bool verbose, bool newline) { + + if (newline) { + PrintAndLogEx(INFO, "." NOLF); + } // sector number from 0 - for (uint8_t sector = startSector; sector <= endSector; sector++) { + for (uint8_t sector = start_sector; sector <= end_sector; sector++) { + // 0-keyA 1-keyB for (uint8_t keyAB = startKeyAB; keyAB <= endKeyAB; keyAB++) { + + // skip already found keys + if (foundKeys[keyAB][sector][0]) { + continue; + } + + int res; + bool selectCard = true; + + // reset current key pointer after each loop + uint8_t *currkey = keys; + // main cycle with key check - for (int i = 0; i < keyListLen; i++) { + for (int i = 0; i < keycount; i++) { // allow client abort every iteration if (kbd_enter_pressed()) { @@ -1340,18 +1501,21 @@ static int plus_key_check(uint8_t startSector, uint8_t endSector, uint8_t startK } uint16_t uKeyNum = 0x4000 + sector * 2 + keyAB; - keyn[0] = uKeyNum >> 8; - keyn[1] = uKeyNum & 0xff; + uint8_t keyn[2] = { uKeyNum >> 8, uKeyNum & 0xff}; - for (int retry = 0; retry < 4; retry++) { - res = MifareAuth4(NULL, keyn, keyList[i], selectCard, true, false, false, true); - if (res == PM3_SUCCESS || res == PM3_EWRONGANSWER) + // authentication loop with retries + for (int retry = 0; retry < MFP_CHK_KEY_TRIES; retry++) { + + res = MifareAuth4(NULL, keyn, currkey, selectCard, true, false, false, true); + if (res == PM3_SUCCESS || res == PM3_EWRONGANSWER) { break; + } - if (verbose) + if (verbose) { PrintAndLogEx(WARNING, "\nretried[%d]...", retry); - else + } else { PrintAndLogEx(NORMAL, "R" NOLF); + } DropField(); selectCard = true; @@ -1360,38 +1524,52 @@ static int plus_key_check(uint8_t startSector, uint8_t endSector, uint8_t startK // key for [sector,keyAB] found if (res == PM3_SUCCESS) { - if (verbose) - PrintAndLogEx(INFO, "\nFound key for sector %d key %s [%s]", sector, keyAB == 0 ? "A" : "B", sprint_hex_inrow(keyList[i], 16)); - else - PrintAndLogEx(NORMAL, "+" NOLF); + + if (verbose) { + PrintAndLogEx(INFO, "Found key for sector " _YELLOW_("%d") " key "_YELLOW_("%s") " [ " _GREEN_("%s") " ]", sector, (keyAB == 0) ? "A" : "B", sprint_hex_inrow(currkey, AES_KEY_LEN)); + } else { + PrintAndLogEx(NORMAL, _GREEN_("+") NOLF); + } foundKeys[keyAB][sector][0] = 0x01; - memcpy(&foundKeys[keyAB][sector][1], keyList[i], AES_KEY_LEN); + memcpy(&foundKeys[keyAB][sector][1], currkey, AES_KEY_LEN); + DropField(); selectCard = true; - msleep(50); +// msleep(50); + + // recursive test of a successful key + if (keycount > 1) { + plus_key_check(start_sector, end_sector, startKeyAB, endKeyAB, currkey, 1, foundKeys, verbose, false); + } // break out from keylist check loop, break; } - if (verbose) - PrintAndLogEx(WARNING, "\nsector %02d key %d [%s] res: %d", sector, keyAB, sprint_hex_inrow(keyList[i], 16), res); + if (verbose) { + PrintAndLogEx(WARNING, "\nsector %02d key %d [%s] res: %d", sector, keyAB, sprint_hex_inrow(currkey, AES_KEY_LEN), res); + } // RES can be: // PM3_ERFTRANS -7 // PM3_EWRONGANSWER -16 if (res == PM3_ERFTRANS) { - if (verbose) + + if (verbose) { PrintAndLogEx(ERR, "\nExchange error. Aborted."); - else + } else { PrintAndLogEx(NORMAL, "E" NOLF); + } DropField(); return PM3_ECARDEXCHANGE; } selectCard = false; + + // set pointer to next key + currkey += AES_KEY_LEN; } } } @@ -1400,18 +1578,31 @@ static int plus_key_check(uint8_t startSector, uint8_t endSector, uint8_t startK return PM3_SUCCESS; } -static void Fill2bPattern(uint8_t keyList[MAX_AES_KEYS_LIST_LEN][AES_KEY_LEN], uint32_t *keyListLen, uint32_t *startPattern) { +static void Fill2bPattern(uint8_t keyList[MAX_AES_KEYS_LIST_LEN][AES_KEY_LEN], uint32_t *n, uint32_t *startPattern) { + + uint32_t cnt = 0; for (uint32_t pt = *startPattern; pt < 0x10000; pt++) { - keyList[*keyListLen][0] = (pt >> 8) & 0xff; - keyList[*keyListLen][1] = pt & 0xff; - memcpy(&keyList[*keyListLen][2], &keyList[*keyListLen][0], 2); - memcpy(&keyList[*keyListLen][4], &keyList[*keyListLen][0], 4); - memcpy(&keyList[*keyListLen][8], &keyList[*keyListLen][0], 8); - (*keyListLen)++; + + for (uint8_t i = 0; i < AES_KEY_LEN; i += 2) { + keyList[*n][i] = (pt >> 8) & 0xff; + keyList[*n][i + 1] = pt & 0xff; + } + + PrintAndLogEx(DEBUG, _YELLOW_("%4d") " - %s", *n, sprint_hex_inrow(keyList[*n], AES_KEY_LEN)); + + // increase number of keys + (*n)++; + cnt++; + *startPattern = pt; - if (*keyListLen == MAX_AES_KEYS_LIST_LEN) + + if (*n == MAX_AES_KEYS_LIST_LEN) { break; + } } + + PrintAndLogEx(SUCCESS, "loaded " _GREEN_("%d") " pattern2 keys", cnt); + (*startPattern)++; } @@ -1422,7 +1613,7 @@ static int CmdHFMFPChk(const char *Cmd) { "Checks keys on MIFARE Plus card", "hf mfp chk -k 000102030405060708090a0b0c0d0e0f -> check key on sector 0 as key A and B\n" "hf mfp chk -s 2 -a -> check default key list on sector 2, only key A\n" - "hf mfp chk -f mfp_default_keys -s0 -e6 -> check keys from dictionary against sectors 0-6\n" + "hf mfp chk -f mfp_default_keys -s 0 -e 6 -> check keys from dictionary against sectors 0-6\n" "hf mfp chk --pattern1b --dump -> check all 1-byte keys pattern and save found keys to file\n" "hf mfp chk --pattern2b --startp2b FA00 -> check all 2-byte keys pattern. Start from key FA00FA00...FA00" ); @@ -1439,6 +1630,7 @@ static int CmdHFMFPChk(const char *Cmd) { arg_lit0(NULL, "pattern2b", "Check all 2-byte combinations of key (0000...0000, 0001...0001, 0002...0002, ...)"), arg_str0(NULL, "startp2b", "", "Start key (2-byte HEX) for 2-byte search (use with `--pattern2b`)"), arg_lit0(NULL, "dump", "Dump found keys to JSON file"), + arg_lit0(NULL, "no-default", "Skip check default keys"), arg_lit0("v", "verbose", "Verbose output"), arg_param_end }; @@ -1446,70 +1638,71 @@ static int CmdHFMFPChk(const char *Cmd) { bool keyA = arg_get_lit(ctx, 1); bool keyB = arg_get_lit(ctx, 2); + uint8_t startSector = arg_get_int_def(ctx, 3, 0); uint8_t endSector = arg_get_int_def(ctx, 4, 0); - uint8_t keyList[MAX_AES_KEYS_LIST_LEN][AES_KEY_LEN] = {{0}}; uint32_t keyListLen = 0; + uint8_t keyList[MAX_AES_KEYS_LIST_LEN][AES_KEY_LEN] = {{0}}; uint8_t foundKeys[2][64][AES_KEY_LEN + 1] = {{{0}}}; - uint8_t vkey[16] = {0}; int vkeylen = 0; + uint8_t vkey[AES_KEY_LEN] = {0}; CLIGetHexWithReturn(ctx, 5, vkey, &vkeylen); - if (vkeylen > 0) { - if (vkeylen == 16) { - memcpy(&keyList[keyListLen], vkey, 16); - keyListLen++; - } else { - PrintAndLogEx(ERR, "Specified key must have 16 bytes. Got %d", vkeylen); - CLIParserFree(ctx); - return PM3_EINVARG; - } - } - uint8_t dict_filename[FILE_PATH_SIZE + 2] = {0}; - int dict_filenamelen = 0; - if (CLIParamStrToBuf(arg_get_str(ctx, 6), dict_filename, FILE_PATH_SIZE, &dict_filenamelen)) { - PrintAndLogEx(FAILED, "File name too long or invalid."); - CLIParserFree(ctx); - return PM3_EINVARG; - } + int fnlen = 0; + char filename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); bool pattern1b = arg_get_lit(ctx, 7); bool pattern2b = arg_get_lit(ctx, 8); - if (pattern1b && pattern2b) { - PrintAndLogEx(ERR, "Pattern search mode must be 2-byte or 1-byte only."); - CLIParserFree(ctx); - return PM3_EINVARG; - } - - if (dict_filenamelen && (pattern1b || pattern2b)) { - PrintAndLogEx(ERR, "Pattern search mode and dictionary mode can't be used in one command."); - CLIParserFree(ctx); - return PM3_EINVARG; - } - - uint32_t startPattern = 0x0000; - uint8_t vpattern[2]; int vpatternlen = 0; + uint8_t vpattern[2]; CLIGetHexWithReturn(ctx, 9, vpattern, &vpatternlen); - if (vpatternlen > 0) { - if (vpatternlen <= 2) { - startPattern = (vpattern[0] << 8) + vpattern[1]; - } else { - PrintAndLogEx(ERR, "Pattern must be 2-bytes. Got %d", vpatternlen); - CLIParserFree(ctx); - return PM3_EINVARG; - } - if (!pattern2b) - PrintAndLogEx(WARNING, "Pattern entered, but search mode not is 2-byte search."); - } bool create_dumpfile = arg_get_lit(ctx, 10); - bool verbose = arg_get_lit(ctx, 11); + bool load_default = ! arg_get_lit(ctx, 11); + bool verbose = arg_get_lit(ctx, 12); + CLIParserFree(ctx); + // sanity checks + if (vkeylen && (vkeylen != AES_KEY_LEN)) { + PrintAndLogEx(ERR, "Specified key must have 16 bytes. Got %d", vkeylen); + return PM3_EINVARG; + } + + if (pattern1b && pattern2b) { + PrintAndLogEx(ERR, "Pattern search mode must be 2-byte or 1-byte only"); + return PM3_EINVARG; + } + + if (fnlen && (pattern1b || pattern2b)) { + PrintAndLogEx(ERR, "Pattern search mode and dictionary mode can't be used in one command"); + return PM3_EINVARG; + } + + if (vpatternlen && pattern2b == false) { + PrintAndLogEx(WARNING, "Pattern entered, but search mode not is 2-byte search"); + return PM3_EINVARG; + } + + if (vpatternlen > 2) { + PrintAndLogEx(ERR, "Pattern must be 2-bytes. Got %d", vpatternlen); + return PM3_EINVARG; + } + + uint32_t startPattern = (vpattern[0] << 8) + vpattern[1]; + + // read card UID + iso14a_card_select_t card; + int nxptype = MTNONE; + int res = mfp_read_card_id(&card, &nxptype); + if (res != PM3_SUCCESS) { + return res; + } + uint8_t startKeyAB = 0; uint8_t endKeyAB = 1; if (keyA && (keyB == false)) { @@ -1524,14 +1717,38 @@ static int CmdHFMFPChk(const char *Cmd) { endSector = startSector; } + // Print generic information + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Check keys") " ----------"); + PrintAndLogEx(INFO, "Start sector... %u", startSector); + PrintAndLogEx(INFO, "End sector..... %u", endSector); + + char keytypestr[6] = {0}; + if (keyA == false && keyB == false) { + strcat(keytypestr, "AB"); + } + if (keyA) { + strcat(keytypestr, "A"); + } + if (keyB) { + strcat(keytypestr, "B"); + } + PrintAndLogEx(INFO, "Key type....... " _YELLOW_("%s"), keytypestr); + PrintAndLogEx(NORMAL, ""); + + // + // Key creation section + // // 1-byte pattern search mode if (pattern1b) { for (int i = 0; i < 0x100; i++) { memset(keyList[i], i, 16); + PrintAndLogEx(DEBUG, _YELLOW_("%3d") " - %s", i, sprint_hex_inrow(keyList[i], AES_KEY_LEN)); } keyListLen = 0x100; + PrintAndLogEx(SUCCESS, "loaded " _GREEN_("%d") " pattern1b keys", 0x100); } // 2-byte pattern search mode @@ -1539,82 +1756,55 @@ static int CmdHFMFPChk(const char *Cmd) { Fill2bPattern(keyList, &keyListLen, &startPattern); } - int res = PM3_SUCCESS; - // dictionary mode - size_t endFilePosition = 0; - if (dict_filenamelen) { + uint8_t *key_block = NULL; + uint32_t keycnt = 0; - uint32_t keycnt = 0; - - res = loadFileDICTIONARYEx((char *)dict_filename, keyList, sizeof(keyList), NULL, 16, &keycnt, 0, &endFilePosition, true); - if (res == PM3_SUCCESS && endFilePosition) { - keyListLen = keycnt; - PrintAndLogEx(SUCCESS, "First part of dictionary successfully loaded."); - } + int ret = mfp_load_keys(&key_block, &keycnt, vkey, vkeylen, filename, fnlen, card.uid, load_default); + if (ret != PM3_SUCCESS) { + return ret; } - if (keyListLen == 0) { - for (int i = 0; i < g_mifare_plus_default_keys_len; i++) { - if (hex_to_bytes(g_mifare_plus_default_keys[i], keyList[keyListLen], 16) != 16) { - break; - } + PrintAndLogEx(INFO, "Start check for keys..."); - keyListLen++; - } + // time + uint64_t t1 = msclock(); + + res = plus_key_check(startSector, endSector, startKeyAB, endKeyAB, key_block, keycnt, foundKeys, verbose, true); + if (res == PM3_EOPABORTED) { + t1 = msclock() - t1; + PrintAndLogEx(INFO, "\ntime in checkkeys " _YELLOW_("%.0f") " seconds\n", (float)t1 / 1000.0); + return res; } - if (keyListLen == 0) { - PrintAndLogEx(ERR, "Key list is empty. Nothing to check."); - return PM3_EINVARG; - } else { - PrintAndLogEx(INFO, "Loaded " _YELLOW_("%"PRIu32) " keys", keyListLen); - } + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "Start check for pattern based keys..."); + while (keyListLen) { - if (verbose == false) { - PrintAndLogEx(INFO, "Search keys"); - } - - while (true) { - res = plus_key_check(startSector, endSector, startKeyAB, endKeyAB, keyList, keyListLen, foundKeys, verbose); + res = plus_key_check(startSector, endSector, startKeyAB, endKeyAB, (uint8_t *)keyList, keyListLen, foundKeys, verbose, true); if (res == PM3_EOPABORTED) { break; } if (pattern2b && startPattern < 0x10000) { - if (verbose == false) { - PrintAndLogEx(NORMAL, "p" NOLF); - } - keyListLen = 0; + PrintAndLogEx(NORMAL, ""); Fill2bPattern(keyList, &keyListLen, &startPattern); continue; } - - if (dict_filenamelen && endFilePosition) { - if (verbose == false) - PrintAndLogEx(NORMAL, "d" NOLF); - - uint32_t keycnt = 0; - res = loadFileDICTIONARYEx((char *)dict_filename, keyList, sizeof(keyList), NULL, 16, &keycnt, endFilePosition, &endFilePosition, false); - if (res == PM3_SUCCESS && endFilePosition) { - keyListLen = keycnt; - } - - continue; - } break; } - if (verbose == false) { - PrintAndLogEx(NORMAL, ""); - } + t1 = msclock() - t1; + PrintAndLogEx(INFO, "\ntime in checkkeys " _YELLOW_("%.0f") " seconds\n", (float)t1 / 1000.0); + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, _GREEN_("found keys:")); // print result char strA[46 + 1] = {0}; char strB[46 + 1] = {0}; - bool has_ndef_key = false; bool printedHeader = false; for (uint8_t s = startSector; s <= endSector; s++) { @@ -1662,29 +1852,12 @@ static int CmdHFMFPChk(const char *Cmd) { uint8_t data[10 + 1 + 2 + 1 + 256 + keys_len]; memset(data, 0, sizeof(data)); - // Mifare Plus info - SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_CONNECT, 0, 0, NULL, 0); - - PacketResponseNG resp; - if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply"); - return PM3_ETIMEOUT; - } - - iso14a_card_select_t card; - memcpy(&card, (iso14a_card_select_t *)resp.data.asBytes, sizeof(iso14a_card_select_t)); - - uint64_t select_status = resp.oldarg[0]; // 0: couldn't read, 1: OK, with ATS, 2: OK, no ATS, 3: proprietary Anticollision - uint8_t atslen = 0; - if (select_status == 1 || select_status == 2) { - memcpy(data, card.uid, card.uidlen); - data[10] = card.sak; - data[11] = card.atqa[1]; - data[12] = card.atqa[0]; - atslen = card.ats_len; - data[13] = atslen; - memcpy(&data[14], card.ats, atslen); - } + memcpy(data, card.uid, card.uidlen); + data[10] = card.sak; + data[11] = card.atqa[1]; + data[12] = card.atqa[0]; + data[13] = card.ats_len; + memcpy(&data[14], card.ats, card.ats_len); char *fptr = calloc(sizeof(char) * (strlen("hf-mfp-") + strlen("-key")) + card.uidlen * 2 + 1, sizeof(uint8_t)); if (fptr == NULL) { @@ -1696,7 +1869,7 @@ static int CmdHFMFPChk(const char *Cmd) { FillFileNameByUID(fptr, card.uid, "-key", card.uidlen); // length: UID(10b)+SAK(1b)+ATQA(2b)+ATSlen(1b)+ATS(atslen)+foundKeys[2][64][AES_KEY_LEN + 1] - memcpy(&data[14 + atslen], foundKeys, keys_len); + memcpy(&data[14 + card.ats_len], foundKeys, keys_len); // 64 here is for how many "rows" there is in the data array. A bit confusing saveFileJSON(fptr, jsfMfPlusKeys, data, 64, NULL); free(fptr); diff --git a/client/src/cmdhfmfp.h b/client/src/cmdhfmfp.h index f6189fadf..377525c03 100644 --- a/client/src/cmdhfmfp.h +++ b/client/src/cmdhfmfp.h @@ -19,20 +19,7 @@ #define CMDHFMFP_H__ #include "common.h" - -typedef enum { - MFP_UNKNOWN = 0, - DESFIRE_MF3ICD40, - DESFIRE_EV1, - DESFIRE_EV2, - DESFIRE_EV2_XL, - DESFIRE_EV3, - DESFIRE_LIGHT, - PLUS_EV1, - PLUS_EV2, - NTAG413DNA, - NTAG424 -} nxp_cardtype_t; +#include "mifare/mifare4.h" typedef struct mfp_key_item { uint8_t a[16]; @@ -46,5 +33,5 @@ typedef struct mfp_keys { int CmdHFMFP(const char *Cmd); int CmdHFMFPNDEFRead(const char *Cmd); - +int mfp_data_crypt(mf4Session_t *mf4session, uint8_t *dati, uint8_t *dato, bool rev); #endif diff --git a/client/src/fileutils.c b/client/src/fileutils.c index f28b2c165..cf9822848 100644 --- a/client/src/fileutils.c +++ b/client/src/fileutils.c @@ -2221,6 +2221,8 @@ int loadFileDICTIONARY(const char *preferredName, void *data, size_t *datalen, u return loadFileDICTIONARYEx(preferredName, data, 0, datalen, keylen, keycnt, 0, NULL, true); } +// this function handles exceptional large dictionaries, +// using start position and end position parameters. int loadFileDICTIONARYEx(const char *preferredName, void *data, size_t maxdatalen, size_t *datalen, uint8_t keylen, uint32_t *keycnt, size_t startFilePosition, size_t *endFilePosition, bool verbose) { @@ -2246,17 +2248,17 @@ int loadFileDICTIONARYEx(const char *preferredName, void *data, size_t maxdatale int retval = PM3_SUCCESS; FILE *f = fopen(path, "r"); - if (!f) { + if (f == NULL) { PrintAndLogEx(WARNING, "file not found or locked `" _YELLOW_("%s") "`", path); - retval = PM3_EFILE; - goto out; + free(path); + return PM3_EFILE; } if (startFilePosition) { if (fseek(f, startFilePosition, SEEK_SET) < 0) { fclose(f); - retval = PM3_EFILE; - goto out; + free(path); + return PM3_EFILE; } } @@ -2264,6 +2266,7 @@ int loadFileDICTIONARYEx(const char *preferredName, void *data, size_t maxdatale // read file while (!feof(f)) { + long filepos = ftell(f); if (!fgets(line, sizeof(line), f)) { @@ -2321,7 +2324,7 @@ int loadFileDICTIONARYEx(const char *preferredName, void *data, size_t maxdatale if (keycnt) { *keycnt = vkeycnt; } -out: + free(path); return retval; } @@ -2346,11 +2349,10 @@ int loadFileDICTIONARY_safe_ex(const char *preferredName, const char *suffix, vo // mf desfire == 3des3k 24 bytes // iclass == 8 bytes // default to 6 bytes. - if (keylen != 4 && keylen != 5 && keylen != 6 && keylen != 8 && keylen != 16 && keylen != 24) { + if (keylen != 4 && keylen != 5 && keylen != 6 && keylen != 8 && keylen != 12 && keylen != 16 && keylen != 24) { keylen = 6; } - size_t mem_size; size_t block_size = 10 * keylen; // double up since its chars @@ -2365,10 +2367,10 @@ int loadFileDICTIONARY_safe_ex(const char *preferredName, const char *suffix, vo free(path); return PM3_EFILE; } - mem_size = block_size; + size_t mem_size = block_size; FILE *f = fopen(path, "r"); - if (!f) { + if (f == NULL) { PrintAndLogEx(WARNING, "file not found or locked `" _YELLOW_("%s") "`", path); retval = PM3_EFILE; goto out; @@ -2400,6 +2402,7 @@ int loadFileDICTIONARY_safe_ex(const char *preferredName, const char *suffix, vo // remove newline/linefeed str_cleanrn(line, strlen(line)); + str_trim(line); // smaller keys than expected is skipped if (strlen(line) < keylen) { @@ -2417,6 +2420,7 @@ int loadFileDICTIONARY_safe_ex(const char *preferredName, const char *suffix, vo // larger keys than expected is skipped if (strlen(line) > keylen) { + PrintAndLogEx(INFO, "too long line (%zu) ... %s", strlen(line), line); continue; } diff --git a/doc/commands.json b/doc/commands.json index b464227de..24649fa41 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -6806,7 +6806,7 @@ "notes": [ "hf mfp chk -k 000102030405060708090a0b0c0d0e0f -> check key on sector 0 as key A and B", "hf mfp chk -s 2 -a -> check default key list on sector 2, only key A", - "hf mfp chk -f mfp_default_keys -s0 -e6 -> check keys from dictionary against sectors 0-6", + "hf mfp chk -f mfp_default_keys -s 0 -e 6 -> check keys from dictionary against sectors 0-6", "hf mfp chk --pattern1b --dump -> check all 1-byte keys pattern and save found keys to file", "hf mfp chk --pattern2b --startp2b FA00 -> check all 2-byte keys pattern. Start from key FA00FA00...FA00" ], @@ -6823,9 +6823,10 @@ "--pattern2b Check all 2-byte combinations of key (0000...0000, 0001...0001, 0002...0002, ...)", "--startp2b Start key (2-byte HEX) for 2-byte search (use with `--pattern2b`)", "--dump Dump found keys to JSON file", + "--no-default Skip check default keys", "-v, --verbose Verbose output" ], - "usage": "hf mfp chk [-habv] [-s <0..255>] [-e <0..255>] [-k ] [-f ] [--pattern1b] [--pattern2b] [--startp2b ] [--dump]" + "usage": "hf mfp chk [-habv] [-s <0..255>] [-e <0..255>] [-k ] [-f ] [--pattern1b] [--pattern2b] [--startp2b ] [--dump] [--no-default]" }, "hf mfp chkey": { "command": "hf mfp chkey", @@ -13376,6 +13377,6 @@ "metadata": { "commands_extracted": 768, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-06-08T19:44:35" + "extracted_on": "2025-06-09T12:58:22" } }