From 2ca051438980006ea76fe2c5c9b1fc0f89f6e9f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AD=90=E6=AD=AA?= Date: Tue, 15 Sep 2026 18:20:50 +0800 Subject: [PATCH 1/5] fix(mifare): propagate GDM write failures Return device-side write errors from GDM commands instead of reporting success, and handle write timeouts before inspecting response status. Co-Authored-By: OpenAI Codex --- client/src/cmdhfmf.c | 57 +++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index c59cfc144..6f52e351a 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -9628,7 +9628,7 @@ static int gdm_write_config(uint8_t wakeup, uint8_t auth_cmd, const uint8_t *key } if (resp.status != PM3_SUCCESS) { PrintAndLogEx(FAILED, "Failed to write config. Status: %d", resp.status); - return PM3_EFAILED; + return resp.status; } return PM3_SUCCESS; } @@ -10000,10 +10000,12 @@ static int CmdHF14AGen4_GDM_SetCfg(const char *Cmd) { if (resp.status == PM3_SUCCESS) { PrintAndLogEx(SUCCESS, "Write ( " _GREEN_("ok") " )"); PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mf gdmgetcfg") "` to verify"); + } else if (resp.status == PM3_ETEAROFF) { + PrintAndLogEx(INFO, "Tear off triggered"); } else { PrintAndLogEx(FAILED, "Write ( " _RED_("fail") " )"); } - return PM3_SUCCESS; + return resp.status; } // Config flag mode @@ -10076,8 +10078,9 @@ static int CmdHF14AGen4_GDM_SetCfg(const char *Cmd) { } // Step 2: Write config - if (gdm_write_config(wakeup_type, auth_cmd, key, config) != PM3_SUCCESS) { - return PM3_EFAILED; + int res = gdm_write_config(wakeup_type, auth_cmd, key, config); + if (res != PM3_SUCCESS) { + return res; } PrintAndLogEx(SUCCESS, "Config updated successfully"); @@ -10182,7 +10185,7 @@ static int CmdHF14AGen4_GDM_SetBlk(const char *Cmd) { } else { PrintAndLogEx(FAILED, "Write ( " _RED_("fail") " )"); } - return PM3_SUCCESS; + return resp.status; } static int CmdHF14AGen4_GDM_SetHidBlk(const char *Cmd) { @@ -10327,7 +10330,7 @@ static int CmdHF14AGen4_GDM_SetHidBlk(const char *Cmd) { } else { PrintAndLogEx(FAILED, "Write ( " _RED_("fail") " )"); } - return PM3_SUCCESS; + return resp.status; } // Shared worker for gdmgetblk / gdmgethidblk: reads a single block (--blk) or a @@ -10579,8 +10582,9 @@ static int CmdHF14AGen4_GDM_SetUid(const char *Cmd) { // Step 3: Write config if needed if (update_config) { - if (gdm_write_config(wakeup_type, auth_cmd, key, config) != PM3_SUCCESS) { - return PM3_EFAILED; + int res = gdm_write_config(wakeup_type, auth_cmd, key, config); + if (res != PM3_SUCCESS) { + return res; } PrintAndLogEx(SUCCESS, "Config updated successfully"); } @@ -10595,7 +10599,7 @@ static int CmdHF14AGen4_GDM_SetUid(const char *Cmd) { } if (resp.status != PM3_SUCCESS) { PrintAndLogEx(FAILED, "Failed to write hidden block 0. Status: %d", resp.status); - return PM3_EFAILED; + return resp.status; } PrintAndLogEx(SUCCESS, "Hidden block 0 updated successfully"); } @@ -10609,7 +10613,7 @@ static int CmdHF14AGen4_GDM_SetUid(const char *Cmd) { } if (resp.status != PM3_SUCCESS) { PrintAndLogEx(FAILED, "Failed to write real block %d. Status: %d", write_payload.block_no, resp.status); - return PM3_EFAILED; + return resp.status; } PrintAndLogEx(SUCCESS, "Real block %d updated successfully", write_payload.block_no); @@ -10679,7 +10683,9 @@ static int CmdHF14AGen4_GDM_Wipe(const char *Cmd) { clearCommandBuffer(); SendCommandNG(CMD_HF_MIFARE_WRITEBL_EX, (uint8_t *)&config_payload, sizeof(config_payload)); PacketResponseNG resp; - if (WaitForResponseTimeout(CMD_HF_MIFARE_WRITEBL_EX, &resp, 1500) == false || resp.status != PM3_SUCCESS) { + if (WaitForResponseTimeout(CMD_HF_MIFARE_WRITEBL_EX, &resp, 1500) == false) { + PrintAndLogEx(FAILED, "Failed to write standard config: timeout"); + } else if (resp.status != PM3_SUCCESS) { PrintAndLogEx(FAILED, "Failed to write standard config. Status: %d", resp.status); } else { PrintAndLogEx(SUCCESS, "Standard config written"); @@ -10815,9 +10821,13 @@ static int CmdHF14AGen4_GDM_SetSig(const char *Cmd) { clearCommandBuffer(); SendCommandNG(CMD_HF_MIFARE_WRITEBL_EX, (uint8_t *)&write_payload, sizeof(write_payload)); PacketResponseNG resp; - if (WaitForResponseTimeout(CMD_HF_MIFARE_WRITEBL_EX, &resp, 1500) == false || resp.status != PM3_SUCCESS) { + if (WaitForResponseTimeout(CMD_HF_MIFARE_WRITEBL_EX, &resp, 1500) == false) { + PrintAndLogEx(FAILED, "Failed to write signature part 1 (block 5): timeout"); + return PM3_ETIMEOUT; + } + if (resp.status != PM3_SUCCESS) { PrintAndLogEx(FAILED, "Failed to write signature part 1 (block 5). Status: %d", resp.status); - return PM3_EFAILED; + return resp.status; } PrintAndLogEx(SUCCESS, "Signature part 1 written to block 5"); @@ -10827,9 +10837,13 @@ static int CmdHF14AGen4_GDM_SetSig(const char *Cmd) { clearCommandBuffer(); SendCommandNG(CMD_HF_MIFARE_WRITEBL_EX, (uint8_t *)&write_payload, sizeof(write_payload)); - if (WaitForResponseTimeout(CMD_HF_MIFARE_WRITEBL_EX, &resp, 1500) == false || resp.status != PM3_SUCCESS) { + if (WaitForResponseTimeout(CMD_HF_MIFARE_WRITEBL_EX, &resp, 1500) == false) { + PrintAndLogEx(FAILED, "Failed to write signature part 2 (block 6): timeout"); + return PM3_ETIMEOUT; + } + if (resp.status != PM3_SUCCESS) { PrintAndLogEx(FAILED, "Failed to write signature part 2 (block 6). Status: %d", resp.status); - return PM3_EFAILED; + return resp.status; } PrintAndLogEx(SUCCESS, "Signature part 2 written to block 6"); @@ -10841,8 +10855,9 @@ static int CmdHF14AGen4_GDM_SetSig(const char *Cmd) { if (config[13] != 0x5A) { config[13] = 0x5A; - if (gdm_write_config(wakeup_type, auth_cmd, key, config) != PM3_SUCCESS) { - return PM3_EFAILED; + int res = gdm_write_config(wakeup_type, auth_cmd, key, config); + if (res != PM3_SUCCESS) { + return res; } PrintAndLogEx(SUCCESS, "Enabled signature sector in config (config[13] = 0x5A)"); } else { @@ -10861,9 +10876,13 @@ static int CmdHF14AGen4_GDM_SetSig(const char *Cmd) { clearCommandBuffer(); SendCommandNG(CMD_HF_MIFARE_WRITEBL_EX, (uint8_t *)&trailer_payload, sizeof(trailer_payload)); - if (WaitForResponseTimeout(CMD_HF_MIFARE_WRITEBL_EX, &resp, 1500) == false || resp.status != PM3_SUCCESS) { + if (WaitForResponseTimeout(CMD_HF_MIFARE_WRITEBL_EX, &resp, 1500) == false) { + PrintAndLogEx(FAILED, "Failed to write signature sector trailer (hidden block 7): timeout"); + return PM3_ETIMEOUT; + } + if (resp.status != PM3_SUCCESS) { PrintAndLogEx(FAILED, "Failed to write signature sector trailer (hidden block 7). Status: %d", resp.status); - return PM3_EFAILED; + return resp.status; } PrintAndLogEx(SUCCESS, "Signature sector trailer written to hidden block 7"); From 4b99a422c831095019271a25b67a2513893cb2e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AD=90=E6=AD=AA?= Date: Sun, 27 Sep 2026 05:16:53 +0800 Subject: [PATCH 2/5] fix(mifare): make GDM wipe failure-safe Stage and verify a valid 4-byte UID before committing the clean configuration, propagate write failures, and preserve recovery when cleanup is incomplete. Correct the sector trailer template. Co-Authored-By: OpenAI Codex --- client/src/cmdhfmf.c | 191 +++++++++++++++++++++++++-------------- doc/magic_cards_notes.md | 2 +- 2 files changed, 123 insertions(+), 70 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 6f52e351a..a0ad2a3dd 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -10620,10 +10620,65 @@ static int CmdHF14AGen4_GDM_SetUid(const char *Cmd) { return PM3_SUCCESS; } +static int gdm_wipe_write_block(const gdm_wakeup_t *w, uint8_t write_cmd, uint8_t block_no, const uint8_t *data) { + mf_writeblock_ex_t payload = { + .wakeup = w->wakeup_type, + .auth_cmd = w->auth_cmd, + .write_cmd = write_cmd, + .block_no = block_no + }; + memcpy(payload.key, w->key, sizeof(payload.key)); + memcpy(payload.block_data, data, sizeof(payload.block_data)); + + clearCommandBuffer(); + SendCommandNG(CMD_HF_MIFARE_WRITEBL_EX, (uint8_t *)&payload, sizeof(payload)); + + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_MIFARE_WRITEBL_EX, &resp, 1500) == false) { + PrintAndLogEx(FAILED, "Block %u write timed out", block_no); + return PM3_ETIMEOUT; + } + if (resp.status != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "Block %u write failed. Status: %d", block_no, resp.status); + return resp.status; + } + return PM3_SUCCESS; +} + +static int gdm_wipe_verify_uid(const uint8_t *expected_uid) { + clearCommandBuffer(); + SendIso14aReader(ISO14A_CONNECT | ISO14A_CLEARTRACE | ISO14A_NO_DISCONNECT, NULL, 0); + + PacketResponseNG resp; + uint8_t select_status = 0; + if (WaitForIso14aReply(&resp, 2500, NULL, &select_status) == false) { + DropField(); + PrintAndLogEx(FAILED, "Could not select the card after writing the clean config"); + return PM3_ETIMEOUT; + } + + iso14a_card_select_t card; + memcpy(&card, resp.data.asBytes, sizeof(card)); + DropField(); + + if (select_status == 0 || card.uidlen != 4 || memcmp(card.uid, expected_uid, 4) != 0) { + PrintAndLogEx(FAILED, "Clean UID verification failed"); + if (select_status != 0) { + PrintAndLogEx(INFO, "Expected UID: %s", sprint_hex(expected_uid, 4)); + PrintAndLogEx(INFO, "Selected UID: %s", sprint_hex(card.uid, card.uidlen)); + } + return PM3_EFAILED; + } + + PrintAndLogEx(SUCCESS, "Selected clean UID: %s", sprint_hex(card.uid, card.uidlen)); + return PM3_SUCCESS; +} + static int CmdHF14AGen4_GDM_Wipe(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf mf gdmwipe", - "Wipe a GDM card to factory defaults.\n" + "Reset a GDM card to a known clean 4-byte template.\n" + "Sets UID 11223344, clears real and hidden data blocks, and restores transport sector trailers.\n" "With no wakeup flag it auto-detects a backdoor (gen1a or gdm alt); a WUPA-only card is rejected.", "hf mf gdmwipe --gdm" ); @@ -10662,49 +10717,40 @@ static int CmdHF14AGen4_GDM_Wipe(const char *Cmd) { uint8_t auth_cmd = gw.auth_cmd; memcpy(key, gw.key, sizeof(key)); - // Step 1: Write Config (gen1a + wupa + 4B UID) - uint8_t standard_config[MFBLOCK_SIZE] = {0x7A, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x08}; + uint8_t clean_config[MFBLOCK_SIZE] = {0x7A, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x08}; uint8_t cur_config[MFBLOCK_SIZE] = {0}; - if (gdm_read_config(wakeup_type, auth_cmd, key, cur_config) == PM3_SUCCESS) { - standard_config[2] = cur_config[2]; + if (gdm_read_config(wakeup_type, auth_cmd, key, cur_config) != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "Cannot preserve the current wakeup style; no data was changed"); + return PM3_EFAILED; + } + if (cur_config[2] == 0x00 || cur_config[2] == 0x85) { + clean_config[2] = cur_config[2]; } else { - PrintAndLogEx(WARNING, "Could not read current config, keeping the default wakeup style"); + clean_config[2] = (wakeup_type == MF_WAKE_GDM_ALT) ? 0x85 : 0x00; + PrintAndLogEx(WARNING, "Unknown wakeup style %02X; using %s", cur_config[2], + clean_config[2] == 0x85 ? "gdm alt (20/23)" : "gen1a (40/43)"); } - mf_writeblock_ex_t config_payload = { - .wakeup = wakeup_type, - .auth_cmd = auth_cmd, - .write_cmd = MIFARE_MAGIC_GDM_WRITE_CFG, // 0xE1 - .block_no = 0 - }; - memcpy(config_payload.key, key, 6); - memcpy(config_payload.block_data, standard_config, MFBLOCK_SIZE); - - clearCommandBuffer(); - SendCommandNG(CMD_HF_MIFARE_WRITEBL_EX, (uint8_t *)&config_payload, sizeof(config_payload)); - PacketResponseNG resp; - if (WaitForResponseTimeout(CMD_HF_MIFARE_WRITEBL_EX, &resp, 1500) == false) { - PrintAndLogEx(FAILED, "Failed to write standard config: timeout"); - } else if (resp.status != PM3_SUCCESS) { - PrintAndLogEx(FAILED, "Failed to write standard config. Status: %d", resp.status); - } else { - PrintAndLogEx(SUCCESS, "Standard config written"); - } - - // Step 2: Wipe sectors int max_sector = wipe_4k ? 40 : 16; uint8_t default_data[MFBLOCK_SIZE] = {0}; - uint8_t default_trailer[MFBLOCK_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; - uint8_t block0_data[MFBLOCK_SIZE] = {0xDE, 0x77, 0x15, 0xB8, 0x04, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - - mf_writeblock_ex_t write_payload = { - .wakeup = wakeup_type, - .auth_cmd = auth_cmd - }; - memcpy(write_payload.key, key, 6); + uint8_t default_trailer[MFBLOCK_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + uint8_t block0_data[MFBLOCK_SIZE] = {0x11, 0x22, 0x33, 0x44, 0x44, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; int failed_blocks = 0; int total_blocks = 0; + uint8_t verify_data[MFBLOCK_SIZE] = {0}; + + // Stage a valid 4-byte UID before changing the active UID source. + if (gdm_wipe_write_block(&gw, ISO14443A_CMD_WRITEBLOCK, 0, block0_data) != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "Could not stage the clean block 0; config was not changed"); + return PM3_EFAILED; + } + if (gdm_try_read(ISO14443A_CMD_READBLOCK, 0, &gw, verify_data) != PM3_SUCCESS || + memcmp(verify_data, block0_data, MFBLOCK_SIZE) != 0) { + PrintAndLogEx(FAILED, "Clean block 0 readback failed; config was not changed"); + return PM3_EPARTIAL; + } + total_blocks++; for (int sec = 0; sec < max_sector; sec++) { int first_blk = mfFirstBlockOfSector(sec); @@ -10715,49 +10761,56 @@ static int CmdHF14AGen4_GDM_Wipe(const char *Cmd) { bool is_trailer = (blk == trailer_blk); uint8_t *data_to_write = is_trailer ? default_trailer : default_data; - // Real block writes - write_payload.write_cmd = ISO14443A_CMD_WRITEBLOCK; // 0xA0 - write_payload.block_no = blk; - if (blk == 0) { - memcpy(write_payload.block_data, block0_data, MFBLOCK_SIZE); - } else { - memcpy(write_payload.block_data, data_to_write, MFBLOCK_SIZE); + if (blk != 0) { + if (gdm_wipe_write_block(&gw, ISO14443A_CMD_WRITEBLOCK, blk, data_to_write) != PM3_SUCCESS) { + failed_blocks++; + } + total_blocks++; } - clearCommandBuffer(); - SendCommandNG(CMD_HF_MIFARE_WRITEBL_EX, (uint8_t *)&write_payload, sizeof(write_payload)); - if (WaitForResponseTimeout(CMD_HF_MIFARE_WRITEBL_EX, &resp, 1500) == false || resp.status != PM3_SUCCESS) { - failed_blocks++; + // Hidden blocks 0 and 1 can still hold the active 7-byte/F3 UID. + if (blk > 1) { + const uint8_t *hidden_data = (blk == 7) ? MF_SIGNATURE_KEYS : data_to_write; + if (gdm_wipe_write_block(&gw, MIFARE_MAGIC_GDM_WRITEBLOCK, blk, hidden_data) != PM3_SUCCESS) { + failed_blocks++; + } + total_blocks++; } - - // Hidden block writes - write_payload.write_cmd = MIFARE_MAGIC_GDM_WRITEBLOCK; // 0xA8 - if (blk == 7) { - memcpy(write_payload.block_data, MF_SIGNATURE_KEYS, MFBLOCK_SIZE); - } - - clearCommandBuffer(); - SendCommandNG(CMD_HF_MIFARE_WRITEBL_EX, (uint8_t *)&write_payload, sizeof(write_payload)); - if (WaitForResponseTimeout(CMD_HF_MIFARE_WRITEBL_EX, &resp, 1500) == false || resp.status != PM3_SUCCESS) { - failed_blocks++; - } - total_blocks += 2; } PrintAndLogEx(INFO, "Wiping sector %d...", sec); } - // report write failures instead of always printing "Wipe completed!" - if (failed_blocks == 0) { - PrintAndLogEx(SUCCESS, "Wipe completed!"); - return PM3_SUCCESS; + if (failed_blocks != 0) { + PrintAndLogEx(FAILED, "Wipe incomplete - " _RED_("%d") " of %d pre-commit block writes failed", failed_blocks, total_blocks); + PrintAndLogEx(HINT, "Hint: config and active UID mode were left unchanged"); + return PM3_EPARTIAL; } - PrintAndLogEx(FAILED, "Wipe incomplete - " _RED_("%d") " of %d block writes failed", failed_blocks, total_blocks); - if (failed_blocks == total_blocks) { - PrintAndLogEx(HINT, "Hint: the card did not accept any data write. Check that the wakeup still matches"); - PrintAndLogEx(HINT, " the card's config (`" _YELLOW_("hf mf gdmgetcfg") "`), then retry"); + // Commit the 4-byte personality only after its UID source is valid. + if (gdm_write_config(wakeup_type, auth_cmd, key, clean_config) != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "Clean config was not committed; the card was partially wiped"); + return PM3_EPARTIAL; } - return PM3_EFAILED; + PrintAndLogEx(SUCCESS, "Clean config written"); + + if (gdm_wipe_verify_uid(block0_data) != PM3_SUCCESS) { + PrintAndLogEx(HINT, "Hint: the magic wakeup remains enabled for recovery"); + return PM3_EPARTIAL; + } + + // The clean 4-byte personality no longer uses hidden UID data. + for (uint8_t blk = 0; blk <= 1; blk++) { + if (gdm_wipe_write_block(&gw, MIFARE_MAGIC_GDM_WRITEBLOCK, blk, default_data) != PM3_SUCCESS) { + failed_blocks++; + } + } + if (failed_blocks != 0) { + PrintAndLogEx(FAILED, "Clean 4-byte mode is active, but hidden UID cleanup failed"); + return PM3_EPARTIAL; + } + + PrintAndLogEx(SUCCESS, "Wipe completed using the clean 4-byte template"); + return PM3_SUCCESS; } static int CmdHF14AGen4_GDM_SetSig(const char *Cmd) { @@ -12310,7 +12363,7 @@ static command_t CommandTable[] = { {"gdmgethidblk", CmdHF14AGen4_GDM_GetHidBlk, IfPm3Iso14443a, "Read hidden block from GDM card"}, {"gdmsethidblk", CmdHF14AGen4_GDM_SetHidBlk, IfPm3Iso14443a, "Write hidden block to GDM card"}, {"gdmsetuid", CmdHF14AGen4_GDM_SetUid, IfPm3Iso14443a, "Set UID on GDM card"}, - {"gdmwipe", CmdHF14AGen4_GDM_Wipe, IfPm3Iso14443a, "Wipe GDM card to factory defaults"}, + {"gdmwipe", CmdHF14AGen4_GDM_Wipe, IfPm3Iso14443a, "Reset GDM card to a clean template"}, {"gdmsetsig", CmdHF14AGen4_GDM_SetSig, IfPm3Iso14443a, "Set MFC EV1 signature on GDM card"}, {"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("ndef") " -----------------------"}, {"ndefformat", CmdHFMFNDEFFormat, IfPm3Iso14443a, "Format MIFARE Classic Tag as NFC Tag"}, diff --git a/doc/magic_cards_notes.md b/doc/magic_cards_notes.md index 2d5a7d1de..ca36afb4a 100644 --- a/doc/magic_cards_notes.md +++ b/doc/magic_cards_notes.md @@ -1171,7 +1171,7 @@ Sectors 2-15 | `gdmgethidblk` | Read hidden block | | `gdmsethidblk` | Write a hidden block | | `gdmsetuid` | Set 4-byte / 7-byte UID, or F3 perso (`--f3d`) | -| `gdmwipe` | Restore card to factory defaults (`-a` for 4K) | +| `gdmwipe` | Reset to a known clean 4-byte template (`-a` for 4K)| | `gdmsetsig` | Write EV1 signature (hidden blocks 5/6), enable sigsec, write default signature key sector | ### libnfc commands From f4c724338748598362948c7f76f334d52293061c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AD=90=E6=AD=AA?= Date: Tue, 6 Oct 2026 04:16:28 +0800 Subject: [PATCH 3/5] fix(mifare): reject WUPA mode before GDM wipe Co-Authored-By: OpenAI Codex --- client/src/cmdhfmf.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index a0ad2a3dd..7bf3519a3 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -10713,6 +10713,11 @@ static int CmdHF14AGen4_GDM_Wipe(const char *Cmd) { if (wres != PM3_SUCCESS) { return wres; } + if (gw.wakeup_type == MF_WAKE_WUPA) { + PrintAndLogEx(WARNING, "hf mf gdmwipe needs a gen1a/gdm backdoor; WUPA magic auth is insufficient"); + PrintAndLogEx(HINT, "Hint: enable one first: " _YELLOW_("hf mf gdmsetcfg --wakestyle gen1a --wupa -k ")); + return PM3_EFAILED; + } uint8_t wakeup_type = gw.wakeup_type; uint8_t auth_cmd = gw.auth_cmd; memcpy(key, gw.key, sizeof(key)); From 44053353155f2a30a83ee1fddd599d77daddf754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AD=90=E6=AD=AA?= Date: Wed, 14 Oct 2026 05:07:07 +0800 Subject: [PATCH 4/5] chore: commands file and wording adjustment --- client/src/cmdhfmf.c | 4 ++-- doc/commands.json | 4 ++-- doc/commands.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 94c4a2c79..24192a9cb 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -10730,8 +10730,8 @@ static int CmdHF14AGen4_GDM_Wipe(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf mf gdmwipe", "Reset a GDM card to a known clean 4-byte template.\n" - "Sets UID 11223344, clears real and hidden data blocks, and restores transport sector trailers.\n" - "With no wakeup flag it auto-detects a backdoor (gen1a or gdm alt); a WUPA-only card is rejected.", + "Sets UID to 11223344, clears real and hidden data blocks, and sets sector trailers to default values.\n" + "With no wakeup flag it auto-detects a backdoor; a WUPA-only card is rejected.", "hf mf gdmwipe --gdm" ); void *argtable[] = { diff --git a/doc/commands.json b/doc/commands.json index c9d10014f..4b20b0016 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -6393,7 +6393,7 @@ }, "hf mf gdmwipe": { "command": "hf mf gdmwipe", - "description": "Wipe a GDM card to factory defaults. With no wakeup flag it auto-detects a backdoor (gen1a or gdm alt); a WUPA-only card is rejected.", + "description": "Reset a GDM card to a known clean 4-byte template. Sets UID to 11223344, clears real and hidden data blocks, and sets sector trailers to default values. With no wakeup flag it auto-detects a backdoor; a WUPA-only card is rejected.", "notes": [ "hf mf gdmwipe --gdm" ], @@ -16787,6 +16787,6 @@ "metadata": { "commands_extracted": 935, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2026-09-27T19:09:12+00:00" + "extracted_on": "2026-10-13T21:05:00+00:00" } } diff --git a/doc/commands.md b/doc/commands.md index 5fb6c6f9d..59ae528a4 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -698,7 +698,7 @@ Check column "offline" for their availability. |`hf mf gdmgethidblk `|N |`Read hidden block from GDM card` |`hf mf gdmsethidblk `|N |`Write hidden block to GDM card` |`hf mf gdmsetuid `|N |`Set UID on GDM card` -|`hf mf gdmwipe `|N |`Wipe GDM card to factory defaults` +|`hf mf gdmwipe `|N |`Reset GDM card to a clean template` |`hf mf gdmsetsig `|N |`Set MFC EV1 signature on GDM card` |`hf mf ndefformat `|N |`Format MIFARE Classic Tag as NFC Tag` |`hf mf ndefread `|N |`Read and print NDEF records from card` From 26e762f80d92b50a0f945cae9278d89f5ff8ab23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AD=90=E6=AD=AA?= Date: Thu, 15 Oct 2026 01:33:15 +0800 Subject: [PATCH 5/5] chore: update command description --- client/src/cmdhfmf.c | 4 ++-- doc/commands.json | 4 ++-- doc/commands.md | 2 +- doc/magic_cards_notes.md | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 24192a9cb..0638d888c 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -10729,7 +10729,7 @@ static int gdm_wipe_verify_uid(const uint8_t *expected_uid) { static int CmdHF14AGen4_GDM_Wipe(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf mf gdmwipe", - "Reset a GDM card to a known clean 4-byte template.\n" + "Wipe a GDM card to a known clean 4-byte template.\n" "Sets UID to 11223344, clears real and hidden data blocks, and sets sector trailers to default values.\n" "With no wakeup flag it auto-detects a backdoor; a WUPA-only card is rejected.", "hf mf gdmwipe --gdm" @@ -12422,7 +12422,7 @@ static command_t CommandTable[] = { {"gdmgethidblk", CmdHF14AGen4_GDM_GetHidBlk, IfPm3Iso14443a, "Read hidden block from GDM card"}, {"gdmsethidblk", CmdHF14AGen4_GDM_SetHidBlk, IfPm3Iso14443a, "Write hidden block to GDM card"}, {"gdmsetuid", CmdHF14AGen4_GDM_SetUid, IfPm3Iso14443a, "Set UID on GDM card"}, - {"gdmwipe", CmdHF14AGen4_GDM_Wipe, IfPm3Iso14443a, "Reset GDM card to a clean template"}, + {"gdmwipe", CmdHF14AGen4_GDM_Wipe, IfPm3Iso14443a, "Wipe GDM card to a clean template"}, {"gdmsetsig", CmdHF14AGen4_GDM_SetSig, IfPm3Iso14443a, "Set MFC EV1 signature on GDM card"}, {"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("ndef") " -----------------------"}, {"ndefformat", CmdHFMFNDEFFormat, IfPm3Iso14443a, "Format MIFARE Classic Tag as NFC Tag"}, diff --git a/doc/commands.json b/doc/commands.json index 4b20b0016..293091c2f 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -6393,7 +6393,7 @@ }, "hf mf gdmwipe": { "command": "hf mf gdmwipe", - "description": "Reset a GDM card to a known clean 4-byte template. Sets UID to 11223344, clears real and hidden data blocks, and sets sector trailers to default values. With no wakeup flag it auto-detects a backdoor; a WUPA-only card is rejected.", + "description": "Wipe a GDM card to a known clean 4-byte template. Sets UID to 11223344, clears real and hidden data blocks, and sets sector trailers to default values. With no wakeup flag it auto-detects a backdoor; a WUPA-only card is rejected.", "notes": [ "hf mf gdmwipe --gdm" ], @@ -16787,6 +16787,6 @@ "metadata": { "commands_extracted": 935, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2026-10-13T21:05:00+00:00" + "extracted_on": "2026-10-14T15:56:50+00:00" } } diff --git a/doc/commands.md b/doc/commands.md index 59ae528a4..df6c6e716 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -698,7 +698,7 @@ Check column "offline" for their availability. |`hf mf gdmgethidblk `|N |`Read hidden block from GDM card` |`hf mf gdmsethidblk `|N |`Write hidden block to GDM card` |`hf mf gdmsetuid `|N |`Set UID on GDM card` -|`hf mf gdmwipe `|N |`Reset GDM card to a clean template` +|`hf mf gdmwipe `|N |`Wipe GDM card to a clean template` |`hf mf gdmsetsig `|N |`Set MFC EV1 signature on GDM card` |`hf mf ndefformat `|N |`Format MIFARE Classic Tag as NFC Tag` |`hf mf ndefread `|N |`Read and print NDEF records from card` diff --git a/doc/magic_cards_notes.md b/doc/magic_cards_notes.md index 6cdf195ea..76dbe9e55 100644 --- a/doc/magic_cards_notes.md +++ b/doc/magic_cards_notes.md @@ -1171,7 +1171,7 @@ Sectors 2-15 | `gdmgethidblk` | Read hidden block | | `gdmsethidblk` | Write a hidden block | | `gdmsetuid` | Set 4-byte / 7-byte UID, or F3 perso (`--f3d`) | -| `gdmwipe` | Reset to a known clean 4-byte template (`-a` for 4K)| +| `gdmwipe` | Wipe to a known clean 4-byte template (`-a` for 4K) | | `gdmsetsig` | Write EV1 signature (hidden blocks 5/6), enable sigsec, write default signature key sector | ### libnfc commands