From c615ef8930e12e99fd770ae692290cc676098459 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sun, 13 Sep 2026 15:52:39 +0200 Subject: [PATCH 1/2] adapting parser --- client/src/parsers/parsehexact.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/client/src/parsers/parsehexact.c b/client/src/parsers/parsehexact.c index ef631a6e5..abe2d4e85 100644 --- a/client/src/parsers/parsehexact.c +++ b/client/src/parsers/parsehexact.c @@ -22,7 +22,8 @@ // sector 0 block 2 8 byte per card identifier, then a constant tail // sector 9 48 bytes, differs completely between cards // sector 11 32 bytes, differs completely between cards -// sector 15 the system identifier, the same on every card +// sector 15 the system text, the same on every card, and in block 2 +// the number printed on the fob, little endian // sector 16, 17 MIFARE Classic EV1 signature, not application data // // The sector 9 and 11 payload is not decoded. It carries no structure in common @@ -83,12 +84,30 @@ int hexact_parser_parse(const uint8_t *dump, size_t dumplen) { const uint8_t *id1 = hexact_sector(dump, dumplen, HEXACT_ID_SECTOR, 1); const uint8_t *id2 = hexact_sector(dump, dumplen, HEXACT_ID_SECTOR, 2); - PrintAndLogEx(INFO, "System............. %.16s", (const char *)id0); + // The sector spells the system out across its three blocks and it reads as + // one line, so join them. Each block pads its text with spaces, and the + // last one keeps four bytes of its own before the text starts. + char system[16 + 16 + 12 + 3] = {0}; + str_append(system, sizeof(system), "%.16s", (const char *)id0); + str_trim(system); + if (id1) { - PrintAndLogEx(INFO, " %.16s", (const char *)id1); + str_append(system, sizeof(system), " %.16s", (const char *)id1); + str_trim(system); } + if (id2) { - PrintAndLogEx(INFO, " %.12s", (const char *)(id2 + 4)); + str_append(system, sizeof(system), " %.12s", (const char *)(id2 + 4)); + str_trim(system); + } + + PrintAndLogEx(INFO, "System............. " _YELLOW_("%s"), system); + + // Sector 15 block 2 keeps the number engraved on the fob as a little endian + // value in front of the INTRATONE text. Checked against the engraving on + // four fobs, all four matched. + if (id2) { + PrintAndLogEx(INFO, "Printed serial..... " _YELLOW_("%u"), MemLeToUint4byte(id2)); } // sector 0 block 2 holds a per card value followed by a tail that is the From e19c6755fe5755d7de456cb52e3961255f82772f Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sun, 13 Sep 2026 15:56:41 +0200 Subject: [PATCH 2/2] hf mf: stop a refused nested auth from hanging autopwn and eating the keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MifareNested collected nonces in 'while (target_nt[i] == 0)' with no attempt counter. When the tag NAKs the nested authentication the loop re-sent the identical frame forever, so the client's 2s wait expired and autopwn returned PM3_ETIMEOUT — throwing away every key recovered up to that point. Reported on a card whose sector 16 refuses the standard MFC EV1 keys: 32 keys cracked, nothing saved. A NAK is a refusal, not a glitch, so the device now gives up on it and reports PM3_EWRONGANSWER. autopwn names the sector it skipped and carries on, and a timeout falls through to the key table and the dump whenever anything was recovered. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 2 ++ armsrc/mifarecmd.c | 10 ++++++++++ client/src/cmdhfmf.c | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac65259f4..d151d752b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ 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] +- Fixed `hf mf autopwn` - a sector that cannot be cracked no longer discards the keys already recovered (@iceman1001) +- Fixed `hf mf nested` - a NAK from the tag now ends the nonce collection instead of retrying until the client times out (@iceman1001) - Fixed `hf mfdes` - select / authenticate failures are now reported once, naming the AID and the failing step (@iceman1001) - Fixed `-f ` - a filename given with its own extension is now used as-is on both load and save (@iceman1001) - Fixed `hf mf view` - VIGIK signature verification now recovers the ISO 9796-1 message and checks it against the card (@iceman1001) diff --git a/armsrc/mifarecmd.c b/armsrc/mifarecmd.c index 07708b964..0cfd20c65 100644 --- a/armsrc/mifarecmd.c +++ b/armsrc/mifarecmd.c @@ -1730,6 +1730,16 @@ void MifareNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo, uint8 auth2_time = auth1_time + delta_time; len = mifare_sendcmd_short(pcs, AUTH_NESTED, MIFARE_AUTH_KEYA + (targetKeyType & 0xF), targetBlockNo, receivedAnswer, sizeof(receivedAnswer), par, &auth2_time); + + // a NAK is the tag refusing this block, not a glitch. re-sending the + // identical frame only ever gets the identical answer back, so give up + // here instead of spinning until the client times us out + if (len == 1) { + if (g_dbglevel >= DBG_INFO) Dbprintf("Nested: Auth2 NAK, tag refused block " _YELLOW_("%d"), targetBlockNo); + isOK = PM3_EWRONGANSWER; + break; + } + if (len != 4) { if (g_dbglevel >= DBG_INFO) Dbprintf("Nested: Auth2 error len=%d", len); continue; diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 8177f0c06..57b7818ce 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -2228,6 +2228,9 @@ static int CmdHF14AMfNested(const char *Cmd) { //TODO: single mode broken? can't case PM3_EFAILED: PrintAndLogEx(FAILED, "Tag isn't vulnerable to Nested Attack (PRNG is not predictable).\n"); break; + case PM3_EWRONGANSWER: + PrintAndLogEx(FAILED, "Tag refused the nested authentication for block " _YELLOW_("%u") "\n", trgBlockNo); + break; case PM3_ESOFT: PrintAndLogEx(FAILED, "No valid key found"); break; @@ -2331,6 +2334,11 @@ static int CmdHF14AMfNested(const char *Cmd) { //TODO: single mode broken? can't case PM3_EFAILED : PrintAndLogEx(FAILED, "Tag isn't vulnerable to Nested Attack (PRNG is not predictable)\n"); break; + case PM3_EWRONGANSWER: + // tag refused this block, nothing to gain by retrying it + PrintAndLogEx(FAILED, "Tag refused the nested authentication for sector " _YELLOW_("%u") "\n", sectorNo); + calibrate = false; + continue; case PM3_ESOFT: //key not found calibrate = false; @@ -2942,6 +2950,20 @@ static int CmdHF14AMfNestedHard(const char *Cmd) { return isOK; } +// how many keys are in hand right now. a sector that can't be cracked should +// not cost the ones that already came out +static uint8_t mfc_found_key_count(const sector_t *e_sector, uint8_t sector_cnt) { + uint8_t n = 0; + for (uint8_t i = 0; i < sector_cnt; i++) { + for (uint8_t j = MF_KEY_A; j <= MF_KEY_B; j++) { + if (e_sector[i].foundKey[j]) { + n++; + } + } + } + return n; +} + static int CmdHF14AMfAutoPWN(const char *Cmd) { CLIParserContext *ctx; @@ -3706,10 +3728,29 @@ tryNested: switch (isOK) { case PM3_ETIMEOUT: { PrintAndLogEx(ERR, "\nError: No response from Proxmark3."); + + uint8_t havekeys = mfc_found_key_count(e_sector, sector_cnt); + if (havekeys) { + PrintAndLogEx(INFO, "Keeping the " _YELLOW_("%u") " key%s recovered so far", + havekeys, + (havekeys == 1) ? "" : "s" + ); + goto all_found; + } + free(e_sector); free(fptr); return isOK; } + case PM3_EWRONGANSWER: { + // the tag NAKed the nested auth for this block. it is a + // refusal, so move on rather than grind the same sector + PrintAndLogEx(FAILED, "Tag refused the nested authentication, skipping sector " _YELLOW_("%3d") " key type " _YELLOW_("%c"), + current_sector_i, + (current_key_type_i == MF_KEY_B) ? 'B' : 'A' + ); + continue; + } case PM3_EOPABORTED: { PrintAndLogEx(WARNING, "\nButton pressed. Aborted."); free(e_sector);