From 2362cabcd424b1b48b430dfdccc9ba4870761682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=AD=90=E6=AD=AA?= Date: Tue, 14 Jul 2026 04:22:00 +0800 Subject: [PATCH] Add ISO15693 magic 'V3' tag support (hf 15 csetuid --v3 / hf 15 cfinalize) --- client/src/cmdhf15.c | 194 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 181 insertions(+), 13 deletions(-) diff --git a/client/src/cmdhf15.c b/client/src/cmdhf15.c index 1711e784c..463300802 100644 --- a/client/src/cmdhf15.c +++ b/client/src/cmdhf15.c @@ -3309,6 +3309,79 @@ static int CmdHF15Restore(const char *Cmd) { return PM3_SUCCESS; } +// ISO15693 magic "V3" tag support +#define ISO15_MAGIC_V3_BLK_UID_LO 0x10 +#define ISO15_MAGIC_V3_BLK_UID_HI 0x11 +#define ISO15_MAGIC_V3_BLK_SIG_A 0x14 +#define ISO15_MAGIC_V3_BLK_SIG_B 0x15 + +// signature check in block 0x14/0x15 +static const uint8_t iso15_magic_v3_sig_a[4] = {0xA5, 0x2B, 0x44, 0x2C}; +static const uint8_t iso15_magic_v3_sig_b[4] = {0x21, 0xAE, 0x93, 0x00}; + +// finalize command write in block 0x14/0x15 +// hf 15 raw -wac -d 022114A52B442C +// hf 15 raw -wac -d 02211569E25D00 +static const uint8_t iso15_magic_v3_fin_a[4] = {0xA5, 0x2B, 0x44, 0x2C}; +static const uint8_t iso15_magic_v3_fin_b[4] = {0x69, 0xE2, 0x5D, 0x00}; + +// Read one 4-byte block from a magic V3 tag in unaddressed mode. +static int hf15_magic_v3_read_blk(uint8_t blockno, uint8_t out[4]) { + + uint16_t approxlen = 2 + 1 + 2; + iso15_raw_cmd_t *packet = (iso15_raw_cmd_t *)calloc(1, sizeof(iso15_raw_cmd_t) + approxlen); + if (packet == NULL) { + PrintAndLogEx(WARNING, "Failed to allocate memory"); + return PM3_EMALLOC; + } + + // enforce OPTION flag so we get the lock byte, keeping data at offset 2 + packet->raw[packet->rawlen++] = arg_get_raw_flag(0, true, false, true); + packet->raw[packet->rawlen++] = ISO15693_READBLOCK; + packet->raw[packet->rawlen++] = blockno; + AddCrc15(packet->raw, packet->rawlen); + packet->rawlen += 2; + packet->flags = (ISO15_CONNECT | ISO15_READ_RESPONSE); + + clearCommandBuffer(); + SendCommandNG(CMD_HF_ISO15693_COMMAND, (uint8_t *)packet, ISO15_RAW_LEN(packet->rawlen)); + free(packet); + + PacketResponseNG resp; + if (WaitForResponseTimeout(CMD_HF_ISO15693_COMMAND, &resp, 2000) == false) { + PrintAndLogEx(DEBUG, "iso15693 timeout"); + return PM3_ETIMEOUT; + } + + ISO15_ERROR_HANDLING_RESPONSE + + uint8_t *d = resp.data.asBytes; + + ISO15_ERROR_HANDLING_CARD_RESPONSE(d, resp.length) + + memcpy(out, d + 2, 4); + return PM3_SUCCESS; +} + +// Write one 4-byte block to a magic V3 tag in unaddressed mode. +static int hf15_magic_v3_write_blk(uint8_t blockno, const uint8_t *data) { + uint16_t flags = arg_get_raw_flag(0, true, false, false); + return hf_15_write_blk(NULL, flags, NULL, true, blockno, data, 4); +} + +// Detect an un-finalized magic V3 tag by its configuration-mode signature. +static bool hf15_magic_v3_is_config_mode(void) { + uint8_t a[4] = {0}; + uint8_t b[4] = {0}; + if (hf15_magic_v3_read_blk(ISO15_MAGIC_V3_BLK_SIG_A, a) != PM3_SUCCESS) { + return false; + } + if (hf15_magic_v3_read_blk(ISO15_MAGIC_V3_BLK_SIG_B, b) != PM3_SUCCESS) { + return false; + } + return (memcmp(a, iso15_magic_v3_sig_a, 4) == 0) && (memcmp(b, iso15_magic_v3_sig_b, 4) == 0); +} + /** * Commandline handling: HF15 CMD CSETUID * Set UID for magic Chinese card @@ -3317,15 +3390,19 @@ static int CmdHF15CSetUID(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf 15 csetuid", - "Set UID for magic Chinese card (only works with such cards)\n", + "Set UID for magic Chinese card (only works with such cards)\n" + "For magic 'V3' tags this writes the UID configuration only and is repeatable;\n" + "run `" _YELLOW_("hf 15 cfinalize") "` afterwards to lock the UID permanently.", "hf 15 csetuid -u E011223344556677 -> use gen1 command\n" - "hf 15 csetuid -u E011223344556677 --v2 -> use gen2 command" + "hf 15 csetuid -u E011223344556677 --v2 -> use gen2 command\n" + "hf 15 csetuid -u E011223344556677 --v3 -> use gen3 (V3) magic tag" ); void *argtable[] = { arg_param_begin, arg_str1("u", "uid", "", "UID, 8 hex bytes"), arg_lit0("2", "v2", "Use gen2 magic command"), + arg_lit0("3", "v3", "Use gen3 (V3) magic tag (repeatable, needs cfinalize)"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, false); @@ -3337,8 +3414,14 @@ static int CmdHF15CSetUID(const char *Cmd) { int uidlen = 0; CLIGetHexWithReturn(ctx, 1, payload.uid, &uidlen); bool use_v2 = arg_get_lit(ctx, 2); + bool use_v3 = arg_get_lit(ctx, 3); CLIParserFree(ctx); + if (use_v2 && use_v3) { + PrintAndLogEx(WARNING, "Select only one of " _YELLOW_("--v2") " / " _YELLOW_("--v3")); + return PM3_EINVARG; + } + if (uidlen != ISO15693_UID_LENGTH) { PrintAndLogEx(WARNING, "UID must include 8 hex bytes, got " _RED_("%i"), uidlen); return PM3_EINVARG; @@ -3360,19 +3443,40 @@ static int CmdHF15CSetUID(const char *Cmd) { } PrintAndLogEx(INFO, "Writing..."); - PacketResponseNG resp; - clearCommandBuffer(); - uint16_t cmd = CMD_HF_ISO15693_CSETUID; - if (use_v2) { - cmd = CMD_HF_ISO15693_CSETUID_V2; - } + if (use_v3) { + /* + for example id: E011223344556677 + [=] 16 | 77 66 55 44 | 0 | wfUD + [=] 17 | 33 22 11 E0 | 0 | 3".. + */ + uint8_t blk_lo[4] = {0}; + uint8_t blk_hi[4] = {0}; + reverse_array_copy(payload.uid + 4, 4, blk_lo); + reverse_array_copy(payload.uid, 4, blk_hi); - SendCommandNG(cmd, (uint8_t *)&payload, sizeof(payload)); - if (WaitForResponseTimeout(cmd, &resp, 2000) == false) { - PrintAndLogEx(WARNING, "timeout while waiting for reply"); - DropField(); - return PM3_ESOFT; + if (hf15_magic_v3_write_blk(ISO15_MAGIC_V3_BLK_UID_LO, blk_lo) != PM3_SUCCESS || + hf15_magic_v3_write_blk(ISO15_MAGIC_V3_BLK_UID_HI, blk_hi) != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "Setting new UID ( " _RED_("fail") " )"); + PrintAndLogEx(NORMAL, ""); + return PM3_ESOFT; + } + + } else { + PacketResponseNG resp; + clearCommandBuffer(); + + uint16_t cmd = CMD_HF_ISO15693_CSETUID; + if (use_v2) { + cmd = CMD_HF_ISO15693_CSETUID_V2; + } + + SendCommandNG(cmd, (uint8_t *)&payload, sizeof(payload)); + if (WaitForResponseTimeout(cmd, &resp, 2000) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply"); + DropField(); + return PM3_ESOFT; + } } PrintAndLogEx(INFO, "Verifying..."); @@ -3397,6 +3501,69 @@ static int CmdHF15CSetUID(const char *Cmd) { return PM3_ESOFT; } +/** + * Commandline handling: HF15 CMD CFINALIZE + * Finalize a magic 'V3' tag - irreversible + */ +static int CmdHF15CFinalize(const char *Cmd) { + + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf 15 cfinalize", + "Finalize a magic ISO15693 'V3' tag.\n" + _RED_("This operation is irreversible.") " After finalize the configuration\n" + "area is erased and the UID can no longer be changed. Set the UID with\n" + "`" _YELLOW_("hf 15 csetuid --v3") "` first, then lock it in with this command.", + "hf 15 cfinalize -y" + ); + + void *argtable[] = { + arg_param_begin, + arg_lit0("y", "yes", "Confirm the irreversible finalize operation"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + bool confirmed = arg_get_lit(ctx, 1); + CLIParserFree(ctx); + + PrintAndLogEx(INFO, "Get current tag"); + + uint8_t carduid[ISO15693_UID_LENGTH] = {0x00}; + if (getUID(true, false, carduid) != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "no tag found"); + return PM3_ESOFT; + } + + // Safety: only proceed on a tag that is actually an un-finalized magic V3. + // Writing the finalize values to any other tag may permanently brick it. + if (hf15_magic_v3_is_config_mode() == false) { + PrintAndLogEx(FAILED, "tag is not an un-finalized magic " _YELLOW_("V3") " tag"); + PrintAndLogEx(HINT, "Hint: signature in blocks 0x14/0x15 not found - already finalized or not a V3 tag"); + return PM3_ESOFT; + } + + PrintAndLogEx(SUCCESS, "Magic " _GREEN_("V3") " tag in configuration mode ( " _GREEN_("ok") " )"); + + if (confirmed == false) { + PrintAndLogEx(WARNING, _RED_("This operation is irreversible!") " The UID will be locked permanently."); + PrintAndLogEx(WARNING, "Add " _YELLOW_("-y") " to confirm and proceed."); + return PM3_EINVARG; + } + + PrintAndLogEx(INFO, "Finalizing..."); + + if (hf15_magic_v3_write_blk(ISO15_MAGIC_V3_BLK_SIG_A, iso15_magic_v3_fin_a) != PM3_SUCCESS || + hf15_magic_v3_write_blk(ISO15_MAGIC_V3_BLK_SIG_B, iso15_magic_v3_fin_b) != PM3_SUCCESS) { + PrintAndLogEx(FAILED, "Finalize ( " _RED_("fail") " )"); + PrintAndLogEx(NORMAL, ""); + return PM3_ESOFT; + } + + PrintAndLogEx(SUCCESS, "Finalize ( " _GREEN_("ok") " )"); + PrintAndLogEx(HINT, "Hint: UID is now locked; the tag behaves like a normal ISO15693 tag"); + PrintAndLogEx(NORMAL, ""); + return PM3_SUCCESS; +} + static int CmdHF15SlixEASEnable(const char *Cmd) { CLIParserContext *ctx; @@ -4146,6 +4313,7 @@ static command_t CommandTable[] = { {"writedsfid", CmdHF15WriteDsfid, IfPm3Iso15693, "Writes the DSFID on an ISO-15693 tag"}, {"-----------", CmdHF15Help, IfPm3Iso15693, "------------------------- " _CYAN_("Magic") " -----------------------"}, {"csetuid", CmdHF15CSetUID, IfPm3Iso15693, "Set UID for magic card"}, + {"cfinalize", CmdHF15CFinalize, IfPm3Iso15693, "Finalize a magic V3 tag (irreversible)"}, {NULL, NULL, NULL, NULL} };