Merge pull request #3493 from 0x6r1an0y/20260819-gdmfix

Improve `hf mf gdm*` commands
This commit is contained in:
Iceman authored and GitHub committed 2026-09-07 15:28:56 +07:00
commit 07209fc358
1 file changed
+376 -151
+376 -151
View File
@@ -9677,6 +9677,144 @@ static int gdm_resolve_wakeup(bool gdm, bool gen1a, bool wupa, int keylen,
return PM3_SUCCESS;
}
// A single wakeup/auth attempt: wakeup type, magic-auth command, and key.
typedef struct {
uint8_t wakeup_type;
uint8_t auth_cmd;
uint8_t key[6];
} gdm_wakeup_t;
static const char *gdm_wakeup_name(const gdm_wakeup_t *w) {
if (w->auth_cmd == MIFARE_MAGIC_GDM_AUTH_KEY) {
return "WUPA + magic auth";
}
if (w->wakeup_type == MF_WAKE_GEN1A) {
return "gen1a (40/43)";
}
return "gdm alt (20/23)";
}
// Build the wakeup attempts to try. Explicit --gdm/--gen1a/--wupa (or a key) => 1 attempt;
static int gdm_wakeup_attempts(bool gdm, bool gen1a, bool wupa, const uint8_t *key, int keylen,
bool prefer_auth, gdm_wakeup_t attempts[3], int *count) {
if (gdm || gen1a || wupa || keylen > 0) {
uint8_t wt = 0, ac = 0;
if (gdm_resolve_wakeup(gdm, gen1a, wupa, keylen, &wt, &ac) != PM3_SUCCESS) {
return PM3_EINVARG;
}
attempts[0].wakeup_type = wt;
attempts[0].auth_cmd = ac;
memcpy(attempts[0].key, key, 6);
*count = 1;
return PM3_SUCCESS;
}
static const uint8_t types_blk[3] = { MF_WAKE_GEN1A, MF_WAKE_GDM_ALT, MF_WAKE_WUPA };
static const uint8_t auths_blk[3] = { 0, 0, MIFARE_MAGIC_GDM_AUTH_KEY };
static const uint8_t types_auth[3] = { MF_WAKE_WUPA, MF_WAKE_GEN1A, MF_WAKE_GDM_ALT };
static const uint8_t auths_auth[3] = { MIFARE_MAGIC_GDM_AUTH_KEY, 0, 0 };
const uint8_t *types = prefer_auth ? types_auth : types_blk;
const uint8_t *auths = prefer_auth ? auths_auth : auths_blk;
for (int i = 0; i < 3; i++) {
attempts[i].wakeup_type = types[i];
attempts[i].auth_cmd = auths[i];
memset(attempts[i].key, 0, 6);
}
*count = 3;
return PM3_SUCCESS;
}
// Read a single block with one wakeup attempt. Quiet: returns status, prints nothing.
static int gdm_try_read(uint8_t read_cmd, int block_no, const gdm_wakeup_t *w, uint8_t *out) {
mf_readblock_ex_t payload = {
.read_cmd = read_cmd,
.block_no = block_no,
.wakeup = w->wakeup_type,
.auth_cmd = w->auth_cmd
};
memcpy(payload.key, w->key, sizeof(payload.key));
clearCommandBuffer();
SendCommandNG(CMD_HF_MIFARE_READBL_EX, (uint8_t *)&payload, sizeof(payload));
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_HF_MIFARE_READBL_EX, &resp, 1500) == false) {
return PM3_ETIMEOUT;
}
if (resp.status != PM3_SUCCESS || resp.length != MFBLOCK_SIZE) {
return PM3_EFAILED;
}
memcpy(out, resp.data.asBytes, MFBLOCK_SIZE);
return PM3_SUCCESS;
}
// Try each attempt against `probe_blk`; return the index of the first that reads
// successfully (data in `out`), or -1 if all fail.
static int gdm_pick_wakeup(uint8_t read_cmd, int probe_blk, const gdm_wakeup_t *attempts,
int count, uint8_t *out) {
for (int i = 0; i < count; i++) {
if (gdm_try_read(read_cmd, probe_blk, &attempts[i], out) == PM3_SUCCESS) {
return i;
}
}
return -1;
}
// Probe with a matching READ to select the WRITE wakeup mode, avoiding partial writes.
static int gdm_resolve_for_write(bool gdm, bool gen1a, bool wupa,
const uint8_t *key, int keylen,
uint8_t probe_read_cmd, int probe_blk,
bool needs_backdoor, const char *cmd_name,
gdm_wakeup_t *out_w,
uint8_t *out_probe_data, bool *out_have_probe) {
if (out_have_probe != NULL) {
*out_have_probe = false;
}
bool explicit_mode = (gdm || gen1a || wupa || keylen > 0);
// config reads can use magic auth; data/hidden reads usually cannot, so try backdoors first
bool prefer_auth = (probe_read_cmd == MIFARE_MAGIC_GDM_READ_CFG);
gdm_wakeup_t attempts[3];
int n_attempts = 0;
if (gdm_wakeup_attempts(gdm, gen1a, wupa, key, keylen, prefer_auth, attempts, &n_attempts) != PM3_SUCCESS) {
return PM3_EINVARG;
}
// Explicit wakeup: no probe, keep legacy single-attempt behaviour.
if (explicit_mode) {
*out_w = attempts[0];
return PM3_SUCCESS;
}
uint8_t buf[MFBLOCK_SIZE] = {0};
int idx = gdm_pick_wakeup(probe_read_cmd, probe_blk, attempts, n_attempts, buf);
if (idx < 0) {
if (needs_backdoor) {
// WUPA magic auth still reads config
gdm_wakeup_t w = { .wakeup_type = MF_WAKE_WUPA, .auth_cmd = MIFARE_MAGIC_GDM_AUTH_KEY };
memset(w.key, 0, sizeof(w.key));
if (gdm_try_read(MIFARE_MAGIC_GDM_READ_CFG, 0, &w, buf) == PM3_SUCCESS) {
PrintAndLogEx(WARNING, "This card only responds to WUPA magic auth; " _YELLOW_("%s") " needs a gen1a/gdm backdoor", cmd_name);
PrintAndLogEx(HINT, "Hint: enable one first: " _YELLOW_("hf mf gdmsetcfg --wakestyle gen1a --wupa -k <key>"));
return PM3_EFAILED;
}
}
PrintAndLogEx(FAILED, "Could not find a working wakeup for " _YELLOW_("%s"), cmd_name);
PrintAndLogEx(HINT, "Hint: tried gen1a/gdm/wupa; card may be sealed or use a non-default magic auth key");
return PM3_EFAILED;
}
*out_w = attempts[idx];
if (out_probe_data != NULL) {
memcpy(out_probe_data, buf, MFBLOCK_SIZE);
}
if (out_have_probe != NULL) {
*out_have_probe = true;
}
PrintAndLogEx(INFO, "Auto-detected wakeup: " _YELLOW_("%s"), gdm_wakeup_name(out_w));
return PM3_SUCCESS;
}
// Read an <on|off> string option into a tri-state: 1 = on, 0 = off, -1 = not provided.
static int gdm_arg_onoff(CLIParserContext *ctx, int idx) {
struct arg_str *a = arg_get_str(ctx, idx);
@@ -9694,7 +9832,8 @@ static int CmdHF14AGen4_GDM_Cfg(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf mf gdmgetcfg",
"Get configuration data from a GDM card.\n"
"By default uses GDM alt wakeup (20/23). Use `--gen1a` or `--wupa` to override.",
"With no wakeup flag it auto-detects the card's access mode (tries WUPA magic auth,\n"
"then gen1a, then gdm alt). Use `--gdm`/`--gen1a`/`--wupa` to force one.",
"hf mf gdmgetcfg\n"
"hf mf gdmgetcfg --gen1a\n"
"hf mf gdmgetcfg --wupa -k FFFFFFFFFFFF"
@@ -9702,9 +9841,9 @@ static int CmdHF14AGen4_GDM_Cfg(const char *Cmd) {
void *argtable[] = {
arg_param_begin,
arg_str0("k", "key", "<hex>", "key 6 bytes (only for --wupa mode)"),
arg_lit0(NULL, "gdm", "use gdm alt (20/23) magic wakeup (default)"),
arg_lit0(NULL, "gen1a", "use gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "use regular wakeup (WUPA + magic auth with key)"),
arg_lit0(NULL, "gdm", "use gdm alt (20/23) magic wakeup"),
arg_lit0(NULL, "gen1a", "access: force gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "access: force regular wakeup (WUPA + magic auth with key)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
@@ -9717,35 +9856,28 @@ static int CmdHF14AGen4_GDM_Cfg(const char *Cmd) {
bool wupa = arg_get_lit(ctx, 4);
CLIParserFree(ctx);
uint8_t wakeup_type = 0;
uint8_t auth_cmd = 0;
if (gdm_resolve_wakeup(gdm, gen1a, wupa, keylen, &wakeup_type, &auth_cmd) != PM3_SUCCESS) {
gdm_wakeup_t attempts[3];
int n_attempts = 0;
if (gdm_wakeup_attempts(gdm, gen1a, wupa, key, keylen, true, attempts, &n_attempts) != PM3_SUCCESS) {
return PM3_EINVARG;
}
mf_readblock_ex_t payload = {
.read_cmd = MIFARE_MAGIC_GDM_READ_CFG,
.block_no = 0,
.wakeup = wakeup_type,
.auth_cmd = auth_cmd
};
memcpy(payload.key, key, sizeof(payload.key));
clearCommandBuffer();
SendCommandNG(CMD_HF_MIFARE_READBL_EX, (uint8_t *)&payload, sizeof(payload));
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_HF_MIFARE_READBL_EX, &resp, 1500) == false) {
PrintAndLogEx(WARNING, "command execution time out");
return PM3_ETIMEOUT;
uint8_t cfg[MFBLOCK_SIZE] = {0};
int idx = gdm_pick_wakeup(MIFARE_MAGIC_GDM_READ_CFG, 0, attempts, n_attempts, cfg);
if (idx < 0) {
PrintAndLogEx(FAILED, "Failed to read config block");
if (n_attempts > 1) {
PrintAndLogEx(HINT, "Hint: tried WUPA/gen1a/gdm wakeups; card may be sealed or use a non-default magic auth key");
}
return PM3_EFAILED;
}
if (n_attempts > 1) {
PrintAndLogEx(INFO, "Auto-detected wakeup: " _YELLOW_("%s"), gdm_wakeup_name(&attempts[idx]));
}
if (resp.status == PM3_SUCCESS && resp.length == MFBLOCK_SIZE) {
parse_gdm_cfg(resp.data.asBytes);
PrintAndLogEx(NORMAL, "");
}
return resp.status;
parse_gdm_cfg(cfg);
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
static int CmdHF14AGen4_GDM_SetCfg(const char *Cmd) {
@@ -9753,25 +9885,28 @@ static int CmdHF14AGen4_GDM_SetCfg(const char *Cmd) {
CLIParserInit(&ctx, "hf mf gdmsetcfg",
"Set configuration data on a GDM card\n"
"Can either write raw 16 bytes (-d) or configure specific flags.\n"
"If flags are provided, it will read the config, update flags, and write it back.",
"If flags are provided, it will read the config, update flags, and write it back.\n"
"With no wakeup flag it auto-detects the card's access mode (WUPA magic auth, gen1a, or gdm alt).\n"
"`access:` flags select how this command talks to the card; `config:` flags change what is stored.",
"hf mf gdmsetcfg -d 850000000000000000005A5A00000008\n"
"hf mf gdmsetcfg --cuid on --gen1 on\n"
"hf mf gdmsetcfg --cuid on --wakestyle gen1a\n"
"hf mf gdmsetcfg --wakestyle off\n"
"hf mf gdmsetcfg --shadow off --sigsec off --wupa -k FFFFFFFFFFFF"
);
void *argtable[] = {
arg_param_begin,
arg_str0("d", "data", "<hex>", "raw bytes to write, 16 hex bytes"),
arg_str0("k", "key", "<hex>", "key 6 bytes (only for regular wakeup)"),
arg_lit0(NULL, "gdm", "use gdm alt (20/23) magic wakeup (default)"),
arg_lit0(NULL, "gen1a", "use gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "use regular wakeup (WUPA + magic auth with key)"),
arg_str0(NULL, "gen1", "<on|off>", "Gen1 mode (7A FF)"),
arg_str0(NULL, "cuid", "<on|off>", "Gen2/CUID mode"),
arg_str0(NULL, "cl2", "<on|off>", "CL2 mode (7-byte UID) - pair with `hf mf gdmsetuid -u <7-byte>` to set a valid UID"),
arg_str0(NULL, "shadow", "<on|off>", "Shadow mode"),
arg_str0(NULL, "magicauth", "<on|off>", "Magic auth"),
arg_str0(NULL, "statenc", "<on|off>", "Static encrypted nonce"),
arg_str0(NULL, "sigsec", "<on|off>", "Signature sector"),
arg_lit0(NULL, "gdm", "access: force gdm alt (20/23) magic wakeup"),
arg_lit0(NULL, "gen1a", "access: force gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "access: force regular wakeup (WUPA + magic auth with key)"),
arg_str0(NULL, "cuid", "<on|off>", "config: Gen2/CUID mode"),
arg_str0(NULL, "cl2", "<on|off>", "config: CL2 mode (7-byte UID) - pair with `hf mf gdmsetuid -u <7-byte>`"),
arg_str0(NULL, "shadow", "<on|off>", "config: Shadow mode"),
arg_str0(NULL, "magicauth", "<on|off>", "config: Magic auth (what --wupa uses)"),
arg_str0(NULL, "statenc", "<on|off>", "config: Static encrypted nonce"),
arg_str0(NULL, "sigsec", "<on|off>", "config: Signature sector"),
arg_str0(NULL, "wakestyle", "<gdm|gen1a|off>", "config: backdoor - gdm (7AFF85), gen1a (7AFF00), or off (8500, seal)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
@@ -9789,13 +9924,29 @@ static int CmdHF14AGen4_GDM_SetCfg(const char *Cmd) {
bool wupa = arg_get_lit(ctx, 5);
// read <on|off> flags into tri-states (1/0/-1) before freeing ctx
int gen1_flag = gdm_arg_onoff(ctx, 6);
int cuid_flag = gdm_arg_onoff(ctx, 7);
int cl2_flag = gdm_arg_onoff(ctx, 8);
int shadow_flag = gdm_arg_onoff(ctx, 9);
int magicauth_flag = gdm_arg_onoff(ctx, 10);
int statenc_flag = gdm_arg_onoff(ctx, 11);
int sigsec_flag = gdm_arg_onoff(ctx, 12);
int cuid_flag = gdm_arg_onoff(ctx, 6);
int cl2_flag = gdm_arg_onoff(ctx, 7);
int shadow_flag = gdm_arg_onoff(ctx, 8);
int magicauth_flag = gdm_arg_onoff(ctx, 9);
int statenc_flag = gdm_arg_onoff(ctx, 10);
int sigsec_flag = gdm_arg_onoff(ctx, 11);
// Backdoor selector, the only control for it: gdm -> 7AFF85, gen1a -> 7AFF00, off -> 8500.
enum { WS_NONE, WS_GDM, WS_GEN1A, WS_OFF } wakestyle = WS_NONE;
struct arg_str *ws = arg_get_str(ctx, 12);
if (ws->count > 0) {
if (strcasecmp(ws->sval[0], "gdm") == 0) {
wakestyle = WS_GDM;
} else if (strcasecmp(ws->sval[0], "gen1a") == 0) {
wakestyle = WS_GEN1A;
} else if (strcasecmp(ws->sval[0], "off") == 0) {
wakestyle = WS_OFF;
} else {
PrintAndLogEx(FAILED, "--wakestyle must be " _YELLOW_("gdm") ", " _YELLOW_("gen1a") " or " _YELLOW_("off") ", got `%s`", ws->sval[0]);
CLIParserFree(ctx);
return PM3_EINVARG;
}
}
CLIParserFree(ctx);
@@ -9804,14 +9955,23 @@ static int CmdHF14AGen4_GDM_SetCfg(const char *Cmd) {
return PM3_EINVARG;
}
uint8_t wakeup_type = 0;
uint8_t auth_cmd = 0;
if (gdm_resolve_wakeup(gdm, gen1a, wupa, keylen, &wakeup_type, &auth_cmd) != PM3_SUCCESS) {
return PM3_EINVARG;
// Auto-detect wakeup by probing a config read (reachable via magic auth or either backdoor).
gdm_wakeup_t gw;
uint8_t probe_cfg[MFBLOCK_SIZE] = {0};
bool have_probe = false;
int wres = gdm_resolve_for_write(gdm, gen1a, wupa, key, keylen,
MIFARE_MAGIC_GDM_READ_CFG, 0, false, "hf mf gdmsetcfg",
&gw, probe_cfg, &have_probe);
if (wres != PM3_SUCCESS) {
return wres;
}
uint8_t wakeup_type = gw.wakeup_type;
uint8_t auth_cmd = gw.auth_cmd;
memcpy(key, gw.key, sizeof(key));
bool any_flag = (gen1_flag != -1 || cuid_flag != -1 || cl2_flag != -1 || shadow_flag != -1 ||
magicauth_flag != -1 || statenc_flag != -1 || sigsec_flag != -1);
bool any_flag = (cuid_flag != -1 || cl2_flag != -1 || shadow_flag != -1 ||
magicauth_flag != -1 || statenc_flag != -1 || sigsec_flag != -1 ||
wakestyle != WS_NONE);
// Logic: if -d is specified, write raw block.
// Else, read config, update flags, write block.
@@ -9852,9 +10012,11 @@ static int CmdHF14AGen4_GDM_SetCfg(const char *Cmd) {
return PM3_EINVARG;
}
// Step 1: Read config
// Step 1: Read config - reuse the block already read while auto-probing the wakeup.
uint8_t config[MFBLOCK_SIZE];
if (gdm_read_config(wakeup_type, auth_cmd, key, config) != PM3_SUCCESS) {
if (have_probe) {
memcpy(config, probe_cfg, MFBLOCK_SIZE);
} else if (gdm_read_config(wakeup_type, auth_cmd, key, config) != PM3_SUCCESS) {
return PM3_EFAILED;
}
bool changed = false;
@@ -9869,21 +10031,28 @@ static int CmdHF14AGen4_GDM_SetCfg(const char *Cmd) {
} \
}
if (gen1_flag != -1) {
if (gen1_flag == 1) {
if (config[0] != 0x7A || config[1] != 0xFF) {
config[0] = 0x7A;
config[1] = 0xFF;
changed = true;
PrintAndLogEx(INFO, "Set Gen1 mode to on");
}
} else {
if (config[0] != 0x85 || config[1] != 0x00) {
config[0] = 0x85;
config[1] = 0x00;
changed = true;
PrintAndLogEx(INFO, "Set Gen1 mode to off");
}
if (wakestyle == WS_OFF) {
if (config[0] != 0x85 || config[1] != 0x00) {
config[0] = 0x85;
config[1] = 0x00;
changed = true;
PrintAndLogEx(INFO, "Set magic wakeup to off (backdoor sealed)");
PrintAndLogEx(WARNING, "Both gdm and gen1a wakeups are now disabled; only WUPA magic auth remains");
}
} else if (wakestyle == WS_GDM || wakestyle == WS_GEN1A) {
if (config[0] != 0x7A || config[1] != 0xFF) {
config[0] = 0x7A;
config[1] = 0xFF;
changed = true;
PrintAndLogEx(INFO, "Set magic wakeup to on");
}
uint8_t style_byte = (wakestyle == WS_GDM) ? 0x85 : 0x00;
if (config[2] != style_byte) {
config[2] = style_byte;
changed = true;
PrintAndLogEx(INFO, "Set wakeup style to %s", (wakestyle == WS_GDM) ? "gdm alt (20/23)" : "gen1a (40/43)");
PrintAndLogEx(WARNING, "After this write the card answers " _YELLOW_("--%s") " only, not the other one",
(wakestyle == WS_GDM) ? "gdm" : "gen1a");
}
}
@@ -9920,7 +10089,7 @@ static int CmdHF14AGen4_GDM_SetBlk(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf mf gdmsetblk",
"Set public block data on a GDM card\n"
"By default uses GDM alt wakeup (20/23). Use `--gen1a` or `--wupa` to override.\n"
"With no wakeup flag it auto-detects the card's access mode (gen1a, gdm alt, or WUPA magic auth).\n"
"`--force` param is used to override warnings like bad ACL writes.\n"
" if not specified, it will exit if detected",
"hf mf gdmsetblk --blk 1 -d 000102030405060708090a0b0c0d0e0f\n"
@@ -9932,9 +10101,9 @@ static int CmdHF14AGen4_GDM_SetBlk(const char *Cmd) {
arg_int1(NULL, "blk", "<dec>", "public block number"),
arg_str1("d", "data", "<hex>", "bytes to write, 16 hex bytes"),
arg_str0("k", "key", "<hex>", "key, 6 hex bytes (only for --wupa mode)"),
arg_lit0(NULL, "gdm", "use gdm alt (20/23) magic wakeup (default)"),
arg_lit0(NULL, "gen1a", "use gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "use regular wakeup (WUPA + magic auth with key)"),
arg_lit0(NULL, "gdm", "access: force gdm alt (20/23) magic wakeup"),
arg_lit0(NULL, "gen1a", "access: force gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "access: force regular wakeup (WUPA + magic auth with key)"),
arg_lit0(NULL, "force", "override warnings"),
arg_param_end
};
@@ -9966,11 +10135,17 @@ static int CmdHF14AGen4_GDM_SetBlk(const char *Cmd) {
return PM3_EINVARG;
}
uint8_t wakeup_type = 0;
uint8_t auth_cmd = 0;
if (gdm_resolve_wakeup(gdm, gen1a, wupa, keylen, &wakeup_type, &auth_cmd) != PM3_SUCCESS) {
return PM3_EINVARG;
// Auto-detect wakeup by probing a public-block read of the target block.
gdm_wakeup_t gw;
int wres = gdm_resolve_for_write(gdm, gen1a, wupa, key, keylen,
ISO14443A_CMD_READBLOCK, b, false, "hf mf gdmsetblk",
&gw, NULL, NULL);
if (wres != PM3_SUCCESS) {
return wres;
}
uint8_t wakeup_type = gw.wakeup_type;
uint8_t auth_cmd = gw.auth_cmd;
memcpy(key, gw.key, sizeof(key));
uint8_t blockno = (uint8_t)b;
@@ -10016,7 +10191,7 @@ static int CmdHF14AGen4_GDM_SetHidBlk(const char *Cmd) {
CLIParserInit(&ctx, "hf mf gdmsethidblk",
"Write hidden block data on a GDM card.\n"
"Hidden blocks 0-7 map to the card's hidden sector area.\n"
"By default uses GDM alt wakeup (20/23). Use `--gen1a` or `--wupa` to override.\n"
"With no wakeup flag it auto-detects a backdoor (gen1a or gdm alt); a WUPA-only card is rejected.\n"
"`--force` param is used to override warnings like bad BCC or ACL writes.\n"
" if not specified, it will exit if detected",
"hf mf gdmsethidblk --blk 0 -d 880494716904EA0C2394510800000000\n"
@@ -10029,9 +10204,9 @@ static int CmdHF14AGen4_GDM_SetHidBlk(const char *Cmd) {
arg_int1(NULL, "blk", "<dec>", "hidden block number (0-7)"),
arg_str1("d", "data", "<hex>", "bytes to write, 16 hex bytes"),
arg_str0("k", "key", "<hex>", "key 6 bytes (only for --wupa mode)"),
arg_lit0(NULL, "gdm", "use gdm alt (20/23) magic wakeup (default)"),
arg_lit0(NULL, "gen1a", "use gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "use regular wakeup (WUPA + magic auth with key)"),
arg_lit0(NULL, "gdm", "access: force gdm alt (20/23) magic wakeup"),
arg_lit0(NULL, "gen1a", "access: force gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "access: force regular wakeup (WUPA + magic auth with key)"),
arg_lit0(NULL, "force", "override warnings"),
arg_param_end
};
@@ -10066,11 +10241,17 @@ static int CmdHF14AGen4_GDM_SetHidBlk(const char *Cmd) {
}
// --- validate key length + resolve wakeup mode ---
uint8_t wakeup_type = 0;
uint8_t auth_cmd = 0;
if (gdm_resolve_wakeup(gdm, gen1a, wupa, keylen, &wakeup_type, &auth_cmd) != PM3_SUCCESS) {
return PM3_EINVARG;
// Auto-detect wakeup by probing a hidden-block read; hidden writes need a gen1a/gdm backdoor.
gdm_wakeup_t gw;
int wres = gdm_resolve_for_write(gdm, gen1a, wupa, key, keylen,
MIFARE_MAGIC_GDM_READBLOCK, b, true, "hf mf gdmsethidblk",
&gw, NULL, NULL);
if (wres != PM3_SUCCESS) {
return wres;
}
uint8_t wakeup_type = gw.wakeup_type;
uint8_t auth_cmd = gw.auth_cmd;
memcpy(key, gw.key, sizeof(key));
uint8_t blockno = (uint8_t)b;
@@ -10171,40 +10352,34 @@ static int gdm_read_blocks(int blk, int sec, const uint8_t *key, int keylen,
return PM3_EINVARG;
}
uint8_t wakeup_type = 0;
uint8_t auth_cmd = 0;
if (gdm_resolve_wakeup(gdm, gen1a, wupa, keylen, &wakeup_type, &auth_cmd) != PM3_SUCCESS) {
gdm_wakeup_t attempts[3];
int n_attempts = 0;
if (gdm_wakeup_attempts(gdm, gen1a, wupa, key, keylen, false, attempts, &n_attempts) != PM3_SUCCESS) {
return PM3_EINVARG;
}
int start_blk = (blk != -1) ? blk : mfFirstBlockOfSector(sec);
int num_blks = (blk != -1) ? 1 : mfNumBlocksPerSector(sec);
for (int i = 0; i < num_blks; i++) {
// probe the first block to pick a working wakeup, then reuse it for the rest
uint8_t data[MFBLOCK_SIZE] = {0};
int idx = gdm_pick_wakeup(read_cmd, start_blk, attempts, n_attempts, data);
if (idx < 0) {
PrintAndLogEx(FAILED, "Failed to read %s %d", label, start_blk);
return PM3_EFAILED;
}
if (n_attempts > 1) {
PrintAndLogEx(INFO, "Auto-detected wakeup: " _YELLOW_("%s"), gdm_wakeup_name(&attempts[idx]));
}
PrintAndLogEx(SUCCESS, "%s %3d: %s", label, start_blk, sprint_hex_inrow(data, MFBLOCK_SIZE));
for (int i = 1; i < num_blks; i++) {
int current_blk = start_blk + i;
mf_readblock_ex_t payload = {
.read_cmd = read_cmd,
.block_no = current_blk,
.wakeup = wakeup_type,
.auth_cmd = auth_cmd
};
memcpy(payload.key, key, sizeof(payload.key));
clearCommandBuffer();
SendCommandNG(CMD_HF_MIFARE_READBL_EX, (uint8_t *)&payload, sizeof(payload));
PacketResponseNG resp;
if (WaitForResponseTimeout(CMD_HF_MIFARE_READBL_EX, &resp, 1500) == false) {
PrintAndLogEx(FAILED, "command execution time out (block %d)", current_blk);
return PM3_ETIMEOUT;
}
if (resp.status == PM3_SUCCESS && resp.length == MFBLOCK_SIZE) {
PrintAndLogEx(SUCCESS, "%s %3d: %s", label, current_blk, sprint_hex_inrow(resp.data.asBytes, MFBLOCK_SIZE));
} else {
PrintAndLogEx(FAILED, "Failed to read %s %d. Status: %d", label, current_blk, resp.status);
return resp.status;
if (gdm_try_read(read_cmd, current_blk, &attempts[idx], data) != PM3_SUCCESS) {
PrintAndLogEx(FAILED, "Failed to read %s %d", label, current_blk);
return PM3_EFAILED;
}
PrintAndLogEx(SUCCESS, "%s %3d: %s", label, current_blk, sprint_hex_inrow(data, MFBLOCK_SIZE));
}
return PM3_SUCCESS;
@@ -10213,7 +10388,8 @@ static int gdm_read_blocks(int blk, int sec, const uint8_t *key, int keylen,
static int CmdHF14AGen4_GDM_GetHidBlk(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf mf gdmgethidblk",
"Read hidden block(s) from a GDM card",
"Read hidden block(s) from a GDM card.\n"
"With no wakeup flag it auto-detects the card's access mode (WUPA magic auth, gen1a, or gdm alt).",
"hf mf gdmgethidblk --blk 0\n"
"hf mf gdmgethidblk --sec 1 --gdm"
);
@@ -10222,9 +10398,9 @@ static int CmdHF14AGen4_GDM_GetHidBlk(const char *Cmd) {
arg_int0(NULL, "blk", "<dec>", "block to read"),
arg_int0(NULL, "sec", "<dec>", "sector to read"),
arg_str0("k", "key", "<hex>", "key 6 bytes (only for --wupa mode)"),
arg_lit0(NULL, "gdm", "use gdm alt (20/23) magic wakeup (default)"),
arg_lit0(NULL, "gen1a", "use gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "use regular wakeup (WUPA + magic auth with key)"),
arg_lit0(NULL, "gdm", "use gdm alt (20/23) magic wakeup"),
arg_lit0(NULL, "gen1a", "access: force gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "access: force regular wakeup (WUPA + magic auth with key)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
@@ -10245,7 +10421,8 @@ static int CmdHF14AGen4_GDM_GetHidBlk(const char *Cmd) {
static int CmdHF14AGen4_GDM_GetBlk(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf mf gdmgetblk",
"Read public block(s) from a GDM card",
"Read public block(s) from a GDM card.\n"
"With no wakeup flag it auto-detects the card's access mode (WUPA magic auth, gen1a, or gdm alt).",
"hf mf gdmgetblk --blk 0\n"
"hf mf gdmgetblk --sec 1 --gdm"
);
@@ -10254,9 +10431,9 @@ static int CmdHF14AGen4_GDM_GetBlk(const char *Cmd) {
arg_int0(NULL, "blk", "<dec>", "block to read"),
arg_int0(NULL, "sec", "<dec>", "sector to read"),
arg_str0("k", "key", "<hex>", "key 6 bytes (only for --wupa mode)"),
arg_lit0(NULL, "gdm", "use gdm alt (20/23) magic wakeup (default)"),
arg_lit0(NULL, "gen1a", "use gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "use regular wakeup (WUPA + magic auth with key)"),
arg_lit0(NULL, "gdm", "use gdm alt (20/23) magic wakeup"),
arg_lit0(NULL, "gen1a", "access: force gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "access: force regular wakeup (WUPA + magic auth with key)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
@@ -10277,7 +10454,8 @@ static int CmdHF14AGen4_GDM_GetBlk(const char *Cmd) {
static int CmdHF14AGen4_GDM_SetUid(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf mf gdmsetuid",
"Set UID on a GDM card",
"Set UID on a GDM card.\n"
"With no wakeup flag it auto-detects a backdoor (gen1a or gdm alt); a WUPA-only card is rejected.",
"hf mf gdmsetuid -u 01020304\n"
"hf mf gdmsetuid -u 01020304050607 --gen1a"
);
@@ -10285,9 +10463,9 @@ static int CmdHF14AGen4_GDM_SetUid(const char *Cmd) {
arg_param_begin,
arg_str1("u", "uid", "<hex>", "UID to set (4 or 7 hex bytes)"),
arg_str0("k", "key", "<hex>", "key 6 bytes (only for --wupa mode)"),
arg_lit0(NULL, "gdm", "use gdm alt (20/23) magic wakeup (default)"),
arg_lit0(NULL, "gen1a", "use gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "use regular wakeup (WUPA + magic auth with key)"),
arg_lit0(NULL, "gdm", "access: force gdm alt (20/23) magic wakeup"),
arg_lit0(NULL, "gen1a", "access: force gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "access: force regular wakeup (WUPA + magic auth with key)"),
arg_lit0(NULL, "f3d", "use F3 perso mode (writes to hidden block 1 instead of 0)"),
arg_param_end
};
@@ -10312,11 +10490,17 @@ static int CmdHF14AGen4_GDM_SetUid(const char *Cmd) {
return PM3_EINVARG;
}
uint8_t wakeup_type = 0;
uint8_t auth_cmd = 0;
if (gdm_resolve_wakeup(gdm, gen1a, wupa, keylen, &wakeup_type, &auth_cmd) != PM3_SUCCESS) {
return PM3_EINVARG;
// Auto-detect wakeup by probing a hidden-block read; gdmsetuid needs a gen1a/gdm backdoor
gdm_wakeup_t gw;
int wres = gdm_resolve_for_write(gdm, gen1a, wupa, key, keylen,
MIFARE_MAGIC_GDM_READBLOCK, 0, true, "hf mf gdmsetuid",
&gw, NULL, NULL);
if (wres != PM3_SUCCESS) {
return wres;
}
uint8_t wakeup_type = gw.wakeup_type;
uint8_t auth_cmd = gw.auth_cmd;
memcpy(key, gw.key, sizeof(key));
// Step 1: Read Config
uint8_t config[MFBLOCK_SIZE];
@@ -10435,19 +10619,21 @@ static int CmdHF14AGen4_GDM_SetUid(const char *Cmd) {
static int CmdHF14AGen4_GDM_Wipe(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf mf gdmwipe",
"Wipe a GDM card to factory defaults",
"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.",
"hf mf gdmwipe --gdm"
);
void *argtable[] = {
arg_param_begin,
arg_str0("k", "key", "<hex>", "key 6 bytes (only for --wupa mode)"),
arg_lit0(NULL, "gdm", "use gdm alt (20/23) magic wakeup (default)"),
arg_lit0(NULL, "gen1a", "use gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "use regular wakeup (WUPA + magic auth with key)"),
arg_lit0(NULL, "gdm", "access: force gdm alt (20/23) magic wakeup"),
arg_lit0(NULL, "gen1a", "access: force gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "access: force regular wakeup (WUPA + magic auth with key)"),
arg_lit0("a", "all", "wipe 4K card (40 sectors) instead of 1K card (16 sectors)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
// allow a bare `hf mf gdmwipe` (all args optional) so the no-flag auto-detect path works
CLIExecWithReturn(ctx, Cmd, argtable, true);
int keylen = 0;
uint8_t key[6] = {0};
@@ -10459,14 +10645,28 @@ static int CmdHF14AGen4_GDM_Wipe(const char *Cmd) {
bool wipe_4k = arg_get_lit(ctx, 5);
CLIParserFree(ctx);
uint8_t wakeup_type = 0;
uint8_t auth_cmd = 0;
if (gdm_resolve_wakeup(gdm, gen1a, wupa, keylen, &wakeup_type, &auth_cmd) != PM3_SUCCESS) {
return PM3_EINVARG;
// Auto-detect wakeup by probing a hidden-block read; gdmwipe rewrites hidden blocks,
// so it needs a gen1a/gdm backdoor (WUPA-only cards are rejected before any write).
gdm_wakeup_t gw;
int wres = gdm_resolve_for_write(gdm, gen1a, wupa, key, keylen,
MIFARE_MAGIC_GDM_READBLOCK, 0, true, "hf mf gdmwipe",
&gw, NULL, NULL);
if (wres != PM3_SUCCESS) {
return wres;
}
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 cur_config[MFBLOCK_SIZE] = {0};
if (gdm_read_config(wakeup_type, auth_cmd, key, cur_config) == PM3_SUCCESS) {
standard_config[2] = cur_config[2];
} else {
PrintAndLogEx(WARNING, "Could not read current config, keeping the default wakeup style");
}
// Step 1: Write Config
uint8_t standard_config[MFBLOCK_SIZE] = {0x7A, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x08};
mf_writeblock_ex_t config_payload = {
.wakeup = wakeup_type,
.auth_cmd = auth_cmd,
@@ -10497,6 +10697,9 @@ static int CmdHF14AGen4_GDM_Wipe(const char *Cmd) {
};
memcpy(write_payload.key, key, 6);
int failed_blocks = 0;
int total_blocks = 0;
for (int sec = 0; sec < max_sector; sec++) {
int first_blk = mfFirstBlockOfSector(sec);
int num_blks = mfNumBlocksPerSector(sec);
@@ -10517,7 +10720,9 @@ static int CmdHF14AGen4_GDM_Wipe(const char *Cmd) {
clearCommandBuffer();
SendCommandNG(CMD_HF_MIFARE_WRITEBL_EX, (uint8_t *)&write_payload, sizeof(write_payload));
WaitForResponseTimeout(CMD_HF_MIFARE_WRITEBL_EX, &resp, 1500);
if (WaitForResponseTimeout(CMD_HF_MIFARE_WRITEBL_EX, &resp, 1500) == false || resp.status != PM3_SUCCESS) {
failed_blocks++;
}
// Hidden block writes
write_payload.write_cmd = MIFARE_MAGIC_GDM_WRITEBLOCK; // 0xA8
@@ -10527,28 +10732,42 @@ static int CmdHF14AGen4_GDM_Wipe(const char *Cmd) {
clearCommandBuffer();
SendCommandNG(CMD_HF_MIFARE_WRITEBL_EX, (uint8_t *)&write_payload, sizeof(write_payload));
WaitForResponseTimeout(CMD_HF_MIFARE_WRITEBL_EX, &resp, 1500);
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);
}
PrintAndLogEx(SUCCESS, "Wipe completed!");
return PM3_SUCCESS;
// report write failures instead of always printing "Wipe completed!"
if (failed_blocks == 0) {
PrintAndLogEx(SUCCESS, "Wipe completed!");
return PM3_SUCCESS;
}
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");
}
return PM3_EFAILED;
}
static int CmdHF14AGen4_GDM_SetSig(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf mf gdmsetsig",
"Set MFC EV1 signature on a GDM card",
"Set MFC EV1 signature on a GDM card.\n"
"With no wakeup flag it auto-detects a backdoor (gen1a or gdm alt); a WUPA-only card is rejected.",
"hf mf gdmsetsig -s <64 hex chars>"
);
void *argtable[] = {
arg_param_begin,
arg_str1("s", "sig", "<hex>", "32-byte signature (64 hex characters)"),
arg_str0("k", "key", "<hex>", "key 6 bytes (only for --wupa mode)"),
arg_lit0(NULL, "gdm", "use gdm alt (20/23) magic wakeup (default)"),
arg_lit0(NULL, "gen1a", "use gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "use regular wakeup (WUPA + magic auth with key)"),
arg_lit0(NULL, "gdm", "access: force gdm alt (20/23) magic wakeup"),
arg_lit0(NULL, "gen1a", "access: force gen1a (40/43) magic wakeup"),
arg_lit0(NULL, "wupa", "access: force regular wakeup (WUPA + magic auth with key)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
@@ -10571,11 +10790,17 @@ static int CmdHF14AGen4_GDM_SetSig(const char *Cmd) {
return PM3_EINVARG;
}
uint8_t wakeup_type = 0;
uint8_t auth_cmd = 0;
if (gdm_resolve_wakeup(gdm, gen1a, wupa, keylen, &wakeup_type, &auth_cmd) != PM3_SUCCESS) {
return PM3_EINVARG;
// Auto-detect wakeup by probing a hidden-block read (block 5); gdmsetsig needs a gen1a/gdm backdoor.
gdm_wakeup_t gw;
int wres = gdm_resolve_for_write(gdm, gen1a, wupa, key, keylen,
MIFARE_MAGIC_GDM_READBLOCK, 5, true, "hf mf gdmsetsig",
&gw, NULL, NULL);
if (wres != PM3_SUCCESS) {
return wres;
}
uint8_t wakeup_type = gw.wakeup_type;
uint8_t auth_cmd = gw.auth_cmd;
memcpy(key, gw.key, sizeof(key));
// Step 1: Write signature part 1 (block 5)
mf_writeblock_ex_t write_payload = {