From 22a307dd69d489e0ae51310c15f6368dc9434faf Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Fri, 6 Mar 2026 17:46:25 +0200 Subject: [PATCH] Implement 'hf mfdes bruteisofid' command --- CHANGELOG.md | 2 + client/src/cmdhfmfdes.c | 239 ++++++++++++++++++++++++++++++++ client/src/mifare/desfirecore.c | 26 ++++ client/src/pm3line_vocabulary.h | 2 + 4 files changed, 269 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f0c4b935..c721610ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +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] +- 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) +- Added tag loss detection & recovery into `hf mfdes bruteaid` (@kormax) - Added --credit option for `hf iclass legrec` command to perform a credit key recovery. This is experimental and unfinished as it only partially works.(@antiklesys) - Added hardening for all host binaries. Exact level of hardening depends on the OS (@doegox) - Added `hf aliro read` command (@kormax) diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index bbeb9bc0d..276e6b241 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -55,6 +55,8 @@ #define MFDES_BRUTEAID_RESELECT_WAIT_MS 200 #define MFDES_BRUTEAID_MAD_START 0xF0000FU #define MFDES_BRUTEAID_MAD_STEP 0x10U +#define MFDES_BRUTEFID_RESELECT_ATTEMPTS 3 +#define MFDES_BRUTEFID_RESELECT_WAIT_MS 500 #define status(x) ( ((uint16_t)(0x91 << 8)) + (uint16_t)x ) /* @@ -2199,6 +2201,93 @@ static int CmdHF14ADesSelectApp(const char *Cmd) { return res; } +static int CmdHF14ADesISOSelectISOFID(DesfireContext_t *dctx, uint16_t fileid, uint8_t *resp, size_t *resplen, bool fieldon) { + // ISO 7816 SELECT FILE command: CLA=00, INS=A4, P1=00, P2=00, Lc=2 + uint8_t data[2] = {0}; + Uint2byteToMemBe(data, fileid); + + return DesfireISOSelectEx(dctx, fieldon, ISSMFDFEF, data, sizeof(data), resp, resplen); +} + +static int CmdHF14ADesSelectISOFID(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf mfdes selectisofid", + "Select file via ISO Select command by 2-byte ISO file identifier.\n" + "Optionally preselect an application by AID or DF name before selecting the file.", + "hf mfdes selectisofid --isofid e104 -> select file 0xE104\n" + "hf mfdes selectisofid --aid 123456 --isofid 00ef -> select file 0x00EF in app 0x123456\n" + "hf mfdes selectisofid --dfname D2760000850100 --isofid 00ef --apdu -> select file 0x00EF after DF name selection and show APDU logs"); + + void *argtable[] = { + arg_param_begin, + arg_lit0("a", "apdu", "Show APDU requests and responses"), + arg_lit0("v", "verbose", "Verbose output"), + arg_str0(NULL, "aid", "", "Application ID (3 hex bytes, big endian)"), + arg_str0(NULL, "dfname", "", "Application ISO DF Name (1-16 hex bytes, big endian)"), + arg_str0(NULL, "isofid", "", "File ISO ID (ISO EF ID) (2 hex bytes, big endian)"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + bool APDULogging = arg_get_lit(ctx, 1); + bool verbose = arg_get_lit(ctx, 2); + + DesfireContext_t dctx = {0}; + int securechann = defaultSecureChannel; + uint32_t appid = 0x000000; + DesfireISOSelectWay selectway = ISWDFName; + int res = CmdDesGetSessionParameters(ctx, &dctx, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 4, + &securechann, DCMNone, &appid, &selectway); + if (res) { + CLIParserFree(ctx); + return res; + } + + uint32_t isoid = 0x0000; + bool isoidpresent = false; + if (CLIGetUint32Hex(ctx, 5, 0x0000, &isoid, &isoidpresent, 2, "File ISO ID must have 2 hex bytes")) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + if (!isoidpresent) { + CLIParserFree(ctx); + PrintAndLogEx(WARNING, "File ISO ID is required"); + return PM3_EINVARG; + } + + bool aidpresent = (arg_get_str(ctx, 3)->count > 0); + bool preselect = aidpresent || (dctx.selectedDFNameLen > 0); + SetAPDULogging(APDULogging); + CLIParserFree(ctx); + + uint8_t resp[250] = {0}; + size_t resplen = 0; + if (preselect) { + res = DesfireSelectAndAuthenticateW(&dctx, securechann, selectway, appid, false, 0, true, verbose); + if (res != PM3_SUCCESS) { + DropField(); + PrintAndLogEx(FAILED, "Application preselect " _RED_("failed")); + return res; + } + } + + res = CmdHF14ADesISOSelectISOFID(&dctx, isoid, resp, &resplen, !preselect); + if (res != PM3_SUCCESS) { + DropField(); + PrintAndLogEx(FAILED, "ISO Select file 0x%04x " _RED_("failed"), isoid); + return res; + } + + if (resplen > 0 && verbose) { + PrintAndLogEx(INFO, "File select response [%zu] %s", resplen, sprint_hex(resp, resplen)); + } + + PrintAndLogEx(SUCCESS, "File 0x%04x selected " _GREEN_("succesfully"), isoid); + DropField(); + return PM3_SUCCESS; +} + + typedef struct { uint32_t current; uint32_t step; @@ -2729,6 +2818,154 @@ static int CmdHF14ADesBruteApps(const char *Cmd) { return PM3_SUCCESS; } +static int CmdHF14ADesBruteISOFIDs(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf mfdes bruteisofid", + "Recover ISO file IDs by bruteforce.\n" + "WARNING: This command takes a loooong time", + "hf mfdes bruteisofid --aid 123456 -> bruteforce ISO file IDs for application 123456\n" + "hf mfdes bruteisofid --start 0000 --end 0fff -> bruteforce specific file ISO ID range"); + + void *argtable[] = { + arg_param_begin, + arg_lit0("a", "apdu", "Show APDU requests and responses"), + arg_lit0("v", "verbose", "Verbose output"), + arg_str0(NULL, "aid", "", "Application ID (3 hex bytes, big endian)"), + arg_str0(NULL, "isoid", "", "Application ISO ID (ISO DF ID) (2 hex bytes, big endian)"), + arg_str0(NULL, "dfname", "", "Application ISO DF Name (5-16 hex bytes, big endian)"), + arg_str0(NULL, "start", "", "Starting File ISO ID (2 hex bytes, big endian)"), + arg_str0(NULL, "end", "", "Last File ISO ID (2 hex bytes, big endian)"), + arg_int0(NULL, "step", "", "Increment step when bruteforcing"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + bool APDULogging = arg_get_lit(ctx, 1); + bool verbose = arg_get_lit(ctx, 2); + + DesfireContext_t dctx = {0}; + int securechann = defaultSecureChannel; + uint32_t appid = 0x000000; + DesfireISOSelectWay selectway = ISW6bAID; + int res = CmdDesGetSessionParameters(ctx, &dctx, 0, 0, 0, 0, 0, 0, 0, 0, 3, 4, 5, + &securechann, DCMNone, &appid, &selectway); + if (res) { + CLIParserFree(ctx); + return res; + } + + uint32_t isoidStart = 0x0000; + if (CLIGetUint32Hex(ctx, 6, 0x0000, &isoidStart, NULL, 2, "File ISO ID start must have 2 hex bytes")) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + + uint32_t isoidEnd = 0xFFFF; + if (CLIGetUint32Hex(ctx, 7, 0xFFFF, &isoidEnd, NULL, 2, "File ISO ID end must have 2 hex bytes")) { + CLIParserFree(ctx); + return PM3_EINVARG; + } + + int stepArg = arg_get_int_def(ctx, 8, 1); + CLIParserFree(ctx); + + if (stepArg <= 0) { + PrintAndLogEx(ERR, "Increment step should be greater than zero"); + return PM3_EINVARG; + } + uint32_t step = (uint32_t)stepArg; + + if (isoidStart > isoidEnd) { + PrintAndLogEx(ERR, "Start should be lower than or equal to end. start: %04x end: %04x", isoidStart, isoidEnd); + return PM3_EINVARG; + } + + SetAPDULogging(APDULogging); + + res = DesfireSelectAndAuthenticateW(&dctx, securechann, selectway, appid, false, 0, true, verbose); + if (res != PM3_SUCCESS) { + DropField(); + PrintAndLogEx(FAILED, "Select or authentication %s " _RED_("failed") ". Result [%d] %s", DesfireWayIDStr(selectway, appid), res, DesfireAuthErrorToStr(res)); + return res; + } + + uint64_t totalCount = ((isoidEnd - isoidStart) / step) + 1; + uint64_t tested = 0; + uint64_t found = 0; + + PrintAndLogEx(INFO, "Bruteforcing file ISO IDs range " _YELLOW_("%04x") "-" _YELLOW_("%04x") " step " _YELLOW_("%u") " candidates " _YELLOW_("%llu"), + isoidStart, isoidEnd, step, (unsigned long long)totalCount); + + if (totalCount > 0x7fffffffULL) { + PrintAndLogEx(INFO, "Enumerating through all File IDs manually, this will take a while!"); + } + + for (uint32_t isofid = isoidStart;; isofid += step) { + if (kbd_enter_pressed()) { + break; + } + + tested++; + float progress = (totalCount > 0) ? ((float)tested / (float)totalCount) * 100.0f : 100.0f; + PrintAndLogEx(INPLACE, "Brute DESFire ISO File ID Progress " _YELLOW_("%0.1f") " %% current ID: %04X", progress, isofid); + + uint8_t resp[250] = {0}; + size_t resplen = 0; + uint8_t *resp_p = verbose ? resp : NULL; + size_t *resplen_p = verbose ? &resplen : NULL; + res = CmdHF14ADesISOSelectISOFID(&dctx, isofid, resp_p, resplen_p, false); + + if (res == PM3_ECARDEXCHANGE || res == PM3_ETIMEOUT || res == PM3_ERFTRANS) { + for (int attempt = 1; attempt <= MFDES_BRUTEFID_RESELECT_ATTEMPTS; attempt++) { + printf("\33[2K\r"); // clear current inplace progress line before logging + PrintAndLogEx(WARNING, "No card response while checking ISO ID " _YELLOW_("%04X") ". Reselecting card (%d/%d)...", + isofid, attempt, MFDES_BRUTEFID_RESELECT_ATTEMPTS); + + msleep(MFDES_BRUTEFID_RESELECT_WAIT_MS); + + res = DesfireSelectAndAuthenticateW(&dctx, securechann, selectway, appid, false, 0, true, verbose); + if (res != PM3_SUCCESS) { + continue; + } + + res = CmdHF14ADesISOSelectISOFID(&dctx, isofid, resp_p, resplen_p, false); + if (res == PM3_SUCCESS || res == PM3_EAPDU_FAIL || + (res != PM3_ECARDEXCHANGE && res != PM3_ETIMEOUT && res != PM3_ERFTRANS)) { + break; + } + } + + if (res == PM3_ECARDEXCHANGE || res == PM3_ETIMEOUT || res == PM3_ERFTRANS) { + PrintAndLogEx(FAILED, "Card is not responding after %d reselect attempts. Aborting at ISO ID " _YELLOW_("%04X"), + MFDES_BRUTEFID_RESELECT_ATTEMPTS, isofid); + DropField(); + return res; + } + } + + if (res == PM3_SUCCESS) { + found++; + printf("\33[2K\r"); // clear current line before printing + PrintAndLogEx(SUCCESS, "Found new ISO file ID " _GREEN_("0x%04X"), isofid); + if (verbose && resplen > 0) { + PrintAndLogEx(INFO, "Response [%zu] " _CYAN_("%s"), resplen, sprint_hex(resp, resplen)); + } + } else { + // Non-success status means no file with this identifier + } + + if (isoidEnd - isofid < step) { + break; + } + } + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, "Done! Found " _GREEN_("%llu") " matching file ISO ID candidate(s)", + (unsigned long long)found); + DropField(); + return PM3_SUCCESS; +} + // MIAFRE DESFire Authentication // keys: // NR DESC KEYLENGTH @@ -6396,12 +6633,14 @@ static command_t CommandTable[] = { {"createapp", CmdHF14ADesCreateApp, IfPm3Iso14443a, "Create Application"}, {"deleteapp", CmdHF14ADesDeleteApp, IfPm3Iso14443a, "Delete Application"}, {"selectapp", CmdHF14ADesSelectApp, IfPm3Iso14443a, "Select Application ID"}, + {"selectisofid", CmdHF14ADesSelectISOFID, IfPm3Iso14443a, "Select file by ISO ID"}, {"-----------", CmdHelp, IfPm3Iso14443a, "------------------------ " _CYAN_("Keys") " -----------------------"}, {"changekey", CmdHF14ADesChangeKey, IfPm3Iso14443a, "Change Key"}, {"chkeysettings", CmdHF14ADesChKeySettings, IfPm3Iso14443a, "Change Key Settings"}, {"getkeysettings", CmdHF14ADesGetKeySettings, IfPm3Iso14443a, "Get Key Settings"}, {"getkeyversions", CmdHF14ADesGetKeyVersions, IfPm3Iso14443a, "Get Key Versions"}, {"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("Files") " -----------------------"}, + {"bruteisofid", CmdHF14ADesBruteISOFIDs, IfPm3Iso14443a, "Recover file ISO IDs by bruteforce"}, {"getfileids", CmdHF14ADesGetFileIDs, IfPm3Iso14443a, "Get File IDs list"}, {"getfileisoids", CmdHF14ADesGetFileISOIDs, IfPm3Iso14443a, "Get File ISO IDs list"}, {"lsfiles", CmdHF14ADesLsFiles, IfPm3Iso14443a, "Show all files list"}, diff --git a/client/src/mifare/desfirecore.c b/client/src/mifare/desfirecore.c index 71fb8391b..c8993257e 100644 --- a/client/src/mifare/desfirecore.c +++ b/client/src/mifare/desfirecore.c @@ -1146,6 +1146,9 @@ int DesfireSelectAndAuthenticateW(DesfireContext_t *dctx, DesfireSecureChannel s res = DesfireISOSelect(dctx, ISSDFName, dctx->selectedDFName, dctx->selectedDFNameLen, resp, &resplen); if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Desfire DF name select " _RED_("error")); + if (res == PM3_ECARDEXCHANGE || res == PM3_ETIMEOUT || res == PM3_ERFTRANS) { + return res; + } return 200; } @@ -1164,6 +1167,9 @@ int DesfireSelectAndAuthenticateW(DesfireContext_t *dctx, DesfireSecureChannel s res = DesfireSelectAIDHex(dctx, id, false, 0); if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Desfire select " _RED_("error")); + if (res == PM3_ECARDEXCHANGE || res == PM3_ETIMEOUT || res == PM3_ERFTRANS) { + return res; + } return 200; } @@ -1177,6 +1183,9 @@ int DesfireSelectAndAuthenticateW(DesfireContext_t *dctx, DesfireSecureChannel s res = DesfireSelectEx(dctx, false, way, id, NULL); if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Desfire %s select " _RED_("error"), DesfireSelectWayToStr(way)); + if (res == PM3_ECARDEXCHANGE || res == PM3_ETIMEOUT || res == PM3_ERFTRANS) { + return res; + } return 202; } if (verbose) { @@ -1189,6 +1198,9 @@ int DesfireSelectAndAuthenticateW(DesfireContext_t *dctx, DesfireSecureChannel s res = DesfireSelectEx(dctx, false, way, id, NULL); if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Desfire %s select " _RED_("error"), DesfireSelectWayToStr(way)); + if (res == PM3_ECARDEXCHANGE || res == PM3_ETIMEOUT || res == PM3_ERFTRANS) { + return res; + } return 202; } @@ -1205,6 +1217,9 @@ int DesfireSelectAndAuthenticateW(DesfireContext_t *dctx, DesfireSecureChannel s res = DesfireSelectAIDHex(dctx, id, false, 0); if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Desfire select " _RED_("error")); + if (res == PM3_ECARDEXCHANGE || res == PM3_ETIMEOUT || res == PM3_ERFTRANS) { + return res; + } return 200; } @@ -1218,6 +1233,9 @@ int DesfireSelectAndAuthenticateW(DesfireContext_t *dctx, DesfireSecureChannel s res = DesfireSelectEx(dctx, true, way, id, NULL); if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Desfire %s select " _RED_("error"), DesfireSelectWayToStr(way)); + if (res == PM3_ECARDEXCHANGE || res == PM3_ETIMEOUT || res == PM3_ERFTRANS) { + return res; + } return 202; } if (verbose) { @@ -1229,6 +1247,9 @@ int DesfireSelectAndAuthenticateW(DesfireContext_t *dctx, DesfireSecureChannel s res = DesfireSelectEx(dctx, false, ISWIsoID, isofileid, NULL); if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "Desfire iso file select " _RED_("error")); + if (res == PM3_ECARDEXCHANGE || res == PM3_ETIMEOUT || res == PM3_ERFTRANS) { + return res; + } return 203; } @@ -3073,6 +3094,11 @@ int DesfireISOSelectEx(DesfireContext_t *dctx, bool fieldon, DesfireISOSelectCon size_t xresplen = 0; uint16_t sw = 0; int res = DesfireExchangeISO(fieldon, dctx, (sAPDU_t) {0x00, ISO7816_SELECT_FILE, cntr, ((resp == NULL) ? 0x0C : 0x00), datalen, data}, APDU_INCLUDE_LE_00, xresp, &xresplen, &sw); + // Distinguish no-response transport errors from valid APDU status errors + // (e.g. 6A82 for "file not found"), which also return PM3_EAPDU_FAIL. + if (res == PM3_EAPDU_FAIL && sw == 0 && xresplen == 0) { + return PM3_ECARDEXCHANGE; + } if (res == PM3_SUCCESS && sw != ISO7816_OK) return PM3_ESOFT; diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index e39cff120..211b3efc8 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -472,9 +472,11 @@ const static vocabulary_t vocabulary[] = { { 0, "hf mfdes getaids" }, { 0, "hf mfdes getappnames" }, { 0, "hf mfdes bruteaid" }, + { 0, "hf mfdes bruteisofid" }, { 0, "hf mfdes createapp" }, { 0, "hf mfdes deleteapp" }, { 0, "hf mfdes selectapp" }, + { 0, "hf mfdes selectisofid" }, { 0, "hf mfdes changekey" }, { 0, "hf mfdes chkeysettings" }, { 0, "hf mfdes getkeysettings" },