From fded3e9cefb1dfd7d6c2433dff76ae536c53cbdb Mon Sep 17 00:00:00 2001 From: Tomas Nilsson Date: Sat, 7 Mar 2026 15:23:48 +0100 Subject: [PATCH 1/6] Implement 'hf mfp dump' command with SL1/SL3 mixed mode support --- client/src/cmdhfmfp.c | 555 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 511 insertions(+), 44 deletions(-) diff --git a/client/src/cmdhfmfp.c b/client/src/cmdhfmfp.c index b72279e41..a2a21b3a6 100644 --- a/client/src/cmdhfmfp.c +++ b/client/src/cmdhfmfp.c @@ -35,8 +35,10 @@ #include "protocols.h" #include "crypto/libpcrypto.h" #include "cmdhfmf.h" // printblock, header +#include "mifare/mifarehost.h" // mf_read_sector (SL1 CRYPTO1) #include "cmdtrace.h" #include "crypto/originality.h" +#include "jansson.h" static const uint8_t mfp_default_key[16] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 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}; @@ -1889,20 +1891,165 @@ static int CmdHFMFPChk(const char *Cmd) { return PM3_SUCCESS; } +static int mfp_load_keys_from_json(const char *filename, uint8_t foundKeys[2][64][AES_KEY_LEN + 1]) { + + json_t *root = NULL; + int res = loadFileJSONroot(filename, (void **)&root, true); + if (res != PM3_SUCCESS) { + return res; + } + + // check file type + json_t *jtype = json_object_get(root, "FileType"); + if (!jtype || !json_is_string(jtype) || strcmp(json_string_value(jtype), "mfpkeys") != 0) { + PrintAndLogEx(ERR, "Key file is not a MIFARE Plus key file"); + json_decref(root); + return PM3_EFILE; + } + + char path[64]; + uint8_t tmpkey[AES_KEY_LEN]; + size_t tmplen = 0; + + for (int i = 0; i < 64; i++) { + snprintf(path, sizeof(path), "$.SectorKeys.%d.KeyA", i); + tmplen = 0; + if (JsonLoadBufAsHex(root, path, tmpkey, AES_KEY_LEN, &tmplen) == 0 && tmplen == AES_KEY_LEN) { + foundKeys[0][i][0] = 1; + memcpy(&foundKeys[0][i][1], tmpkey, AES_KEY_LEN); + } + + snprintf(path, sizeof(path), "$.SectorKeys.%d.KeyB", i); + tmplen = 0; + if (JsonLoadBufAsHex(root, path, tmpkey, AES_KEY_LEN, &tmplen) == 0 && tmplen == AES_KEY_LEN) { + foundKeys[1][i][0] = 1; + memcpy(&foundKeys[1][i][1], tmpkey, AES_KEY_LEN); + } + } + + json_decref(root); + return PM3_SUCCESS; +} + +// Security level for each sector +#define MFP_SL_UNKNOWN 0 +#define MFP_SL_1 1 +#define MFP_SL_3 3 + +// Load MFC (CRYPTO1) keys from a binary key file (first half keyA, second half keyB) +static int mfp_load_mfc_keys_from_bin(const char *filename, uint8_t mfcFoundKeys[2][64][MIFARE_KEY_SIZE + 1], uint8_t numSectors) { + + uint8_t *keyA = NULL; + uint8_t *keyB = NULL; + size_t alen = 0, blen = 0; + + int res = loadFileBinaryKey(filename, "", (void **)&keyA, (void **)&keyB, &alen, &blen, true); + if (res != PM3_SUCCESS) { + return res; + } + + for (uint8_t s = 0; s < numSectors && s * MIFARE_KEY_SIZE < alen; s++) { + mfcFoundKeys[0][s][0] = 1; + memcpy(&mfcFoundKeys[0][s][1], keyA + s * MIFARE_KEY_SIZE, MIFARE_KEY_SIZE); + } + + for (uint8_t s = 0; s < numSectors && s * MIFARE_KEY_SIZE < blen; s++) { + mfcFoundKeys[1][s][0] = 1; + memcpy(&mfcFoundKeys[1][s][1], keyB + s * MIFARE_KEY_SIZE, MIFARE_KEY_SIZE); + } + + free(keyA); + free(keyB); + return PM3_SUCCESS; +} + +// Try to read a sector using SL1 (CRYPTO1) with the given 6-byte key +static int mfp_read_sector_sl1(uint8_t sectorNo, uint8_t keyType, const uint8_t *key6, uint8_t *dataout, bool verbose) { + int res = mf_read_sector(sectorNo, keyType, key6, dataout); + if (verbose && res != PM3_SUCCESS) { + PrintAndLogEx(DEBUG, "SL1 read sector %u keyType %u failed: %d", sectorNo, keyType, res); + } + return res; +} + +// Build a list of default MFC 6-byte keys for SL1 probing +static int mfp_load_mfc_default_keys(uint8_t **pkeyBlock, uint32_t *pkeycnt) { + size_t numDefaults = ARRAYLEN(g_mifare_default_keys); + *pkeyBlock = calloc(numDefaults, MIFARE_KEY_SIZE); + if (*pkeyBlock == NULL) { + return PM3_EMALLOC; + } + + for (size_t i = 0; i < numDefaults; i++) { + num_to_bytes(g_mifare_default_keys[i], MIFARE_KEY_SIZE, *pkeyBlock + i * MIFARE_KEY_SIZE); + } + *pkeycnt = numDefaults; + return PM3_SUCCESS; +} + +// Probe MFC (CRYPTO1) keys against sectors that still need keys +static int mfp_sl1_key_check(uint8_t numSectors, uint8_t *keys, uint32_t keycnt, + uint8_t mfcFoundKeys[2][64][MIFARE_KEY_SIZE + 1], bool verbose) { + + for (uint8_t s = 0; s < numSectors; s++) { + for (uint8_t kt = 0; kt < 2; kt++) { + if (mfcFoundKeys[kt][s][0]) { + continue; + } + + if (kbd_enter_pressed()) { + return PM3_EOPABORTED; + } + + uint8_t blockNo = mfFirstBlockOfSector(s); + + for (uint32_t i = 0; i < keycnt; i++) { + uint64_t found_key = 0; + int res = mf_check_keys(blockNo, kt, true, 1, keys + i * MIFARE_KEY_SIZE, &found_key); + if (res == PM3_SUCCESS) { + mfcFoundKeys[kt][s][0] = 1; + num_to_bytes(found_key, MIFARE_KEY_SIZE, &mfcFoundKeys[kt][s][1]); + + if (verbose) { + PrintAndLogEx(INFO, "SL1 key found: sector %u key%s [ " _GREEN_("%s") " ]", + s, (kt == 0) ? "A" : "B", + sprint_hex_inrow(&mfcFoundKeys[kt][s][1], MIFARE_KEY_SIZE)); + } else { + PrintAndLogEx(NORMAL, "+" NOLF); + } + break; + } + } + } + } + return PM3_SUCCESS; +} + static int CmdHFMFPDump(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf mfp dump", "Dump MIFARE Plus tag to file (bin/json)\n" + "Supports both SL3 (AES) and SL1 (CRYPTO1) sectors.\n" + "Automatically detects sector security level.\n" "If no given, UID will be used as filename", "hf mfp dump\n" - "hf mfp dump --keys hf-mf-066C8B78-key.bin --> MIFARE Plus with keys from specified file\n"); + "hf mfp dump --keys hf-mfp-01020304-key.json\n" + "hf mfp dump -k ffffffffffffffffffffffffffffffff\n" + "hf mfp dump --dict mfp_default_keys\n" + "hf mfp dump --keys hf-mfp-01020304-key.json --mfc-keys hf-mf-01020304-key.bin\n" + "hf mfp dump --keys hf-mfp-01020304-key.json --mfc-dict mfc_default_keys\n"); void *argtable[] = { arg_param_begin, - arg_str0("f", "file", "", "Specify a filename for dump file"), - arg_str0("k", "keys", "", "Specify a filename for keys file"), -// arg_lit0(NULL, "ns", "no save to file"), -// arg_lit0("v", "verbose", "Verbose output"), + arg_str0("f", "file", "", "Specify a filename for dump file"), + arg_str0(NULL, "keys", "", "AES key file from `hf mfp chk --dump` (JSON)"), + arg_str0("k", "key", "", "AES key for all sectors (16 hex bytes)"), + arg_str0(NULL, "dict", "", "AES dictionary file"), + arg_str0(NULL, "mfc-keys", "", "MFC key file for SL1 sectors (.bin from `hf mf chk`)"), + arg_str0(NULL, "mfc-dict", "", "MFC dictionary file for SL1 sectors"), + arg_lit0(NULL, "ns", "No save to file"), + arg_lit0("v", "verbose", "Verbose output"), + arg_lit0(NULL, "no-default", "Skip default key probing for unknown sectors"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); @@ -1915,53 +2062,373 @@ static int CmdHFMFPDump(const char *Cmd) { char key_fn[FILE_PATH_SIZE] = {0}; CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)key_fn, FILE_PATH_SIZE, &keyfnlen); -// bool nosave = arg_get_lit(ctx, 3); -// bool verbose = arg_get_lit(ctx, 4); + int userkeylen = 0; + uint8_t userkey[AES_KEY_LEN] = {0}; + CLIGetHexWithReturn(ctx, 3, userkey, &userkeylen); + + int dictfnlen = 0; + char dict_fn[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)dict_fn, FILE_PATH_SIZE, &dictfnlen); + + int mfckeyfnlen = 0; + char mfc_key_fn[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 5), (uint8_t *)mfc_key_fn, FILE_PATH_SIZE, &mfckeyfnlen); + + int mfcdictfnlen = 0; + char mfc_dict_fn[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)mfc_dict_fn, FILE_PATH_SIZE, &mfcdictfnlen); + + bool nosave = arg_get_lit(ctx, 7); + bool verbose = arg_get_lit(ctx, 8); + bool no_default = arg_get_lit(ctx, 9); + CLIParserFree(ctx); - PrintAndLogEx(INFO, " To be implemented, feel free to contribute!"); - return PM3_ENOTIMPL; + if (userkeylen > 0 && userkeylen != AES_KEY_LEN) { + PrintAndLogEx(ERR, "AES key must be 16 bytes. Got %d", userkeylen); + return PM3_EINVARG; + } - /* - mfpSetVerboseMode(verbose); + mfpSetVerboseMode(verbose); - // read card - uint8_t *mem = calloc(MIFARE_4K_MAXBLOCK * MFBLOCK_SIZE, sizeof(uint8_t)); - if (mem == NULL) { - PrintAndLogEx(WARNING, "Failed to allocate memory"); + // read card info + iso14a_card_select_t card; + int nxptype = MTNONE; + int res = mfp_read_card_id(&card, &nxptype); + if (res != PM3_SUCCESS) { + PrintAndLogEx(ERR, "Failed to select card"); + return res; + } + + // determine number of sectors from ATQA + uint16_t ATQA = card.atqa[0] + (card.atqa[1] << 8); + uint8_t numSectors; + if (ATQA & 0x0002) { + numSectors = MIFARE_4K_MAXSECTOR; // 40 sectors (4K) + } else { + numSectors = MIFARE_2K_MAXSECTOR; // 32 sectors (2K) + } + + PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); + PrintAndLogEx(INFO, "UID......... " _GREEN_("%s"), sprint_hex(card.uid, card.uidlen)); + PrintAndLogEx(INFO, "ATQA........ " _GREEN_("%02X %02X"), card.atqa[1], card.atqa[0]); + PrintAndLogEx(INFO, "SAK......... " _GREEN_("%02X"), card.sak); + PrintAndLogEx(INFO, "Sectors..... " _GREEN_("%u") " (%s)", numSectors, (numSectors == MIFARE_4K_MAXSECTOR) ? "4K" : "2K"); + PrintAndLogEx(NORMAL, ""); + + // ======================================== + // SL3 (AES) Key loading + // ======================================== + uint8_t aesFoundKeys[2][64][AES_KEY_LEN + 1]; + memset(aesFoundKeys, 0, sizeof(aesFoundKeys)); + + // 1a. Load AES keys from JSON key file (from hf mfp chk --dump) + if (keyfnlen > 0) { + res = mfp_load_keys_from_json(key_fn, aesFoundKeys); + if (res != PM3_SUCCESS) { + PrintAndLogEx(WARNING, "Failed to load AES key file, continuing without"); + } else { + int cnt = 0; + for (uint8_t s = 0; s < numSectors; s++) { + if (aesFoundKeys[0][s][0]) cnt++; + if (aesFoundKeys[1][s][0]) cnt++; + } + PrintAndLogEx(SUCCESS, "Loaded " _GREEN_("%d") " AES keys from key file", cnt); + } + } + + // 1b. Apply user-supplied AES key to all slots that don't have one yet + if (userkeylen == AES_KEY_LEN) { + int applied = 0; + for (uint8_t s = 0; s < numSectors; s++) { + for (uint8_t kt = 0; kt < 2; kt++) { + if (aesFoundKeys[kt][s][0] == 0) { + aesFoundKeys[kt][s][0] = 1; + memcpy(&aesFoundKeys[kt][s][1], userkey, AES_KEY_LEN); + applied++; + } + } + } + PrintAndLogEx(SUCCESS, "Applied user AES key to " _GREEN_("%d") " key slots", applied); + } + + // 1c. Probe AES dictionary + defaults for missing AES key slots + { + bool need_aes_probe = false; + for (uint8_t s = 0; s < numSectors; s++) { + if (aesFoundKeys[0][s][0] == 0 || aesFoundKeys[1][s][0] == 0) { + need_aes_probe = true; + break; + } + } + + if (need_aes_probe && !no_default) { + uint8_t *key_block = NULL; + uint32_t keycnt = 0; + res = mfp_load_keys(&key_block, &keycnt, NULL, 0, dict_fn, dictfnlen, card.uid, true); + if (res == PM3_SUCCESS && keycnt > 0) { + PrintAndLogEx(INFO, "Probing " _YELLOW_("%u") " AES keys against %u sectors...", keycnt, numSectors); + uint8_t end_sector = numSectors - 1; + res = plus_key_check(0, end_sector, 0, 1, key_block, keycnt, aesFoundKeys, verbose, true); + if (res == PM3_EOPABORTED) { + PrintAndLogEx(WARNING, "\nAborted"); + } + PrintAndLogEx(NORMAL, ""); + } + free(key_block); + } + } + + // ======================================== + // SL1 (CRYPTO1) Key loading + // ======================================== + // mfcFoundKeys[keytype][sector][0]=found, [1..6]=key + uint8_t mfcFoundKeys[2][64][MIFARE_KEY_SIZE + 1]; + memset(mfcFoundKeys, 0, sizeof(mfcFoundKeys)); + + // 2a. Load MFC keys from binary key file (from hf mf chk/autopwn) + if (mfckeyfnlen > 0) { + res = mfp_load_mfc_keys_from_bin(mfc_key_fn, mfcFoundKeys, numSectors); + if (res != PM3_SUCCESS) { + PrintAndLogEx(WARNING, "Failed to load MFC key file, continuing without"); + } else { + int cnt = 0; + for (uint8_t s = 0; s < numSectors; s++) { + if (mfcFoundKeys[0][s][0]) cnt++; + if (mfcFoundKeys[1][s][0]) cnt++; + } + PrintAndLogEx(SUCCESS, "Loaded " _GREEN_("%d") " MFC (CRYPTO1) keys from key file", cnt); + } + } + + // 2b. Load MFC dictionary keys and/or defaults for SL1 probing + { + bool need_mfc_probe = false; + for (uint8_t s = 0; s < numSectors; s++) { + if (mfcFoundKeys[0][s][0] == 0 || mfcFoundKeys[1][s][0] == 0) { + need_mfc_probe = true; + break; + } + } + + if (need_mfc_probe && !no_default) { + // load from MFC dictionary file + if (mfcdictfnlen > 0) { + uint32_t loaded = 0; + uint8_t *dict_keys = NULL; + res = loadFileDICTIONARY_safe(mfc_dict_fn, (void **)&dict_keys, MIFARE_KEY_SIZE, &loaded); + if (res == PM3_SUCCESS && loaded > 0) { + PrintAndLogEx(INFO, "Probing " _YELLOW_("%u") " MFC dict keys for SL1 sectors...", loaded); + mfp_sl1_key_check(numSectors, dict_keys, loaded, mfcFoundKeys, verbose); + PrintAndLogEx(NORMAL, ""); + } + free(dict_keys); + } + + // always try MFC default keys + uint8_t *def_keys = NULL; + uint32_t def_cnt = 0; + if (mfp_load_mfc_default_keys(&def_keys, &def_cnt) == PM3_SUCCESS && def_cnt > 0) { + PrintAndLogEx(INFO, "Probing " _YELLOW_("%u") " MFC default keys for SL1 sectors...", def_cnt); + mfp_sl1_key_check(numSectors, def_keys, def_cnt, mfcFoundKeys, verbose); + PrintAndLogEx(NORMAL, ""); + } + free(def_keys); + } + } + + // ======================================== + // Read phase - detect SL per sector and read + // ======================================== + uint16_t totalBlocks = 0; + for (uint8_t s = 0; s < numSectors; s++) { + totalBlocks += mfNumBlocksPerSector(s); + } + + uint8_t *carddata = calloc(totalBlocks * MFBLOCK_SIZE, sizeof(uint8_t)); + if (carddata == NULL) { + PrintAndLogEx(ERR, "Failed to allocate memory"); + return PM3_EMALLOC; + } + + uint8_t sectorSL[64]; + memset(sectorSL, MFP_SL_UNKNOWN, sizeof(sectorSL)); + + uint8_t sectorRead[64]; + memset(sectorRead, 0, sizeof(sectorRead)); + + PrintAndLogEx(INFO, "Reading card data (detecting SL per sector)..."); + + int sectorsRead = 0; + int sl3Count = 0; + int sl1Count = 0; + + for (uint8_t s = 0; s < numSectors; s++) { + + if (kbd_enter_pressed()) { + PrintAndLogEx(WARNING, "\naborted via keyboard"); + break; + } + + bool readOK = false; + uint16_t blockOffset = mfFirstBlockOfSector(s); + uint8_t blocksInSector = mfNumBlocksPerSector(s); + + // --- Try SL3 (AES) first --- + for (uint8_t kt = 0; kt < 2 && !readOK; kt++) { + if (aesFoundKeys[kt][s][0] == 0) { + continue; + } + + uint8_t sector_data[16 * 16] = {0}; + res = mfpReadSector(s, kt, &aesFoundKeys[kt][s][1], sector_data, verbose); + if (res == PM3_SUCCESS) { + memcpy(carddata + (blockOffset * MFBLOCK_SIZE), sector_data, blocksInSector * MFBLOCK_SIZE); + sectorRead[s] = 1; + sectorSL[s] = MFP_SL_3; + readOK = true; + sectorsRead++; + sl3Count++; + } else if (verbose) { + PrintAndLogEx(DEBUG, "Sector %u SL3 key%s failed: %d", s, (kt == 0) ? "A" : "B", res); + } + } + + // --- Try SL1 (CRYPTO1) if SL3 failed --- + for (uint8_t kt = 0; kt < 2 && !readOK; kt++) { + if (mfcFoundKeys[kt][s][0] == 0) { + continue; + } + + uint8_t sector_data[16 * 16] = {0}; + res = mfp_read_sector_sl1(s, kt, &mfcFoundKeys[kt][s][1], sector_data, verbose); + if (res == PM3_SUCCESS) { + memcpy(carddata + (blockOffset * MFBLOCK_SIZE), sector_data, blocksInSector * MFBLOCK_SIZE); + sectorRead[s] = 1; + sectorSL[s] = MFP_SL_1; + readOK = true; + sectorsRead++; + sl1Count++; + } else if (verbose) { + PrintAndLogEx(DEBUG, "Sector %u SL1 key%s failed: %d", s, (kt == 0) ? "A" : "B", res); + } + } + + if (readOK) { + PrintAndLogEx(INPLACE, "Reading sector %3d / %3d ( " _GREEN_("ok, %s") " )", + s, numSectors - 1, + (sectorSL[s] == MFP_SL_3) ? "SL3" : "SL1"); + } else { + PrintAndLogEx(INPLACE, "Reading sector %3d / %3d ( " _RED_("fail") " )", s, numSectors - 1); + } + } + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "Successfully read " _GREEN_("%d") " / %d sectors (SL3: %d, SL1: %d)", sectorsRead, numSectors, sl3Count, sl1Count); + PrintAndLogEx(NORMAL, ""); + + // ======================================== + // Print sector summary + // ======================================== + PrintAndLogEx(INFO, "-----+----+----------------------------------+----------------------------------"); + PrintAndLogEx(INFO, " Sec | SL | key A | key B"); + PrintAndLogEx(INFO, "-----+----+----------------------------------+----------------------------------"); + + for (uint8_t s = 0; s < numSectors; s++) { + char strA[46 + 1] = {0}; + char strB[46 + 1] = {0}; + const char *slStr; + + switch (sectorSL[s]) { + case MFP_SL_3: + slStr = _GREEN_("3 "); + if (aesFoundKeys[0][s][0]) { + snprintf(strA, sizeof(strA), _GREEN_("%s"), sprint_hex_inrow(&aesFoundKeys[0][s][1], AES_KEY_LEN)); + } else { + snprintf(strA, sizeof(strA), _RED_("%s"), "--------------------------------"); + } + if (aesFoundKeys[1][s][0]) { + snprintf(strB, sizeof(strB), _GREEN_("%s"), sprint_hex_inrow(&aesFoundKeys[1][s][1], AES_KEY_LEN)); + } else { + snprintf(strB, sizeof(strB), _RED_("%s"), "--------------------------------"); + } + break; + case MFP_SL_1: + slStr = _YELLOW_("1 "); + if (mfcFoundKeys[0][s][0]) { + snprintf(strA, sizeof(strA), _GREEN_("%s") " ", sprint_hex_inrow(&mfcFoundKeys[0][s][1], MIFARE_KEY_SIZE)); + } else { + snprintf(strA, sizeof(strA), _RED_("%s"), "--------------------------------"); + } + if (mfcFoundKeys[1][s][0]) { + snprintf(strB, sizeof(strB), _GREEN_("%s") " ", sprint_hex_inrow(&mfcFoundKeys[1][s][1], MIFARE_KEY_SIZE)); + } else { + snprintf(strB, sizeof(strB), _RED_("%s"), "--------------------------------"); + } + break; + default: + slStr = _RED_("? "); + snprintf(strA, sizeof(strA), _RED_("%s"), "--------------------------------"); + snprintf(strB, sizeof(strB), _RED_("%s"), "--------------------------------"); + break; + } + + PrintAndLogEx(INFO, " " _YELLOW_("%03d") " | %s | %s | %s", s, slStr, strA, strB); + } + PrintAndLogEx(INFO, "-----+----+----------------------------------+----------------------------------"); + PrintAndLogEx(NORMAL, ""); + + // ======================================== + // Display block data + // ======================================== + for (uint8_t s = 0; s < numSectors; s++) { + if (sectorRead[s] == 0) { + continue; + } + + mf_print_sector_hdr(s); + uint16_t blockOffset = mfFirstBlockOfSector(s); + for (uint8_t b = 0; b < mfNumBlocksPerSector(s); b++) { + mf_print_block_one(blockOffset + b, carddata + ((blockOffset + b) * MFBLOCK_SIZE), verbose); + } + } + PrintAndLogEx(NORMAL, ""); + + if (nosave) { + PrintAndLogEx(INFO, "Called with no-save option"); + free(carddata); + return PM3_SUCCESS; + } + + // ======================================== + // Save dump + // ======================================== + size_t dumpsize = totalBlocks * MFBLOCK_SIZE; + + // generate filename from UID if not provided + if (datafnlen < 1) { + char *fptr = calloc(sizeof(char) * (strlen("hf-mfp-") + strlen("-dump")) + card.uidlen * 2 + 1, sizeof(uint8_t)); + if (fptr == NULL) { + PrintAndLogEx(ERR, "Failed to allocate memory"); + free(carddata); return PM3_EMALLOC; } + strcpy(fptr, "hf-mfp-"); + FillFileNameByUID(fptr, card.uid, "-dump", card.uidlen); + strcpy(data_fn, fptr); + free(fptr); + } + pm3_save_mf_dump(data_fn, carddata, dumpsize, jsfCardMemory); - // iso14a_card_select_t card ; - // int res = mfp_read_tag(&card, mem, key_fn); - // if (res != PM3_SUCCESS) { - // free(mem); - // return res; - // } + if (sectorsRead != numSectors) { + PrintAndLogEx(HINT, "Partial dump: %d of %d sectors read", sectorsRead, numSectors); + PrintAndLogEx(HINT, "Hint: use " _YELLOW_("`hf mfp chk --dump`") " and/or " _YELLOW_("`hf mf chk`") " to find more keys"); + } - - // Skip saving card data to file - if (nosave) { - PrintAndLogEx(INFO, "Called with no save option"); - free(mem); - return PM3_SUCCESS; - } - - // Save to file - // if (strlen(data_fn) < 1) { - // char *fptr = calloc(sizeof(char) * (strlen("hf-mfp-") + strlen("-dump")) + card.uidlen * 2 + 1, sizeof(uint8_t)); - // strcpy(fptr, "hf-mfp-"); - // FillFileNameByUID(fptr, card.uid, "-dump", card.uidlen); - // strcpy(data_fn, fptr); - // free(fptr); - // } - - // pm3_save_mf_dump(filename, dump, MIFARE_4K_MAX_BYTES, jsfCardMemory); - - free(mem); - return PM3_SUCCESS; - */ + free(carddata); + return PM3_SUCCESS; } static int CmdHFMFPMAD(const char *Cmd) { @@ -2333,7 +2800,7 @@ static command_t CommandTable[] = { {"-----------", CmdHelp, IfPm3Iso14443a, "------------------- " _CYAN_("operations") " ---------------------"}, {"auth", CmdHFMFPAuth, IfPm3Iso14443a, "Authentication"}, {"chk", CmdHFMFPChk, IfPm3Iso14443a, "Check keys"}, - {"dump", CmdHFMFPDump, IfPm3Iso14443a, "Dump MIFARE Plus tag to binary file"}, + {"dump", CmdHFMFPDump, IfPm3Iso14443a, "Dump MIFARE Plus tag to file"}, {"info", CmdHFMFPInfo, IfPm3Iso14443a, "Tag information"}, {"mad", CmdHFMFPMAD, IfPm3Iso14443a, "Check and print MAD"}, {"rdbl", CmdHFMFPRdbl, IfPm3Iso14443a, "Read blocks from card"}, From 44a40064b79fc0a3db1c17c9aa47c6541bd8bd9d Mon Sep 17 00:00:00 2001 From: Tomas Nilsson Date: Sat, 7 Mar 2026 16:41:26 +0100 Subject: [PATCH 2/6] Fix SL1 key probing hang in hf mfp dump Replace mf_check_keys (which hangs in firmware infinite retry loop) with mf_read_sector for SL1 key probing. Defer MFC key probing to the read phase so it only runs per-sector when SL3 auth fails. Also fix mfcProbeKeys memory leak on early exit paths. --- client/src/cmdhfmfp.c | 153 ++++++++++++++++++++++++++---------------- 1 file changed, 95 insertions(+), 58 deletions(-) diff --git a/client/src/cmdhfmfp.c b/client/src/cmdhfmfp.c index a2a21b3a6..d4db69723 100644 --- a/client/src/cmdhfmfp.c +++ b/client/src/cmdhfmfp.c @@ -230,10 +230,16 @@ static int mfp_read_card_id(iso14a_card_select_t *card, int *nxptype) { return PM3_ERFTRANS; } + uint64_t select_status = resp.oldarg[0]; // 0: couldn't read, 1: OK with ATS, 2: OK no ATS, 3: proprietary + if (select_status == 0) { + PrintAndLogEx(ERR, "No card present or card not responding"); + 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)) { @@ -1465,6 +1471,11 @@ static int CmdHFMFPChConf(const char *Cmd) { return PM3_SUCCESS; } +// Progress indicators (non-verbose mode): +// '.' progress heartbeat, printed every 10 key attempts +// '+' key found for a sector +// 'R' retry after transient communication error +// 'E' exchange error, aborts the check 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) { @@ -1987,42 +1998,38 @@ static int mfp_load_mfc_default_keys(uint8_t **pkeyBlock, uint32_t *pkeycnt) { return PM3_SUCCESS; } -// Probe MFC (CRYPTO1) keys against sectors that still need keys -static int mfp_sl1_key_check(uint8_t numSectors, uint8_t *keys, uint32_t keycnt, +// Try to find an MFC (CRYPTO1) key for a sector by attempting to read it. +// Uses mf_read_sector which has a proper timeout, unlike mf_check_keys +// which can hang in firmware if the card doesn't respond to ISO 14443-3. +// Returns true if a working key was found. +static bool mfp_sl1_try_keys(uint8_t sectorNo, uint8_t *keys, uint32_t keycnt, uint8_t mfcFoundKeys[2][64][MIFARE_KEY_SIZE + 1], bool verbose) { - for (uint8_t s = 0; s < numSectors; s++) { - for (uint8_t kt = 0; kt < 2; kt++) { - if (mfcFoundKeys[kt][s][0]) { - continue; - } + uint8_t dummy[16 * 16] = {0}; - if (kbd_enter_pressed()) { - return PM3_EOPABORTED; - } + for (uint8_t kt = 0; kt < 2; kt++) { + if (mfcFoundKeys[kt][sectorNo][0]) { + continue; + } - uint8_t blockNo = mfFirstBlockOfSector(s); - - for (uint32_t i = 0; i < keycnt; i++) { - uint64_t found_key = 0; - int res = mf_check_keys(blockNo, kt, true, 1, keys + i * MIFARE_KEY_SIZE, &found_key); - if (res == PM3_SUCCESS) { - mfcFoundKeys[kt][s][0] = 1; - num_to_bytes(found_key, MIFARE_KEY_SIZE, &mfcFoundKeys[kt][s][1]); - - if (verbose) { - PrintAndLogEx(INFO, "SL1 key found: sector %u key%s [ " _GREEN_("%s") " ]", - s, (kt == 0) ? "A" : "B", - sprint_hex_inrow(&mfcFoundKeys[kt][s][1], MIFARE_KEY_SIZE)); - } else { - PrintAndLogEx(NORMAL, "+" NOLF); - } - break; + for (uint32_t i = 0; i < keycnt; i++) { + uint8_t *trykey = keys + i * MIFARE_KEY_SIZE; + int res = mf_read_sector(sectorNo, kt, trykey, dummy); + if (res == PM3_SUCCESS) { + mfcFoundKeys[kt][sectorNo][0] = 1; + memcpy(&mfcFoundKeys[kt][sectorNo][1], trykey, MIFARE_KEY_SIZE); + if (verbose) { + PrintAndLogEx(INFO, "SL1 key found: sector %u key%s [ " _GREEN_("%s") " ]", + sectorNo, (kt == 0) ? "A" : "B", + sprint_hex_inrow(trykey, MIFARE_KEY_SIZE)); + } else { + PrintAndLogEx(NORMAL, "+" NOLF); } + return true; } } } - return PM3_SUCCESS; + return false; } static int CmdHFMFPDump(const char *Cmd) { @@ -2201,40 +2208,43 @@ static int CmdHFMFPDump(const char *Cmd) { } } - // 2b. Load MFC dictionary keys and/or defaults for SL1 probing - { - bool need_mfc_probe = false; - for (uint8_t s = 0; s < numSectors; s++) { - if (mfcFoundKeys[0][s][0] == 0 || mfcFoundKeys[1][s][0] == 0) { - need_mfc_probe = true; - break; - } + // 2b. Build combined MFC key list for SL1 probing during read phase. + // We don't probe upfront because mf_check_keys can hang in firmware + // if the card is in SL3 mode. Instead we try keys per-sector during + // the read phase, only for sectors where SL3 auth failed. + uint8_t *mfcProbeKeys = NULL; + uint32_t mfcProbeKeyCnt = 0; + + if (!no_default) { + // load MFC dictionary file + uint8_t *dict_keys = NULL; + uint32_t dict_cnt = 0; + if (mfcdictfnlen > 0) { + loadFileDICTIONARY_safe(mfc_dict_fn, (void **)&dict_keys, MIFARE_KEY_SIZE, &dict_cnt); } - if (need_mfc_probe && !no_default) { - // load from MFC dictionary file - if (mfcdictfnlen > 0) { - uint32_t loaded = 0; - uint8_t *dict_keys = NULL; - res = loadFileDICTIONARY_safe(mfc_dict_fn, (void **)&dict_keys, MIFARE_KEY_SIZE, &loaded); - if (res == PM3_SUCCESS && loaded > 0) { - PrintAndLogEx(INFO, "Probing " _YELLOW_("%u") " MFC dict keys for SL1 sectors...", loaded); - mfp_sl1_key_check(numSectors, dict_keys, loaded, mfcFoundKeys, verbose); - PrintAndLogEx(NORMAL, ""); + // load MFC defaults + uint8_t *def_keys = NULL; + uint32_t def_cnt = 0; + mfp_load_mfc_default_keys(&def_keys, &def_cnt); + + // merge into single list + uint32_t total = dict_cnt + def_cnt; + if (total > 0) { + mfcProbeKeys = calloc(total, MIFARE_KEY_SIZE); + if (mfcProbeKeys) { + if (dict_cnt > 0) { + memcpy(mfcProbeKeys, dict_keys, dict_cnt * MIFARE_KEY_SIZE); } - free(dict_keys); + if (def_cnt > 0) { + memcpy(mfcProbeKeys + dict_cnt * MIFARE_KEY_SIZE, def_keys, def_cnt * MIFARE_KEY_SIZE); + } + mfcProbeKeyCnt = total; + PrintAndLogEx(SUCCESS, "Loaded " _GREEN_("%u") " MFC keys for SL1 sector probing", total); } - - // always try MFC default keys - uint8_t *def_keys = NULL; - uint32_t def_cnt = 0; - if (mfp_load_mfc_default_keys(&def_keys, &def_cnt) == PM3_SUCCESS && def_cnt > 0) { - PrintAndLogEx(INFO, "Probing " _YELLOW_("%u") " MFC default keys for SL1 sectors...", def_cnt); - mfp_sl1_key_check(numSectors, def_keys, def_cnt, mfcFoundKeys, verbose); - PrintAndLogEx(NORMAL, ""); - } - free(def_keys); } + free(dict_keys); + free(def_keys); } // ======================================== @@ -2248,6 +2258,7 @@ static int CmdHFMFPDump(const char *Cmd) { uint8_t *carddata = calloc(totalBlocks * MFBLOCK_SIZE, sizeof(uint8_t)); if (carddata == NULL) { PrintAndLogEx(ERR, "Failed to allocate memory"); + free(mfcProbeKeys); return PM3_EMALLOC; } @@ -2295,6 +2306,7 @@ static int CmdHFMFPDump(const char *Cmd) { } // --- Try SL1 (CRYPTO1) if SL3 failed --- + // First try pre-loaded keys from MFC key file for (uint8_t kt = 0; kt < 2 && !readOK; kt++) { if (mfcFoundKeys[kt][s][0] == 0) { continue; @@ -2314,6 +2326,28 @@ static int CmdHFMFPDump(const char *Cmd) { } } + // If still not read, try probing MFC dictionary/default keys + if (!readOK && mfcProbeKeys != NULL && mfcProbeKeyCnt > 0) { + if (mfp_sl1_try_keys(s, mfcProbeKeys, mfcProbeKeyCnt, mfcFoundKeys, verbose)) { + // Key found and stored in mfcFoundKeys, now read sector + for (uint8_t kt = 0; kt < 2 && !readOK; kt++) { + if (mfcFoundKeys[kt][s][0] == 0) { + continue; + } + uint8_t sector_data[16 * 16] = {0}; + res = mfp_read_sector_sl1(s, kt, &mfcFoundKeys[kt][s][1], sector_data, verbose); + if (res == PM3_SUCCESS) { + memcpy(carddata + (blockOffset * MFBLOCK_SIZE), sector_data, blocksInSector * MFBLOCK_SIZE); + sectorRead[s] = 1; + sectorSL[s] = MFP_SL_1; + readOK = true; + sectorsRead++; + sl1Count++; + } + } + } + } + if (readOK) { PrintAndLogEx(INPLACE, "Reading sector %3d / %3d ( " _GREEN_("ok, %s") " )", s, numSectors - 1, @@ -2398,6 +2432,7 @@ static int CmdHFMFPDump(const char *Cmd) { if (nosave) { PrintAndLogEx(INFO, "Called with no-save option"); free(carddata); + free(mfcProbeKeys); return PM3_SUCCESS; } @@ -2412,6 +2447,7 @@ static int CmdHFMFPDump(const char *Cmd) { if (fptr == NULL) { PrintAndLogEx(ERR, "Failed to allocate memory"); free(carddata); + free(mfcProbeKeys); return PM3_EMALLOC; } strcpy(fptr, "hf-mfp-"); @@ -2428,6 +2464,7 @@ static int CmdHFMFPDump(const char *Cmd) { } free(carddata); + free(mfcProbeKeys); return PM3_SUCCESS; } From 3e8d52db581cf2b6e3da05efe1a754ebb050dcc9 Mon Sep 17 00:00:00 2001 From: Tomas Nilsson Date: Sat, 7 Mar 2026 17:23:59 +0100 Subject: [PATCH 3/6] Optimize hf mfp dump: phased SL classification and quiet probing Restructure dump into 4 phases: 1. Classify sectors via single MFC probe (SL3 vs SL1) 2. AES dictionary only on SL3 sectors 3. MFC dictionary only on SL1 sectors 4. Read with found keys Suppress firmware debug output during key probing to prevent auth error message flood, matching MifareChkKeys behavior. --- client/src/cmdhfmfp.c | 337 ++++++++++++++++++++++++++++-------------- 1 file changed, 230 insertions(+), 107 deletions(-) diff --git a/client/src/cmdhfmfp.c b/client/src/cmdhfmfp.c index d4db69723..b393af092 100644 --- a/client/src/cmdhfmfp.c +++ b/client/src/cmdhfmfp.c @@ -39,6 +39,7 @@ #include "cmdtrace.h" #include "crypto/originality.h" #include "jansson.h" +#include "preferences.h" // getDeviceDebugLevel, setDeviceDebugLevel static const uint8_t mfp_default_key[16] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 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}; @@ -2002,6 +2003,10 @@ static int mfp_load_mfc_default_keys(uint8_t **pkeyBlock, uint32_t *pkeycnt) { // Uses mf_read_sector which has a proper timeout, unlike mf_check_keys // which can hang in firmware if the card doesn't respond to ISO 14443-3. // Returns true if a working key was found. +// Try MFC (CRYPTO1) keys on a sector using mf_read_sector. +// Returns true if a working key was found (stored in mfcFoundKeys). +// Bails out early if the card doesn't respond to ISO 14443-3 select +// (PM3_ETIMEOUT), which means the sector is SL3-only. static bool mfp_sl1_try_keys(uint8_t sectorNo, uint8_t *keys, uint32_t keycnt, uint8_t mfcFoundKeys[2][64][MIFARE_KEY_SIZE + 1], bool verbose) { @@ -2027,6 +2032,14 @@ static bool mfp_sl1_try_keys(uint8_t sectorNo, uint8_t *keys, uint32_t keycnt, } return true; } + // Timeout means card doesn't respond to ISO 14443-3 select at all. + // This sector is SL3-only; no point trying more CRYPTO1 keys. + if (res == PM3_ETIMEOUT) { + if (verbose) { + PrintAndLogEx(DEBUG, "Sector %u not responding to ISO 14443-3, skipping MFC probe", sectorNo); + } + return false; + } } } return false; @@ -2124,12 +2137,18 @@ static int CmdHFMFPDump(const char *Cmd) { PrintAndLogEx(NORMAL, ""); // ======================================== - // SL3 (AES) Key loading + // Phase 0: Key file loading (no card interaction yet) // ======================================== + + // AES keys: aesFoundKeys[keytype][sector][0]=found, [1..16]=key uint8_t aesFoundKeys[2][64][AES_KEY_LEN + 1]; memset(aesFoundKeys, 0, sizeof(aesFoundKeys)); - // 1a. Load AES keys from JSON key file (from hf mfp chk --dump) + // MFC keys: mfcFoundKeys[keytype][sector][0]=found, [1..6]=key + uint8_t mfcFoundKeys[2][64][MIFARE_KEY_SIZE + 1]; + memset(mfcFoundKeys, 0, sizeof(mfcFoundKeys)); + + // 0a. Load AES keys from JSON key file (from hf mfp chk --dump) if (keyfnlen > 0) { res = mfp_load_keys_from_json(key_fn, aesFoundKeys); if (res != PM3_SUCCESS) { @@ -2144,7 +2163,7 @@ static int CmdHFMFPDump(const char *Cmd) { } } - // 1b. Apply user-supplied AES key to all slots that don't have one yet + // 0b. Apply user-supplied AES key to all slots that don't have one yet if (userkeylen == AES_KEY_LEN) { int applied = 0; for (uint8_t s = 0; s < numSectors; s++) { @@ -2159,41 +2178,7 @@ static int CmdHFMFPDump(const char *Cmd) { PrintAndLogEx(SUCCESS, "Applied user AES key to " _GREEN_("%d") " key slots", applied); } - // 1c. Probe AES dictionary + defaults for missing AES key slots - { - bool need_aes_probe = false; - for (uint8_t s = 0; s < numSectors; s++) { - if (aesFoundKeys[0][s][0] == 0 || aesFoundKeys[1][s][0] == 0) { - need_aes_probe = true; - break; - } - } - - if (need_aes_probe && !no_default) { - uint8_t *key_block = NULL; - uint32_t keycnt = 0; - res = mfp_load_keys(&key_block, &keycnt, NULL, 0, dict_fn, dictfnlen, card.uid, true); - if (res == PM3_SUCCESS && keycnt > 0) { - PrintAndLogEx(INFO, "Probing " _YELLOW_("%u") " AES keys against %u sectors...", keycnt, numSectors); - uint8_t end_sector = numSectors - 1; - res = plus_key_check(0, end_sector, 0, 1, key_block, keycnt, aesFoundKeys, verbose, true); - if (res == PM3_EOPABORTED) { - PrintAndLogEx(WARNING, "\nAborted"); - } - PrintAndLogEx(NORMAL, ""); - } - free(key_block); - } - } - - // ======================================== - // SL1 (CRYPTO1) Key loading - // ======================================== - // mfcFoundKeys[keytype][sector][0]=found, [1..6]=key - uint8_t mfcFoundKeys[2][64][MIFARE_KEY_SIZE + 1]; - memset(mfcFoundKeys, 0, sizeof(mfcFoundKeys)); - - // 2a. Load MFC keys from binary key file (from hf mf chk/autopwn) + // 0c. Load MFC keys from binary key file (from hf mf chk/autopwn) if (mfckeyfnlen > 0) { res = mfp_load_mfc_keys_from_bin(mfc_key_fn, mfcFoundKeys, numSectors); if (res != PM3_SUCCESS) { @@ -2208,27 +2193,21 @@ static int CmdHFMFPDump(const char *Cmd) { } } - // 2b. Build combined MFC key list for SL1 probing during read phase. - // We don't probe upfront because mf_check_keys can hang in firmware - // if the card is in SL3 mode. Instead we try keys per-sector during - // the read phase, only for sectors where SL3 auth failed. + // 0d. Build combined MFC probe key list (dictionary + defaults) uint8_t *mfcProbeKeys = NULL; uint32_t mfcProbeKeyCnt = 0; if (!no_default) { - // load MFC dictionary file uint8_t *dict_keys = NULL; uint32_t dict_cnt = 0; if (mfcdictfnlen > 0) { loadFileDICTIONARY_safe(mfc_dict_fn, (void **)&dict_keys, MIFARE_KEY_SIZE, &dict_cnt); } - // load MFC defaults uint8_t *def_keys = NULL; uint32_t def_cnt = 0; mfp_load_mfc_default_keys(&def_keys, &def_cnt); - // merge into single list uint32_t total = dict_cnt + def_cnt; if (total > 0) { mfcProbeKeys = calloc(total, MIFARE_KEY_SIZE); @@ -2240,7 +2219,6 @@ static int CmdHFMFPDump(const char *Cmd) { memcpy(mfcProbeKeys + dict_cnt * MIFARE_KEY_SIZE, def_keys, def_cnt * MIFARE_KEY_SIZE); } mfcProbeKeyCnt = total; - PrintAndLogEx(SUCCESS, "Loaded " _GREEN_("%u") " MFC keys for SL1 sector probing", total); } } free(dict_keys); @@ -2248,7 +2226,177 @@ static int CmdHFMFPDump(const char *Cmd) { } // ======================================== - // Read phase - detect SL per sector and read + // Phase 1: Classify sectors as SL3 or SL1 + // ======================================== + // Try one MFC key (FFFFFFFFFFFF) on each sector to determine SL. + // mf_read_sector returns: + // PM3_SUCCESS -> SL1 confirmed, key found + // PM3_EUNDEF -> SL1 confirmed (card responded to ISO 14443-3 auth), wrong key + // PM3_ETIMEOUT -> SL3 (card doesn't respond to ISO 14443-3) + + // Suppress firmware debug messages during classification and key probing. + // The many auth attempts produce a flood of "Auth error" / "Can't select card" + // messages from the firmware that interfere with other tools reading stdout. + uint8_t dbg_curr = DBG_NONE; + if (getDeviceDebugLevel(&dbg_curr) != PM3_SUCCESS) { + free(mfcProbeKeys); + return PM3_EFAILED; + } + setDeviceDebugLevel(DBG_NONE, false); + + uint8_t sectorSL[64]; + memset(sectorSL, MFP_SL_UNKNOWN, sizeof(sectorSL)); + + // Sectors with pre-loaded keys already have a known SL + for (uint8_t s = 0; s < numSectors; s++) { + if (aesFoundKeys[0][s][0] || aesFoundKeys[1][s][0]) { + sectorSL[s] = MFP_SL_3; + } + if (mfcFoundKeys[0][s][0] || mfcFoundKeys[1][s][0]) { + sectorSL[s] = MFP_SL_1; + } + } + + // Probe unclassified sectors with FFFFFFFFFFFF (CRYPTO1) + { + uint8_t probe_key[MIFARE_KEY_SIZE]; + memset(probe_key, 0xFF, MIFARE_KEY_SIZE); + uint8_t dummy[16 * 16] = {0}; + PrintAndLogEx(INFO, "Classifying sector security levels..."); + + for (uint8_t s = 0; s < numSectors; s++) { + if (sectorSL[s] != MFP_SL_UNKNOWN) { + continue; + } + + DropField(); + res = mf_read_sector(s, 0, probe_key, dummy); + if (res == PM3_SUCCESS) { + // FFFFFFFFFFFF worked -> SL1 with default key + sectorSL[s] = MFP_SL_1; + mfcFoundKeys[0][s][0] = 1; + memcpy(&mfcFoundKeys[0][s][1], probe_key, MIFARE_KEY_SIZE); + } else if (res == PM3_EUNDEF) { + // Card responded to ISO 14443-3 but wrong key -> SL1 + sectorSL[s] = MFP_SL_1; + } else { + // Timeout or other error -> SL3 + sectorSL[s] = MFP_SL_3; + } + } + DropField(); + + int pre_sl3 = 0, pre_sl1 = 0; + for (uint8_t s = 0; s < numSectors; s++) { + if (sectorSL[s] == MFP_SL_3) pre_sl3++; + else if (sectorSL[s] == MFP_SL_1) pre_sl1++; + } + PrintAndLogEx(SUCCESS, "Sector classification: " _GREEN_("%d") " SL3, " _YELLOW_("%d") " SL1", + pre_sl3, pre_sl1); + } + + // ======================================== + // Phase 2: AES key probing (SL3 sectors only) + // ======================================== + // plus_key_check skips sectors where foundKeys[][sector][0] is set, + // so we mark SL1 sectors with a dummy key to exclude them. + { + // Save and temporarily mark SL1 sectors so plus_key_check skips them + uint8_t sl1_backup[2][64]; + memset(sl1_backup, 0, sizeof(sl1_backup)); + + for (uint8_t s = 0; s < numSectors; s++) { + if (sectorSL[s] == MFP_SL_1) { + for (uint8_t kt = 0; kt < 2; kt++) { + sl1_backup[kt][s] = aesFoundKeys[kt][s][0]; + if (aesFoundKeys[kt][s][0] == 0) { + // Mark as "found" with dummy so plus_key_check skips it + aesFoundKeys[kt][s][0] = 0xFF; + } + } + } + } + + bool need_aes_probe = false; + for (uint8_t s = 0; s < numSectors; s++) { + if (sectorSL[s] == MFP_SL_3 && + (aesFoundKeys[0][s][0] == 0 || aesFoundKeys[1][s][0] == 0)) { + need_aes_probe = true; + break; + } + } + + if (need_aes_probe && !no_default) { + uint8_t *key_block = NULL; + uint32_t keycnt = 0; + res = mfp_load_keys(&key_block, &keycnt, NULL, 0, dict_fn, dictfnlen, card.uid, true); + if (res == PM3_SUCCESS && keycnt > 0) { + int sl3_unknown = 0; + for (uint8_t s = 0; s < numSectors; s++) { + if (sectorSL[s] == MFP_SL_3 && + (aesFoundKeys[0][s][0] == 0 || aesFoundKeys[1][s][0] == 0)) { + sl3_unknown++; + } + } + PrintAndLogEx(INFO, "Probing " _YELLOW_("%u") " AES keys against %d SL3 sectors...", keycnt, sl3_unknown); + uint8_t end_sector = numSectors - 1; + res = plus_key_check(0, end_sector, 0, 1, key_block, keycnt, aesFoundKeys, verbose, true); + if (res == PM3_EOPABORTED) { + PrintAndLogEx(WARNING, "\nAborted"); + } + PrintAndLogEx(NORMAL, ""); + } + free(key_block); + } + + // Restore SL1 sector dummy markers + for (uint8_t s = 0; s < numSectors; s++) { + if (sectorSL[s] == MFP_SL_1) { + for (uint8_t kt = 0; kt < 2; kt++) { + if (aesFoundKeys[kt][s][0] == 0xFF) { + aesFoundKeys[kt][s][0] = sl1_backup[kt][s]; + } + } + } + } + } + + // ======================================== + // Phase 3: MFC key probing (SL1 sectors only) + // ======================================== + if (mfcProbeKeyCnt > 0 && mfcProbeKeys != NULL) { + int sl1_need_keys = 0; + for (uint8_t s = 0; s < numSectors; s++) { + if (sectorSL[s] == MFP_SL_1 && + (mfcFoundKeys[0][s][0] == 0 || mfcFoundKeys[1][s][0] == 0)) { + sl1_need_keys++; + } + } + + if (sl1_need_keys > 0) { + PrintAndLogEx(INFO, "Probing " _YELLOW_("%u") " MFC keys against %d SL1 sectors...", mfcProbeKeyCnt, sl1_need_keys); + for (uint8_t s = 0; s < numSectors; s++) { + if (kbd_enter_pressed()) { + PrintAndLogEx(WARNING, "\naborted via keyboard"); + break; + } + if (sectorSL[s] != MFP_SL_1) { + continue; + } + if (mfcFoundKeys[0][s][0] && mfcFoundKeys[1][s][0]) { + continue; + } + mfp_sl1_try_keys(s, mfcProbeKeys, mfcProbeKeyCnt, mfcFoundKeys, verbose); + } + PrintAndLogEx(NORMAL, ""); + } + } + + // Restore firmware debug level before reading + setDeviceDebugLevel(dbg_curr, false); + + // ======================================== + // Phase 4: Read sectors with found keys // ======================================== uint16_t totalBlocks = 0; for (uint8_t s = 0; s < numSectors; s++) { @@ -2262,13 +2410,10 @@ static int CmdHFMFPDump(const char *Cmd) { return PM3_EMALLOC; } - uint8_t sectorSL[64]; - memset(sectorSL, MFP_SL_UNKNOWN, sizeof(sectorSL)); - uint8_t sectorRead[64]; memset(sectorRead, 0, sizeof(sectorRead)); - PrintAndLogEx(INFO, "Reading card data (detecting SL per sector)..."); + PrintAndLogEx(INFO, "Reading card data..."); int sectorsRead = 0; int sl3Count = 0; @@ -2285,65 +2430,43 @@ static int CmdHFMFPDump(const char *Cmd) { uint16_t blockOffset = mfFirstBlockOfSector(s); uint8_t blocksInSector = mfNumBlocksPerSector(s); - // --- Try SL3 (AES) first --- - for (uint8_t kt = 0; kt < 2 && !readOK; kt++) { - if (aesFoundKeys[kt][s][0] == 0) { - continue; - } + if (sectorSL[s] == MFP_SL_3) { + // --- Try SL3 (AES) --- + for (uint8_t kt = 0; kt < 2 && !readOK; kt++) { + if (aesFoundKeys[kt][s][0] == 0) { + continue; + } - uint8_t sector_data[16 * 16] = {0}; - res = mfpReadSector(s, kt, &aesFoundKeys[kt][s][1], sector_data, verbose); - if (res == PM3_SUCCESS) { - memcpy(carddata + (blockOffset * MFBLOCK_SIZE), sector_data, blocksInSector * MFBLOCK_SIZE); - sectorRead[s] = 1; - sectorSL[s] = MFP_SL_3; - readOK = true; - sectorsRead++; - sl3Count++; - } else if (verbose) { - PrintAndLogEx(DEBUG, "Sector %u SL3 key%s failed: %d", s, (kt == 0) ? "A" : "B", res); + uint8_t sector_data[16 * 16] = {0}; + res = mfpReadSector(s, kt, &aesFoundKeys[kt][s][1], sector_data, verbose); + if (res == PM3_SUCCESS) { + memcpy(carddata + (blockOffset * MFBLOCK_SIZE), sector_data, blocksInSector * MFBLOCK_SIZE); + sectorRead[s] = 1; + readOK = true; + sectorsRead++; + sl3Count++; + } else if (verbose) { + PrintAndLogEx(DEBUG, "Sector %u SL3 key%s failed: %d", s, (kt == 0) ? "A" : "B", res); + } } - } + } else if (sectorSL[s] == MFP_SL_1) { + // --- Try SL1 (CRYPTO1) --- + DropField(); + for (uint8_t kt = 0; kt < 2 && !readOK; kt++) { + if (mfcFoundKeys[kt][s][0] == 0) { + continue; + } - // --- Try SL1 (CRYPTO1) if SL3 failed --- - // First try pre-loaded keys from MFC key file - for (uint8_t kt = 0; kt < 2 && !readOK; kt++) { - if (mfcFoundKeys[kt][s][0] == 0) { - continue; - } - - uint8_t sector_data[16 * 16] = {0}; - res = mfp_read_sector_sl1(s, kt, &mfcFoundKeys[kt][s][1], sector_data, verbose); - if (res == PM3_SUCCESS) { - memcpy(carddata + (blockOffset * MFBLOCK_SIZE), sector_data, blocksInSector * MFBLOCK_SIZE); - sectorRead[s] = 1; - sectorSL[s] = MFP_SL_1; - readOK = true; - sectorsRead++; - sl1Count++; - } else if (verbose) { - PrintAndLogEx(DEBUG, "Sector %u SL1 key%s failed: %d", s, (kt == 0) ? "A" : "B", res); - } - } - - // If still not read, try probing MFC dictionary/default keys - if (!readOK && mfcProbeKeys != NULL && mfcProbeKeyCnt > 0) { - if (mfp_sl1_try_keys(s, mfcProbeKeys, mfcProbeKeyCnt, mfcFoundKeys, verbose)) { - // Key found and stored in mfcFoundKeys, now read sector - for (uint8_t kt = 0; kt < 2 && !readOK; kt++) { - if (mfcFoundKeys[kt][s][0] == 0) { - continue; - } - uint8_t sector_data[16 * 16] = {0}; - res = mfp_read_sector_sl1(s, kt, &mfcFoundKeys[kt][s][1], sector_data, verbose); - if (res == PM3_SUCCESS) { - memcpy(carddata + (blockOffset * MFBLOCK_SIZE), sector_data, blocksInSector * MFBLOCK_SIZE); - sectorRead[s] = 1; - sectorSL[s] = MFP_SL_1; - readOK = true; - sectorsRead++; - sl1Count++; - } + uint8_t sector_data[16 * 16] = {0}; + res = mfp_read_sector_sl1(s, kt, &mfcFoundKeys[kt][s][1], sector_data, verbose); + if (res == PM3_SUCCESS) { + memcpy(carddata + (blockOffset * MFBLOCK_SIZE), sector_data, blocksInSector * MFBLOCK_SIZE); + sectorRead[s] = 1; + readOK = true; + sectorsRead++; + sl1Count++; + } else if (verbose) { + PrintAndLogEx(DEBUG, "Sector %u SL1 key%s failed: %d", s, (kt == 0) ? "A" : "B", res); } } } From 7bdd677e3687e1409628629ffd4e3ab8699358ec Mon Sep 17 00:00:00 2001 From: apply-science <106422483+apply-science@users.noreply.github.com> Date: Sat, 7 Mar 2026 17:55:28 +0100 Subject: [PATCH 4/6] Update CHANGELOG with new commands and features Signed-off-by: apply-science <106422483+apply-science@users.noreply.github.com> --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c721610ed..dd03e92f7 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 mfp dump` command (@apply-science) - Added `hf mfdes bruteisofid` and `hf mfdes selectisofid` commands (@kormax) - Added DESFire AID values related to LEAF (@kormax) - Added `dict`, `ascii`, `mad` presets for `hf mfdes bruteaid` (@kormax) From 03d6da87e2ef2ea644f3540c5d433a51f1227d96 Mon Sep 17 00:00:00 2001 From: Tomas Nilsson Date: Sun, 8 Mar 2026 18:35:11 +0100 Subject: [PATCH 5/6] Simplify hf mfp dump: remove key probing, load-and-read only Restructure hf mfp dump to match hf mf dump pattern: load keys from files, read sectors, save. Remove all key probing/checking logic (use hf mfp chk and hf mf chk separately for key discovery). Use MF_KEY_A/MF_KEY_B defines instead of magic numbers. Replace custom JSON parser with existing loadFileJSON infrastructure. --- client/src/cmdhfmfp.c | 390 +++++++----------------------------------- 1 file changed, 59 insertions(+), 331 deletions(-) diff --git a/client/src/cmdhfmfp.c b/client/src/cmdhfmfp.c index b393af092..a6b182fd1 100644 --- a/client/src/cmdhfmfp.c +++ b/client/src/cmdhfmfp.c @@ -38,8 +38,6 @@ #include "mifare/mifarehost.h" // mf_read_sector (SL1 CRYPTO1) #include "cmdtrace.h" #include "crypto/originality.h" -#include "jansson.h" -#include "preferences.h" // getDeviceDebugLevel, setDeviceDebugLevel static const uint8_t mfp_default_key[16] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 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}; @@ -1905,41 +1903,37 @@ static int CmdHFMFPChk(const char *Cmd) { static int mfp_load_keys_from_json(const char *filename, uint8_t foundKeys[2][64][AES_KEY_LEN + 1]) { - json_t *root = NULL; - int res = loadFileJSONroot(filename, (void **)&root, true); + // loadFileJSON handles "mfpkeys" file type via loadFileJSONex. + // Buffer layout: UID(7) + pad(3) + SAK(1) + ATQA(2) + ATSlen(1) + ATS(atslen) + // then flat keys: KeyA0(16) KeyB0(16) KeyA1(16) KeyB1(16) ... + uint8_t data[14 + 256 + (2 * 64 * AES_KEY_LEN)]; + memset(data, 0, sizeof(data)); + size_t datalen = 0; + + int res = loadFileJSON(filename, data, sizeof(data), &datalen, NULL); if (res != PM3_SUCCESS) { return res; } - // check file type - json_t *jtype = json_object_get(root, "FileType"); - if (!jtype || !json_is_string(jtype) || strcmp(json_string_value(jtype), "mfpkeys") != 0) { - PrintAndLogEx(ERR, "Key file is not a MIFARE Plus key file"); - json_decref(root); - return PM3_EFILE; - } - - char path[64]; - uint8_t tmpkey[AES_KEY_LEN]; - size_t tmplen = 0; + uint8_t atslen = data[13]; + size_t key_offset = 14 + atslen; for (int i = 0; i < 64; i++) { - snprintf(path, sizeof(path), "$.SectorKeys.%d.KeyA", i); - tmplen = 0; - if (JsonLoadBufAsHex(root, path, tmpkey, AES_KEY_LEN, &tmplen) == 0 && tmplen == AES_KEY_LEN) { - foundKeys[0][i][0] = 1; - memcpy(&foundKeys[0][i][1], tmpkey, AES_KEY_LEN); - } + size_t off = key_offset + (i * 2 * AES_KEY_LEN); + uint8_t *ka = data + off; + uint8_t *kb = data + off + AES_KEY_LEN; - snprintf(path, sizeof(path), "$.SectorKeys.%d.KeyB", i); - tmplen = 0; - if (JsonLoadBufAsHex(root, path, tmpkey, AES_KEY_LEN, &tmplen) == 0 && tmplen == AES_KEY_LEN) { - foundKeys[1][i][0] = 1; - memcpy(&foundKeys[1][i][1], tmpkey, AES_KEY_LEN); + // check if key is non-zero (present in JSON) + if (memcmp(ka, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", AES_KEY_LEN) != 0) { + foundKeys[MF_KEY_A][i][0] = 1; + memcpy(&foundKeys[MF_KEY_A][i][1], ka, AES_KEY_LEN); + } + if (memcmp(kb, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", AES_KEY_LEN) != 0) { + foundKeys[MF_KEY_B][i][0] = 1; + memcpy(&foundKeys[MF_KEY_B][i][1], kb, AES_KEY_LEN); } } - json_decref(root); return PM3_SUCCESS; } @@ -1961,13 +1955,13 @@ static int mfp_load_mfc_keys_from_bin(const char *filename, uint8_t mfcFoundKeys } for (uint8_t s = 0; s < numSectors && s * MIFARE_KEY_SIZE < alen; s++) { - mfcFoundKeys[0][s][0] = 1; - memcpy(&mfcFoundKeys[0][s][1], keyA + s * MIFARE_KEY_SIZE, MIFARE_KEY_SIZE); + mfcFoundKeys[MF_KEY_A][s][0] = 1; + memcpy(&mfcFoundKeys[MF_KEY_A][s][1], keyA + s * MIFARE_KEY_SIZE, MIFARE_KEY_SIZE); } for (uint8_t s = 0; s < numSectors && s * MIFARE_KEY_SIZE < blen; s++) { - mfcFoundKeys[1][s][0] = 1; - memcpy(&mfcFoundKeys[1][s][1], keyB + s * MIFARE_KEY_SIZE, MIFARE_KEY_SIZE); + mfcFoundKeys[MF_KEY_B][s][0] = 1; + memcpy(&mfcFoundKeys[MF_KEY_B][s][1], keyB + s * MIFARE_KEY_SIZE, MIFARE_KEY_SIZE); } free(keyA); @@ -1984,92 +1978,25 @@ static int mfp_read_sector_sl1(uint8_t sectorNo, uint8_t keyType, const uint8_t return res; } -// Build a list of default MFC 6-byte keys for SL1 probing -static int mfp_load_mfc_default_keys(uint8_t **pkeyBlock, uint32_t *pkeycnt) { - size_t numDefaults = ARRAYLEN(g_mifare_default_keys); - *pkeyBlock = calloc(numDefaults, MIFARE_KEY_SIZE); - if (*pkeyBlock == NULL) { - return PM3_EMALLOC; - } - - for (size_t i = 0; i < numDefaults; i++) { - num_to_bytes(g_mifare_default_keys[i], MIFARE_KEY_SIZE, *pkeyBlock + i * MIFARE_KEY_SIZE); - } - *pkeycnt = numDefaults; - return PM3_SUCCESS; -} - -// Try to find an MFC (CRYPTO1) key for a sector by attempting to read it. -// Uses mf_read_sector which has a proper timeout, unlike mf_check_keys -// which can hang in firmware if the card doesn't respond to ISO 14443-3. -// Returns true if a working key was found. -// Try MFC (CRYPTO1) keys on a sector using mf_read_sector. -// Returns true if a working key was found (stored in mfcFoundKeys). -// Bails out early if the card doesn't respond to ISO 14443-3 select -// (PM3_ETIMEOUT), which means the sector is SL3-only. -static bool mfp_sl1_try_keys(uint8_t sectorNo, uint8_t *keys, uint32_t keycnt, - uint8_t mfcFoundKeys[2][64][MIFARE_KEY_SIZE + 1], bool verbose) { - - uint8_t dummy[16 * 16] = {0}; - - for (uint8_t kt = 0; kt < 2; kt++) { - if (mfcFoundKeys[kt][sectorNo][0]) { - continue; - } - - for (uint32_t i = 0; i < keycnt; i++) { - uint8_t *trykey = keys + i * MIFARE_KEY_SIZE; - int res = mf_read_sector(sectorNo, kt, trykey, dummy); - if (res == PM3_SUCCESS) { - mfcFoundKeys[kt][sectorNo][0] = 1; - memcpy(&mfcFoundKeys[kt][sectorNo][1], trykey, MIFARE_KEY_SIZE); - if (verbose) { - PrintAndLogEx(INFO, "SL1 key found: sector %u key%s [ " _GREEN_("%s") " ]", - sectorNo, (kt == 0) ? "A" : "B", - sprint_hex_inrow(trykey, MIFARE_KEY_SIZE)); - } else { - PrintAndLogEx(NORMAL, "+" NOLF); - } - return true; - } - // Timeout means card doesn't respond to ISO 14443-3 select at all. - // This sector is SL3-only; no point trying more CRYPTO1 keys. - if (res == PM3_ETIMEOUT) { - if (verbose) { - PrintAndLogEx(DEBUG, "Sector %u not responding to ISO 14443-3, skipping MFC probe", sectorNo); - } - return false; - } - } - } - return false; -} - static int CmdHFMFPDump(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf mfp dump", "Dump MIFARE Plus tag to file (bin/json)\n" - "Supports both SL3 (AES) and SL1 (CRYPTO1) sectors.\n" - "Automatically detects sector security level.\n" + "Reads sectors using keys from `hf mfp chk --dump` (AES/SL3)\n" + "and/or `hf mf chk` key file (CRYPTO1/SL1) for mixed-mode cards.\n" "If no given, UID will be used as filename", - "hf mfp dump\n" "hf mfp dump --keys hf-mfp-01020304-key.json\n" - "hf mfp dump -k ffffffffffffffffffffffffffffffff\n" - "hf mfp dump --dict mfp_default_keys\n" "hf mfp dump --keys hf-mfp-01020304-key.json --mfc-keys hf-mf-01020304-key.bin\n" - "hf mfp dump --keys hf-mfp-01020304-key.json --mfc-dict mfc_default_keys\n"); + "hf mfp dump -k ffffffffffffffffffffffffffffffff\n"); void *argtable[] = { arg_param_begin, arg_str0("f", "file", "", "Specify a filename for dump file"), arg_str0(NULL, "keys", "", "AES key file from `hf mfp chk --dump` (JSON)"), arg_str0("k", "key", "", "AES key for all sectors (16 hex bytes)"), - arg_str0(NULL, "dict", "", "AES dictionary file"), arg_str0(NULL, "mfc-keys", "", "MFC key file for SL1 sectors (.bin from `hf mf chk`)"), - arg_str0(NULL, "mfc-dict", "", "MFC dictionary file for SL1 sectors"), arg_lit0(NULL, "ns", "No save to file"), arg_lit0("v", "verbose", "Verbose output"), - arg_lit0(NULL, "no-default", "Skip default key probing for unknown sectors"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); @@ -2086,21 +2013,12 @@ static int CmdHFMFPDump(const char *Cmd) { uint8_t userkey[AES_KEY_LEN] = {0}; CLIGetHexWithReturn(ctx, 3, userkey, &userkeylen); - int dictfnlen = 0; - char dict_fn[FILE_PATH_SIZE] = {0}; - CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)dict_fn, FILE_PATH_SIZE, &dictfnlen); - int mfckeyfnlen = 0; char mfc_key_fn[FILE_PATH_SIZE] = {0}; - CLIParamStrToBuf(arg_get_str(ctx, 5), (uint8_t *)mfc_key_fn, FILE_PATH_SIZE, &mfckeyfnlen); + CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)mfc_key_fn, FILE_PATH_SIZE, &mfckeyfnlen); - int mfcdictfnlen = 0; - char mfc_dict_fn[FILE_PATH_SIZE] = {0}; - CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)mfc_dict_fn, FILE_PATH_SIZE, &mfcdictfnlen); - - bool nosave = arg_get_lit(ctx, 7); - bool verbose = arg_get_lit(ctx, 8); - bool no_default = arg_get_lit(ctx, 9); + bool nosave = arg_get_lit(ctx, 5); + bool verbose = arg_get_lit(ctx, 6); CLIParserFree(ctx); @@ -2137,7 +2055,7 @@ static int CmdHFMFPDump(const char *Cmd) { PrintAndLogEx(NORMAL, ""); // ======================================== - // Phase 0: Key file loading (no card interaction yet) + // Load keys // ======================================== // AES keys: aesFoundKeys[keytype][sector][0]=found, [1..16]=key @@ -2148,7 +2066,7 @@ static int CmdHFMFPDump(const char *Cmd) { uint8_t mfcFoundKeys[2][64][MIFARE_KEY_SIZE + 1]; memset(mfcFoundKeys, 0, sizeof(mfcFoundKeys)); - // 0a. Load AES keys from JSON key file (from hf mfp chk --dump) + // Load AES keys from JSON key file (from hf mfp chk --dump) if (keyfnlen > 0) { res = mfp_load_keys_from_json(key_fn, aesFoundKeys); if (res != PM3_SUCCESS) { @@ -2156,18 +2074,18 @@ static int CmdHFMFPDump(const char *Cmd) { } else { int cnt = 0; for (uint8_t s = 0; s < numSectors; s++) { - if (aesFoundKeys[0][s][0]) cnt++; - if (aesFoundKeys[1][s][0]) cnt++; + if (aesFoundKeys[MF_KEY_A][s][0]) cnt++; + if (aesFoundKeys[MF_KEY_B][s][0]) cnt++; } PrintAndLogEx(SUCCESS, "Loaded " _GREEN_("%d") " AES keys from key file", cnt); } } - // 0b. Apply user-supplied AES key to all slots that don't have one yet + // Apply user-supplied AES key to all slots that don't have one yet if (userkeylen == AES_KEY_LEN) { int applied = 0; for (uint8_t s = 0; s < numSectors; s++) { - for (uint8_t kt = 0; kt < 2; kt++) { + for (uint8_t kt = MF_KEY_A; kt <= MF_KEY_B; kt++) { if (aesFoundKeys[kt][s][0] == 0) { aesFoundKeys[kt][s][0] = 1; memcpy(&aesFoundKeys[kt][s][1], userkey, AES_KEY_LEN); @@ -2178,7 +2096,7 @@ static int CmdHFMFPDump(const char *Cmd) { PrintAndLogEx(SUCCESS, "Applied user AES key to " _GREEN_("%d") " key slots", applied); } - // 0c. Load MFC keys from binary key file (from hf mf chk/autopwn) + // Load MFC keys from binary key file (from hf mf chk) if (mfckeyfnlen > 0) { res = mfp_load_mfc_keys_from_bin(mfc_key_fn, mfcFoundKeys, numSectors); if (res != PM3_SUCCESS) { @@ -2186,218 +2104,29 @@ static int CmdHFMFPDump(const char *Cmd) { } else { int cnt = 0; for (uint8_t s = 0; s < numSectors; s++) { - if (mfcFoundKeys[0][s][0]) cnt++; - if (mfcFoundKeys[1][s][0]) cnt++; + if (mfcFoundKeys[MF_KEY_A][s][0]) cnt++; + if (mfcFoundKeys[MF_KEY_B][s][0]) cnt++; } PrintAndLogEx(SUCCESS, "Loaded " _GREEN_("%d") " MFC (CRYPTO1) keys from key file", cnt); } } - // 0d. Build combined MFC probe key list (dictionary + defaults) - uint8_t *mfcProbeKeys = NULL; - uint32_t mfcProbeKeyCnt = 0; - - if (!no_default) { - uint8_t *dict_keys = NULL; - uint32_t dict_cnt = 0; - if (mfcdictfnlen > 0) { - loadFileDICTIONARY_safe(mfc_dict_fn, (void **)&dict_keys, MIFARE_KEY_SIZE, &dict_cnt); - } - - uint8_t *def_keys = NULL; - uint32_t def_cnt = 0; - mfp_load_mfc_default_keys(&def_keys, &def_cnt); - - uint32_t total = dict_cnt + def_cnt; - if (total > 0) { - mfcProbeKeys = calloc(total, MIFARE_KEY_SIZE); - if (mfcProbeKeys) { - if (dict_cnt > 0) { - memcpy(mfcProbeKeys, dict_keys, dict_cnt * MIFARE_KEY_SIZE); - } - if (def_cnt > 0) { - memcpy(mfcProbeKeys + dict_cnt * MIFARE_KEY_SIZE, def_keys, def_cnt * MIFARE_KEY_SIZE); - } - mfcProbeKeyCnt = total; - } - } - free(dict_keys); - free(def_keys); - } - // ======================================== - // Phase 1: Classify sectors as SL3 or SL1 + // Read sectors with loaded keys // ======================================== - // Try one MFC key (FFFFFFFFFFFF) on each sector to determine SL. - // mf_read_sector returns: - // PM3_SUCCESS -> SL1 confirmed, key found - // PM3_EUNDEF -> SL1 confirmed (card responded to ISO 14443-3 auth), wrong key - // PM3_ETIMEOUT -> SL3 (card doesn't respond to ISO 14443-3) - - // Suppress firmware debug messages during classification and key probing. - // The many auth attempts produce a flood of "Auth error" / "Can't select card" - // messages from the firmware that interfere with other tools reading stdout. - uint8_t dbg_curr = DBG_NONE; - if (getDeviceDebugLevel(&dbg_curr) != PM3_SUCCESS) { - free(mfcProbeKeys); - return PM3_EFAILED; - } - setDeviceDebugLevel(DBG_NONE, false); + // Determine SL for each sector based on which keys are available uint8_t sectorSL[64]; memset(sectorSL, MFP_SL_UNKNOWN, sizeof(sectorSL)); - - // Sectors with pre-loaded keys already have a known SL for (uint8_t s = 0; s < numSectors; s++) { - if (aesFoundKeys[0][s][0] || aesFoundKeys[1][s][0]) { + if (aesFoundKeys[MF_KEY_A][s][0] || aesFoundKeys[MF_KEY_B][s][0]) { sectorSL[s] = MFP_SL_3; } - if (mfcFoundKeys[0][s][0] || mfcFoundKeys[1][s][0]) { + if (mfcFoundKeys[MF_KEY_A][s][0] || mfcFoundKeys[MF_KEY_B][s][0]) { sectorSL[s] = MFP_SL_1; } } - // Probe unclassified sectors with FFFFFFFFFFFF (CRYPTO1) - { - uint8_t probe_key[MIFARE_KEY_SIZE]; - memset(probe_key, 0xFF, MIFARE_KEY_SIZE); - uint8_t dummy[16 * 16] = {0}; - PrintAndLogEx(INFO, "Classifying sector security levels..."); - - for (uint8_t s = 0; s < numSectors; s++) { - if (sectorSL[s] != MFP_SL_UNKNOWN) { - continue; - } - - DropField(); - res = mf_read_sector(s, 0, probe_key, dummy); - if (res == PM3_SUCCESS) { - // FFFFFFFFFFFF worked -> SL1 with default key - sectorSL[s] = MFP_SL_1; - mfcFoundKeys[0][s][0] = 1; - memcpy(&mfcFoundKeys[0][s][1], probe_key, MIFARE_KEY_SIZE); - } else if (res == PM3_EUNDEF) { - // Card responded to ISO 14443-3 but wrong key -> SL1 - sectorSL[s] = MFP_SL_1; - } else { - // Timeout or other error -> SL3 - sectorSL[s] = MFP_SL_3; - } - } - DropField(); - - int pre_sl3 = 0, pre_sl1 = 0; - for (uint8_t s = 0; s < numSectors; s++) { - if (sectorSL[s] == MFP_SL_3) pre_sl3++; - else if (sectorSL[s] == MFP_SL_1) pre_sl1++; - } - PrintAndLogEx(SUCCESS, "Sector classification: " _GREEN_("%d") " SL3, " _YELLOW_("%d") " SL1", - pre_sl3, pre_sl1); - } - - // ======================================== - // Phase 2: AES key probing (SL3 sectors only) - // ======================================== - // plus_key_check skips sectors where foundKeys[][sector][0] is set, - // so we mark SL1 sectors with a dummy key to exclude them. - { - // Save and temporarily mark SL1 sectors so plus_key_check skips them - uint8_t sl1_backup[2][64]; - memset(sl1_backup, 0, sizeof(sl1_backup)); - - for (uint8_t s = 0; s < numSectors; s++) { - if (sectorSL[s] == MFP_SL_1) { - for (uint8_t kt = 0; kt < 2; kt++) { - sl1_backup[kt][s] = aesFoundKeys[kt][s][0]; - if (aesFoundKeys[kt][s][0] == 0) { - // Mark as "found" with dummy so plus_key_check skips it - aesFoundKeys[kt][s][0] = 0xFF; - } - } - } - } - - bool need_aes_probe = false; - for (uint8_t s = 0; s < numSectors; s++) { - if (sectorSL[s] == MFP_SL_3 && - (aesFoundKeys[0][s][0] == 0 || aesFoundKeys[1][s][0] == 0)) { - need_aes_probe = true; - break; - } - } - - if (need_aes_probe && !no_default) { - uint8_t *key_block = NULL; - uint32_t keycnt = 0; - res = mfp_load_keys(&key_block, &keycnt, NULL, 0, dict_fn, dictfnlen, card.uid, true); - if (res == PM3_SUCCESS && keycnt > 0) { - int sl3_unknown = 0; - for (uint8_t s = 0; s < numSectors; s++) { - if (sectorSL[s] == MFP_SL_3 && - (aesFoundKeys[0][s][0] == 0 || aesFoundKeys[1][s][0] == 0)) { - sl3_unknown++; - } - } - PrintAndLogEx(INFO, "Probing " _YELLOW_("%u") " AES keys against %d SL3 sectors...", keycnt, sl3_unknown); - uint8_t end_sector = numSectors - 1; - res = plus_key_check(0, end_sector, 0, 1, key_block, keycnt, aesFoundKeys, verbose, true); - if (res == PM3_EOPABORTED) { - PrintAndLogEx(WARNING, "\nAborted"); - } - PrintAndLogEx(NORMAL, ""); - } - free(key_block); - } - - // Restore SL1 sector dummy markers - for (uint8_t s = 0; s < numSectors; s++) { - if (sectorSL[s] == MFP_SL_1) { - for (uint8_t kt = 0; kt < 2; kt++) { - if (aesFoundKeys[kt][s][0] == 0xFF) { - aesFoundKeys[kt][s][0] = sl1_backup[kt][s]; - } - } - } - } - } - - // ======================================== - // Phase 3: MFC key probing (SL1 sectors only) - // ======================================== - if (mfcProbeKeyCnt > 0 && mfcProbeKeys != NULL) { - int sl1_need_keys = 0; - for (uint8_t s = 0; s < numSectors; s++) { - if (sectorSL[s] == MFP_SL_1 && - (mfcFoundKeys[0][s][0] == 0 || mfcFoundKeys[1][s][0] == 0)) { - sl1_need_keys++; - } - } - - if (sl1_need_keys > 0) { - PrintAndLogEx(INFO, "Probing " _YELLOW_("%u") " MFC keys against %d SL1 sectors...", mfcProbeKeyCnt, sl1_need_keys); - for (uint8_t s = 0; s < numSectors; s++) { - if (kbd_enter_pressed()) { - PrintAndLogEx(WARNING, "\naborted via keyboard"); - break; - } - if (sectorSL[s] != MFP_SL_1) { - continue; - } - if (mfcFoundKeys[0][s][0] && mfcFoundKeys[1][s][0]) { - continue; - } - mfp_sl1_try_keys(s, mfcProbeKeys, mfcProbeKeyCnt, mfcFoundKeys, verbose); - } - PrintAndLogEx(NORMAL, ""); - } - } - - // Restore firmware debug level before reading - setDeviceDebugLevel(dbg_curr, false); - - // ======================================== - // Phase 4: Read sectors with found keys - // ======================================== uint16_t totalBlocks = 0; for (uint8_t s = 0; s < numSectors; s++) { totalBlocks += mfNumBlocksPerSector(s); @@ -2406,7 +2135,7 @@ static int CmdHFMFPDump(const char *Cmd) { uint8_t *carddata = calloc(totalBlocks * MFBLOCK_SIZE, sizeof(uint8_t)); if (carddata == NULL) { PrintAndLogEx(ERR, "Failed to allocate memory"); - free(mfcProbeKeys); + return PM3_EMALLOC; } @@ -2432,7 +2161,7 @@ static int CmdHFMFPDump(const char *Cmd) { if (sectorSL[s] == MFP_SL_3) { // --- Try SL3 (AES) --- - for (uint8_t kt = 0; kt < 2 && !readOK; kt++) { + for (uint8_t kt = MF_KEY_A; kt <= MF_KEY_B && !readOK; kt++) { if (aesFoundKeys[kt][s][0] == 0) { continue; } @@ -2446,13 +2175,13 @@ static int CmdHFMFPDump(const char *Cmd) { sectorsRead++; sl3Count++; } else if (verbose) { - PrintAndLogEx(DEBUG, "Sector %u SL3 key%s failed: %d", s, (kt == 0) ? "A" : "B", res); + PrintAndLogEx(DEBUG, "Sector %u SL3 key%s failed: %d", s, (kt == MF_KEY_A) ? "A" : "B", res); } } } else if (sectorSL[s] == MFP_SL_1) { // --- Try SL1 (CRYPTO1) --- DropField(); - for (uint8_t kt = 0; kt < 2 && !readOK; kt++) { + for (uint8_t kt = MF_KEY_A; kt <= MF_KEY_B && !readOK; kt++) { if (mfcFoundKeys[kt][s][0] == 0) { continue; } @@ -2466,7 +2195,7 @@ static int CmdHFMFPDump(const char *Cmd) { sectorsRead++; sl1Count++; } else if (verbose) { - PrintAndLogEx(DEBUG, "Sector %u SL1 key%s failed: %d", s, (kt == 0) ? "A" : "B", res); + PrintAndLogEx(DEBUG, "Sector %u SL1 key%s failed: %d", s, (kt == MF_KEY_A) ? "A" : "B", res); } } } @@ -2500,26 +2229,26 @@ static int CmdHFMFPDump(const char *Cmd) { switch (sectorSL[s]) { case MFP_SL_3: slStr = _GREEN_("3 "); - if (aesFoundKeys[0][s][0]) { - snprintf(strA, sizeof(strA), _GREEN_("%s"), sprint_hex_inrow(&aesFoundKeys[0][s][1], AES_KEY_LEN)); + if (aesFoundKeys[MF_KEY_A][s][0]) { + snprintf(strA, sizeof(strA), _GREEN_("%s"), sprint_hex_inrow(&aesFoundKeys[MF_KEY_A][s][1], AES_KEY_LEN)); } else { snprintf(strA, sizeof(strA), _RED_("%s"), "--------------------------------"); } - if (aesFoundKeys[1][s][0]) { - snprintf(strB, sizeof(strB), _GREEN_("%s"), sprint_hex_inrow(&aesFoundKeys[1][s][1], AES_KEY_LEN)); + if (aesFoundKeys[MF_KEY_B][s][0]) { + snprintf(strB, sizeof(strB), _GREEN_("%s"), sprint_hex_inrow(&aesFoundKeys[MF_KEY_B][s][1], AES_KEY_LEN)); } else { snprintf(strB, sizeof(strB), _RED_("%s"), "--------------------------------"); } break; case MFP_SL_1: slStr = _YELLOW_("1 "); - if (mfcFoundKeys[0][s][0]) { - snprintf(strA, sizeof(strA), _GREEN_("%s") " ", sprint_hex_inrow(&mfcFoundKeys[0][s][1], MIFARE_KEY_SIZE)); + if (mfcFoundKeys[MF_KEY_A][s][0]) { + snprintf(strA, sizeof(strA), _GREEN_("%s") " ", sprint_hex_inrow(&mfcFoundKeys[MF_KEY_A][s][1], MIFARE_KEY_SIZE)); } else { snprintf(strA, sizeof(strA), _RED_("%s"), "--------------------------------"); } - if (mfcFoundKeys[1][s][0]) { - snprintf(strB, sizeof(strB), _GREEN_("%s") " ", sprint_hex_inrow(&mfcFoundKeys[1][s][1], MIFARE_KEY_SIZE)); + if (mfcFoundKeys[MF_KEY_B][s][0]) { + snprintf(strB, sizeof(strB), _GREEN_("%s") " ", sprint_hex_inrow(&mfcFoundKeys[MF_KEY_B][s][1], MIFARE_KEY_SIZE)); } else { snprintf(strB, sizeof(strB), _RED_("%s"), "--------------------------------"); } @@ -2555,7 +2284,7 @@ static int CmdHFMFPDump(const char *Cmd) { if (nosave) { PrintAndLogEx(INFO, "Called with no-save option"); free(carddata); - free(mfcProbeKeys); + return PM3_SUCCESS; } @@ -2570,7 +2299,7 @@ static int CmdHFMFPDump(const char *Cmd) { if (fptr == NULL) { PrintAndLogEx(ERR, "Failed to allocate memory"); free(carddata); - free(mfcProbeKeys); + return PM3_EMALLOC; } strcpy(fptr, "hf-mfp-"); @@ -2587,7 +2316,6 @@ static int CmdHFMFPDump(const char *Cmd) { } free(carddata); - free(mfcProbeKeys); return PM3_SUCCESS; } From 7159711734bb88263ec1377dc5043267803fef8c Mon Sep 17 00:00:00 2001 From: Tomas Nilsson Date: Sun, 8 Mar 2026 19:18:24 +0100 Subject: [PATCH 6/6] Auto-detect key files by UID in hf mfp dump When no --keys or --mfc-keys arguments are given, automatically look for hf-mfp--key.json and hf-mf--key.bin files, matching the output of hf mfp chk --dump and hf mf chk --dump. Fail with helpful message if no keys are available, matching the behaviour of hf mf dump. --- client/src/cmdhfmfp.c | 66 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/client/src/cmdhfmfp.c b/client/src/cmdhfmfp.c index a6b182fd1..3dc3f5078 100644 --- a/client/src/cmdhfmfp.c +++ b/client/src/cmdhfmfp.c @@ -1901,16 +1901,16 @@ static int CmdHFMFPChk(const char *Cmd) { return PM3_SUCCESS; } -static int mfp_load_keys_from_json(const char *filename, uint8_t foundKeys[2][64][AES_KEY_LEN + 1]) { +static int mfp_load_keys_from_json(const char *filename, uint8_t foundKeys[2][64][AES_KEY_LEN + 1], bool verbose) { - // loadFileJSON handles "mfpkeys" file type via loadFileJSONex. + // loadFileJSONex handles "mfpkeys" file type. // Buffer layout: UID(7) + pad(3) + SAK(1) + ATQA(2) + ATSlen(1) + ATS(atslen) // then flat keys: KeyA0(16) KeyB0(16) KeyA1(16) KeyB1(16) ... uint8_t data[14 + 256 + (2 * 64 * AES_KEY_LEN)]; memset(data, 0, sizeof(data)); size_t datalen = 0; - int res = loadFileJSON(filename, data, sizeof(data), &datalen, NULL); + int res = loadFileJSONex(filename, data, sizeof(data), &datalen, verbose, NULL); if (res != PM3_SUCCESS) { return res; } @@ -1943,13 +1943,13 @@ static int mfp_load_keys_from_json(const char *filename, uint8_t foundKeys[2][64 #define MFP_SL_3 3 // Load MFC (CRYPTO1) keys from a binary key file (first half keyA, second half keyB) -static int mfp_load_mfc_keys_from_bin(const char *filename, uint8_t mfcFoundKeys[2][64][MIFARE_KEY_SIZE + 1], uint8_t numSectors) { +static int mfp_load_mfc_keys_from_bin(const char *filename, uint8_t mfcFoundKeys[2][64][MIFARE_KEY_SIZE + 1], uint8_t numSectors, bool verbose) { uint8_t *keyA = NULL; uint8_t *keyB = NULL; size_t alen = 0, blen = 0; - int res = loadFileBinaryKey(filename, "", (void **)&keyA, (void **)&keyB, &alen, &blen, true); + int res = loadFileBinaryKey(filename, "", (void **)&keyA, (void **)&keyB, &alen, &blen, verbose); if (res != PM3_SUCCESS) { return res; } @@ -1984,7 +1984,9 @@ static int CmdHFMFPDump(const char *Cmd) { "Dump MIFARE Plus tag to file (bin/json)\n" "Reads sectors using keys from `hf mfp chk --dump` (AES/SL3)\n" "and/or `hf mf chk` key file (CRYPTO1/SL1) for mixed-mode cards.\n" + "Key files are auto-detected by UID if not specified.\n" "If no given, UID will be used as filename", + "hf mfp dump\n" "hf mfp dump --keys hf-mfp-01020304-key.json\n" "hf mfp dump --keys hf-mfp-01020304-key.json --mfc-keys hf-mf-01020304-key.bin\n" "hf mfp dump -k ffffffffffffffffffffffffffffffff\n"); @@ -2066,11 +2068,25 @@ static int CmdHFMFPDump(const char *Cmd) { uint8_t mfcFoundKeys[2][64][MIFARE_KEY_SIZE + 1]; memset(mfcFoundKeys, 0, sizeof(mfcFoundKeys)); + // Auto-detect AES key file by UID if not specified + char *aes_fptr = NULL; + if (keyfnlen == 0) { + aes_fptr = calloc(sizeof(char) * (strlen("hf-mfp-") + strlen("-key")) + card.uidlen * 2 + 1, sizeof(uint8_t)); + if (aes_fptr != NULL) { + strcpy(aes_fptr, "hf-mfp-"); + FillFileNameByUID(aes_fptr, card.uid, "-key", card.uidlen); + strncpy(key_fn, aes_fptr, FILE_PATH_SIZE - 1); + keyfnlen = strlen(key_fn); + } + } + // Load AES keys from JSON key file (from hf mfp chk --dump) if (keyfnlen > 0) { - res = mfp_load_keys_from_json(key_fn, aesFoundKeys); + res = mfp_load_keys_from_json(key_fn, aesFoundKeys, (aes_fptr == NULL)); if (res != PM3_SUCCESS) { - PrintAndLogEx(WARNING, "Failed to load AES key file, continuing without"); + if (aes_fptr == NULL) { + PrintAndLogEx(WARNING, "Failed to load AES key file, continuing without"); + } } else { int cnt = 0; for (uint8_t s = 0; s < numSectors; s++) { @@ -2080,6 +2096,7 @@ static int CmdHFMFPDump(const char *Cmd) { PrintAndLogEx(SUCCESS, "Loaded " _GREEN_("%d") " AES keys from key file", cnt); } } + free(aes_fptr); // Apply user-supplied AES key to all slots that don't have one yet if (userkeylen == AES_KEY_LEN) { @@ -2096,11 +2113,25 @@ static int CmdHFMFPDump(const char *Cmd) { PrintAndLogEx(SUCCESS, "Applied user AES key to " _GREEN_("%d") " key slots", applied); } + // Auto-detect MFC key file by UID if not specified + char *mfc_fptr = NULL; + if (mfckeyfnlen == 0) { + mfc_fptr = calloc(sizeof(char) * (strlen("hf-mf-") + strlen("-key.bin")) + card.uidlen * 2 + 1, sizeof(uint8_t)); + if (mfc_fptr != NULL) { + strcpy(mfc_fptr, "hf-mf-"); + FillFileNameByUID(mfc_fptr, card.uid, "-key.bin", card.uidlen); + strncpy(mfc_key_fn, mfc_fptr, FILE_PATH_SIZE - 1); + mfckeyfnlen = strlen(mfc_key_fn); + } + } + // Load MFC keys from binary key file (from hf mf chk) if (mfckeyfnlen > 0) { - res = mfp_load_mfc_keys_from_bin(mfc_key_fn, mfcFoundKeys, numSectors); + res = mfp_load_mfc_keys_from_bin(mfc_key_fn, mfcFoundKeys, numSectors, (mfc_fptr == NULL)); if (res != PM3_SUCCESS) { - PrintAndLogEx(WARNING, "Failed to load MFC key file, continuing without"); + if (mfc_fptr == NULL) { + PrintAndLogEx(WARNING, "Failed to load MFC key file, continuing without"); + } } else { int cnt = 0; for (uint8_t s = 0; s < numSectors; s++) { @@ -2110,6 +2141,23 @@ static int CmdHFMFPDump(const char *Cmd) { PrintAndLogEx(SUCCESS, "Loaded " _GREEN_("%d") " MFC (CRYPTO1) keys from key file", cnt); } } + free(mfc_fptr); + + // Check that we have at least some keys to work with + bool have_keys = (userkeylen == AES_KEY_LEN); + if (!have_keys) { + for (uint8_t s = 0; s < numSectors; s++) { + if (aesFoundKeys[MF_KEY_A][s][0] || aesFoundKeys[MF_KEY_B][s][0] || + mfcFoundKeys[MF_KEY_A][s][0] || mfcFoundKeys[MF_KEY_B][s][0]) { + have_keys = true; + break; + } + } + } + if (!have_keys) { + PrintAndLogEx(ERR, "No keys available. Run " _YELLOW_("`hf mfp chk --dump`") " and/or " _YELLOW_("`hf mf chk --dump`") " first"); + return PM3_ENODATA; + } // ======================================== // Read sectors with loaded keys