Merge pull request #3653 from 0x6r1an0y/20260917-15magic-fix

Improve `hf 15 csetuid` command
This commit is contained in:
Iceman authored and GitHub committed 2026-09-26 21:48:47 +07:00
commit 9ba8b13da6
8 files changed
+433 -169

No files matched your search

+12
View File
@@ -2048,6 +2048,18 @@ static void PacketReceived(PacketCommandNG *packet) {
SetTag15693Uid_v2(payload->uid);
break;
}
case CMD_HF_ISO15693_CSETUID_V3: {
struct p {
uint8_t uid[8];
} PACKED;
struct p *payload = (struct p *) packet->data.asBytes;
SetTag15693Uid_v3(payload->uid);
break;
}
case CMD_HF_ISO15693_CFINALIZE_V3: {
FinalizeTag15693_v3();
break;
}
case CMD_HF_ISO15693_SLIX_DISABLE_EAS: {
struct p {
uint8_t pwd[4];
+306 -35
View File
@@ -3202,51 +3202,322 @@ void LockPassSlixIso15693(uint32_t pass_id, uint32_t password) {
//
//-----------------------------------------------------------------------------
// Backdoor blocks of a magic "Gen1" tag, see doc/magic_cards_notes.md
#define ISO15_MAGIC_GEN1_BLK_UID_LO 0x38
#define ISO15_MAGIC_GEN1_BLK_UID_HI 0x39
#define ISO15_MAGIC_GEN1_BLK_UNLOCK_A 0x3E
#define ISO15_MAGIC_GEN1_BLK_UNLOCK_B 0x3F
static const uint8_t g_magic_gen1_blank[4] = {0x00, 0x00, 0x00, 0x00};
static const uint8_t g_magic_gen1_unlock_b[4] = {0x69, 0x96, 0x00, 0x00};
// true when every byte is zero
static bool magic_15_is_blank(const uint8_t *data, uint8_t len) {
for (uint8_t i = 0; i < len; i++) {
if (data[i] != 0x00) {
return false;
}
}
return true;
}
// PM3_SUCCESS means the tag answered with block data, *data then points into
// the receive buffer and stays valid until the next command overwrites it.
static int magic_15_read_blk(uint8_t blockno, uint8_t **data, uint8_t *len, uint32_t *start_time, uint32_t *eof_time) {
uint8_t cmd[5] = {ISO15_REQ_DATARATE_HIGH | ISO15_REQ_OPTION, ISO15693_READBLOCK, blockno, 0x00, 0x00};
AddCrc15(cmd, 3);
uint16_t recvlen = 0;
int res = SendDataTag(cmd, sizeof(cmd), false, true, s_iso15_recvbuf, sizeof(s_iso15_recvbuf),
*start_time, ISO15693_READER_TIMEOUT, eof_time, &recvlen);
*start_time = *eof_time + DELAY_ISO15693_VICC_TO_VCD_READER;
if (res != PM3_SUCCESS) {
return res;
}
// flags + at least four data bytes + crc
if (recvlen < 7) {
return PM3_EWRONGANSWER;
}
// GetIso15693AnswerFromTag() doesn't check the crc, so do it here
if (CheckCrc15(s_iso15_recvbuf, recvlen) == false) {
return PM3_ECRC;
}
if ((s_iso15_recvbuf[0] & ISO15_RES_ERROR) == ISO15_RES_ERROR) {
return PM3_EWRONGANSWER;
}
// the option flag makes the tag prepend a lock byte, blocks can be wider than four bytes
uint8_t offset = (recvlen > 7) ? 2 : 1;
*data = s_iso15_recvbuf + offset;
*len = recvlen - offset - 2;
return PM3_SUCCESS;
}
// Unaddressed block write, leaves the field on for the next command
static int magic_15_write_blk(uint8_t blockno, const uint8_t *data, uint32_t *start_time, uint32_t *eof_time) {
uint8_t cmd[9] = {ISO15_REQ_DATARATE_HIGH, ISO15693_WRITEBLOCK, blockno, data[0], data[1], data[2], data[3], 0x00, 0x00};
AddCrc15(cmd, 7);
uint16_t recvlen = 0;
int res = SendDataTag(cmd, sizeof(cmd), false, true, s_iso15_recvbuf, sizeof(s_iso15_recvbuf),
*start_time, ISO15693_READER_TIMEOUT_WRITE, eof_time, &recvlen);
*start_time = *eof_time + DELAY_ISO15693_VICC_TO_VCD_READER;
if (res != PM3_SUCCESS) {
return res;
}
// flags + crc
if (recvlen < 3) {
return PM3_EWRONGANSWER;
}
if (CheckCrc15(s_iso15_recvbuf, recvlen) == false) {
return PM3_ECRC;
}
if ((s_iso15_recvbuf[0] & ISO15_RES_ERROR) == ISO15_RES_ERROR) {
return PM3_EWRONGANSWER;
}
return PM3_SUCCESS;
}
// On a real Gen1 tag these four blocks sit outside the readable memory and the read fails.
// A tag that answers with data is a normal tag and we must not overwrite it.
static bool magic_gen1_is_backdoor(uint32_t *start_time, uint32_t *eof_time) {
static const uint8_t blocks[4] = {
ISO15_MAGIC_GEN1_BLK_UID_LO,
ISO15_MAGIC_GEN1_BLK_UID_HI,
ISO15_MAGIC_GEN1_BLK_UNLOCK_A,
ISO15_MAGIC_GEN1_BLK_UNLOCK_B
};
for (uint8_t i = 0; i < ARRAYLEN(blocks); i++) {
uint8_t *data = NULL;
uint8_t len = 0;
if (magic_15_read_blk(blocks[i], &data, &len, start_time, eof_time) != PM3_SUCCESS) {
continue;
}
if (magic_15_is_blank(data, len)) {
continue;
}
// leftover of the unlock write of an earlier attempt on this same tag
if (blocks[i] == ISO15_MAGIC_GEN1_BLK_UNLOCK_B && len >= 4 &&
memcmp(data, g_magic_gen1_unlock_b, 4) == 0) {
continue;
}
if (g_dbglevel >= DBG_ERROR) {
Dbprintf("SetTag15693Uid: block 0x%02X holds data, not a magic Gen1 tag", blocks[i]);
}
return false;
}
return true;
}
// Blank the four blocks again so a failed attempt leaves no unlock values behind
static void magic_gen1_rollback(uint32_t *start_time, uint32_t *eof_time) {
magic_15_write_blk(ISO15_MAGIC_GEN1_BLK_UID_LO, g_magic_gen1_blank, start_time, eof_time);
magic_15_write_blk(ISO15_MAGIC_GEN1_BLK_UID_HI, g_magic_gen1_blank, start_time, eof_time);
magic_15_write_blk(ISO15_MAGIC_GEN1_BLK_UNLOCK_A, g_magic_gen1_blank, start_time, eof_time);
magic_15_write_blk(ISO15_MAGIC_GEN1_BLK_UNLOCK_B, g_magic_gen1_blank, start_time, eof_time);
}
// Set the UID on Magic ISO15693 tag (based on Iceman's LUA-script).
void SetTag15693Uid(const uint8_t *uid) {
LED_A_ON();
uint8_t cmd[4][9] = {
{ISO15_REQ_DATARATE_HIGH, ISO15693_WRITEBLOCK, 0x3e, 0x00, 0x00, 0x00, 0x00, 0xE9, 0x8F},
{ISO15_REQ_DATARATE_HIGH, ISO15693_WRITEBLOCK, 0x3f, 0x69, 0x96, 0x00, 0x00, 0x8A, 0xBB},
// Command 3 : 02 21 38 u8u7u6u5 (where uX = uid byte X)
{ISO15_REQ_DATARATE_HIGH, ISO15693_WRITEBLOCK, 0x38, uid[7], uid[6], uid[5], uid[4]},
// Command 4 : 02 21 39 u4u3u2u1 (where uX = uid byte X)
{ISO15_REQ_DATARATE_HIGH, ISO15693_WRITEBLOCK, 0x39, uid[3], uid[2], uid[1], uid[0]}
};
AddCrc15(cmd[2], 7);
AddCrc15(cmd[3], 7);
memset(s_iso15_recvbuf, 0, sizeof(s_iso15_recvbuf));
uint32_t start_time = 0;
uint32_t eof_time = 0;
uint16_t recvlen = 0;
Iso15693InitReader();
StartCountSspClk();
uint32_t start_time = 0, eof_time = 0;
int res = PM3_SUCCESS;
uint8_t origuid[8] = {0};
if (get_uid_slix(start_time, &eof_time, origuid) != PM3_SUCCESS) {
reply_ng(CMD_HF_ISO15693_CSETUID, PM3_ETIMEOUT, NULL, 0);
switch_off();
return;
}
start_time = eof_time + DELAY_ISO15693_VICC_TO_VCD_READER;
for (int i = 0; i < 4; i++) {
res = SendDataTag(
cmd[i],
sizeof(cmd[i]),
(i == 0) ? true : false,
true,
s_iso15_recvbuf,
sizeof(s_iso15_recvbuf),
start_time,
ISO15693_READER_TIMEOUT_WRITE,
&eof_time,
&recvlen
);
start_time = eof_time + DELAY_ISO15693_VICC_TO_VCD_READER;
if (magic_gen1_is_backdoor(&start_time, &eof_time) == false) {
reply_ng(CMD_HF_ISO15693_CSETUID, PM3_EWRONGANSWER, NULL, 0);
switch_off();
return;
}
reply_ng(CMD_HF_ISO15693_CSETUID, res, NULL, 0);
const uint8_t uid_lo[4] = {uid[7], uid[6], uid[5], uid[4]};
const uint8_t uid_hi[4] = {uid[3], uid[2], uid[1], uid[0]};
// the unlock writes are best-effort, a tag already unlocked rejects them
magic_15_write_blk(ISO15_MAGIC_GEN1_BLK_UNLOCK_A, g_magic_gen1_blank, &start_time, &eof_time);
magic_15_write_blk(ISO15_MAGIC_GEN1_BLK_UNLOCK_B, g_magic_gen1_unlock_b, &start_time, &eof_time);
int res = magic_15_write_blk(ISO15_MAGIC_GEN1_BLK_UID_LO, uid_lo, &start_time, &eof_time);
if (res == PM3_SUCCESS) {
res = magic_15_write_blk(ISO15_MAGIC_GEN1_BLK_UID_HI, uid_hi, &start_time, &eof_time);
}
// get_uid_slix answers LSB first, the requested uid is MSB first
uint8_t newuid[8] = {0}, revuid[8] = {0};
int res_uid = get_uid_slix(start_time, &eof_time, newuid);
reverse_array_copy(newuid, sizeof(newuid), revuid);
start_time = eof_time + DELAY_ISO15693_VICC_TO_VCD_READER;
if (res == PM3_SUCCESS && res_uid == PM3_SUCCESS && memcmp(revuid, uid, sizeof(revuid)) == 0) {
reply_ng(CMD_HF_ISO15693_CSETUID, PM3_SUCCESS, NULL, 0);
switch_off();
return;
}
// only roll back while the tag still shows its original UID, blanking the
// blocks after a UID change would destroy what we just wrote
if (res_uid == PM3_SUCCESS && memcmp(newuid, origuid, sizeof(newuid)) == 0) {
magic_gen1_rollback(&start_time, &eof_time);
}
reply_ng(CMD_HF_ISO15693_CSETUID, PM3_ESOFT, NULL, 0);
switch_off();
}
// Magic "V3" tags answer with a signature in 0x14/0x15 while the UID is still changeable
#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
static const uint8_t g_magic_v3_sig_a[4] = {0xA5, 0x2B, 0x44, 0x2C};
static const uint8_t g_magic_v3_sig_b[4] = {0x21, 0xAE, 0x93, 0x00};
static const uint8_t g_magic_v3_fin_a[4] = {0xA5, 0x2B, 0x44, 0x2C};
static const uint8_t g_magic_v3_fin_b[4] = {0x69, 0xE2, 0x5D, 0x00};
// Writing the finalize values to anything that is not an un-finalized V3 tag bricks it.
// PM3_SUCCESS means both signature blocks were read, *in_config says what they hold.
static int magic_v3_read_config(bool *in_config, uint32_t *start_time, uint32_t *eof_time) {
uint8_t *data = NULL;
uint8_t len = 0;
*in_config = false;
// the second read overwrites the buffer, so compare 0x14 before asking for 0x15
int res = magic_15_read_blk(ISO15_MAGIC_V3_BLK_SIG_A, &data, &len, start_time, eof_time);
if (res != PM3_SUCCESS) {
return res;
}
bool sig_a = (len >= 4 && memcmp(data, g_magic_v3_sig_a, 4) == 0);
res = magic_15_read_blk(ISO15_MAGIC_V3_BLK_SIG_B, &data, &len, start_time, eof_time);
if (res != PM3_SUCCESS) {
return res;
}
*in_config = (sig_a && len >= 4 && memcmp(data, g_magic_v3_sig_b, 4) == 0);
return PM3_SUCCESS;
}
// Set the UID on a magic "V3" tag, repeatable until the tag is finalized
void SetTag15693Uid_v3(const uint8_t *uid) {
LED_A_ON();
memset(s_iso15_recvbuf, 0, sizeof(s_iso15_recvbuf));
Iso15693InitReader();
StartCountSspClk();
uint32_t start_time = 0, eof_time = 0;
uint8_t curuid[8] = {0};
if (get_uid_slix(start_time, &eof_time, curuid) != PM3_SUCCESS) {
reply_ng(CMD_HF_ISO15693_CSETUID_V3, PM3_ETIMEOUT, NULL, 0);
switch_off();
return;
}
start_time = eof_time + DELAY_ISO15693_VICC_TO_VCD_READER;
bool in_config = false;
if (magic_v3_read_config(&in_config, &start_time, &eof_time) != PM3_SUCCESS || in_config == false) {
reply_ng(CMD_HF_ISO15693_CSETUID_V3, PM3_EWRONGANSWER, NULL, 0);
switch_off();
return;
}
const uint8_t uid_lo[4] = {uid[7], uid[6], uid[5], uid[4]};
const uint8_t uid_hi[4] = {uid[3], uid[2], uid[1], uid[0]};
int res = magic_15_write_blk(ISO15_MAGIC_V3_BLK_UID_LO, uid_lo, &start_time, &eof_time);
if (res == PM3_SUCCESS) {
res = magic_15_write_blk(ISO15_MAGIC_V3_BLK_UID_HI, uid_hi, &start_time, &eof_time);
}
if (res == PM3_SUCCESS) {
// get_uid_slix answers LSB first, the requested uid is MSB first
uint8_t newuid[8] = {0}, revuid[8] = {0};
res = get_uid_slix(start_time, &eof_time, newuid);
reverse_array_copy(newuid, sizeof(newuid), revuid);
if (res == PM3_SUCCESS && memcmp(revuid, uid, sizeof(revuid)) != 0) {
res = PM3_ESOFT;
}
}
reply_ng(CMD_HF_ISO15693_CSETUID_V3, (res == PM3_SUCCESS) ? PM3_SUCCESS : PM3_ESOFT, NULL, 0);
switch_off();
}
// Lock the UID of a magic "V3" tag, irreversible
void FinalizeTag15693_v3(void) {
LED_A_ON();
memset(s_iso15_recvbuf, 0, sizeof(s_iso15_recvbuf));
Iso15693InitReader();
StartCountSspClk();
uint32_t start_time = 0, eof_time = 0;
uint8_t curuid[8] = {0};
if (get_uid_slix(start_time, &eof_time, curuid) != PM3_SUCCESS) {
reply_ng(CMD_HF_ISO15693_CFINALIZE_V3, PM3_ETIMEOUT, NULL, 0);
switch_off();
return;
}
start_time = eof_time + DELAY_ISO15693_VICC_TO_VCD_READER;
bool in_config = false;
if (magic_v3_read_config(&in_config, &start_time, &eof_time) != PM3_SUCCESS || in_config == false) {
reply_ng(CMD_HF_ISO15693_CFINALIZE_V3, PM3_EWRONGANSWER, NULL, 0);
switch_off();
return;
}
int res = magic_15_write_blk(ISO15_MAGIC_V3_BLK_SIG_A, g_magic_v3_fin_a, &start_time, &eof_time);
if (res == PM3_SUCCESS) {
res = magic_15_write_blk(ISO15_MAGIC_V3_BLK_SIG_B, g_magic_v3_fin_b, &start_time, &eof_time);
}
// finalize wipes the memory, so the signature blocks must read back as something
// else. a read that fails proves nothing and counts as a failure too
if (res == PM3_SUCCESS) {
if (magic_v3_read_config(&in_config, &start_time, &eof_time) != PM3_SUCCESS || in_config) {
res = PM3_ESOFT;
}
}
reply_ng(CMD_HF_ISO15693_CFINALIZE_V3, (res == PM3_SUCCESS) ? PM3_SUCCESS : PM3_ESOFT, NULL, 0);
switch_off();
}
+2
View File
@@ -59,6 +59,8 @@ int SendDataTagEOF(uint8_t *recv, uint16_t max_recv_len, uint32_t start_time, ui
void SetTag15693Uid(const uint8_t *uid);
void SetTag15693Uid_v2(const uint8_t *uid);
void SetTag15693Uid_v3(const uint8_t *uid);
void FinalizeTag15693_v3(void);
void WritePasswordSlixIso15693(const uint8_t *old_password, const uint8_t *new_password, uint8_t pwd_id);
void DisablePrivacySlixIso15693(const uint8_t *password);
+69 -126
View File
@@ -3324,79 +3324,6 @@ 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
@@ -3406,6 +3333,8 @@ 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"
"Gen1 and V3 probe their backdoor blocks first and refuse to write if they\n"
"read back real data, to avoid corrupting a normal (non-magic) tag.\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"
@@ -3459,38 +3388,50 @@ static int CmdHF15CSetUID(const char *Cmd) {
PrintAndLogEx(INFO, "Writing...");
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);
PacketResponseNG resp;
clearCommandBuffer();
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;
}
uint16_t cmd = CMD_HF_ISO15693_CSETUID;
if (use_v2) {
cmd = CMD_HF_ISO15693_CSETUID_V2;
} else if (use_v3) {
cmd = CMD_HF_ISO15693_CSETUID_V3;
}
} else {
PacketResponseNG resp;
clearCommandBuffer();
SendCommandNG(cmd, (uint8_t *)&payload, sizeof(payload));
if (WaitForResponseTimeout(cmd, &resp, 2000) == false) {
PrintAndLogEx(WARNING, "timeout while waiting for reply");
DropField();
return PM3_ESOFT;
}
uint16_t cmd = CMD_HF_ISO15693_CSETUID;
if (use_v2) {
cmd = CMD_HF_ISO15693_CSETUID_V2;
}
// Gen1 and V3 identify the tag and verify the new UID on the device, V2 has neither
if (use_v2 == false) {
SendCommandNG(cmd, (uint8_t *)&payload, sizeof(payload));
if (WaitForResponseTimeout(cmd, &resp, 2000) == false) {
PrintAndLogEx(WARNING, "timeout while waiting for reply");
DropField();
return PM3_ESOFT;
switch (resp.status) {
case PM3_SUCCESS:
break;
case PM3_ETIMEOUT:
PrintAndLogEx(FAILED, "no tag found");
return PM3_ESOFT;
case PM3_EWRONGANSWER:
if (use_v3) {
PrintAndLogEx(FAILED, "tag is not an un-finalized magic " _YELLOW_("V3") " tag");
PrintAndLogEx(HINT, "Hint: signature in blocks 0x14/0x15 not found");
PrintAndLogEx(HINT, "already finalized or not a V3 tag");
} else {
PrintAndLogEx(FAILED, "tag doesn't look like a blank/magic " _YELLOW_("Gen1") " tag ( " _RED_("fail") " )");
PrintAndLogEx(HINT, "Hint: block 0x38/0x39/0x3E/0x3F must be unreadable or blank");
PrintAndLogEx(HINT, "refusing to risk overwriting a normal tag");
}
return PM3_ESOFT;
default:
PrintAndLogEx(FAILED, "Setting new UID ( " _RED_("fail") " )");
PrintAndLogEx(NORMAL, "");
return PM3_ESOFT;
}
}
@@ -3525,10 +3466,10 @@ 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"
_RED_("This operation is irreversible.") " It locks the UID for good,\n"
"clears the configuration blocks and shifts the block numbering. Set it with\n"
"`" _YELLOW_("hf 15 csetuid --v3") "` first, then lock it in with this command.",
"hf 15 cfinalize -y"
);
void *argtable[] = {
@@ -3540,34 +3481,36 @@ static int CmdHF15CFinalize(const char *Cmd) {
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, _RED_("This operation is irreversible!") " The UID is locked and the blocks move.");
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) {
PacketResponseNG resp;
clearCommandBuffer();
SendCommandNG(CMD_HF_ISO15693_CFINALIZE_V3, NULL, 0);
if (WaitForResponseTimeout(CMD_HF_ISO15693_CFINALIZE_V3, &resp, 2000) == false) {
PrintAndLogEx(WARNING, "timeout while waiting for reply");
DropField();
return PM3_ESOFT;
}
if (resp.status == PM3_ETIMEOUT) {
PrintAndLogEx(FAILED, "no tag found");
return PM3_ESOFT;
}
if (resp.status == PM3_EWRONGANSWER) {
PrintAndLogEx(FAILED, "tag is not an un-finalized magic " _YELLOW_("V3") " tag");
PrintAndLogEx(HINT, "Hint: signature in blocks 0x14/0x15 not found");
PrintAndLogEx(HINT, "already finalized or not a V3 tag");
return PM3_ESOFT;
}
if (resp.status != PM3_SUCCESS) {
PrintAndLogEx(FAILED, "Finalize ( " _RED_("fail") " )");
PrintAndLogEx(NORMAL, "");
return PM3_ESOFT;
+35 -6
View File
@@ -1913,7 +1913,7 @@
},
"hf 15 cfinalize": {
"command": "hf 15 cfinalize",
"description": "Finalize a magic ISO15693 'V3' tag. This operation is irreversible. After finalize the configuration area is erased and the UID can no longer be changed. Set the UID with `hf 15 csetuid --v3` first, then lock it in with this command.",
"description": "Finalize a magic ISO15693 'V3' tag. This operation is irreversible. It locks the UID for good, clears the configuration blocks and shifts the block numbering. Set it with `hf 15 csetuid --v3` first, then lock it in with this command.",
"notes": [
"hf 15 cfinalize -y"
],
@@ -1926,7 +1926,7 @@
},
"hf 15 csetuid": {
"command": "hf 15 csetuid",
"description": "Set UID for magic Chinese card (only works with such cards) For magic 'V3' tags this writes the UID configuration only and is repeatable; run `hf 15 cfinalize` afterwards to lock the UID permanently.",
"description": "Set UID for magic Chinese card (only works with such cards) Gen1 and V3 probe their backdoor blocks first and refuse to write if they read back real data, to avoid corrupting a normal (non-magic) tag. For magic 'V3' tags this writes the UID configuration only and is repeatable; run `hf 15 cfinalize` afterwards to lock the UID permanently.",
"notes": [
"hf 15 csetuid -u E011223344556677 -> use gen1 command",
"hf 15 csetuid -u E011223344556677 --v2 -> use gen2 command",
@@ -9853,6 +9853,20 @@
],
"usage": "hf ntag424 getfs [-h] --fileno <dec>"
},
"hf ntag424 gettt": {
"command": "hf ntag424 gettt",
"description": "Read and print Tag Tamper status. Will authenticate if key information is provided.",
"notes": [
"hf ntag424 gettt --keyno 0 -k 00000000000000000000000000000000"
],
"offline": false,
"options": [
"-h, --help This help",
"--keyno <dec> Key number",
"-k, --key <hex> Key for authentication (HEX 16 bytes)"
],
"usage": "hf ntag424 gettt [-h] [--keyno <dec>] [-k <hex>]"
},
"hf ntag424 help": {
"command": "hf ntag424 help",
"description": "help This help view Display content from tag dump file --------------------------------------------------------------------------------------- hf ntag424 info available offline: no Get info about NXP NTAG424 DNA Family styled tag.",
@@ -9885,6 +9899,21 @@
],
"usage": "hf ntag424 read [-h] --fileno <1|2|3> [--keyno <dec>] [-k <hex>] [-o <dec>] -l <dec> [-m <plain|mac|encrypt>]"
},
"hf ntag424 setconfig": {
"command": "hf ntag424 setconfig",
"description": "Change a PICC configuration option. Several options are one-way, see the datasheet before using this.",
"notes": [
"hf ntag424 setconfig -k 00000000000000000000000000000000 --option 07 -d 0100"
],
"offline": false,
"options": [
"-h, --help This help",
"-k, --key0 <hex> Authentication key (must be key 0, HEX 16 bytes)",
"--option <hex> Configuration option (HEX 1 byte)",
"-d, --data <hex> Option data (HEX, 1..47 bytes)"
],
"usage": "hf ntag424 setconfig [-h] -k <hex> --option <hex> -d <hex> [-d <hex>]..."
},
"hf ntag424 view": {
"command": "hf ntag424 view",
"description": "Print a NTAG 424 DNA dump file (bin/eml/json)",
@@ -12776,7 +12805,7 @@
"-1, --ht1 Card type Hitag 1",
"-2, --ht2 Card type Hitag 2",
"-s, --hts Card type Hitag S",
"-m, --htm Card type Hitag \u03bc"
"-m, --htm Card type Hitag \u00b5"
],
"usage": "lf hitag eload [-h12sm] -f <fn>"
},
@@ -12794,7 +12823,7 @@
"-1, --ht1 Card type Hitag 1",
"-2, --ht2 Card type Hitag 2 (default)",
"-s, --hts Card type Hitag S",
"-m, --htm Card type Hitag \u03bc"
"-m, --htm Card type Hitag \u00b5"
],
"usage": "lf hitag eview [-hv12sm]"
},
@@ -16616,8 +16645,8 @@
}
},
"metadata": {
"commands_extracted": 924,
"commands_extracted": 926,
"extracted_by": "PM3Help2JSON v1.00",
"extracted_on": "2026-09-16T22:14:12+00:00"
"extracted_on": "2026-09-22T14:39:12+00:00"
}
}
+2
View File
@@ -855,6 +855,8 @@ Check column "offline" for their availability.
|`hf ntag424 getfs `|N |`Get file settings`
|`hf ntag424 changefs `|N |`Change file settings`
|`hf ntag424 changekey `|N |`Change key`
|`hf ntag424 gettt `|N |`Get Tag Tamper status`
|`hf ntag424 setconfig `|N |`Set PICC configuration option`
### hf saflok
+5 -2
View File
@@ -2716,7 +2716,7 @@ V3: blocks `0x14` and `0x15` hold a signature while the tag is not finalized.
```
hf 15 raw -ac -d 022014 # A5 2B 44 2C
hf 15 raw -ac -d 022015 # 21 AE 93 00 (69 E2 5D 00 after finalize)
hf 15 raw -ac -d 022015 # 21 AE 93 00 (00 00 00 00 after finalize)
```
@@ -2765,7 +2765,10 @@ hf 15 raw -acw -d 02211077665544
hf 15 raw -acw -d 022111332211E0
```
Finalize locks the UID for good and can't be undone, block `0x15` changes to `69 E2 5D 00`:
Finalize locks the UID for good and can't be undone. It clears the configuration
blocks and changes the block numbering: on the tag tested, data written to block
`0x38` before finalize read back at block `0x20` afterwards, a shift of `0x18`.
User data is moved, not erased. Note down what is where before finalizing:
```
hf 15 raw -acw -d 022114A52B442C
+2
View File
@@ -1034,6 +1034,8 @@ typedef struct {
#define CMD_HF_ISO15693_CSETUID 0x0316
#define CMD_HF_ISO15693_CSETUID_V2 0x0333
#define CMD_HF_ISO15693_CSETUID_V3 0x0334
#define CMD_HF_ISO15693_CFINALIZE_V3 0x0335
#define CMD_LF_SNIFF_RAW_ADC 0x0360