diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 5d0925370..0638d888c 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -9680,7 +9680,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; } @@ -10052,10 +10052,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 @@ -10128,8 +10130,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"); @@ -10234,7 +10237,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) { @@ -10379,7 +10382,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 @@ -10631,8 +10634,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"); } @@ -10647,7 +10651,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"); } @@ -10661,18 +10665,73 @@ 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); 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" - "With no wakeup flag it auto-detects a backdoor (gen1a or gdm alt); a WUPA-only card is rejected.", + "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" ); void *argtable[] = { @@ -10706,51 +10765,49 @@ 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)); - // 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 || 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); @@ -10761,49 +10818,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) { @@ -10867,9 +10931,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"); @@ -10879,9 +10947,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"); @@ -10893,8 +10965,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 { @@ -10913,9 +10986,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"); @@ -12345,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, "Wipe GDM card to factory defaults"}, + {"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 d0428d74c..d542b76df 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": "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" ], @@ -16808,6 +16808,6 @@ "metadata": { "commands_extracted": 936, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2026-10-11T13:59:06+00:00" + "extracted_on": "2026-10-14T15:56:50+00:00" } } diff --git a/doc/commands.md b/doc/commands.md index 180881561..2c4e28201 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 |`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 09a9ee771..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` | Restore card to factory defaults (`-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