From d5beb6650863f7a5df745a5943998ce893302f2c Mon Sep 17 00:00:00 2001 From: sup3rgiu Date: Thu, 22 May 2025 19:57:30 +0200 Subject: [PATCH 01/31] Fixed SimulateIso14443aTag() to make MFU counter increments persistent in emulator memory. - Fixed arguments for `SimulateIso14443aInit` in `hf_msdsal.c`, `hf_cardhopper.c`, `hf_reblay.c` and `hf_tcprst.c`. --- armsrc/Standalone/hf_cardhopper.c | 4 +-- armsrc/Standalone/hf_msdsal.c | 2 +- armsrc/Standalone/hf_reblay.c | 2 +- armsrc/Standalone/hf_tcprst.c | 6 ++-- armsrc/iso14443a.c | 51 ++++++++++++++++--------------- armsrc/iso14443a.h | 2 +- 6 files changed, 33 insertions(+), 34 deletions(-) diff --git a/armsrc/Standalone/hf_cardhopper.c b/armsrc/Standalone/hf_cardhopper.c index 5d20a8037..4dbb17587 100644 --- a/armsrc/Standalone/hf_cardhopper.c +++ b/armsrc/Standalone/hf_cardhopper.c @@ -233,10 +233,8 @@ static void become_card(void) { tag_response_info_t *canned; uint32_t cuid; - uint32_t counters[3] = { 0 }; - uint8_t tearings[3] = { 0xbd, 0xbd, 0xbd }; uint8_t pages; - SimulateIso14443aInit(tagType, flags, data, NULL, 0, &canned, &cuid, counters, tearings, &pages); + SimulateIso14443aInit(tagType, flags, data, NULL, 0, &canned, &cuid, &pages); DbpString(_CYAN_("[@]") " Setup done - entering emulation loop"); int fromReaderLen; diff --git a/armsrc/Standalone/hf_msdsal.c b/armsrc/Standalone/hf_msdsal.c index 711e653a4..5e36a92c5 100644 --- a/armsrc/Standalone/hf_msdsal.c +++ b/armsrc/Standalone/hf_msdsal.c @@ -379,7 +379,7 @@ void RunMod(void) { BigBuf_free_keep_EM(); // tag type: 11 = ISO/IEC 14443-4 - javacard (JCOP) - if (SimulateIso14443aInit(11, flags, data, NULL, 0, &responses, &cuid, NULL, NULL, NULL) == false) { + if (SimulateIso14443aInit(11, flags, data, NULL, 0, &responses, &cuid, NULL) == false) { BigBuf_free_keep_EM(); reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0); DbpString(_RED_("Error initializing the emulation process!")); diff --git a/armsrc/Standalone/hf_reblay.c b/armsrc/Standalone/hf_reblay.c index 1b84eb3f7..8ecba8cf4 100644 --- a/armsrc/Standalone/hf_reblay.c +++ b/armsrc/Standalone/hf_reblay.c @@ -268,7 +268,7 @@ void RunMod() { BigBuf_free_keep_EM(); // 4 = ISO/IEC 14443-4 - javacard (JCOP) - if (SimulateIso14443aInit(4, flags, data, NULL, 0, &responses, &cuid, NULL, NULL, NULL) == false) { + if (SimulateIso14443aInit(4, flags, data, NULL, 0, &responses, &cuid, NULL) == false) { BigBuf_free_keep_EM(); reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0); DbpString(_RED_("Error initializing the emulation process!")); diff --git a/armsrc/Standalone/hf_tcprst.c b/armsrc/Standalone/hf_tcprst.c index e6f75bc75..c2b3ff51d 100644 --- a/armsrc/Standalone/hf_tcprst.c +++ b/armsrc/Standalone/hf_tcprst.c @@ -118,8 +118,6 @@ void RunMod(void) { uint8_t tagType = 10; // 10 = ST25TA IKEA Rothult tag_response_info_t *responses; uint32_t cuid = 0; - uint32_t counters[3] = { 0x00, 0x00, 0x00 }; - uint8_t tearings[3] = { 0xbd, 0xbd, 0xbd }; uint8_t pages = 0; // command buffers @@ -193,7 +191,7 @@ void RunMod(void) { memcpy(data, stuid, sizeof(stuid)); - if (SimulateIso14443aInit(tagType, flags, data, NULL, 0, &responses, &cuid, counters, tearings, &pages) == false) { + if (SimulateIso14443aInit(tagType, flags, data, NULL, 0, &responses, &cuid, &pages) == false) { BigBuf_free_keep_EM(); reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0); DbpString(_YELLOW_("!!") "Error initializing the simulation process!"); @@ -371,7 +369,7 @@ void RunMod(void) { memcpy(data, stuid, sizeof(stuid)); - if (SimulateIso14443aInit(tagType, flags, data, NULL, 0, &responses, &cuid, counters, tearings, &pages) == false) { + if (SimulateIso14443aInit(tagType, flags, data, NULL, 0, &responses, &cuid, &pages) == false) { BigBuf_free_keep_EM(); reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0); DbpString(_YELLOW_("!!") "Error initializing the simulation process!"); diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 8bca5d2de..b7d17f4b8 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -1184,7 +1184,7 @@ bool prepare_allocated_tag_modulation(tag_response_info_t *response_info, uint8_ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, uint8_t *ats, size_t ats_len, tag_response_info_t **responses, - uint32_t *cuid, uint32_t counters[3], uint8_t tearings[3], uint8_t *pages) { + uint32_t *cuid, uint8_t *pages) { uint8_t sak = 0; // The first response contains the ATQA (note: bytes are transmitted in reverse order). static uint8_t rATQA[2] = { 0x00 }; @@ -1231,14 +1231,11 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, mfu_dump_t *mfu_header = (mfu_dump_t *) BigBuf_get_EM_addr(); *pages = MAX(mfu_header->pages, 15); - // counters and tearing flags + // tearing flags // for old dumps with all zero headers, we need to set default values. for (uint8_t i = 0; i < 3; i++) { - - counters[i] = le24toh(mfu_header->counter_tearing[i]); - - if (mfu_header->counter_tearing[i][3] != 0x00) { - tearings[i] = mfu_header->counter_tearing[i][3]; + if (mfu_header->counter_tearing[i][3] == 0x00) { + mfu_header->counter_tearing[i][3] = 0xBD; } } @@ -1286,14 +1283,11 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, mfu_dump_t *mfu_header = (mfu_dump_t *) BigBuf_get_EM_addr(); *pages = MAX(mfu_header->pages, 19); - // counters and tearing flags + // tearing flags // for old dumps with all zero headers, we need to set default values. for (uint8_t i = 0; i < 3; i++) { - - counters[i] = le24toh(mfu_header->counter_tearing[i]); - - if (mfu_header->counter_tearing[i][3] != 0x00) { - tearings[i] = mfu_header->counter_tearing[i][3]; + if (mfu_header->counter_tearing[i][3] == 0x00) { + mfu_header->counter_tearing[i][3] = 0xBD; } } @@ -1539,8 +1533,6 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin tag_response_info_t *responses; uint32_t cuid = 0; uint32_t nonce = 0; - uint32_t counters[3] = { 0x00, 0x00, 0x00 }; - uint8_t tearings[3] = { 0xbd, 0xbd, 0xbd }; uint8_t pages = 0; // Here, we collect CUID, block1, keytype1, NT1, NR1, AR1, CUID, block2, keytyp2, NT2, NR2, AR2 @@ -1584,12 +1576,22 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin .modulation_n = 0 }; - if (SimulateIso14443aInit(tagType, flags, useruid, ats, ats_len, &responses, &cuid, counters, tearings, &pages) == false) { + if (SimulateIso14443aInit(tagType, flags, useruid, ats, ats_len, &responses, &cuid, &pages) == false) { BigBuf_free_keep_EM(); reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0); return; } + mfu_dump_t *mfu_em_dump = NULL; + if (tagType == 2 || tagType == 7) { + mfu_em_dump = (mfu_dump_t *)BigBuf_get_EM_addr(); + if (!mfu_em_dump) { + if (g_dbglevel >= DBG_ERROR) Dbprintf("[-] ERROR: Failed to get EM address for MFU/NTAG operations."); + reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EMALLOC, NULL, 0); + return; + } + } + // We need to listen to the high-frequency, peak-detected path. iso14443a_setup(FPGA_HF_ISO14443A_TAGSIM_LISTEN); @@ -1870,8 +1872,8 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin // send NACK 0x0 == invalid argument EmSend4bit(CARD_NACK_IV); } else { - uint8_t cmd[] = {0x00, 0x00, 0x00, 0x14, 0xa5}; - htole24(counters[index], cmd); + uint8_t cmd[] = {0, 0, 0, 0x14, 0xa5}; + memcpy(cmd, mfu_em_dump->counter_tearing[index], 3); AddCrc14A(cmd, sizeof(cmd) - 2); EmSendCmd(cmd, sizeof(cmd)); } @@ -1882,13 +1884,16 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin // send NACK 0x0 == invalid argument EmSend4bit(CARD_NACK_IV); } else { - uint32_t val = le24toh(receivedCmd + 2) + counters[index]; + uint32_t val = le24toh(mfu_em_dump->counter_tearing[index]); // get current counter value + val += le24toh(receivedCmd + 2); // increment in + // if new value + old value is bigger 24bits, fail if (val > 0xFFFFFF) { // send NACK 0x4 == counter overflow EmSend4bit(CARD_NACK_NA); } else { - counters[index] = val; + htole24(val, mfu_em_dump->counter_tearing[index]); + // send ACK EmSend4bit(CARD_ACK); } @@ -1902,7 +1907,7 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin EmSend4bit(CARD_NACK_IV); } else { uint8_t cmd[3] = {0, 0, 0}; - cmd[0] = tearings[index]; + cmd[0] = mfu_em_dump->counter_tearing[index][3]; AddCrc14A(cmd, sizeof(cmd) - 2); EmSendCmd(cmd, sizeof(cmd)); } @@ -4093,8 +4098,6 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid, uint8_t *getdata_response, size_t getdata_response_len) { tag_response_info_t *responses; uint32_t cuid = 0; - uint32_t counters[3] = { 0x00, 0x00, 0x00 }; - uint8_t tearings[3] = { 0xbd, 0xbd, 0xbd }; uint8_t pages = 0; // command buffers @@ -4135,7 +4138,7 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid, .modulation_n = 0 }; - if (SimulateIso14443aInit(tagType, flags, uid, ats, ats_len, &responses, &cuid, counters, tearings, &pages) == false) { + if (SimulateIso14443aInit(tagType, flags, uid, ats, ats_len, &responses, &cuid, &pages) == false) { BigBuf_free_keep_EM(); reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0); return; diff --git a/armsrc/iso14443a.h b/armsrc/iso14443a.h index d27083f12..420d6e0c0 100644 --- a/armsrc/iso14443a.h +++ b/armsrc/iso14443a.h @@ -152,7 +152,7 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid, bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, uint8_t *ats, size_t ats_len, tag_response_info_t **responses, - uint32_t *cuid, uint32_t counters[3], uint8_t tearings[3], uint8_t *pages); + uint32_t *cuid, uint8_t *pages); bool GetIso14443aCommandFromReader(uint8_t *received, uint16_t received_maxlen, uint8_t *par, int *len); void iso14443a_antifuzz(uint32_t flags); From a7534850546f1b6486790138981fb448e4059f86 Mon Sep 17 00:00:00 2001 From: sup3rgiu Date: Thu, 22 May 2025 20:09:19 +0200 Subject: [PATCH 02/31] Updated Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eb4b7276..da1570a0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Fixed `make install` on OSX thanks DaveItsLong (@doegox) - Added new standalone mode `HF_ST25_TEAROFF` to store/restore ST25TB tags with tearoff for counters (@seclabz) - Added `hf_mfu_ultra.lua` script enables restoring dump to ULTRA/UL-5 tags and clearing previously written ULTRA tags (@mak-42) +- Fixed `hf mfu sim` to make persistent the counter increases in the emulator memory ## [Blue Ice.4.20142][2025-03-25] - Added `des_talk.py` script for easier MIFARE DESFire handling (@trigat) From 6765c2294c2f31dba5e02901fc22bd21b2287ca7 Mon Sep 17 00:00:00 2001 From: sup3rgiu Date: Thu, 22 May 2025 20:10:58 +0200 Subject: [PATCH 03/31] Updated changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da1570a0b..992e00a41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Fixed `make install` on OSX thanks DaveItsLong (@doegox) - Added new standalone mode `HF_ST25_TEAROFF` to store/restore ST25TB tags with tearoff for counters (@seclabz) - Added `hf_mfu_ultra.lua` script enables restoring dump to ULTRA/UL-5 tags and clearing previously written ULTRA tags (@mak-42) -- Fixed `hf mfu sim` to make persistent the counter increases in the emulator memory +- Fixed `hf mfu sim` to make persistent the counter increases in the emulator memory (@sup3rgiu) ## [Blue Ice.4.20142][2025-03-25] - Added `des_talk.py` script for easier MIFARE DESFire handling (@trigat) From c312bae5169d417777cc236e16c60325f2e140ee Mon Sep 17 00:00:00 2001 From: James Churchill Date: Sat, 11 Jan 2025 04:09:38 +1100 Subject: [PATCH 04/31] Add "hf mf bambukeys" command to generate Bambu Lab keys --- client/src/cmdhfmf.c | 61 ++++++++++++++++++++++++++++++++- client/src/pm3line_vocabulary.h | 1 + doc/commands.json | 23 +++++++++++-- doc/commands.md | 1 + 4 files changed, 82 insertions(+), 4 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 70dc058f8..4c1a9ab2a 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -4503,7 +4503,7 @@ void printKeyTableEx(size_t sectorscnt, sector_t *e_sector, uint8_t start_sector _YELLOW_("H") ":Hardnested / " _YELLOW_("C") ":statiCnested / " _YELLOW_("A") ":keyA " - " )" + " )" ); if (sectorscnt == 18) { PrintAndLogEx(INFO, "( " _MAGENTA_("*") " ) These sectors used for signature. Lays outside of user memory"); @@ -10219,6 +10219,64 @@ static int CmdHF14AMfISEN(const char *Cmd) { return PM3_SUCCESS; } +static int CmdHF14AMfBambuKeys(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf mf bambukeys", + "Generate keys for a Bambu Lab filament tag", + "hf mf bambukeys -r\n" + "hf mf bambukeys -r -d\n" + "hf mf bambukeys -u 11223344\n" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0("u", "uid", "", "UID (4 hex bytes)"), + arg_lit0("r", NULL, "Read UID from tag"), + arg_lit0("d", NULL, "Dump keys to file"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, true); + + int u_len = 0; + uint8_t uid[7] = {0x00}; + CLIGetHexWithReturn(ctx, 1, uid, &u_len); + bool use_tag = arg_get_lit(ctx, 2); + bool dump_keys = arg_get_lit(ctx, 3); + CLIParserFree(ctx); + + if (use_tag) { + // read uid from tag + int res = mf_read_uid(uid, &u_len, NULL); + if (res != PM3_SUCCESS) { + return res; + } + } + + if (u_len != 4) { + PrintAndLogEx(WARNING, "Key must be 4 hex bytes"); + return PM3_EINVARG; + } + + PrintAndLogEx(INFO, "-----------------------------------"); + PrintAndLogEx(INFO, " UID 4b... " _YELLOW_("%s"), sprint_hex(uid, 4)); + PrintAndLogEx(INFO, "-----------------------------------"); + + uint8_t keys[32 * 6]; + mfc_algo_bambu_all(uid, (void*)keys); + + for (int block = 0; block < 32; block++) { + PrintAndLogEx(INFO, "%d: %012" PRIX64, block, bytes_to_num(keys + (block * 6), 6)); + } + + if (dump_keys) { + char fn[FILE_PATH_SIZE] = {0}; + snprintf(fn, sizeof(fn), "hf-mf-%s-key", sprint_hex_inrow(uid, 4)); + saveFileEx(fn, ".bin", keys, 32 * 6, spDump); + } + + return PM3_SUCCESS; +} + static command_t CommandTable[] = { {"help", CmdHelp, AlwaysAvailable, "This help"}, {"list", CmdHF14AMfList, AlwaysAvailable, "List MIFARE history"}, @@ -10237,6 +10295,7 @@ static command_t CommandTable[] = { {"fchk", CmdHF14AMfChk_fast, IfPm3Iso14443a, "Check keys fast, targets all keys on card"}, {"decrypt", CmdHf14AMfDecryptBytes, AlwaysAvailable, "Decrypt Crypto1 data from sniff or trace"}, {"supercard", CmdHf14AMfSuperCard, IfPm3Iso14443a, "Extract info from a `super card`"}, + {"bambukeys", CmdHF14AMfBambuKeys, AlwaysAvailable, "Generate key table for Bambu Lab filament tag"}, {"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("operations") " -----------------------"}, {"auth4", CmdHF14AMfAuth4, IfPm3Iso14443a, "ISO14443-4 AES authentication"}, {"acl", CmdHF14AMfAcl, AlwaysAvailable, "Decode and print MIFARE Classic access rights bytes"}, diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index dfa82819a..d637f4e6e 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -356,6 +356,7 @@ const static vocabulary_t vocabulary[] = { { 0, "hf mf fchk" }, { 1, "hf mf decrypt" }, { 0, "hf mf supercard" }, + { 1, "hf mf blgen" }, { 0, "hf mf auth4" }, { 1, "hf mf acl" }, { 0, "hf mf dump" }, diff --git a/doc/commands.json b/doc/commands.json index 50f8e4065..46e8245d2 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -4411,6 +4411,23 @@ ], "usage": "hf mf autopwn [-hablv] [-k ]... [-s ] [-f ] [--suffix ] [--slow] [--mem] [--ns] [--mini] [--1k] [--2k] [--4k] [--in] [--im] [--is] [--ia] [--i2] [--i5]" }, + "hf mf bambukeys": { + "command": "hf mf bambukeys", + "description": "Generate keys for a Bambu Lab filament tag", + "notes": [ + "hf mf bambukeys -r", + "hf mf bambukeys -r -d", + "hf mf bambukeys -u 11223344" + ], + "offline": true, + "options": [ + "-h, --help This help", + "-u, --uid UID (4 hex bytes)", + "-r Read UID from tag", + "-d Dump keys to file" + ], + "usage": "hf mf bambukeys [-hrd] [-u ]" + }, "hf mf brute": { "command": "hf mf brute", "description": "This is a smart bruteforce, exploiting common patterns, bugs and bad designs in key generators.", @@ -5145,7 +5162,7 @@ }, "hf mf help": { "command": "hf mf help", - "description": "help This help list List MIFARE history hardnested Nested attack for hardened MIFARE Classic cards decrypt Decrypt Crypto1 data from sniff or trace acl Decode and print MIFARE Classic access rights bytes mad Checks and prints MAD value Value blocks view Display content from tag dump file ginfo Info about configuration of the card gdmparsecfg Parse config block to card --------------------------------------------------------------------------------------- hf mf list available offline: yes Alias of `trace list -t mf -c` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", + "description": "help This help list List MIFARE history hardnested Nested attack for hardened MIFARE Classic cards decrypt Decrypt Crypto1 data from sniff or trace bambukeys Generate key table for Bambu Lab filament tag acl Decode and print MIFARE Classic access rights bytes mad Checks and prints MAD value Value blocks view Display content from tag dump file ginfo Info about configuration of the card gdmparsecfg Parse config block to card --------------------------------------------------------------------------------------- hf mf list available offline: yes Alias of `trace list -t mf -c` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol", "notes": [ "hf mf list --frame -> show frame delay times", "hf mf list -1 -> use trace buffer" @@ -13352,8 +13369,8 @@ } }, "metadata": { - "commands_extracted": 767, + "commands_extracted": 768, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-05-23T15:21:08" + "extracted_on": "2025-05-24T14:16:42" } } diff --git a/doc/commands.md b/doc/commands.md index bc90193ca..4db5746ac 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -527,6 +527,7 @@ Check column "offline" for their availability. |`hf mf fchk `|N |`Check keys fast, targets all keys on card` |`hf mf decrypt `|Y |`Decrypt Crypto1 data from sniff or trace` |`hf mf supercard `|N |`Extract info from a `super card`` +|`hf mf bambukeys `|Y |`Generate key table for Bambu Lab filament tag` |`hf mf auth4 `|N |`ISO14443-4 AES authentication` |`hf mf acl `|Y |`Decode and print MIFARE Classic access rights bytes` |`hf mf dump `|N |`Dump MIFARE Classic tag to binary file` From 23928b40419be154679d97a4fad7318108b92a68 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Mon, 26 May 2025 16:01:06 +0800 Subject: [PATCH 05/31] Updated hf iclass tear Updated hf iclass tear with the following improvements: 1- Show failed read if ran in verbose mode 2- Improved out logic when tearing block 1 3- Showing fuses comparison table when tearoff affects block 1 fuses --- client/src/cmdhficlass.c | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 2b16e13ab..41bcaf471 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -3251,6 +3251,10 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { // if there was an error reading repeat the tearoff with the same delay if (decrease && (tearoff_start > tearoff_increment) && (tearoff_start >= tearoff_original_start)) { tearoff_start -= tearoff_increment; + if(verbose){ + PrintAndLogEx(INFO, " -> Read failed, retearing with "_CYAN_("%u")" us", tearoff_start); + } + } bool tear_success = true; @@ -3284,18 +3288,42 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { } } + bool goto_out = false; if (blockno == 1) { if (data_read[0] != data_read_orig[0]) { PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(SUCCESS, "Application limit changed, from %u to %u", data_read_orig[0], data_read[0]); + PrintAndLogEx(SUCCESS, "Application limit changed, from "_YELLOW_("%u")" to "_YELLOW_("%u"), data_read_orig[0], data_read[0]); isok = PM3_SUCCESS; - goto out; + goto_out = true; } if (data_read[7] != data_read_orig[7]) { PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(SUCCESS, "Fuse changed, from %02x to %02x", data_read_orig[7], data_read[7]); + PrintAndLogEx(SUCCESS, "Fuse changed, from "_YELLOW_("%02x")" to "_YELLOW_("%02x"), data_read_orig[7], data_read[7]); + + const char *flag_names[8] = { + "RA", + "Fprod0", + "Fprod1", + "Crypt0 (*1)", + "Crypt1 (*0)", + "Coding0", + "Coding1", + "Fpers (*1)" + }; + PrintAndLogEx(INFO, _YELLOW_("%-10s %-10s %-10s"), "Fuse", "Original", "Changed"); + PrintAndLogEx(INFO, "---------------------------------------"); + for (int i = 7; i >= 0; --i) { + int bit1 = (data_read_orig[7] >> i) & 1; + int bit2 = (data_read[7] >> i) & 1; + PrintAndLogEx(INFO, "%-10s %-10d %-10d", flag_names[i], bit1, bit2); + } + isok = PM3_SUCCESS; + goto_out = true; + } + + if (goto_out) { goto out; } } From b378a369d112035306f3135489d027ad0973c4a0 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Mon, 26 May 2025 23:53:33 +0800 Subject: [PATCH 06/31] Updated hf iclass tear to break endless read loop Updated hf iclass tear to break endless read loop when the card can't be read anymore during the tear operation. Set a 10 attempts limit. --- client/src/cmdhficlass.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 41bcaf471..f1da50f14 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -3219,7 +3219,7 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { first_read = false; reread = false; bool decrease = false; - + int readcount = 0; while (first_read == false) { if (kbd_enter_pressed()) { @@ -3246,6 +3246,12 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { } else if (res != PM3_SUCCESS) { decrease = true; } + if (readcount >= 10){ + PrintAndLogEx(WARNING, "\n"_RED_("Read failed too many times, giving up")); + isok = PM3_EFAILED; + goto out; + } + readcount++; } // if there was an error reading repeat the tearoff with the same delay From 176b543069a3c49dcf464941415de98db1a61581 Mon Sep 17 00:00:00 2001 From: Kara Zajac Date: Mon, 26 May 2025 15:46:23 -0400 Subject: [PATCH 07/31] Saflok Parsing Added when a Saflok card is detected, it decrypts and parses the data, outputting it to the screen. Previous security researchers did this work, and I merely adapted it from the Flipper Zero code to the Proxmark3 code. Their info is below: // Decryption and parsing from: https://gitee.com/wangshuoyue/unsaflok // Decryption algorithm and parsing published by Shuoyue Wang // Parsing also inspired by Lennert Wouters and Ian Carroll's DEFCON 32 talk // https://defcon.org/html/defcon-32/dc-32-speakers.html // FZ parser by @Torron, with help from @xtruan, @zacharyweiss, @evilmog and kara (@Arkwin) Signed-off-by: Kara Zajac --- client/src/cmdhfmf.c | 292 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 291 insertions(+), 1 deletion(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 41d4a1a96..a05cfbb1f 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -48,6 +48,295 @@ #include "mifare/mifarehost.h" #include "crypto/originality.h" + +// Defines for Saflok parsing +#define SAFLOK_YEAR_OFFSET 1980 +#define SAFLOK_BASIC_ACCESS_BYTE_NUM 17 +#define SAFLOK_KEY_LENGTH 6 +#define SAFLOK_UID_LENGTH 4 // Matches Mifare 4-byte UID +#define SAFLOK_MAGIC_TABLE_SIZE 192 +#define SAFLOK_CHECK_SECTOR 1 + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + + +// Structure for Saflok key levels +typedef struct { + uint8_t level_num; + const char* level_name; +} SaflokKeyLevel; + +// Static array for Saflok key levels +static const SaflokKeyLevel saflok_key_levels[] = { + {1, "Guest Key"}, + {2, "Connectors"}, + {3, "Suite"}, + {4, "Limited Use"}, + {5, "Failsafe"}, + {6, "Inhibit"}, + {7, "Pool/Meeting Master"}, + {8, "Housekeeping"}, + {9, "Floor Key"}, + {10, "Section Key"}, + {11, "Rooms Master"}, + {12, "Grand Master"}, + {13, "Emergency"}, + {14, "Electronic Lockout"}, + {15, "Secondary Programming Key (SPK)"}, + {16, "Primary Programming Key (PPK)"}, +}; + +// Lookup table for Saflok decryption +static const uint8_t saflok_c_aDecode[256] = { + 0xEA, 0x0D, 0xD9, 0x74, 0x4E, 0x28, 0xFD, 0xBA, 0x7B, 0x98, 0x87, 0x78, 0xDD, 0x8D, 0xB5, + 0x1A, 0x0E, 0x30, 0xF3, 0x2F, 0x6A, 0x3B, 0xAC, 0x09, 0xB9, 0x20, 0x6E, 0x5B, 0x2B, 0xB6, + 0x21, 0xAA, 0x17, 0x44, 0x5A, 0x54, 0x57, 0xBE, 0x0A, 0x52, 0x67, 0xC9, 0x50, 0x35, 0xF5, + 0x41, 0xA0, 0x94, 0x60, 0xFE, 0x24, 0xA2, 0x36, 0xEF, 0x1E, 0x6B, 0xF7, 0x9C, 0x69, 0xDA, + 0x9B, 0x6F, 0xAD, 0xD8, 0xFB, 0x97, 0x62, 0x5F, 0x1F, 0x38, 0xC2, 0xD7, 0x71, 0x31, 0xF0, + 0x13, 0xEE, 0x0F, 0xA3, 0xA7, 0x1C, 0xD5, 0x11, 0x4C, 0x45, 0x2C, 0x04, 0xDB, 0xA6, 0x2E, + 0xF8, 0x64, 0x9A, 0xB8, 0x53, 0x66, 0xDC, 0x7A, 0x5D, 0x03, 0x07, 0x80, 0x37, 0xFF, 0xFC, + 0x06, 0xBC, 0x26, 0xC0, 0x95, 0x4A, 0xF1, 0x51, 0x2D, 0x22, 0x18, 0x01, 0x79, 0x5E, 0x76, + 0x1D, 0x7F, 0x14, 0xE3, 0x9E, 0x8A, 0xBB, 0x34, 0xBF, 0xF4, 0xAB, 0x48, 0x63, 0x55, 0x3E, + 0x56, 0x8C, 0xD1, 0x12, 0xED, 0xC3, 0x49, 0x8E, 0x92, 0x9D, 0xCA, 0xB1, 0xE5, 0xCE, 0x4D, + 0x3F, 0xFA, 0x73, 0x05, 0xE0, 0x4B, 0x93, 0xB2, 0xCB, 0x08, 0xE1, 0x96, 0x19, 0x3D, 0x83, + 0x39, 0x75, 0xEC, 0xD6, 0x3C, 0xD0, 0x70, 0x81, 0x16, 0x29, 0x15, 0x6C, 0xC7, 0xE7, 0xE2, + 0xF6, 0xB7, 0xE8, 0x25, 0x6D, 0x3A, 0xE6, 0xC8, 0x99, 0x46, 0xB0, 0x85, 0x02, 0x61, 0x1B, + 0x8B, 0xB3, 0x9F, 0x0B, 0x2A, 0xA8, 0x77, 0x10, 0xC1, 0x88, 0xCC, 0xA4, 0xDE, 0x43, 0x58, + 0x23, 0xB4, 0xA1, 0xA5, 0x5C, 0xAE, 0xA9, 0x7E, 0x42, 0x40, 0x90, 0xD2, 0xE9, 0x84, 0xCF, + 0xE4, 0xEB, 0x47, 0x4F, 0x82, 0xD4, 0xC5, 0x8F, 0xCD, 0xD3, 0x86, 0x00, 0x59, 0xDF, 0xF2, + 0x0C, 0x7C, 0xC6, 0xBD, 0xF9, 0x7D, 0xC4, 0x91, 0x27, 0x89, 0x32, 0x72, 0x33, 0x65, 0x68, + 0xAF +}; + +// Function to decrypt Saflok card data +static void DecryptSaflokCardData( + const uint8_t strCard[SAFLOK_BASIC_ACCESS_BYTE_NUM], + // int length, // length is always SAFLOK_BASIC_ACCESS_BYTE_NUM + uint8_t decryptedCard[SAFLOK_BASIC_ACCESS_BYTE_NUM]) { + int i, num, num2, num3, num4, b = 0, b2 = 0; + + for(i = 0; i < SAFLOK_BASIC_ACCESS_BYTE_NUM; i++) { + num = saflok_c_aDecode[strCard[i]] - (i + 1); + if(num < 0) num += 256; + decryptedCard[i] = num; + } + + b = decryptedCard[10]; + b2 = b & 1; + + for(num2 = SAFLOK_BASIC_ACCESS_BYTE_NUM; num2 > 0; num2--) { + b = decryptedCard[num2 - 1]; + for(num3 = 8; num3 > 0; num3--) { + num4 = num2 + num3; + if(num4 > SAFLOK_BASIC_ACCESS_BYTE_NUM) num4 -= SAFLOK_BASIC_ACCESS_BYTE_NUM; + int b3 = decryptedCard[num4 - 1]; + int b4 = (b3 & 0x80) >> 7; + b3 = ((b3 << 1) & 0xFF) | b2; + b2 = (b & 0x80) >> 7; + b = ((b << 1) & 0xFF) | b4; + decryptedCard[num4 - 1] = b3; + } + decryptedCard[num2 - 1] = b; + } +} + +// Function to calculate Saflok checksum +static uint8_t CalculateCheckSum(uint8_t data[SAFLOK_BASIC_ACCESS_BYTE_NUM]) { + int sum = 0; + for(int i = 0; i < SAFLOK_BASIC_ACCESS_BYTE_NUM - 1; i++) { + sum += data[i]; + } + sum = 255 - (sum & 0xFF); + return sum & 0xFF; +} + +// Function to parse and print Saflok data +static void ParseAndPrintSaflokData(const sector_t* sector0_info, const sector_t* sector1_info) { + (void)sector1_info; // Not directly used for payload parsing currently + + if (!sector0_info) { + PrintAndLogEx(WARNING, "Saflok: Sector 0 information not available for parsing."); + return; + } + + uint8_t key_bytes_for_s0[MIFARE_KEY_SIZE]; + uint8_t key_type_for_s0; // CORRECTED: Was MifareKeyType, now uint8_t + bool s0_key_found = false; + + // Prioritize Key A for Sector 0 if available + if (sector0_info->foundKey[MF_KEY_A]) { + num_to_bytes(sector0_info->Key[MF_KEY_A], MIFARE_KEY_SIZE, key_bytes_for_s0); + key_type_for_s0 = MF_KEY_A; // MF_KEY_A is typically #define'd as 0x60 + s0_key_found = true; + PrintAndLogEx(DEBUG, "Saflok: Using Sector 0 Key A for reading blocks."); + } else if (sector0_info->foundKey[MF_KEY_B]) { // Fallback to Key B for Sector 0 + num_to_bytes(sector0_info->Key[MF_KEY_B], MIFARE_KEY_SIZE, key_bytes_for_s0); + key_type_for_s0 = MF_KEY_B; // MF_KEY_B is typically #define'd as 0x61 + s0_key_found = true; + PrintAndLogEx(DEBUG, "Saflok: Using Sector 0 Key B for reading blocks."); + } + + if (!s0_key_found) { + PrintAndLogEx(WARNING, "Saflok: No known keys for Sector 0. Cannot read blocks 1 & 2 for parsing."); + return; + } + + uint8_t block1_content[MFBLOCK_SIZE]; + uint8_t block2_content[MFBLOCK_SIZE]; + + // Read absolute block 1 (data block within sector 0) + if (mf_read_block(1, key_type_for_s0, key_bytes_for_s0, block1_content) != PM3_SUCCESS) { + PrintAndLogEx(WARNING, "Saflok: Failed to read card Block 1 using Sector 0 %s key.", (key_type_for_s0 == MF_KEY_A) ? "A" : "B"); + return; + } + PrintAndLogEx(DEBUG, "Saflok: Successfully read card Block 1."); + + // Read absolute block 2 (data block within sector 0) + if (mf_read_block(2, key_type_for_s0, key_bytes_for_s0, block2_content) != PM3_SUCCESS) { + PrintAndLogEx(WARNING, "Saflok: Failed to read card Block 2 using Sector 0 %s key.", (key_type_for_s0 == MF_KEY_A) ? "A" : "B"); + return; + } + PrintAndLogEx(DEBUG, "Saflok: Successfully read card Block 2."); + + uint8_t basicAccess[SAFLOK_BASIC_ACCESS_BYTE_NUM]; + uint8_t decodedBA[SAFLOK_BASIC_ACCESS_BYTE_NUM]; + + memcpy(basicAccess, block1_content, 16); // 16 bytes from Block 1 + memcpy(basicAccess + 16, block2_content, 1); // 1 byte from Block 2 + + DecryptSaflokCardData(basicAccess, decodedBA); + + + // Byte 0: Key level, LED warning bit, and subgroup functions + uint8_t key_level = (decodedBA[0] & 0xF0) >> 4; + uint8_t led_warning = (decodedBA[0] & 0x08) >> 3; + + // Byte 1: Key ID + uint8_t key_id = decodedBA[1]; + + // Byte 2 & 3: KeyRecord, including OpeningKey flag + uint8_t key_record_high = decodedBA[2] & 0x7F; + uint8_t opening_key = (decodedBA[2] & 0x80) >> 7; + uint16_t key_record = (key_record_high << 8) | decodedBA[3]; + + // Byte 5 & 6: EncryptSequence + Combination + uint16_t sequence_combination_number = ((decodedBA[5] & 0x0F) << 8) | decodedBA[6]; + + // Byte 7: OverrideDeadbolt and Days + uint8_t override_deadbolt = (decodedBA[7] & 0x80) >> 7; + uint8_t restricted_weekday = decodedBA[7] & 0x7F; + + // Weekday names array + static const char* weekdays[] = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}; + + // Buffer to store the resulting string (sufficient size for all weekdays) + char restricted_weekday_string[128] = {0}; + int restricted_count = 0; + + // Check each bit from Monday to Sunday + for(int i = 0; i < 7; i++) { + if(restricted_weekday & (0b01000000 >> i)) { + // If the bit is set, append the corresponding weekday to the buffer + if(restricted_count > 0) { + strcat(restricted_weekday_string, ", "); + } + strcat(restricted_weekday_string, weekdays[i]); + restricted_count++; + } + } + + // Determine if all weekdays are restricted + if(restricted_weekday == 0b01111100) { + strcpy(restricted_weekday_string, "weekdays"); + } + // If there are specific restricted days + else if(restricted_weekday == 0b00000011) { + strcpy(restricted_weekday_string, "weekends"); + } + // If no weekdays are restricted + else if(restricted_weekday == 0) { + strcpy(restricted_weekday_string, "none"); + } + + // Bytes 14-15: Property number and part of creation year + uint8_t creation_year_high_bits = (decodedBA[14] & 0xF0); + uint16_t property_id = ((decodedBA[14] & 0x0F) << 8) | decodedBA[15]; + + // Bytes 11-13: Creation date since SAFLOK_YEAR_OFFSET Jan 1st + uint16_t creation_year = (((decodedBA[11] & 0xF0) >> 4) + SAFLOK_YEAR_OFFSET) | creation_year_high_bits; + uint8_t creation_month = decodedBA[11] & 0x0F; + uint8_t creation_day = (decodedBA[12] >> 3) & 0x1F; + uint8_t creation_hour = ((decodedBA[12] & 0x07) << 2) | ((decodedBA[13] & 0xC0) >> 6); + uint8_t creation_minute = decodedBA[13] & 0x3F; + + // Bytes 8-10: Expiry interval / absolute time components + uint8_t interval_year_val = (decodedBA[8] >> 4); + uint8_t interval_month_val = decodedBA[8] & 0x0F; + uint8_t interval_day_val = (decodedBA[9] >> 3) & 0x1F; + uint8_t expiry_hour = ((decodedBA[9] & 0x07) << 2) | ((decodedBA[10] & 0xC0) >> 6); + uint8_t expiry_minute = decodedBA[10] & 0x3F; + + uint16_t expire_year = creation_year + interval_year_val; + uint8_t expire_month = creation_month + interval_month_val; + uint8_t expire_day = creation_day + interval_day_val; + + // Handle month rollover for expiration + while(expire_month > 12) { + expire_month -= 12; + expire_year++; + } + + // Handle day rollover for expiration + static const uint8_t days_in_month_lookup[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // 1-indexed month + if (expire_month > 0 && expire_month <= 12) { + while(true) { + uint8_t max_days = days_in_month_lookup[expire_month]; + if(expire_month == 2 && (expire_year % 4 == 0 && (expire_year % 100 != 0 || expire_year % 400 == 0))) { + max_days = 29; // Leap year + } + if(expire_day <= max_days) { + break; + } + if (max_days == 0) { // Should not happen with valid month + PrintAndLogEx(WARNING, "Saflok: Invalid day/month for expiration rollover calculation."); + break; + } + expire_day -= max_days; + expire_month++; + if(expire_month > 12) { + expire_month = 1; + expire_year++; + } + } + } else if (expire_month != 0) { // Allow 0 if it signifies no expiration or error + PrintAndLogEx(WARNING, "Saflok: Invalid expiration month (%u) before day rollover.", expire_month); + } + + uint8_t checksum = decodedBA[16]; + uint8_t checksum_calculated = CalculateCheckSum(decodedBA); + bool checksum_valid = (checksum_calculated == checksum); + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--- " _CYAN_("Saflok Details")); + PrintAndLogEx(SUCCESS, "Key Level: %u (%s)", saflok_key_levels[key_level].level_num, saflok_key_levels[key_level].level_name); + PrintAndLogEx(SUCCESS, "LED Warning: %s", led_warning ? "Yes" : "No"); + PrintAndLogEx(SUCCESS, "Key ID: %u (0x%02X)", key_id, key_id); + PrintAndLogEx(SUCCESS, "Key Record: %u (0x%04X)", key_record, key_record); + PrintAndLogEx(SUCCESS, "Opening Key: %s", opening_key ? "Yes" : "No"); + PrintAndLogEx(SUCCESS, "Sequence Number & Combination: %u (0x%02X)", sequence_combination_number, sequence_combination_number); + PrintAndLogEx(SUCCESS, "Override Deadbolt: %s", override_deadbolt ? "Yes" : "No"); + PrintAndLogEx(SUCCESS, "Restricted Weekdays: %s", restricted_weekday_string); + PrintAndLogEx(SUCCESS, "Property ID: %u (0x%04X)", property_id, property_id); + PrintAndLogEx(SUCCESS, "Creation Date: %04u-%02u-%02u %02u:%02u", creation_year, creation_month, creation_day, creation_hour, creation_minute); + PrintAndLogEx(SUCCESS, "Expiration Date: %04u-%02u-%02u %02u:%02u", expire_year, expire_month, expire_day, expiry_hour, expiry_minute); + PrintAndLogEx(SUCCESS, "Checksum Valid: %s", checksum_valid ? "Yes" : "No"); +} + + + static int CmdHelp(const char *Cmd); /* @@ -9889,8 +10178,9 @@ static int CmdHF14AMfInfo(const char *Cmd) { PrintAndLogEx(SUCCESS, "unknown"); } - if (e_sector[1].foundKey[MF_KEY_A] && (e_sector[1].Key[MF_KEY_A] == 0x2A2C13CC242A)) { + if (keycnt > 1 && e_sector != NULL && e_sector[1].foundKey[MF_KEY_A] && (e_sector[1].Key[MF_KEY_A] == 0x2A2C13CC242A)) { PrintAndLogEx(SUCCESS, "dormakaba Saflok detected"); + ParseAndPrintSaflokData(&e_sector[0], &e_sector[1]); } } else { From e35a4e292d617ebe04421d24823fbe3c6df4ae54 Mon Sep 17 00:00:00 2001 From: Kara Zajac Date: Mon, 26 May 2025 23:39:25 -0400 Subject: [PATCH 08/31] Used make style Fixed some code comments and ran make style Signed-off-by: Kara Zajac --- client/src/cmdhfmf.c | 58 ++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index a05cfbb1f..3343ad899 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -66,7 +66,7 @@ typedef struct { // Structure for Saflok key levels typedef struct { uint8_t level_num; - const char* level_name; + const char *level_name; } SaflokKeyLevel; // Static array for Saflok key levels @@ -116,22 +116,28 @@ static void DecryptSaflokCardData( const uint8_t strCard[SAFLOK_BASIC_ACCESS_BYTE_NUM], // int length, // length is always SAFLOK_BASIC_ACCESS_BYTE_NUM uint8_t decryptedCard[SAFLOK_BASIC_ACCESS_BYTE_NUM]) { - int i, num, num2, num3, num4, b = 0, b2 = 0; + int i; + int num; + int num2; + int num3; + int num4; + int b = 0; + int b2 = 0; - for(i = 0; i < SAFLOK_BASIC_ACCESS_BYTE_NUM; i++) { + for (i = 0; i < SAFLOK_BASIC_ACCESS_BYTE_NUM; i++) { num = saflok_c_aDecode[strCard[i]] - (i + 1); - if(num < 0) num += 256; + if (num < 0) num += 256; decryptedCard[i] = num; } b = decryptedCard[10]; b2 = b & 1; - for(num2 = SAFLOK_BASIC_ACCESS_BYTE_NUM; num2 > 0; num2--) { + for (num2 = SAFLOK_BASIC_ACCESS_BYTE_NUM; num2 > 0; num2--) { b = decryptedCard[num2 - 1]; - for(num3 = 8; num3 > 0; num3--) { + for (num3 = 8; num3 > 0; num3--) { num4 = num2 + num3; - if(num4 > SAFLOK_BASIC_ACCESS_BYTE_NUM) num4 -= SAFLOK_BASIC_ACCESS_BYTE_NUM; + if (num4 > SAFLOK_BASIC_ACCESS_BYTE_NUM) num4 -= SAFLOK_BASIC_ACCESS_BYTE_NUM; int b3 = decryptedCard[num4 - 1]; int b4 = (b3 & 0x80) >> 7; b3 = ((b3 << 1) & 0xFF) | b2; @@ -146,7 +152,7 @@ static void DecryptSaflokCardData( // Function to calculate Saflok checksum static uint8_t CalculateCheckSum(uint8_t data[SAFLOK_BASIC_ACCESS_BYTE_NUM]) { int sum = 0; - for(int i = 0; i < SAFLOK_BASIC_ACCESS_BYTE_NUM - 1; i++) { + for (int i = 0; i < SAFLOK_BASIC_ACCESS_BYTE_NUM - 1; i++) { sum += data[i]; } sum = 255 - (sum & 0xFF); @@ -228,19 +234,19 @@ static void ParseAndPrintSaflokData(const sector_t* sector0_info, const sector_t // Byte 7: OverrideDeadbolt and Days uint8_t override_deadbolt = (decodedBA[7] & 0x80) >> 7; uint8_t restricted_weekday = decodedBA[7] & 0x7F; - + // Weekday names array - static const char* weekdays[] = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}; - + static const char *weekdays[] = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}; + // Buffer to store the resulting string (sufficient size for all weekdays) char restricted_weekday_string[128] = {0}; int restricted_count = 0; - + // Check each bit from Monday to Sunday - for(int i = 0; i < 7; i++) { - if(restricted_weekday & (0b01000000 >> i)) { + for (int i = 0; i < 7; i++) { + if (restricted_weekday & (0b01000000 >> i)) { // If the bit is set, append the corresponding weekday to the buffer - if(restricted_count > 0) { + if (restricted_count > 0) { strcat(restricted_weekday_string, ", "); } strcat(restricted_weekday_string, weekdays[i]); @@ -249,18 +255,18 @@ static void ParseAndPrintSaflokData(const sector_t* sector0_info, const sector_t } // Determine if all weekdays are restricted - if(restricted_weekday == 0b01111100) { + if (restricted_weekday == 0b01111100) { strcpy(restricted_weekday_string, "weekdays"); } // If there are specific restricted days - else if(restricted_weekday == 0b00000011) { + else if (restricted_weekday == 0b00000011) { strcpy(restricted_weekday_string, "weekends"); } // If no weekdays are restricted - else if(restricted_weekday == 0) { + else if (restricted_weekday == 0) { strcpy(restricted_weekday_string, "none"); } - + // Bytes 14-15: Property number and part of creation year uint8_t creation_year_high_bits = (decodedBA[14] & 0xF0); uint16_t property_id = ((decodedBA[14] & 0x0F) << 8) | decodedBA[15]; @@ -284,7 +290,7 @@ static void ParseAndPrintSaflokData(const sector_t* sector0_info, const sector_t uint8_t expire_day = creation_day + interval_day_val; // Handle month rollover for expiration - while(expire_month > 12) { + while (expire_month > 12) { expire_month -= 12; expire_year++; } @@ -292,12 +298,12 @@ static void ParseAndPrintSaflokData(const sector_t* sector0_info, const sector_t // Handle day rollover for expiration static const uint8_t days_in_month_lookup[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // 1-indexed month if (expire_month > 0 && expire_month <= 12) { - while(true) { + while (true) { uint8_t max_days = days_in_month_lookup[expire_month]; - if(expire_month == 2 && (expire_year % 4 == 0 && (expire_year % 100 != 0 || expire_year % 400 == 0))) { + if (expire_month == 2 && (expire_year % 4 == 0 && (expire_year % 100 != 0 || expire_year % 400 == 0))) { max_days = 29; // Leap year } - if(expire_day <= max_days) { + if (expire_day <= max_days) { break; } if (max_days == 0) { // Should not happen with valid month @@ -306,13 +312,13 @@ static void ParseAndPrintSaflokData(const sector_t* sector0_info, const sector_t } expire_day -= max_days; expire_month++; - if(expire_month > 12) { + if (expire_month > 12) { expire_month = 1; expire_year++; } } } else if (expire_month != 0) { // Allow 0 if it signifies no expiration or error - PrintAndLogEx(WARNING, "Saflok: Invalid expiration month (%u) before day rollover.", expire_month); + PrintAndLogEx(WARNING, "Saflok: Invalid expiration month (%u) before day rollover.", expire_month); } uint8_t checksum = decodedBA[16]; @@ -4800,7 +4806,7 @@ void printKeyTableEx(size_t sectorscnt, sector_t *e_sector, uint8_t start_sector _YELLOW_("H") ":Hardnested / " _YELLOW_("C") ":statiCnested / " _YELLOW_("A") ":keyA " - " )" + " )" ); if (sectorscnt == 18) { PrintAndLogEx(INFO, "( " _MAGENTA_("*") " ) These sectors used for signature. Lays outside of user memory"); From 4e07fc2b3151df704d1730f288743a16fbcbd4fd Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 27 May 2025 09:43:11 +0200 Subject: [PATCH 09/31] if enabled but no delay, then disable tear off just in case. enforce user to set a delay. if not this function will be triggered over and over which might confuse users normal operation --- armsrc/appmain.c | 1 + 1 file changed, 1 insertion(+) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 774014d6a..5537cff79 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -99,6 +99,7 @@ int tearoff_hook(void) { if (g_tearoff_enabled) { if (g_tearoff_delay_us == 0) { Dbprintf(_RED_("No tear-off delay configured!")); + g_tearoff_enabled = false; return PM3_SUCCESS; // SUCCESS = the hook didn't do anything } SpinDelayUsPrecision(g_tearoff_delay_us); From 585670d55c684bd9ed5c93213d1069ca83386368 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 27 May 2025 09:44:27 +0200 Subject: [PATCH 10/31] hf iclass tear - text output and when e-purse get cleared it stops and informs user --- client/src/cmdhficlass.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index f1da50f14..6ba663897 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -3209,9 +3209,14 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { goto out; } - PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us - "_YELLOW_("%u")" / "_YELLOW_("%d")" loops", params.delay_us, (tearoff_end & 0xFFFF), loop_count+1, tearoff_loop); + if (tearoff_loop > 1) { + PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us - "_YELLOW_("%u")" loops", params.delay_us, (tearoff_end & 0xFFFF), loop_count + 1); + } else { + PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us", params.delay_us, (tearoff_end & 0xFFFF)); + } // write block - don't check the return value. As a tear-off occurred, the write failed. + // when tear off is enabled, the return code will always be PM3_ETEAROFF iclass_write_block(blockno, data, mac, key, use_credit_key, elite, rawkey, use_replay, verbose, auth, shallow_mod); // read the data back @@ -3228,7 +3233,8 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { goto out; } - if (blockno == 1) { + // skip authentication for config and e-purse blocks (1,2) + if (blockno < 3) { read_auth = false; } @@ -3295,6 +3301,16 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { } bool goto_out = false; + if (blockno == 2) { + if (memcmp(data_read, ff_data, 8) == 0 && memcmp(data_read_orig, ff_data, 8) != 0) { + PrintAndLogEx(SUCCESS, "E-purse has been teared ( %s )", _GREEN_("ok")); + PrintAndLogEx(HINT, "Hint: try `hf iclass creditepurse -d FEFFFEFF --ki 1`"); + PrintAndLogEx(HINT, "Hint: try `hf iclass wrbl -d 'FFFFFFFF FFFF FEFF' --blk 2 --ki 1 --credit`"); + isok = PM3_SUCCESS; + goto_out = true; + } + } + if (blockno == 1) { if (data_read[0] != data_read_orig[0]) { PrintAndLogEx(NORMAL, ""); @@ -3322,13 +3338,20 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { for (int i = 7; i >= 0; --i) { int bit1 = (data_read_orig[7] >> i) & 1; int bit2 = (data_read[7] >> i) & 1; - PrintAndLogEx(INFO, "%-10s %-10d %-10d", flag_names[i], bit1, bit2); + PrintAndLogEx(INFO, "%-11s %-10d %-10d", flag_names[i], bit1, bit2); } isok = PM3_SUCCESS; goto_out = true; } + // if more OTP bits set.. + if (data_read[1] > data_read_orig[1] || + data_read[2] > data_read_orig[2]) { + PrintAndLogEx(SUCCESS, "More OTP bits got set!!!"); + isok = PM3_SUCCESS; + } + if (goto_out) { goto out; } From 01e57db5f175bba49f8cb987b5e8988b68a76de3 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 27 May 2025 10:12:58 +0200 Subject: [PATCH 11/31] text --- client/src/cmdhficlass.c | 11 ++++++----- doc/commands.json | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 6ba663897..1a917c1a8 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -2958,7 +2958,8 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { CLIParserInit(&ctx, "hf iclass tear", "Tear off an iCLASS tag block\n" "e-purse usually 300-500us to trigger the erase phase\n" - "also seen 1800-2100us on some cards\n", + "also seen 1800-2100us on some cards\n" + "Make sure you know the target card credit key. Typical `--ki 1` or `--ki 3`\n", "hf iclass tear --blk 10 -d AAAAAAAAAAAAAAAA -k 001122334455667B -s 300 -e 600\n" "hf iclass tear --blk 10 -d AAAAAAAAAAAAAAAA --ki 0 -s 300 -e 600\n" "hf iclass tear --blk 2 -d fdffffffffffffff --ki 1 --credit -s 400 -e 500" @@ -3138,8 +3139,8 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { } if (pagemap == 0x0) { - PrintAndLogEx(WARNING, _RED_("No auth possible. Read only if RA is enabled")); - goto out; + PrintAndLogEx(WARNING, _RED_("No auth possible. Read only if RA is enabled")); + goto out; } bool read_auth = auth; @@ -3210,7 +3211,7 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { } if (tearoff_loop > 1) { - PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us - "_YELLOW_("%u")" loops", params.delay_us, (tearoff_end & 0xFFFF), loop_count + 1); + PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us - "_YELLOW_("%u")" loops ", params.delay_us, (tearoff_end & 0xFFFF), loop_count + 1); } else { PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us", params.delay_us, (tearoff_end & 0xFFFF)); } @@ -3347,7 +3348,7 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { // if more OTP bits set.. if (data_read[1] > data_read_orig[1] || - data_read[2] > data_read_orig[2]) { + data_read[2] > data_read_orig[2]) { PrintAndLogEx(SUCCESS, "More OTP bits got set!!!"); isok = PM3_SUCCESS; } diff --git a/doc/commands.json b/doc/commands.json index 4d678aaeb..8f43d716a 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -3718,7 +3718,7 @@ }, "hf iclass tear": { "command": "hf iclass tear", - "description": "Tear off an iCLASS tag block e-purse usually 300-500us to trigger the erase phase also seen 1800-2100us on some cards", + "description": "Tear off an iCLASS tag block e-purse usually 300-500us to trigger the erase phase also seen 1800-2100us on some cards Make sure you know the target card credit key. Typical `--ki 1` or `--ki 3`", "notes": [ "hf iclass tear --blk 10 -d AAAAAAAAAAAAAAAA -k 001122334455667B -s 300 -e 600", "hf iclass tear --blk 10 -d AAAAAAAAAAAAAAAA --ki 0 -s 300 -e 600", @@ -13354,6 +13354,6 @@ "metadata": { "commands_extracted": 767, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-05-25T12:38:36" + "extracted_on": "2025-05-27T08:11:15" } } From ada340de948f7eae577070f5a758d70aecec7dd3 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 27 May 2025 14:52:04 +0200 Subject: [PATCH 12/31] fix exit call --- client/src/cmdhficlass.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 1a917c1a8..d9bea6771 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -3211,14 +3211,14 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { } if (tearoff_loop > 1) { - PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us - "_YELLOW_("%u")" loops ", params.delay_us, (tearoff_end & 0xFFFF), loop_count + 1); + PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us - "_YELLOW_("%3u")" loops", params.delay_us, (tearoff_end & 0xFFFF), loop_count + 1); } else { PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us", params.delay_us, (tearoff_end & 0xFFFF)); } // write block - don't check the return value. As a tear-off occurred, the write failed. // when tear off is enabled, the return code will always be PM3_ETEAROFF - iclass_write_block(blockno, data, mac, key, use_credit_key, elite, rawkey, use_replay, verbose, auth, shallow_mod); + iclass_write_block(blockno, data, mac, key, use_credit_key, elite, rawkey, use_replay, false, auth, shallow_mod); // read the data back uint8_t data_read[8] = {0}; @@ -3352,10 +3352,10 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { PrintAndLogEx(SUCCESS, "More OTP bits got set!!!"); isok = PM3_SUCCESS; } + } - if (goto_out) { - goto out; - } + if (goto_out) { + goto out; } } From bbd6f51586922c1ed540c556a3eee8258c35bd9a Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Wed, 28 May 2025 23:59:17 +0800 Subject: [PATCH 13/31] Updated hf iclass info for silicon check Updated hf iclass info to use silicone identification based on CSN rather than hf 14b responsiveness This reverted https://github.com/RfidResearchGroup/proxmark3/commit/4f85def6b07ad9cefe78d9bd11b18f14fad5dcb7 --- client/src/cmdhf14b.c | 11 ++++------- client/src/cmdhf14b.h | 1 - client/src/cmdhficlass.c | 7 +++---- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/client/src/cmdhf14b.c b/client/src/cmdhf14b.c index 69011be6c..f61d7d1fb 100644 --- a/client/src/cmdhf14b.c +++ b/client/src/cmdhf14b.c @@ -1411,7 +1411,7 @@ static bool HF14B_ask_ct_reader(bool verbose) { return false; } -bool HF14B_picopass_reader(bool verbose, bool info) { +static bool HF14B_picopass_reader(bool verbose) { iso14b_raw_cmd_t packet = { .flags = (ISO14B_CONNECT | ISO14B_SELECT_PICOPASS | ISO14B_DISCONNECT), @@ -1437,10 +1437,8 @@ bool HF14B_picopass_reader(bool verbose, bool info) { return false; } memcpy(card, resp.data.asBytes, sizeof(picopass_hdr_t)); - if (info) { - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(SUCCESS, "iCLASS / Picopass CSN: " _GREEN_("%s"), sprint_hex(card->csn, sizeof(card->csn))); - } + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, "iCLASS / Picopass CSN: " _GREEN_("%s"), sprint_hex(card->csn, sizeof(card->csn))); free(card); return true; } @@ -3038,7 +3036,6 @@ int infoHF14B(bool verbose, bool do_aid_search) { // get and print general info about all known 14b chips int readHF14B(bool loop, bool verbose, bool read_plot) { bool found = false; - bool info = true; int res = PM3_SUCCESS; do { found = false; @@ -3054,7 +3051,7 @@ int readHF14B(bool loop, bool verbose, bool read_plot) { goto plot; // Picopass - found |= HF14B_picopass_reader(verbose, info); + found |= HF14B_picopass_reader(verbose); if (found) goto plot; diff --git a/client/src/cmdhf14b.h b/client/src/cmdhf14b.h index 067718507..065dbc29c 100644 --- a/client/src/cmdhf14b.h +++ b/client/src/cmdhf14b.h @@ -31,6 +31,5 @@ int select_card_14443b_4(bool disconnect, iso14b_card_select_t *card); int infoHF14B(bool verbose, bool do_aid_search); int readHF14B(bool loop, bool verbose, bool read_plot); -bool HF14B_picopass_reader(bool verbose, bool info); #endif diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index d9bea6771..b3118cb0a 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -40,7 +40,6 @@ #include "crypto/asn1utils.h" // ASN1 decoder #include "preferences.h" #include "generator.h" -#include "cmdhf14b.h" #include "cmdhw.h" #include "hidsio.h" @@ -5985,10 +5984,10 @@ int info_iclass(bool shallow_mod) { uint8_t cardtype = get_mem_config(hdr); PrintAndLogEx(SUCCESS, " Card type.... " _GREEN_("%s"), card_types[cardtype]); - if (HF14B_picopass_reader(false, false)) { - PrintAndLogEx(SUCCESS, " Card chip.... "_YELLOW_("Old Silicon (14b support)")); - } else { + if (memcmp(hdr->csn + 4, "\xFE\xFF\x12\xE0", 4) == 0) { PrintAndLogEx(SUCCESS, " Card chip.... "_YELLOW_("NEW Silicon (No 14b support)")); + } else { + PrintAndLogEx(SUCCESS, " Card chip.... "_YELLOW_("Old Silicon (14b support)")); } if (legacy) { From 503c03caa244bda4522f9820d193f0c52672d6d3 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Thu, 29 May 2025 00:00:19 +0800 Subject: [PATCH 14/31] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 631bb3084..b192e419f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Changed `hf iclass info` - now uses CSN values based checks (@antiklesys) - Changed `hf iclass dump` - now uses default AA1 key when called without a key or key index (@iceman1001) - Renamed `hf iclass trbl` to `hf iclass tear` (@iceman1001) - Changed `hw tearoff` - the device side message is now debug log controlled (@iceman1001) From b2e587afa5c12c439d7a0f844190f7a8736c5407 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 28 May 2025 20:51:51 +0200 Subject: [PATCH 15/31] missing break, migth lead to strange behavior --- armsrc/iso14443a.c | 1 + 1 file changed, 1 insertion(+) diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 732221592..2012ba9b4 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -4276,6 +4276,7 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid, dynamic_response_info.response_n = 3 + offset; } } + break; } break; From 0479a1b82eec6ac1dd7244c144d40de4300328f5 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 28 May 2025 20:52:14 +0200 Subject: [PATCH 16/31] text --- client/src/cmdhficlass.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index b3118cb0a..bd5dd3681 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -3845,9 +3845,9 @@ static void HFiClassCalcNewKey(uint8_t *CSN, uint8_t *OLDKEY, uint8_t *NEWKEY, u xor_div_key[i] = old_div_key[i] ^ new_div_key[i]; } if (verbose) { - PrintAndLogEx(SUCCESS, "Old div key......... %s", sprint_hex(old_div_key, 8)); - PrintAndLogEx(SUCCESS, "New div key......... %s", sprint_hex(new_div_key, 8)); - PrintAndLogEx(SUCCESS, "Xor div key......... " _YELLOW_("%s") "\n", sprint_hex(xor_div_key, 8)); + PrintAndLogEx(SUCCESS, "Old div key........ %s", sprint_hex(old_div_key, 8)); + PrintAndLogEx(SUCCESS, "New div key........ %s", sprint_hex(new_div_key, 8)); + PrintAndLogEx(SUCCESS, "Xor div key........ " _YELLOW_("%s") "\n", sprint_hex(xor_div_key, 8)); } } From a223570dbf01758b4a16fedd651e1fdc461ac00a Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 28 May 2025 20:52:51 +0200 Subject: [PATCH 17/31] hf iclass tear - only inform about read failures and keep on, instead of quiting command --- client/src/cmdhficlass.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index bd5dd3681..6cd08c7ee 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -3252,21 +3252,20 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { } else if (res != PM3_SUCCESS) { decrease = true; } - if (readcount >= 10){ - PrintAndLogEx(WARNING, "\n"_RED_("Read failed too many times, giving up")); - isok = PM3_EFAILED; - goto out; - } + readcount++; } + if (readcount > 1) { + PrintAndLogEx(WARNING, "\nRead block failed "_RED_("%d") " times", readcount); + } + // if there was an error reading repeat the tearoff with the same delay if (decrease && (tearoff_start > tearoff_increment) && (tearoff_start >= tearoff_original_start)) { tearoff_start -= tearoff_increment; - if(verbose){ + if (verbose) { PrintAndLogEx(INFO, " -> Read failed, retearing with "_CYAN_("%u")" us", tearoff_start); } - } bool tear_success = true; From 18e4c072e7bd0a254549c7fc4566d5cc9895fa99 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 28 May 2025 20:54:05 +0200 Subject: [PATCH 18/31] style --- client/src/cmdhfmf.c | 6 +++--- client/src/pm3line_vocabulary.h | 2 +- doc/commands.json | 2 +- tools/mfc/card_only/staticnested_0nt.c | 4 +++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 8de811a78..23facd528 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -160,7 +160,7 @@ static uint8_t CalculateCheckSum(uint8_t data[SAFLOK_BASIC_ACCESS_BYTE_NUM]) { } // Function to parse and print Saflok data -static void ParseAndPrintSaflokData(const sector_t* sector0_info, const sector_t* sector1_info) { +static void ParseAndPrintSaflokData(const sector_t *sector0_info, const sector_t *sector1_info) { (void)sector1_info; // Not directly used for payload parsing currently if (!sector0_info) { @@ -4806,7 +4806,7 @@ void printKeyTableEx(size_t sectorscnt, sector_t *e_sector, uint8_t start_sector _YELLOW_("H") ":Hardnested / " _YELLOW_("C") ":statiCnested / " _YELLOW_("A") ":keyA " - " )" + " )" ); if (sectorscnt == 18) { PrintAndLogEx(INFO, "( " _MAGENTA_("*") " ) These sectors used for signature. Lays outside of user memory"); @@ -10568,7 +10568,7 @@ static int CmdHF14AMfBambuKeys(const char *Cmd) { PrintAndLogEx(INFO, "-----------------------------------"); uint8_t keys[32 * 6]; - mfc_algo_bambu_all(uid, (void*)keys); + mfc_algo_bambu_all(uid, (void *)keys); for (int block = 0; block < 32; block++) { PrintAndLogEx(INFO, "%d: %012" PRIX64, block, bytes_to_num(keys + (block * 6), 6)); diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index d637f4e6e..7f91e8d60 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -356,7 +356,7 @@ const static vocabulary_t vocabulary[] = { { 0, "hf mf fchk" }, { 1, "hf mf decrypt" }, { 0, "hf mf supercard" }, - { 1, "hf mf blgen" }, + { 1, "hf mf bambukeys" }, { 0, "hf mf auth4" }, { 1, "hf mf acl" }, { 0, "hf mf dump" }, diff --git a/doc/commands.json b/doc/commands.json index e9400a96b..eb9cdcee3 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -13371,6 +13371,6 @@ "metadata": { "commands_extracted": 768, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-05-27T08:11:15" + "extracted_on": "2025-05-28T18:50:22" } } diff --git a/tools/mfc/card_only/staticnested_0nt.c b/tools/mfc/card_only/staticnested_0nt.c index 44bfaaf11..088b4aadc 100644 --- a/tools/mfc/card_only/staticnested_0nt.c +++ b/tools/mfc/card_only/staticnested_0nt.c @@ -272,7 +272,9 @@ static uint64_t **unpredictable_nested(NtpKs1List *pNKL, uint32_t keyCounts[]) { pthread_cond_wait(&status_cond, &status_mutex); activeThreads = 0; for (int i = 0; i < NUM_THREADS; i++) { - if (thread_status[i]) activeThreads++; + if (thread_status[i]) { + activeThreads++; + } } } From 7b9fe29cf31f105fd87285d0ce417e665d3d24ca Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Thu, 29 May 2025 11:05:52 +0200 Subject: [PATCH 19/31] hf iclass info - show AA2 key if found --- client/src/cmdhficlass.c | 55 ++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 6cd08c7ee..ebc9e0824 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -44,14 +44,17 @@ #include "hidsio.h" +#define ICLASS_DEBIT_KEYTYPE ( 0x88 ) +#define ICLASS_CREDIT_KEYTYPE ( 0x18 ) + #define NUM_CSNS 9 #define MAC_ITEM_SIZE 24 // csn(8) + epurse(8) + nr(4) + mac(4) = 24 bytes #define ICLASS_KEYS_MAX 8 #define ICLASS_AUTH_RETRY 10 #define ICLASS_CFG_BLK_SR_BIT 0xA0 // indicates SIO present when set in block6[0] (legacy tags) -#define ICLASS_DECRYPTION_BIN "iclass_decryptionkey.bin" -#define ICLASS_DEFAULT_KEY_DIC "iclass_default_keys.dic" -#define ICLASS_DEFAULT_KEY_ELITE_DIC "iclass_elite_keys.dic" +#define ICLASS_DECRYPTION_BIN "iclass_decryptionkey.bin" +#define ICLASS_DEFAULT_KEY_DIC "iclass_default_keys.dic" +#define ICLASS_DEFAULT_KEY_ELITE_DIC "iclass_elite_keys.dic" static void print_picopass_info(const picopass_hdr_t *hdr); void print_picopass_header(const picopass_hdr_t *hdr); @@ -2803,10 +2806,10 @@ static int CmdHFiClass_ReadBlock(const char *Cmd) { int blockno = arg_get_int_def(ctx, 3, 0); - uint8_t keyType = 0x88; //debit key + uint8_t keyType = ICLASS_DEBIT_KEYTYPE; if (arg_get_lit(ctx, 4)) { PrintAndLogEx(SUCCESS, "Using " _YELLOW_("credit") " key"); - keyType = 0x18; //credit key + keyType = ICLASS_CREDIT_KEYTYPE; } bool elite = arg_get_lit(ctx, 5); @@ -3080,11 +3083,10 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { int loop_count = 0; int isok = 0; bool read_ok = false; - uint8_t keyType = 0x88; // debit key - + uint8_t keyType = ICLASS_DEBIT_KEYTYPE; if (use_credit_key) { PrintAndLogEx(SUCCESS, "Using " _YELLOW_("credit") " key"); - keyType = 0x18; // credit key + keyType = ICLASS_CREDIT_KEYTYPE; } if (auth == false) { @@ -3142,9 +3144,9 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { goto out; } - bool read_auth = auth; // perform initial read here, repeat if failed or 00s + bool read_auth = auth; uint8_t data_read_orig[8] = {0}; uint8_t ff_data[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; bool first_read = false; @@ -3346,9 +3348,17 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { // if more OTP bits set.. if (data_read[1] > data_read_orig[1] || - data_read[2] > data_read_orig[2]) { + data_read[2] > data_read_orig[2]) { PrintAndLogEx(SUCCESS, "More OTP bits got set!!!"); + + data_read[7] = 0xBC; + res = iclass_write_block(blockno, data_read, mac, key, use_credit_key, elite, rawkey, use_replay, verbose, auth, shallow_mod); + if (res != PM3_SUCCESS) { + PrintAndLogEx(INFO, "Stabilize the bits ( "_RED_("failed") " )" ); + } + isok = PM3_SUCCESS; + goto_out = true; } } @@ -5991,25 +6001,38 @@ int info_iclass(bool shallow_mod) { if (legacy) { int res = PM3_ESOFT; - uint8_t key_type = 0x88; // debit key - uint8_t dump[PICOPASS_BLOCK_SIZE * 8] = {0}; // we take all raw bytes from response memcpy(dump, p_response, sizeof(picopass_hdr_t)); + bool found_aa1 = false; + bool found_aa2 = false; uint8_t key[8] = {0}; for (uint8_t i = 0; i < ARRAYLEN(iClass_Key_Table); i++) { memcpy(key, iClass_Key_Table[i], sizeof(key)); - res = iclass_read_block_ex(key, 6, key_type, false, false, false, false, true, false, dump + (PICOPASS_BLOCK_SIZE * 6), false); + + if (found_aa1 == false) { + res = iclass_read_block_ex(key, 6, ICLASS_DEBIT_KEYTYPE, false, false, false, false, true, false, dump + (PICOPASS_BLOCK_SIZE * 6), false); + if (res == PM3_SUCCESS) { + PrintAndLogEx(SUCCESS, " AA1 Key...... " _GREEN_("%s"), sprint_hex_inrow(key, sizeof(key))); + found_aa1 = true; + } + } + + res = iclass_read_block_ex(key, 6, ICLASS_CREDIT_KEYTYPE, false, false, false, false, true, false, dump + (PICOPASS_BLOCK_SIZE * 7), false); if (res == PM3_SUCCESS) { - PrintAndLogEx(SUCCESS, " AA1 Key...... " _GREEN_("%s"), sprint_hex_inrow(key, sizeof(key))); + PrintAndLogEx(SUCCESS, " AA2 Key...... " _GREEN_("%s"), sprint_hex_inrow(key, sizeof(key))); + found_aa2 = true; + } + + if (found_aa1 && found_aa2) { break; } } - if (res == PM3_SUCCESS) { - res = iclass_read_block_ex(key, 7, key_type, false, false, false, false, true, false, dump + (PICOPASS_BLOCK_SIZE * 7), false); + if (found_aa1) { + res = iclass_read_block_ex(key, 7, ICLASS_DEBIT_KEYTYPE, false, false, false, false, true, false, dump + (PICOPASS_BLOCK_SIZE * 7), false); if (res == PM3_SUCCESS) { BLOCK79ENCRYPTION aa1_encryption = (dump[(6 * PICOPASS_BLOCK_SIZE) + 7] & 0x03); From f0022e4280cd0730f34af8883d0a354e5f8eabb2 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Thu, 29 May 2025 14:52:49 +0200 Subject: [PATCH 20/31] text --- client/src/cmdhficlass.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index ebc9e0824..377bcffb2 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -3132,6 +3132,12 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { return PM3_ESOFT; } + if (memcmp(r->header.hdr.csn + 4, "\xFE\xFF\x12\xE0", 4) == 0) { + PrintAndLogEx(SUCCESS, "CSN................... %s ( new silicon )", sprint_hex_inrow(r->header.hdr.csn, PICOPASS_BLOCK_SIZE)); + } else { + PrintAndLogEx(SUCCESS, "CSN................... %s ( old silicon )", sprint_hex_inrow(r->header.hdr.csn, PICOPASS_BLOCK_SIZE)); + } + picopass_hdr_t *hdr = &r->header.hdr; uint8_t pagemap = get_pagemap(hdr); if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) { From 804acfbefafdd5f38aae0997fe1d119b0b4310e9 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Fri, 30 May 2025 01:37:13 +0200 Subject: [PATCH 21/31] the device side of iclass tear off is implemented. The base was done by @antiklesys. This version differs by the concept of trying to stabilize weak bits by performing a write operation in conjuction with the detected tear. Its untested but I can replicate most of the tears we performed client side. You will need to call the proxmark3 client with `-f` , `./pm3 -f` to force flush out text which is needed for the inplace printing. I thought this was done automatically but it wasnt. `hf iclass tear --arm + all the normal params` to run on device side --- CHANGELOG.md | 1 + armsrc/appmain.c | 4 + armsrc/iclass.c | 428 ++++++++++++++++++++++++++++++++++++++- armsrc/iclass.h | 1 + armsrc/iso14443b.c | 3 + client/src/cmdhficlass.c | 417 +++++++++++++++++++++----------------- include/iclass_cmd.h | 11 + include/pm3_cmd.h | 1 + 8 files changed, 674 insertions(+), 192 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eeadb9b4e..00233936b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Modified `hf iclass tear` - now has a device side implementation also. @antiklesys (@iceman1001) - Changed `hf iclass info` - now uses CSN values based checks (@antiklesys) - Changed `hf iclass dump` - now uses default AA1 key when called without a key or key index (@iceman1001) - Renamed `hf iclass trbl` to `hf iclass tear` (@iceman1001) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 5537cff79..9399592c9 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -2230,6 +2230,10 @@ static void PacketReceived(PacketCommandNG *packet) { iclass_credit_epurse((iclass_credit_epurse_t *)packet->data.asBytes); break; } + case CMD_HF_ICLASS_TEARBL: { + iClass_TearBlock((iclass_tearblock_req_t *)packet->data.asBytes); + break; + } #endif #ifdef WITH_HFSNIFF diff --git a/armsrc/iclass.c b/armsrc/iclass.c index cfb73cde2..38627ea70 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -38,6 +38,7 @@ #include "iso15693.h" #include "iclass_cmd.h" // iclass_card_select_t struct #include "i2c.h" // i2c defines (SIM module access) +#include "printf.h" uint8_t get_pagemap(const picopass_hdr_t *hdr) { return (hdr->conf.fuses & (FUSE_CRYPT0 | FUSE_CRYPT1)) >> 3; @@ -1244,7 +1245,6 @@ static bool iclass_send_cmd_with_retries(uint8_t *cmd, size_t cmdsize, uint8_t * while (tries-- > 0) { iclass_send_as_reader(cmd, cmdsize, start_time, eof_time, shallow_mod); - if (resp == NULL) { return true; } @@ -1582,8 +1582,9 @@ bool iclass_read_block(uint16_t blockno, uint8_t *data, uint32_t *start_time, ui uint8_t c[] = {ICLASS_CMD_READ_OR_IDENTIFY, blockno, 0x00, 0x00}; AddCrc(c + 1, 1); bool isOK = iclass_send_cmd_with_retries(c, sizeof(c), resp, sizeof(resp), 10, 2, start_time, ICLASS_READER_TIMEOUT_OTHERS, eof_time, shallow_mod); - if (isOK) + if (isOK) { memcpy(data, resp, 8); + } return isOK; } @@ -1780,13 +1781,13 @@ static bool iclass_writeblock_ext(uint8_t blockno, uint8_t *data, uint8_t *mac, } } else if (blockno == 3 || blockno == 4) { // check response. Key updates always return 0xffffffffffffffff - uint8_t all_ff[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; - if (memcmp(all_ff, resp, 8)) { + uint8_t all_ff[PICOPASS_BLOCK_SIZE] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + if (memcmp(all_ff, resp, PICOPASS_BLOCK_SIZE)) { return false; } } else { // check response. All other updates return unchanged data - if (memcmp(data, resp, 8)) { + if (memcmp(data, resp, PICOPASS_BLOCK_SIZE)) { return false; } } @@ -1829,7 +1830,7 @@ void iClass_WriteBlock(uint8_t *msg) { } // new block data - memcpy(write + 2, payload->data, 8); + memcpy(write + 2, payload->data, PICOPASS_BLOCK_SIZE); uint8_t pagemap = get_pagemap(&hdr); if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) { @@ -1847,7 +1848,7 @@ void iClass_WriteBlock(uint8_t *msg) { // Secure tags uses MAC uint8_t wb[9]; wb[0] = payload->req.blockno; - memcpy(wb + 1, payload->data, 8); + memcpy(wb + 1, payload->data, PICOPASS_BLOCK_SIZE); if (payload->req.use_credit_key) doMAC_N(wb, sizeof(wb), hdr.key_c, mac); @@ -2074,6 +2075,417 @@ out: reply_ng(CMD_HF_ICLASS_CREDIT_EPURSE, PM3_SUCCESS, (uint8_t *)&res, sizeof(uint8_t)); } +static void iclass_cmp_print(uint8_t *b1, uint8_t *b2, const char *header1, const char *header2) { + + char line1[240] = {0}; + char line2[240] = {0}; + + strcat(line1, header1); + strcat(line2, header2); + + for (uint8_t i = 0; i < PICOPASS_BLOCK_SIZE; i++) { + + int l1 = strlen(line1); + int l2 = strlen(line2); + + uint8_t hi1 = NIBBLE_HIGH(b1[i]); + uint8_t low1 = NIBBLE_LOW(b1[i]); + + uint8_t hi2 = NIBBLE_HIGH(b2[i]); + uint8_t low2 = NIBBLE_LOW(b2[i]); + + if (hi1 != hi2) { + sprintf(line1 + l1, _RED_("%1X"), hi1); + sprintf(line2 + l2, _GREEN_("%1X"), hi2); + } else { + sprintf(line1 + l1, "%1X", hi1); + sprintf(line2 + l2, "%1X", hi2); + } + + l1 = strlen(line1); + l2 = strlen(line2); + + if (low1 != low2) { + sprintf(line1 + l1, _RED_("%1X"), low1); + sprintf(line2 + l2, _GREEN_("%1X"), low2); + } else { + sprintf(line1 + l1, "%1X", low1); + sprintf(line2 + l2, "%1X", low2); + } + } + DbpString(line1); + DbpString(line2); +} + +void iClass_TearBlock(iclass_tearblock_req_t *msg) { + + if (msg == NULL) { + reply_ng(CMD_HF_ICLASS_TEARBL, PM3_ESOFT, NULL, 0); + return; + } + + // local variable copies + int tear_start = msg->tear_start; + int tear_end = msg->tear_end; + int tear_inc = msg->increment; + int tear_loop = msg->tear_loop; + + int loop_count = 0; + + uint32_t start_time = 0; + uint32_t eof_time = 0; + + int isok = PM3_SUCCESS; + + uint8_t data[8] = {0}; + memcpy(data, msg->data, sizeof(data)); + + uint8_t mac[4] = {0}; + memcpy(mac, msg->mac, sizeof(mac)); + + picopass_hdr_t hdr = {0}; + iclass_auth_req_t req = { + .blockno = msg->req.blockno, + .do_auth = msg->req.do_auth, + .send_reply = msg->req.send_reply, + .shallow_mod = msg->req.shallow_mod, + .use_credit_key = msg->req.use_credit_key, + .use_elite = msg->req.use_elite, + .use_raw = msg->req.use_raw, + .use_replay = msg->req.use_replay + }; + memcpy(req.key, msg->req.key, PICOPASS_BLOCK_SIZE); + + LED_A_ON(); + Iso15693InitReader(); + + // save old debug log level + int oldbg = g_dbglevel; + + // no debug logging please + g_dbglevel = DBG_NONE; + + // select + bool res = select_iclass_tag(&hdr, req.use_credit_key, &eof_time, req.shallow_mod); + if (res == false) { + DbpString(_RED_("Failed to select iClass tag")); + isok = PM3_ECARDEXCHANGE; + goto out; + } + + // authenticate + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; + res = authenticate_iclass_tag(&req, &hdr, &start_time, &eof_time, mac); + if (res == false) { + DbpString(_RED_("Failed to authenticate with iClass tag")); + isok = PM3_ECARDEXCHANGE; + goto out; + } + + uint8_t data_read_orig[PICOPASS_BLOCK_SIZE] = {0}; + + // read block + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; + res = iclass_read_block(req.blockno, data_read_orig, &start_time, &eof_time, req.shallow_mod); + if (res == false) { + Dbprintf("Failed to read block %u", req.blockno); + isok = PM3_ECARDEXCHANGE; + goto out; + } + + bool erase_phase = false; + bool read_ok = false; + + // static uint8_t empty[PICOPASS_BLOCK_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + static uint8_t zeros[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + uint8_t ff_data[PICOPASS_BLOCK_SIZE] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + uint8_t data_read[PICOPASS_BLOCK_SIZE] = {0}; + + // create READ command + uint8_t cmd_read[] = {ICLASS_CMD_READ_OR_IDENTIFY, req.blockno, 0x00, 0x00}; + AddCrc(cmd_read + 1, 1); + + // create WRITE COMMAND and new block data + uint8_t cmd_write[14] = { 0x80 | ICLASS_CMD_UPDATE, req.blockno }; + uint8_t cmd_write_len = 14; + memcpy(cmd_write + 2, data, PICOPASS_BLOCK_SIZE); + + uint8_t pagemap = get_pagemap(&hdr); + if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) { + // Unsecured tags uses CRC16, but don't include the UPDATE operation code + // byte0 = update op + // byte1 = block no + // byte2..9 = new block data + AddCrc(cmd_write + 1, 9); + cmd_write_len -= 2; + } else { + + if (req.use_replay) { + memcpy(cmd_write + 10, mac, sizeof(mac)); + } else { + // Secure tags uses MAC + uint8_t wb[9]; + wb[0] = req.blockno; + memcpy(wb + 1, data, PICOPASS_BLOCK_SIZE); + + if (req.use_credit_key) + doMAC_N(wb, sizeof(wb), hdr.key_c, mac); + else + doMAC_N(wb, sizeof(wb), hdr.key_d, mac); + + memcpy(cmd_write + 10, mac, sizeof(mac)); + } + } + + // Main loop + while ((tear_start <= tear_end) && (read_ok == false)) { + + if (BUTTON_PRESS() || data_available()) { + isok = PM3_EOPABORTED; + goto out; + } + + // set tear off trigger + g_tearoff_enabled = true; + g_tearoff_delay_us = (tear_start & 0xFFFF); + + if (tear_loop > 1) { + DbprintfEx(FLAG_INPLACE, "[" _BLUE_("#") "] Tear off delay " _YELLOW_("%u") " / " _YELLOW_("%u") " us - " _YELLOW_("%3u") " iter", tear_start, tear_end, loop_count + 1); + } else { + DbprintfEx(FLAG_INPLACE, "[" _BLUE_("#") "] Tear off delay " _YELLOW_("%u") " / " _YELLOW_("%u") " us", tear_start, tear_end); + } + + // write block + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; + iclass_send_as_reader(cmd_write, cmd_write_len, &start_time, &eof_time, req.shallow_mod); + + tearoff_hook(); + + switch_off(); + + // start reading block + + // reinit + Iso15693InitReader(); + + // select tag + res = select_iclass_tag(&hdr, req.use_credit_key, &eof_time, req.shallow_mod); + if (res == false) { + continue; + } + + // skip authentication for config and e-purse blocks (1,2) + if (req.blockno > 2) { + + // authenticate + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; + res = authenticate_iclass_tag(&req, &hdr, &start_time, &eof_time, NULL); + if (res == false) { + DbpString("Failed to authenticate after tear"); + continue; + } + } + + // read again and keep field on + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; + res = iclass_read_block(req.blockno, data_read, &start_time, &eof_time, req.shallow_mod); + if (res == false) { + DbpString("Failed to read block after tear"); + continue; + } + + // + bool tear_success = true; + + if (memcmp(data_read, data, PICOPASS_BLOCK_SIZE) != 0) { + tear_success = false; + } + + if ((tear_success == false) && + (memcmp(data_read, zeros, PICOPASS_BLOCK_SIZE) != 0) && + (memcmp(data_read, data_read_orig, PICOPASS_BLOCK_SIZE) != 0)) { + + // tearoff succeeded (partially) + + if (memcmp(data_read, ff_data, PICOPASS_BLOCK_SIZE) == 0 && + memcmp(data_read_orig, ff_data, PICOPASS_BLOCK_SIZE) != 0) { + + erase_phase = true; + DbpString(""); + DbpString(_CYAN_("Erase phase hit... ALL ONES")); + + iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: "); + + } else { + + if (erase_phase) { + DbpString(""); + DbpString(_MAGENTA_("Tearing! Write phase (post erase)")); + iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: "); + } else { + DbpString(""); + DbpString(_CYAN_("Tearing! unknown phase")); + iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: "); + } + } + + // shall we exit? well it depends on some things. + bool goto_out = false; + + if (req.blockno == 2) { + if (memcmp(data_read, ff_data, PICOPASS_BLOCK_SIZE) == 0 && memcmp(data_read_orig, ff_data, PICOPASS_BLOCK_SIZE) != 0) { + DbpString(""); + Dbprintf("E-purse has been teared ( %s )", _GREEN_("ok")); + isok = PM3_SUCCESS; + goto_out = true; + } + } + + if (req.blockno == 1) { + + if (data_read[0] != data_read_orig[0]) { + DbpString(""); + Dbprintf("Application limit changed, from "_YELLOW_("%u")" to "_YELLOW_("%u"), data_read_orig[0], data_read[0]); + isok = PM3_SUCCESS; + goto_out = true; + } + + if (data_read[7] != data_read_orig[7]) { + DbpString(""); + Dbprintf("Fuse changed, from "_YELLOW_("%02x")" to "_YELLOW_("%02x"), data_read_orig[7], data_read[7]); + + const char *flag_names[8] = { + "RA", + "Fprod0", + "Fprod1", + "Crypt0 (*1)", + "Crypt1 (*0)", + "Coding0", + "Coding1", + "Fpers (*1)" + }; + Dbprintf(_YELLOW_("%-10s %-10s %-10s"), "Fuse", "Original", "Changed"); + Dbprintf("---------------------------------------"); + for (int i = 7; i >= 0; --i) { + int bit1 = (data_read_orig[7] >> i) & 1; + int bit2 = (data_read[7] >> i) & 1; + Dbprintf("%-11s %-10d %-10d", flag_names[i], bit1, bit2); + } + + isok = PM3_SUCCESS; + goto_out = true; + } + + // if more OTP bits set.. + if (data_read[1] > data_read_orig[1] || + data_read[2] > data_read_orig[2]) { + DbpString(""); + DbpString("More OTP bits got set!!!"); + + // step 4 if bits changed attempt to write the new bits to the tag + if (data_read[7] == 0xBC) { + data_read[7] = 0xAC; + } + + // prepare WRITE command + cmd_write_len = 14; + memcpy(cmd_write + 2, data_read, PICOPASS_BLOCK_SIZE); + + if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) { + // Unsecured tags uses CRC16, but don't include the UPDATE operation code + // byte0 = update op + // byte1 = block no + // byte2..9 = new block data + AddCrc(cmd_write + 1, 9); + cmd_write_len -= 2; + } else { + + if (req.use_replay) { + memcpy(cmd_write + 10, mac, sizeof(mac)); + } else { + // Secure tags uses MAC + uint8_t wb[9]; + wb[0] = req.blockno; + memcpy(wb + 1, data_read, PICOPASS_BLOCK_SIZE); + + if (req.use_credit_key) + doMAC_N(wb, sizeof(wb), hdr.key_c, mac); + else + doMAC_N(wb, sizeof(wb), hdr.key_d, mac); + + memcpy(cmd_write + 10, mac, sizeof(mac)); + } + } + + // write block + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; + iclass_send_as_reader(cmd_write, cmd_write_len, &start_time, &eof_time, req.shallow_mod); + + uint16_t resp_len = 0; + uint8_t resp[ICLASS_BUFFER_SIZE] = {0}; + res = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_UPDATE, &eof_time, false, true, &resp_len); + if (res == PM3_SUCCESS && resp_len == 10) { + Dbprintf("Wrote to block"); + } + + switch_off(); + + Iso15693InitReader(); + + // select tag, during which we read block1 + res = select_iclass_tag(&hdr, req.use_credit_key, &eof_time, req.shallow_mod); + if (res) { + + if (memcmp(&hdr.conf, cmd_write + 2, PICOPASS_BLOCK_SIZE) == 0) { + Dbprintf("Stabilize the bits ( "_GREEN_("ok") " )"); + } else { + Dbprintf("Stabilize the bits ( "_RED_("failed") " )"); + } + } + + isok = PM3_SUCCESS; + goto_out = true; + } + } + + if (goto_out) { + goto out; + } + } + + // tearoff succeeded with expected values, which is unlikely + if (tear_success) { + read_ok = true; + tear_success = true; + DbpString(""); + DbpString("tear success!"); + } + + loop_count++; + + // increase tear off delay + if (loop_count == tear_loop) { + tear_start += tear_inc; + loop_count = 0; + } + } + +out: + + switch_off(); + + // reset tear off trigger + g_tearoff_enabled = false; + + // restore debug message levels + g_dbglevel = oldbg; + + if (msg->req.send_reply) { + reply_ng(CMD_HF_ICLASS_TEARBL, isok, NULL, 0); + } +} + void iClass_Restore(iclass_restore_req_t *msg) { // sanitation @@ -2292,8 +2704,6 @@ void iClass_Recover(iclass_recover_req_t *msg) { //Step2 Privilege Escalation: attempt to read AA2 with credentials for AA1 uint8_t blockno = 24; - uint8_t cmd_read[] = {ICLASS_CMD_READ_OR_IDENTIFY, blockno, 0x00, 0x00}; - AddCrc(cmd_read + 1, 1); int priv_esc_tries = 0; bool priv_esc = false; while (!priv_esc) { diff --git a/armsrc/iclass.h b/armsrc/iclass.h index 2185fd794..4e242b254 100644 --- a/armsrc/iclass.h +++ b/armsrc/iclass.h @@ -72,4 +72,5 @@ uint8_t get_pagemap(const picopass_hdr_t *hdr); void iclass_send_as_reader(uint8_t *frame, int len, uint32_t *start_time, uint32_t *end_time, bool shallow_mod); void iClass_Recover(iclass_recover_req_t *msg); +void iClass_TearBlock(iclass_tearblock_req_t *msg); #endif diff --git a/armsrc/iso14443b.c b/armsrc/iso14443b.c index 0a42c3e97..ed440c0c0 100644 --- a/armsrc/iso14443b.c +++ b/armsrc/iso14443b.c @@ -2135,6 +2135,9 @@ static int iso14443b_select_picopass_card(picopass_hdr_t *hdr) { static uint8_t act_all[] = { ICLASS_CMD_ACTALL }; static uint8_t identify[] = { ICLASS_CMD_READ_OR_IDENTIFY }; static uint8_t read_conf[] = { ICLASS_CMD_READ_OR_IDENTIFY, 0x01, 0xfa, 0x22 }; + + // ICLASS_CMD_SELECT 0x81 tells ISO14443b/BPSK coding/106 kbits/s + // ICLASS_CMD_SELECT 0x41 tells ISO14443b/BPSK coding/423 kbits/s uint8_t select[] = { 0x80 | ICLASS_CMD_SELECT, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; uint8_t read_aia[] = { ICLASS_CMD_READ_OR_IDENTIFY, 0x05, 0xde, 0x64}; uint8_t read_check_cc[] = { 0x80 | ICLASS_CMD_READCHECK, 0x02 }; diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 377bcffb2..86dff7e43 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -2985,6 +2985,7 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { arg_int0("e", NULL, "", "tearoff delay end (in us) must be a higher value than the start delay"), arg_int0(NULL, "loop", "", "number of times to loop per tearoff time"), arg_int0(NULL, "sleep", "", "Sleep between each tear"), + arg_lit0(NULL, "arm", "Runs the commands on device side and tries to stabilize tears"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, false); @@ -3017,6 +3018,7 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { int tearoff_end = arg_get_int_def(ctx, 14, tearoff_start + tearoff_increment + 500); int tearoff_loop = arg_get_int_def(ctx, 15, 1); int tearoff_sleep = arg_get_int_def(ctx, 16, 0); + bool run_on_device = arg_get_lit(ctx, 17); CLIParserFree(ctx); @@ -3081,7 +3083,7 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { } int loop_count = 0; - int isok = 0; + int isok = PM3_SUCCESS; bool read_ok = false; uint8_t keyType = ICLASS_DEBIT_KEYTYPE; if (use_credit_key) { @@ -3188,52 +3190,58 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { // clear trace log SendCommandNG(CMD_BUFF_CLEAR, NULL, 0); - PrintAndLogEx(INFO, "---------------------------------------"); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(INFO, "Press " _GREEN_("") " to exit"); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(INFO, "--------------- " _CYAN_("start") " -----------------\n"); - // Main loop - while ((tearoff_start <= tearoff_end) && (read_ok == false)) { + if (run_on_device) { - if (kbd_enter_pressed()) { - PrintAndLogEx(WARNING, "\naborted via keyboard."); - isok = PM3_EOPABORTED; - goto out; - } + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "---------------------------------------"); + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " to abort"); + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--------------- " _CYAN_("start") " -----------------\n"); - // set tear off trigger - clearCommandBuffer(); - tearoff_params_t params = { - .delay_us = (tearoff_start & 0xFFFF), - .on = true, - .off = false + iclass_tearblock_req_t payload = { + .req.use_raw = rawkey, + .req.use_elite = elite, + .req.use_credit_key = use_credit_key, + .req.use_replay = use_replay, + .req.blockno = blockno, + .req.send_reply = true, + .req.do_auth = auth, + .req.shallow_mod = shallow_mod, + .tear_start = tearoff_start, + .tear_end = tearoff_end, + .increment = tearoff_increment, + .tear_loop = tearoff_loop, }; + memcpy(payload.req.key, key, PICOPASS_BLOCK_SIZE); + memcpy(payload.data, data, sizeof(payload.data)); + memcpy(payload.mac, mac, sizeof(payload.mac)); - int res = handle_tearoff(¶ms, verbose); - if (res != PM3_SUCCESS) { - PrintAndLogEx(WARNING, "Failed to configure tear off"); - isok = PM3_ESOFT; - goto out; + clearCommandBuffer(); + SendCommandNG(CMD_HF_ICLASS_TEARBL, (uint8_t *)&payload, sizeof(payload)); + + if (WaitForResponseTimeout(CMD_HF_ICLASS_TEARBL, &resp, 1000)) { + if (resp.status == PM3_EOPABORTED) { + PrintAndLogEx(DEBUG, "Button pressed, user aborted"); + isok = resp.status; + } } - if (tearoff_loop > 1) { - PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us - "_YELLOW_("%3u")" loops", params.delay_us, (tearoff_end & 0xFFFF), loop_count + 1); - } else { - PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us", params.delay_us, (tearoff_end & 0xFFFF)); - } + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "Done!"); + PrintAndLogEx(NORMAL, ""); + clearCommandBuffer(); + return isok; - // write block - don't check the return value. As a tear-off occurred, the write failed. - // when tear off is enabled, the return code will always be PM3_ETEAROFF - iclass_write_block(blockno, data, mac, key, use_credit_key, elite, rawkey, use_replay, false, auth, shallow_mod); + } else { - // read the data back - uint8_t data_read[8] = {0}; - first_read = false; - reread = false; - bool decrease = false; - int readcount = 0; - while (first_read == false) { + PrintAndLogEx(INFO, "---------------------------------------"); + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "Press " _GREEN_("") " to exit"); + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "--------------- " _CYAN_("start") " -----------------\n"); + // Main loop + while ((tearoff_start <= tearoff_end) && (read_ok == false)) { if (kbd_enter_pressed()) { PrintAndLogEx(WARNING, "\naborted via keyboard."); @@ -3241,159 +3249,202 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { goto out; } - // skip authentication for config and e-purse blocks (1,2) - if (blockno < 3) { - read_auth = false; - } + // set tear off trigger + clearCommandBuffer(); + tearoff_params_t params = { + .delay_us = (tearoff_start & 0xFFFF), + .on = true, + .off = false + }; - res = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read, false); - if (res == PM3_SUCCESS && !reread) { - if (memcmp(data_read, zeros, 8) == 0) { - reread = true; - } else { - first_read = true; - reread = false; - } - } else if (res == PM3_SUCCESS && reread) { - first_read = true; - reread = false; - } else if (res != PM3_SUCCESS) { - decrease = true; - } - - readcount++; - } - - if (readcount > 1) { - PrintAndLogEx(WARNING, "\nRead block failed "_RED_("%d") " times", readcount); - } - - // if there was an error reading repeat the tearoff with the same delay - if (decrease && (tearoff_start > tearoff_increment) && (tearoff_start >= tearoff_original_start)) { - tearoff_start -= tearoff_increment; - if (verbose) { - PrintAndLogEx(INFO, " -> Read failed, retearing with "_CYAN_("%u")" us", tearoff_start); - } - } - - bool tear_success = true; - bool expected_values = true; - - if (memcmp(data_read, data, 8) != 0) { - tear_success = false; - } - - if ((tear_success == false) && (memcmp(data_read, zeros, 8) != 0) && (memcmp(data_read, data_read_orig, 8) != 0)) { - - // tearoff succeeded (partially) - - expected_values = false; - - if (memcmp(data_read, ff_data, 8) == 0 && memcmp(data_read_orig, ff_data, 8) != 0) { - erase_phase = true; - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(SUCCESS, _CYAN_("Erase phase hit... ALL ONES")); - iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: "); - } else { - - if (erase_phase) { - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(SUCCESS, _MAGENTA_("Tearing! Write phase (post erase)")); - iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: "); - } else { - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(SUCCESS, _CYAN_("Tearing! unknown phase")); - iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: "); - } - } - - bool goto_out = false; - if (blockno == 2) { - if (memcmp(data_read, ff_data, 8) == 0 && memcmp(data_read_orig, ff_data, 8) != 0) { - PrintAndLogEx(SUCCESS, "E-purse has been teared ( %s )", _GREEN_("ok")); - PrintAndLogEx(HINT, "Hint: try `hf iclass creditepurse -d FEFFFEFF --ki 1`"); - PrintAndLogEx(HINT, "Hint: try `hf iclass wrbl -d 'FFFFFFFF FFFF FEFF' --blk 2 --ki 1 --credit`"); - isok = PM3_SUCCESS; - goto_out = true; - } - } - - if (blockno == 1) { - if (data_read[0] != data_read_orig[0]) { - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(SUCCESS, "Application limit changed, from "_YELLOW_("%u")" to "_YELLOW_("%u"), data_read_orig[0], data_read[0]); - isok = PM3_SUCCESS; - goto_out = true; - } - - if (data_read[7] != data_read_orig[7]) { - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(SUCCESS, "Fuse changed, from "_YELLOW_("%02x")" to "_YELLOW_("%02x"), data_read_orig[7], data_read[7]); - - const char *flag_names[8] = { - "RA", - "Fprod0", - "Fprod1", - "Crypt0 (*1)", - "Crypt1 (*0)", - "Coding0", - "Coding1", - "Fpers (*1)" - }; - PrintAndLogEx(INFO, _YELLOW_("%-10s %-10s %-10s"), "Fuse", "Original", "Changed"); - PrintAndLogEx(INFO, "---------------------------------------"); - for (int i = 7; i >= 0; --i) { - int bit1 = (data_read_orig[7] >> i) & 1; - int bit2 = (data_read[7] >> i) & 1; - PrintAndLogEx(INFO, "%-11s %-10d %-10d", flag_names[i], bit1, bit2); - } - - isok = PM3_SUCCESS; - goto_out = true; - } - - // if more OTP bits set.. - if (data_read[1] > data_read_orig[1] || - data_read[2] > data_read_orig[2]) { - PrintAndLogEx(SUCCESS, "More OTP bits got set!!!"); - - data_read[7] = 0xBC; - res = iclass_write_block(blockno, data_read, mac, key, use_credit_key, elite, rawkey, use_replay, verbose, auth, shallow_mod); - if (res != PM3_SUCCESS) { - PrintAndLogEx(INFO, "Stabilize the bits ( "_RED_("failed") " )" ); - } - - isok = PM3_SUCCESS; - goto_out = true; - } - } - - if (goto_out) { + int res = handle_tearoff(¶ms, verbose); + if (res != PM3_SUCCESS) { + PrintAndLogEx(WARNING, "Failed to configure tear off"); + isok = PM3_ESOFT; goto out; } - } - if (tear_success) { // tearoff succeeded with expected values + if (tearoff_loop > 1) { + PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us - "_YELLOW_("%3u")" iter", params.delay_us, (tearoff_end & 0xFFFF), loop_count + 1); + } else { + PrintAndLogEx(INPLACE, " Tear off delay "_YELLOW_("%u")" / "_YELLOW_("%d")" us", params.delay_us, (tearoff_end & 0xFFFF)); + } - read_ok = true; - tear_success = true; + // write block - don't check the return value. As a tear-off occurred, the write failed. + // when tear off is enabled, the return code will always be PM3_ETEAROFF + iclass_write_block(blockno, data, mac, key, use_credit_key, elite, rawkey, use_replay, false, auth, shallow_mod); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(INFO, "Read: " _GREEN_("%s") " %s" - , sprint_hex_inrow(data_read, sizeof(data_read)), - (expected_values) ? _GREEN_(" -> Expected values!") : "" - ); - } + // read the data back + uint8_t data_read[8] = {0}; + first_read = false; + reread = false; + bool decrease = false; + int readcount = 0; + while (first_read == false) { - loop_count++; + if (kbd_enter_pressed()) { + PrintAndLogEx(WARNING, "\naborted via keyboard."); + isok = PM3_EOPABORTED; + goto out; + } - if (loop_count == tearoff_loop) { - tearoff_start += tearoff_increment; - loop_count = 0; - } + // skip authentication for config and e-purse blocks (1,2) + if (blockno < 3) { + read_auth = false; + } - if (tearoff_sleep) { - msleep(tearoff_sleep); + res = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read, false); + if (res == PM3_SUCCESS && !reread) { + if (memcmp(data_read, zeros, 8) == 0) { + reread = true; + } else { + first_read = true; + reread = false; + } + } else if (res == PM3_SUCCESS && reread) { + first_read = true; + reread = false; + } else if (res != PM3_SUCCESS) { + decrease = true; + } + + readcount++; + } + + if (readcount > 1) { + PrintAndLogEx(WARNING, "\nRead block failed "_RED_("%d") " times", readcount); + } + + // if there was an error reading repeat the tearoff with the same delay + if (decrease && (tearoff_start > tearoff_increment) && (tearoff_start >= tearoff_original_start)) { + tearoff_start -= tearoff_increment; + if (verbose) { + PrintAndLogEx(INFO, " -> Read failed, retearing with "_CYAN_("%u")" us", tearoff_start); + } + } + + bool tear_success = true; + bool expected_values = true; + + if (memcmp(data_read, data, 8) != 0) { + tear_success = false; + } + + if ((tear_success == false) && + (memcmp(data_read, zeros, 8) != 0) && + (memcmp(data_read, data_read_orig, 8) != 0)) { + + // tearoff succeeded (partially) + + expected_values = false; + + if (memcmp(data_read, ff_data, 8) == 0 && + memcmp(data_read_orig, ff_data, 8) != 0) { + erase_phase = true; + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, _CYAN_("Erase phase hit... ALL ONES")); + iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: "); + } else { + + if (erase_phase) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, _MAGENTA_("Tearing! Write phase (post erase)")); + iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: "); + } else { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, _CYAN_("Tearing! unknown phase")); + iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: "); + } + } + + bool goto_out = false; + if (blockno == 2) { + if (memcmp(data_read, ff_data, 8) == 0 && memcmp(data_read_orig, ff_data, 8) != 0) { + PrintAndLogEx(SUCCESS, "E-purse has been teared ( %s )", _GREEN_("ok")); + PrintAndLogEx(HINT, "Hint: try `hf iclass creditepurse -d FEFFFEFF --ki 1`"); + PrintAndLogEx(HINT, "Hint: try `hf iclass wrbl -d 'FFFFFFFF FFFF FEFF' --blk 2 --ki 1 --credit`"); + isok = PM3_SUCCESS; + goto_out = true; + } + } + + if (blockno == 1) { + if (data_read[0] != data_read_orig[0]) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, "Application limit changed, from "_YELLOW_("%u")" to "_YELLOW_("%u"), data_read_orig[0], data_read[0]); + isok = PM3_SUCCESS; + goto_out = true; + } + + if (data_read[7] != data_read_orig[7]) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, "Fuse changed, from "_YELLOW_("%02x")" to "_YELLOW_("%02x"), data_read_orig[7], data_read[7]); + + const char *flag_names[8] = { + "RA", + "Fprod0", + "Fprod1", + "Crypt0 (*1)", + "Crypt1 (*0)", + "Coding0", + "Coding1", + "Fpers (*1)" + }; + PrintAndLogEx(INFO, _YELLOW_("%-10s %-10s %-10s"), "Fuse", "Original", "Changed"); + PrintAndLogEx(INFO, "---------------------------------------"); + for (int i = 7; i >= 0; --i) { + int bit1 = (data_read_orig[7] >> i) & 1; + int bit2 = (data_read[7] >> i) & 1; + PrintAndLogEx(INFO, "%-11s %-10d %-10d", flag_names[i], bit1, bit2); + } + + isok = PM3_SUCCESS; + goto_out = true; + } + + // if more OTP bits set.. + if (data_read[1] > data_read_orig[1] || + data_read[2] > data_read_orig[2]) { + PrintAndLogEx(SUCCESS, "More OTP bits got set!!!"); + + data_read[7] = 0xBC; + res = iclass_write_block(blockno, data_read, mac, key, use_credit_key, elite, rawkey, use_replay, verbose, auth, shallow_mod); + if (res != PM3_SUCCESS) { + PrintAndLogEx(INFO, "Stabilize the bits ( "_RED_("failed") " )"); + } + + isok = PM3_SUCCESS; + goto_out = true; + } + } + + if (goto_out) { + goto out; + } + } + + if (tear_success) { // tearoff succeeded with expected values + + read_ok = true; + tear_success = true; + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "Read: " _GREEN_("%s") " %s" + , sprint_hex_inrow(data_read, sizeof(data_read)), + (expected_values) ? _GREEN_(" -> Expected values!") : "" + ); + } + + loop_count++; + + if (loop_count == tearoff_loop) { + tearoff_start += tearoff_increment; + loop_count = 0; + } + + if (tearoff_sleep) { + msleep(tearoff_sleep); + } } } diff --git a/include/iclass_cmd.h b/include/iclass_cmd.h index e4761584f..eab734ac7 100644 --- a/include/iclass_cmd.h +++ b/include/iclass_cmd.h @@ -87,6 +87,17 @@ typedef struct { uint8_t mac[4]; } PACKED iclass_writeblock_req_t; +// iCLASS tearoff block request data structure +typedef struct { + iclass_auth_req_t req; + uint8_t data[8]; + uint8_t mac[4]; + int tear_start; + int tear_end; + int increment; + int tear_loop; +} PACKED iclass_tearblock_req_t; + // iCLASS write block request data structure typedef struct { iclass_auth_req_t req; diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index abd8e1e5a..82623f2dd 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -668,6 +668,7 @@ typedef struct { #define CMD_HF_ICLASS_RESTORE 0x039B #define CMD_HF_ICLASS_CREDIT_EPURSE 0x039C #define CMD_HF_ICLASS_RECOVER 0x039D +#define CMD_HF_ICLASS_TEARBL 0x039E // For ISO1092 / FeliCa From 83837699e1bb3a4f7e2ace1b8982db31892eb95d Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Fri, 30 May 2025 01:38:55 +0200 Subject: [PATCH 22/31] text , but the ultralight detect in 14a info is a bit confusing. This PR was intended to make it more clear. We still need to improve the text output --- client/src/cmdhf14a.c | 156 +++++++++++++++++++++++++++++++++++------- client/src/cmdhfmf.c | 5 +- client/src/cmdhfmfu.c | 8 +-- doc/commands.json | 13 ++-- 4 files changed, 146 insertions(+), 36 deletions(-) diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index 6e30b0db9..479bef132 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -1827,14 +1827,17 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta const char *product_type_str = ""; const char *major_product_version_str = ""; const char *storage_size_str = ""; + if (version_hw_available) { + switch (version_hw->product_type & 0x0F) { - case 0x1: + case 0x1: { product_type_str = "MIFARE DESFire"; // special cases, override product_type_str when needed if (version_hw->product_type == 0x91) { product_type_str = "Apple Wallet DESFire Applet"; } + // general rule switch (version_hw->major_product_version & 0x0F) { case 0x01: @@ -1847,6 +1850,7 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta major_product_version_str = "EV3"; break; } + // special cases, override major_product_version_str when needed switch (version_hw->major_product_version) { case 0x00: @@ -1860,7 +1864,8 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta break; } break; - case 0x2: + } + case 0x2: { product_type_str = "MIFARE Plus"; switch (version_hw->major_product_version) { case 0x11: @@ -1870,15 +1875,23 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta major_product_version_str = "EV2"; break; default: - major_product_version_str = "Unknown"; + major_product_version_str = "n/a"; } break; - case 0x3: + } + case 0x3: { product_type_str = "MIFARE Ultralight"; switch (version_hw->major_product_version) { - case 0x01: + case 0x01: { major_product_version_str = "EV1"; + + if (version_hw->storage_size == 0x0B) { + storage_size_str = "48b"; + } else if (version_hw->storage_size == 0x0E) { + storage_size_str = "128b"; + } break; + } case 0x02: major_product_version_str = "Nano"; break; @@ -1886,10 +1899,11 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta major_product_version_str = "AES"; break; default: - major_product_version_str = "Unknown"; + major_product_version_str = "n/a"; } break; - case 0x4: + } + case 0x4: { product_type_str = "NTAG"; switch (version_hw->major_product_version) { case 0x01: @@ -1909,37 +1923,66 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta major_product_version_str = "4xx"; break; default: - major_product_version_str = "Unknown"; + major_product_version_str = "n/a"; } break; - case 0x7: + } + case 0x7: { product_type_str = "NTAG I2C"; break; - case 0x8: + } + case 0x8: { product_type_str = "MIFARE DESFire Light"; break; - case 0x9: + } + case 0x9: { product_type_str = "MIFARE Hospitality"; switch (version_hw->major_product_version) { case 0x01: major_product_version_str = "AES"; break; default: - major_product_version_str = "Unknown"; + major_product_version_str = "n/a"; } break; - default: + } + default: { product_type_str = "Unknown NXP tag"; + break; + } } - uint32_t size = 1 << (version_hw->storage_size >> 1); - static char size_str[16]; - if (size < 1024) { - snprintf(size_str, sizeof(size_str), "%s%uB", (version_hw->storage_size & 0x01) == 0 ? "" : "~", size); - } else { - snprintf(size_str, sizeof(size_str), "%s%uK", (version_hw->storage_size & 0x01) == 0 ? "" : "~", size / 1024); + + if (storage_size_str == NULL) { + static char size_str[16]; + uint16_t usize = 1 << ((version_hw->storage_size >> 1) + 1); + uint16_t lsize = 1 << (version_hw->storage_size >> 1); + + // is LSB set? + if ((version_hw->storage_size & 0x01) == 1) { + + // if set, its a range between upper size and lower size + + if (lsize < 1024) { + snprintf(size_str, sizeof(size_str), "%u - %uB", usize, lsize); + } else { + snprintf(size_str, sizeof(size_str), "%u - %uK", (usize / 1024), (lsize / 1024)); + } + + } else { + + // if not set, it's lower size + if (lsize < 1024) { + snprintf(size_str, sizeof(size_str), "%uB", lsize); + } else { + snprintf(size_str, sizeof(size_str), "%uK", lsize / 1024); + } + } + + storage_size_str = size_str; + } - storage_size_str = size_str; } + char tag_info[128]; if ((sak & 0x44) == 0x40) { @@ -1951,25 +1994,36 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta } type |= MTISO18092; } + if ((sak & 0x02) == 0x00) { // SAK b2=0 + if ((sak & 0x08) == 0x08) { // SAK b2=0 b4=1 + if ((sak & 0x10) == 0x10) { // SAK b2=0 b4=1 b5=1 + if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=1 b5=1 b1=1, SAK=0x19 printTag("MIFARE Classic 2K"); type |= MTCLASSIC; } else { // SAK b2=0 b4=1 b5=1 b1=0 + if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=1 b5=1 b1=0 b6=1, SAK=0x38 printTag("SmartMX with MIFARE Classic 4K"); type |= MTCLASSIC; } else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 + if (select_status == 4) { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS + if (version_hw_available) { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS GetVersion snprintf(tag_info, sizeof(tag_info), "%s %s %s in SL1", product_type_str, major_product_version_str, storage_size_str); printTag(tag_info); type |= MTPLUS; + } else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 ATS No_GetVersion + if (ats_hist_len > 0) { + if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 0)) { + if (memcmp(ats_hist + 4, "\x00\x35\xC7", 3) == 0) { printTag("MIFARE Plus S 4K in SL1"); } else if (memcmp(ats_hist + 4, "\x01\xBC\xD6", 3) == 0) { @@ -1988,7 +2042,9 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta } } } + } else { // SAK b2=0 b4=1 b5=1 b1=0 b6=0 no_ATS, SAK=0x18 + if ((atqa & 0x0040) == 0x0040) { printTag("MIFARE Classic 4K CL2"); } else { @@ -2000,28 +2056,38 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta } } else { // SAK b2=0 b4=1 b5=0 + if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=1 b5=0 b1=1, SAK=0x09 + if ((atqa & 0x0040) == 0x0040) { printTag("MIFARE Mini 0.3K CL2"); } else { printTag("MIFARE Mini 0.3K"); } type |= MTMINI; + } else { // SAK b2=0 b4=1 b5=0 b1=0 + if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=1 b5=0 b1=0 b6=1, SAK=0x28 printTag("SmartMX with MIFARE Classic 1K"); printTag("FM1208-10 with MIFARE Classic 1K"); printTag("FM1216-137 with MIFARE Classic 1K"); type |= MTCLASSIC; } else { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 + if (select_status == 4) { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS + if (version_hw_available) { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS GetVersion snprintf(tag_info, sizeof(tag_info), "%s %s %s in SL1", product_type_str, major_product_version_str, storage_size_str); printTag(tag_info); type |= MTPLUS; + } else { // SAK b2=0 b4=1 b5=0 b1=0 b6=0 ATS No_GetVersion + if (ats_hist_len > 0) { + if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 0)) { + if (memcmp(ats_hist + 4, "\x00\x35\xC7", 3) == 0) { printTag("MIFARE Plus S 2K in SL1"); } else if (memcmp(ats_hist + 4, "\x01\xBC\xD6", 3) == 0) { @@ -2030,7 +2096,9 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta printTag("Unrecognized MIFARE Plus??"); } type |= MTPLUS; + } else if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x21\x30", 4) == 0)) { + if (memcmp(ats_hist + 4, "\x00\xF6\xD1", 3) == 0) { printTag("MIFARE Plus SE 1K 17pF"); } else if (memcmp(ats_hist + 4, "\x10\xF6\xD1", 3) == 0) { @@ -2039,7 +2107,9 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta printTag("Unrecognized MIFARE Plus SE??"); } type |= MTPLUS; + } else { + if ((atqa & 0x0040) == 0x0040) { printTag("MIFARE Classic 1K CL2 with ATS!"); } else { @@ -2060,8 +2130,11 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta } } } + } else { // SAK b2=0 b4=0 + if ((sak & 0x10) == 0x10) { // SAK b2=0 b4=0 b5=1 + if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=0 b5=1 b1=1, SAK=0x11 printTag("MIFARE Plus 4K in SL2"); type |= MTPLUS; @@ -2069,33 +2142,48 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta printTag("MIFARE Plus 2K in SL2"); type |= MTPLUS; } + } else { // SAK b2=0 b4=0 b5=0 + if ((sak & 0x01) == 0x01) { // SAK b2=0 b4=0 b5=0 b1=1 printTag("TNP3xxx (TagNPlay, Activision Game Appliance)"); type |= MTCLASSIC; + } else { // SAK b2=0 b4=0 b5=0 b1=0 + if ((sak & 0x20) == 0x20) { // SAK b2=0 b4=0 b5=0 b1=0 b6=1, SAK=0x20 + if (select_status == 1) { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS + if (version_hw_available) { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS GetVersion + if ((version_hw->product_type & 0x7F) == 0x02) { snprintf(tag_info, sizeof(tag_info), "%s %s %s in SL0/SL3", product_type_str, major_product_version_str, storage_size_str); type |= MTPLUS; + } else if (((version_hw->product_type & 0x7F) == 0x01) || (version_hw->product_type == 0x08) || (version_hw->product_type == 0x91)) { snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str); type |= MTDESFIRE; + } else if (version_hw->product_type == 0x04) { snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str); type |= (MTDESFIRE | MT424); + } else { snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str); } printTag(tag_info); + } else { // SAK b2=0 b4=0 b5=0 b1=0 b6=1 ATS No GetVersion + if (ats_hist_len > 0) { + if ((ats_hist_len == 9) && (memcmp(ats_hist, "\xC1\x05\x2F\x2F", 4) == 0)) { + if (memcmp(ats_hist + 4, "\x00\x35\xC7", 3) == 0) { + if ((atqa & 0xFF0F) == 0x0004) { printTag("MIFARE Plus S 2K in SL0/SL3"); } else if ((atqa & 0xFF0F) == 0x0002) { @@ -2103,21 +2191,28 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta } else { printTag("Unrecognized MIFARE Plus??"); } + } else if (memcmp(ats_hist + 4, "\x01\xBC\xD6", 3) == 0) { printTag("MIFARE Plus X 2K/4K in SL0/SL3"); + } else if (memcmp(ats_hist + 4, "\x00\xF6\xD1", 3) == 0) { printTag("MIFARE Plus SE 1K 17pF"); + } else if (memcmp(ats_hist + 4, "\x10\xF6\xD1", 3) == 0) { printTag("MIFARE Plus SE 1K 70pF"); + } else { - printTag("Unrecognized MIFARE Plus??"); + printTag("Unknown MIFARE Plus"); } type |= MTPLUS; + } else { + if ((atqa == 0x0001) || (atqa == 0x0004)) { printTag("HID SEOS (smartmx / javacard)"); type |= HID_SEOS; } + if (atqa == 0x0004) { printTag("EMV"); type |= MTEMV; @@ -2128,11 +2223,15 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta } else { printTag("Unknown tag claims to support RATS in SAK but does not..."); } + } else { // SAK b2=0 b4=0 b5=0 b1=0 b6=0, SAK=0x00 + if (version_hw_available) { // SAK b2=0 b4=0 b5=0 b1=0 b6=0 GetVersion snprintf(tag_info, sizeof(tag_info), "%s %s %s", product_type_str, major_product_version_str, storage_size_str); printTag(tag_info); + } else { // SAK b2=0 b4=0 b5=0 b1=0 b6=0 No_GetVersion + int status = mfuc_test_authentication_support(); if (status == PM3_SUCCESS) { // TODO: read page 2/3, then ?? @@ -2141,6 +2240,7 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta } else { printTag("MIFARE Ultralight"); } + } type |= MTULTRALIGHT; } @@ -2148,20 +2248,25 @@ static int detect_nxp_card_print(uint8_t sak, uint16_t atqa, uint64_t select_sta } } } else { // SAK b2=1 + if (sak == 0x0A) { + if (atqa == 0x0003) { // Uses Shanghai algo printTag("FM11RF005SH (FUDAN Shanghai Metro)"); type |= MTFUDAN; + } else if (atqa == 0x0005) { printTag("FM11RF005M (FUDAN ISO14443A w Crypto-1 algo)"); type |= MTFUDAN; } + } else if (sak == 0x53) { printTag("FM11RF08SH (FUDAN)"); type |= MTFUDAN; } } + if (type == MTNONE) { PrintAndLogEx(WARNING, " failed to fingerprint"); } @@ -2803,8 +2908,9 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { } DropField(); - if (verbose == false && found) + if (verbose == false && found) { PrintAndLogEx(INFO, "----------------------------------------------------"); + } } } @@ -2814,7 +2920,9 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { PrintAndLogEx(INFO, "proprietary iso18092 card found"); } else { - PrintAndLogEx(INFO, "proprietary non iso14443-4 card found, RATS not supported"); + PrintAndLogEx(INFO, ""); + PrintAndLogEx(INFO, "Proprietary non iso14443-4 card found"); + PrintAndLogEx(INFO, "RATS not supported"); if ((card.sak & 0x20) == 0x20) { PrintAndLogEx(INFO, "--> SAK incorrectly claims that card supports RATS <--"); } @@ -2829,7 +2937,7 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { return PM3_EFAILED; } - PrintAndLogEx(INFO, ""); +// PrintAndLogEx(INFO, ""); uint16_t isMagic = 0; diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 23facd528..9963d8a68 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -10167,7 +10167,8 @@ static int CmdHF14AMfInfo(const char *Cmd) { && card.sak == 0x08 && memcmp(blockdata + 5, "\x88\x04\x00\x45", 4) == 0) { PrintAndLogEx(SUCCESS, "NXP MF1ICS5004"); } else if (fKeyType == MF_KEY_BD) { - PrintAndLogEx(SUCCESS, _RED_("Unknown card with backdoor, please report details!")); + PrintAndLogEx(SUCCESS, _RED_("Unknown card with backdoor")); + PrintAndLogEx(INFO, "Please report details!"); } else // other cards if (card.sak == 0x08 && memcmp(blockdata + 5, "\x88\x04\x00\x46", 4) == 0) { @@ -10181,7 +10182,7 @@ static int CmdHF14AMfInfo(const char *Cmd) { } else if (card.sak == 0x08 && memcmp(blockdata + 5, "\x88\x04\x00\xc0", 4) == 0) { PrintAndLogEx(SUCCESS, "NXP MF1ICS5035"); } else { - PrintAndLogEx(SUCCESS, "unknown"); + PrintAndLogEx(SUCCESS, "n/a"); } if (keycnt > 1 && e_sector != NULL && e_sector[1].foundKey[MF_KEY_A] && (e_sector[1].Key[MF_KEY_A] == 0x2A2C13CC242A)) { diff --git a/client/src/cmdhfmfu.c b/client/src/cmdhfmfu.c index 0c3fc2e7e..bd38eafcc 100644 --- a/client/src/cmdhfmfu.c +++ b/client/src/cmdhfmfu.c @@ -299,7 +299,7 @@ static const char *getUlev1CardSizeStr(uint8_t fsize) { // is LSB set? if (fsize & 1) - snprintf(buf, sizeof(buf), "%02X, (%u <-> %u bytes)", fsize, usize, lsize); + snprintf(buf, sizeof(buf), "%02X, (%u - %u bytes)", fsize, usize, lsize); else snprintf(buf, sizeof(buf), "%02X, (%u bytes)", fsize, lsize); return buf; @@ -3925,17 +3925,17 @@ static int CmdHF14AMfUAESAuth(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf mfu aesauth", "Tests AES key on Mifare Ultralight AES tags.\n" - "If key is not specified, null key will be tried.\n" + "If no key is specified, null key will be tried.\n" "Key index 0: DataProtKey (default)\n" "Key index 1: UIDRetrKey\n" "Key index 2: OriginalityKey\n", "hf mfu aesauth\n" - "hf mfu aesauth --key <32 hex chars> --index <0..2>" + "hf mfu aesauth --key <16 hex bytes> --index <0..2>" ); void *argtable[] = { arg_param_begin, - arg_str0(NULL, "key", "", "Authentication key (16 bytes in hex)"), + arg_str0(NULL, "key", "", "AES key (16 hex bytes)"), arg_int0("i", "index", "<0..2>", "Key index, default: 0"), arg_lit0("k", NULL, "Keep field on (only if a key is provided)"), arg_param_end diff --git a/doc/commands.json b/doc/commands.json index eb9cdcee3..81e45bcd0 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -3742,9 +3742,10 @@ "-i tearoff delay increment (in us) - default 10", "-e tearoff delay end (in us) must be a higher value than the start delay", "--loop number of times to loop per tearoff time", - "--sleep Sleep between each tear" + "--sleep Sleep between each tear", + "--arm Runs the commands on device side and tries to stabilize tears" ], - "usage": "hf iclass tear [-hv] [-k ] [--ki ] --blk -d [-m ] [--credit] [--elite] [--raw] [--nr] [--shallow] -s [-i ] [-e ] [--loop ] [--sleep ]" + "usage": "hf iclass tear [-hv] [-k ] [--ki ] --blk -d [-m ] [--credit] [--elite] [--raw] [--nr] [--shallow] -s [-i ] [-e ] [--loop ] [--sleep ] [--arm]" }, "hf iclass unhash": { "command": "hf iclass unhash", @@ -7078,15 +7079,15 @@ }, "hf mfu aesauth": { "command": "hf mfu aesauth", - "description": "Tests AES key on Mifare Ultralight AES tags. If key is not specified, null key will be tried. Key index 0: DataProtKey (default) Key index 1: UIDRetrKey Key index 2: OriginalityKey", + "description": "Tests AES key on Mifare Ultralight AES tags. If no key is specified, null key will be tried. Key index 0: DataProtKey (default) Key index 1: UIDRetrKey Key index 2: OriginalityKey", "notes": [ "hf mfu aesauth", - "hf mfu aesauth --key <32 hex chars> --index <0..2>" + "hf mfu aesauth --key <16 hex bytes> --index <0..2>" ], "offline": false, "options": [ "-h, --help This help", - "--key Authentication key (16 bytes in hex)", + "--key AES key (16 hex bytes)", "-i, --index <0..2> Key index, default: 0", "-k Keep field on (only if a key is provided)" ], @@ -13371,6 +13372,6 @@ "metadata": { "commands_extracted": 768, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-05-28T18:50:22" + "extracted_on": "2025-05-29T23:30:20" } } From 04cfe2a43e0928e47b908bf2e712dcd26d412005 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Fri, 30 May 2025 12:36:42 +0800 Subject: [PATCH 23/31] Modified iclass recover operations 1- Renamed legreclookup to legbrute to be in line with the command name 2- Updated estimate values with speed increase gains 3- Improved some if statements readability in iclass.c and added start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; to increase speed by ~8% (1.86 loops per second to 2.01 loops per second = ~560 more loops per hour). Tried disabling some arm communications/comments but the speed increase was negligible (~1 sec / 1000 updates). --- CHANGELOG.md | 3 ++- armsrc/iclass.c | 32 ++++++++++++++++++++------------ client/src/cmdhficlass.c | 6 +++--- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00233936b..f934ad183 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Changed `hf iclass legrec` - code optimizations gaining a ~8% speed increase (@antiklesys) - Modified `hf iclass tear` - now has a device side implementation also. @antiklesys (@iceman1001) - Changed `hf iclass info` - now uses CSN values based checks (@antiklesys) - Changed `hf iclass dump` - now uses default AA1 key when called without a key or key index (@iceman1001) @@ -10,7 +11,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Changed `hw tearoff` - the device side message is now debug log controlled (@iceman1001) - Changed `pm3.sh` - Serial ports enumeration on Proxspace3.xx / MINGW environments, now using powershell.exe since wmic is deprecated (@iceman1001) - Fixed and updated `hf iclass trbl` to correctly use the credit key when passed and show partial tearoff results (@antiklesys) -- Fixed `hf iclass legbrute` was not correctly parsin the index value +- Fixed `hf iclass legbrute` was not correctly parsing the index value - Fixed `hf mf ekeyprn` - failed to download emulator memory due to wrong size calculation (@iceman1001) - Fixed `hf mf fchk --mem` to actually use flash dict (@doegox) - Fixed `make install` on OSX thanks DaveItsLong (@doegox) diff --git a/armsrc/iclass.c b/armsrc/iclass.c index 38627ea70..bcf5df684 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -2708,6 +2708,7 @@ void iClass_Recover(iclass_recover_req_t *msg) { bool priv_esc = false; while (!priv_esc) { //The privilege escalation is done with a readcheck and not just a normal read! + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; iclass_send_as_reader(read_check_cc, sizeof(read_check_cc), &start_time, &eof_time, shallow_mod); // expect a 8-byte response here res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len); @@ -2747,27 +2748,29 @@ void iClass_Recover(iclass_recover_req_t *msg) { Dbhexdump(8, genkeyblock, false); } //Reset cypher state + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod); - res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len); + //res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len); //try to authenticate with the original mac to verify the write happened memcpy(msg->req.key, original_mac, 8); + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1); if (msg->test) { - if (res != true) { - DbpString(_RED_("*** CARD EPURSE IS SILENT! RISK OF BRICKING! DO NOT EXECUTE KEY UPDATES! SCAN IT ON READER FOR EPURSE UPDATE, COLLECT NEW TRACES AND TRY AGAIN! ***")); - goto out; - } else { + if (res) { DbpString(_GREEN_("*** CARD EPURSE IS LOUD! OK TO ATTEMPT KEY RETRIEVAL! RUN AGAIN WITH -notest ***")); completed = true; goto out; + } else { + DbpString(_RED_("*** CARD EPURSE IS SILENT! RISK OF BRICKING! DO NOT EXECUTE KEY UPDATES! SCAN IT ON READER FOR EPURSE UPDATE, COLLECT NEW TRACES AND TRY AGAIN! ***")); + goto out; } } else { - if (res != true) { - DbpString("Write Operation : "_GREEN_("VERIFIED! Card Key Updated!")); - written = true; - } else { + if (res) { DbpString("Write Operation : "_RED_("FAILED! Card Key is the Original. Retrying...")); write_error = true; + } else { + DbpString("Write Operation : "_GREEN_("VERIFIED! Card Key Updated!")); + written = true; } } } @@ -2775,10 +2778,12 @@ void iClass_Recover(iclass_recover_req_t *msg) { if (!write_error) { //Step6 Perform 8 authentication attempts + 1 to verify if we found the weak key for (int i = 0; i < 8 ; ++i) { + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod); - res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len); + //res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len); //need to craft the authentication payload accordingly memcpy(msg->req.key, iclass_mac_table[i], 8); + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1); //mac1 here shouldn't matter if (res == true) { bits_found = i; @@ -2794,9 +2799,10 @@ void iClass_Recover(iclass_recover_req_t *msg) { uint8_t revert_retries = 0; while (!reverted) { //Regain privilege escalation with a readcheck + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; iclass_send_as_reader(read_check_cc, sizeof(read_check_cc), &start_time, &eof_time, shallow_mod); // TODO: check result - GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len); + //GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len); DbpString(_YELLOW_("Attempting to restore the original key. ")); if (iclass_writeblock_ext(blockno, genkeyblock, mac2, use_mac, shallow_mod)) { @@ -2806,12 +2812,14 @@ void iClass_Recover(iclass_recover_req_t *msg) { } DbpString(_YELLOW_("Verifying Key Restore...")); //Do a readcheck first to reset the cypher state + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod); // TODO: check result - GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len); + //GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len); //need to craft the authentication payload accordingly memcpy(msg->req.key, original_mac, 8); + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1); if (res == true) { DbpString("Restore of Original Key "_GREEN_("VERIFIED! Card is usable again.")); diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 86dff7e43..b127cb5bf 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -4578,7 +4578,7 @@ void generate_key_block_inverted(const uint8_t *startingKey, uint64_t index, uin } } -static int CmdHFiClassLegRecLookUp(const char *Cmd) { +static int CmdHFiClassLegBrute(const char *Cmd) { //Standalone Command Start CLIParserContext *ctx; @@ -4805,7 +4805,7 @@ static int CmdHFiClassLegacyRecSim(void) { PrintAndLogEx(SUCCESS, "Original Key: " _GREEN_("%s"), sprint_hex(original_key, sizeof(original_key))); PrintAndLogEx(SUCCESS, "Weak Key: " _GREEN_("%s"), sprint_hex(key, sizeof(key))); PrintAndLogEx(SUCCESS, "Key Updates Required to Weak Key: " _GREEN_("%d"), index); - PrintAndLogEx(SUCCESS, "Estimated Time: ~" _GREEN_("%d")" hours", index / 6545); + PrintAndLogEx(SUCCESS, "Estimated Time: ~" _GREEN_("%d")" hours", index / 7250); } index++; @@ -5896,7 +5896,7 @@ static command_t CommandTable[] = { {"loclass", CmdHFiClass_loclass, AlwaysAvailable, "Use loclass to perform bruteforce reader attack"}, {"lookup", CmdHFiClassLookUp, AlwaysAvailable, "Uses authentication trace to check for key in dictionary file"}, {"legrec", CmdHFiClassLegacyRecover, IfPm3Iclass, "Recovers 24 bits of the diversified key of a legacy card provided a valid nr-mac combination"}, - {"legbrute", CmdHFiClassLegRecLookUp, AlwaysAvailable, "Bruteforces 40 bits of a partial diversified key, provided 24 bits of the key and two valid nr-macs"}, + {"legbrute", CmdHFiClassLegBrute, AlwaysAvailable, "Bruteforces 40 bits of a partial diversified key, provided 24 bits of the key and two valid nr-macs"}, {"unhash", CmdHFiClassUnhash, AlwaysAvailable, "Reverses a diversified key to retrieve hash0 pre-images after DES encryption"}, {"-----------", CmdHelp, IfPm3Iclass, "-------------------- " _CYAN_("Simulation") " -------------------"}, {"sim", CmdHFiClassSim, IfPm3Iclass, "Simulate iCLASS tag"}, From c32f655023d63565597d1c14dcfd540ac4e30583 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Fri, 30 May 2025 13:00:35 +0800 Subject: [PATCH 24/31] Improved hf iclass tear erase phase readability Improved readability of erase phase during iclass tear (client and arm side). It is redundant to see a list of FF during the erase phase (which can be pretty lengthy), so it will only show it once when all bits are FF and then will resume printing the moment bits start changing again post erase phase. --- armsrc/iclass.c | 10 ++++++---- client/src/cmdhficlass.c | 9 ++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/armsrc/iclass.c b/armsrc/iclass.c index bcf5df684..4b6097712 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -2311,11 +2311,13 @@ void iClass_TearBlock(iclass_tearblock_req_t *msg) { if (memcmp(data_read, ff_data, PICOPASS_BLOCK_SIZE) == 0 && memcmp(data_read_orig, ff_data, PICOPASS_BLOCK_SIZE) != 0) { - erase_phase = true; - DbpString(""); - DbpString(_CYAN_("Erase phase hit... ALL ONES")); + if(erase_phase == false){ + DbpString(""); + DbpString(_CYAN_("Erase phase hit... ALL ONES")); - iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: "); + iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: "); + } + erase_phase = true; } else { diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index b127cb5bf..a60df6ebb 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -3340,10 +3340,13 @@ static int CmdHFiClass_TearBlock(const char *Cmd) { if (memcmp(data_read, ff_data, 8) == 0 && memcmp(data_read_orig, ff_data, 8) != 0) { + + if (erase_phase == false){ + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, _CYAN_("Erase phase hit... ALL ONES")); + iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: "); + } erase_phase = true; - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(SUCCESS, _CYAN_("Erase phase hit... ALL ONES")); - iclass_cmp_print(data_read_orig, data_read, "Original: ", "Read: "); } else { if (erase_phase) { From db9667d0fbbe634c1f54498a567e5432627a6bb5 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Fri, 30 May 2025 13:02:11 +0800 Subject: [PATCH 25/31] Update CHANGELOG.md Signed-off-by: Antiklesys --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f934ad183..f2395069c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,9 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Changed `hf iclass tear` - readability improvements for erase phase (@antiklesys) - Changed `hf iclass legrec` - code optimizations gaining a ~8% speed increase (@antiklesys) -- Modified `hf iclass tear` - now has a device side implementation also. @antiklesys (@iceman1001) +- Modified `hf iclass tear` - now has a device side implementation also. (@antiklesys) (@iceman1001) - Changed `hf iclass info` - now uses CSN values based checks (@antiklesys) - Changed `hf iclass dump` - now uses default AA1 key when called without a key or key index (@iceman1001) - Renamed `hf iclass trbl` to `hf iclass tear` (@iceman1001) From 2105dbc37946a553e7f7f5c6acc9aea7b53f81ec Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Fri, 30 May 2025 13:05:25 +0800 Subject: [PATCH 26/31] Update iclass.c Clarified what tear success means Signed-off-by: Antiklesys --- armsrc/iclass.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armsrc/iclass.c b/armsrc/iclass.c index 4b6097712..652993a0a 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -2461,7 +2461,7 @@ void iClass_TearBlock(iclass_tearblock_req_t *msg) { read_ok = true; tear_success = true; DbpString(""); - DbpString("tear success!"); + DbpString("tear success (expected values)!"); } loop_count++; From 23d9783b2673e5c781e3c2c7b85cbf6161deb228 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Sat, 31 May 2025 02:15:13 +0800 Subject: [PATCH 27/31] Updated hf iclass legrec Updated hf iclass legrec: 1- Fixed communication timing inconsistencies by moving away from iclass_writeblock_ext to iclass_writeblock_sp which supports start_time and end_time 2- Reduced number of debug messages being printed Overall this reduces slightly the speed of the process, but it should make it more stable as the timings are now all correctly being accounted for. --- armsrc/iclass.c | 116 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 39 deletions(-) diff --git a/armsrc/iclass.c b/armsrc/iclass.c index 652993a0a..792c7ab9c 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -1795,6 +1795,28 @@ static bool iclass_writeblock_ext(uint8_t blockno, uint8_t *data, uint8_t *mac, return true; } +static bool iclass_writeblock_sp(uint8_t blockno, uint8_t *data, uint8_t *mac, bool shallow_mod, uint32_t *start_time, uint32_t *eof_time) { + + // write command: cmd, 1 blockno, 8 data, 4 mac + uint8_t write[14] = { 0x80 | ICLASS_CMD_UPDATE, blockno }; + uint8_t write_len = 14; + memcpy(write + 2, data, 8); + memcpy(write + 10, mac, 4); + + uint8_t resp[10] = {0}; + bool isOK = iclass_send_cmd_with_retries(write, write_len, resp, sizeof(resp), 10, 3, start_time, ICLASS_READER_TIMEOUT_UPDATE, eof_time, shallow_mod); + if (isOK == false) { + return false; + } + + // check response. All other updates return unchanged data + if (memcmp(data, resp, PICOPASS_BLOCK_SIZE)) { + return false; + } + + return true; +} + // turn off afterwards void iClass_WriteBlock(uint8_t *msg) { @@ -2657,8 +2679,14 @@ void iClass_Recover(iclass_recover_req_t *msg) { uint8_t mac2[4] = {0}; picopass_hdr_t hdr = {0}; bool res = false; + int status_message = 0; while (!card_select || !card_auth) { + if (msg->test) { + Dbprintf(_YELLOW_("*Cycled Reader*") " ------------ TEST Index - Loops: "_YELLOW_("%3d / %3d") " *", loops, msg->loop); + } else { + Dbprintf(_YELLOW_("*Cycled Reader*") " ------------ Index: "_RED_("%3d")" Loops: "_YELLOW_("%3d / %3d") " *", index, loops, msg->loop); + } Iso15693InitReader(); //has to be at the top as it starts tracing if (!msg->debug) { set_tracing(false); //disable tracing to prevent crashes - set to true for debugging @@ -2667,18 +2695,11 @@ void iClass_Recover(iclass_recover_req_t *msg) { clear_trace(); //if we're debugging better to clear the trace but do it only on the first loop } } - if (msg->test) { - Dbprintf(_YELLOW_("*Cycled Reader*") " ----------------- TEST Index - Loops: "_YELLOW_("%3d / %3d") " --------------*", loops, msg->loop); - } else { - Dbprintf(_YELLOW_("*Cycled Reader*") " ----------------- Index: "_RED_("%3d")" Loops: "_YELLOW_("%3d / %3d") " --------------*", index, loops, msg->loop); - } //Step0 Card Select Routine eof_time = 0; //reset eof time res = select_iclass_tag(&hdr, false, &eof_time, shallow_mod); - if (res == false) { - DbpString(_RED_("Unable to select card after reader cycle! Retrying...")); - } else { - DbpString(_GREEN_("Card selected successfully!")); + if (res) { + status_message = 1; //card select successful card_select = true; } @@ -2687,10 +2708,8 @@ void iClass_Recover(iclass_recover_req_t *msg) { memcpy(original_mac, msg->req.key, 8); start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1); - if (res == false) { - DbpString(_RED_("Unable to authenticate on AA1 using macs! Retrying...")); - } else { - DbpString(_GREEN_("AA1 authentication with macs successful!")); + if (res) { + status_message = 2; //authentication with AA1 macs successful card_auth = true; } } @@ -2715,10 +2734,9 @@ void iClass_Recover(iclass_recover_req_t *msg) { // expect a 8-byte response here res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len); if (res2 != PM3_SUCCESS || resp_len != 8) { - DbpString(_YELLOW_("Privilege Escalation -> ")_RED_("Read failed! Trying again...")); priv_esc_tries++; } else { - DbpString(_YELLOW_("Privilege Escalation -> ")_GREEN_("Response OK!")); + status_message = 3; //privilege escalation successful priv_esc = true; } if (priv_esc_tries == 5) { @@ -2740,19 +2758,16 @@ void iClass_Recover(iclass_recover_req_t *msg) { wb[0] = blockno; memcpy(wb + 1, genkeyblock, 8); doMAC_N(wb, sizeof(wb), div_key2, mac2); - bool use_mac = true; bool written = false; bool write_error = false; while (written == false && write_error == false) { //Step5 Perform Write - if (iclass_writeblock_ext(blockno, genkeyblock, mac2, use_mac, shallow_mod)) { - DbpString("Wrote key: "); - Dbhexdump(8, genkeyblock, false); + if (iclass_writeblock_sp(blockno, genkeyblock, mac2, shallow_mod, &start_time, &eof_time)) { + status_message = 4; //wrote new key on the card - unverified } //Reset cypher state start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod); - //res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len); //try to authenticate with the original mac to verify the write happened memcpy(msg->req.key, original_mac, 8); start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; @@ -2768,10 +2783,9 @@ void iClass_Recover(iclass_recover_req_t *msg) { } } else { if (res) { - DbpString("Write Operation : "_RED_("FAILED! Card Key is the Original. Retrying...")); - write_error = true; + write_error = true; //failed to update the key, the card's key is the original one } else { - DbpString("Write Operation : "_GREEN_("VERIFIED! Card Key Updated!")); + status_message = 5; //verified the card key was updated to the new one written = true; } } @@ -2782,16 +2796,12 @@ void iClass_Recover(iclass_recover_req_t *msg) { for (int i = 0; i < 8 ; ++i) { start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod); - //res2 = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len); //need to craft the authentication payload accordingly memcpy(msg->req.key, iclass_mac_table[i], 8); start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1); //mac1 here shouldn't matter if (res == true) { bits_found = i; - DbpString(_RED_("--------------------------------------------------------")); - Dbprintf("Decimal Value of last 3 bits: " _GREEN_("[%3d]"), bits_found); - DbpString(_RED_("--------------------------------------------------------")); recovered = true; } } @@ -2805,36 +2815,31 @@ void iClass_Recover(iclass_recover_req_t *msg) { iclass_send_as_reader(read_check_cc, sizeof(read_check_cc), &start_time, &eof_time, shallow_mod); // TODO: check result //GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len); - - DbpString(_YELLOW_("Attempting to restore the original key. ")); - if (iclass_writeblock_ext(blockno, genkeyblock, mac2, use_mac, shallow_mod)) { - DbpString("Restore of Original Key "_GREEN_("successful.")); - } else { - DbpString("Restore of Original Key " _RED_("failed.")); + start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; + if (iclass_writeblock_sp(blockno, genkeyblock, mac2, shallow_mod, &start_time, &eof_time)) { + status_message = 6; //restore of original key successful but unverified } - DbpString(_YELLOW_("Verifying Key Restore...")); //Do a readcheck first to reset the cypher state start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; iclass_send_as_reader(read_check_cc2, sizeof(read_check_cc2), &start_time, &eof_time, shallow_mod); // TODO: check result //GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, &eof_time, false, true, &resp_len); - //need to craft the authentication payload accordingly memcpy(msg->req.key, original_mac, 8); start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1); if (res == true) { - DbpString("Restore of Original Key "_GREEN_("VERIFIED! Card is usable again.")); + status_message = 7; //restore of original key verified - card usable again reverted = true; if (recovered) { goto restore; } - } else { - DbpString("Restore of Original Key "_RED_("VERIFICATION FAILED! Trying again...")); } revert_retries++; if (revert_retries >= 7) { //must always be an odd number! + DbpString("Wrote key: "); + Dbhexdump(8, genkeyblock, false); Dbprintf(_RED_("Attempted to restore original key for %3d times and failed. Stopping. Card is likely unusable."), revert_retries); goto out; } @@ -2842,11 +2847,41 @@ void iClass_Recover(iclass_recover_req_t *msg) { } + if(msg->debug || msg->test){ + if(status_message >= 1){ + DbpString("Card Select:............."_GREEN_("Ok!")); + } + if(status_message >= 2){ + DbpString("AA1 macs authentication:."_GREEN_("Ok!")); + } + if(status_message >= 3){ + DbpString("Privilege Escalation:...."_GREEN_("Ok!")); + } + if(status_message >= 4){ + DbpString("Wrote key: "); + Dbhexdump(8, genkeyblock, false); + } + if(status_message >= 5){ + DbpString("Key Update:.............."_GREEN_("Verified!")); + } + if(status_message >= 6){ + DbpString("Original Key Restore:...."_GREEN_("Ok!")); + } + if(status_message >= 7){ + DbpString("Original Key Restore:...."_GREEN_("Verified!")); + } + }else{ + Dbhexdump(8, genkeyblock, false); + } + + if (loops >= msg->loop) { completed = true; goto out; } - if (!write_error) { //if there was a write error, re-run the loop for the same key index + if (write_error && (msg->debug || msg->test)) { //if there was a write error, re-run the loop for the same key index + DbpString("Loop Error: "_RED_("Repeating Loop!")); + }else{ loops++; index++; } @@ -2862,6 +2897,9 @@ restore: partialkey[i] = genkeyblock[i] ^ bits_found; } + //Print the bits decimal value + DbpString(_RED_("--------------------------------------------------------")); + Dbprintf("Decimal Value of last 3 bits: " _GREEN_("[%3d]"), bits_found); //Print the 24 bits found from k1 DbpString(_RED_("--------------------------------------------------------")); DbpString(_RED_("SUCCESS! Raw Key Partial Bytes: ")); From 749c23a6b54927fba1d413dd7028505c38123de7 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Fri, 30 May 2025 20:46:27 +0200 Subject: [PATCH 28/31] iclass tear on device side , moved around debug printing not to disturb timings between reading and writing tag during stabilize weak bit phase --- armsrc/iclass.c | 72 +++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/armsrc/iclass.c b/armsrc/iclass.c index 792c7ab9c..5d7159e8b 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -2368,44 +2368,10 @@ void iClass_TearBlock(iclass_tearblock_req_t *msg) { if (req.blockno == 1) { - if (data_read[0] != data_read_orig[0]) { - DbpString(""); - Dbprintf("Application limit changed, from "_YELLOW_("%u")" to "_YELLOW_("%u"), data_read_orig[0], data_read[0]); - isok = PM3_SUCCESS; - goto_out = true; - } - - if (data_read[7] != data_read_orig[7]) { - DbpString(""); - Dbprintf("Fuse changed, from "_YELLOW_("%02x")" to "_YELLOW_("%02x"), data_read_orig[7], data_read[7]); - - const char *flag_names[8] = { - "RA", - "Fprod0", - "Fprod1", - "Crypt0 (*1)", - "Crypt1 (*0)", - "Coding0", - "Coding1", - "Fpers (*1)" - }; - Dbprintf(_YELLOW_("%-10s %-10s %-10s"), "Fuse", "Original", "Changed"); - Dbprintf("---------------------------------------"); - for (int i = 7; i >= 0; --i) { - int bit1 = (data_read_orig[7] >> i) & 1; - int bit2 = (data_read[7] >> i) & 1; - Dbprintf("%-11s %-10d %-10d", flag_names[i], bit1, bit2); - } - - isok = PM3_SUCCESS; - goto_out = true; - } - // if more OTP bits set.. if (data_read[1] > data_read_orig[1] || data_read[2] > data_read_orig[2]) { - DbpString(""); - DbpString("More OTP bits got set!!!"); + // step 4 if bits changed attempt to write the new bits to the tag if (data_read[7] == 0xBC) { @@ -2455,6 +2421,9 @@ void iClass_TearBlock(iclass_tearblock_req_t *msg) { switch_off(); + DbpString(""); + DbpString("More OTP bits got set!!!"); + Iso15693InitReader(); // select tag, during which we read block1 @@ -2471,6 +2440,39 @@ void iClass_TearBlock(iclass_tearblock_req_t *msg) { isok = PM3_SUCCESS; goto_out = true; } + + if (data_read[0] != data_read_orig[0]) { + DbpString(""); + Dbprintf("Application limit changed, from "_YELLOW_("%u")" to "_YELLOW_("%u"), data_read_orig[0], data_read[0]); + isok = PM3_SUCCESS; + goto_out = true; + } + + if (data_read[7] != data_read_orig[7]) { + DbpString(""); + Dbprintf("Fuse changed, from "_YELLOW_("%02x")" to "_YELLOW_("%02x"), data_read_orig[7], data_read[7]); + + const char *flag_names[8] = { + "RA", + "Fprod0", + "Fprod1", + "Crypt0 (*1)", + "Crypt1 (*0)", + "Coding0", + "Coding1", + "Fpers (*1)" + }; + Dbprintf(_YELLOW_("%-10s %-10s %-10s"), "Fuse", "Original", "Changed"); + Dbprintf("---------------------------------------"); + for (int i = 7; i >= 0; --i) { + int bit1 = (data_read_orig[7] >> i) & 1; + int bit2 = (data_read[7] >> i) & 1; + Dbprintf("%-11s %-10d %-10d", flag_names[i], bit1, bit2); + } + + isok = PM3_SUCCESS; + goto_out = true; + } } if (goto_out) { From 2b2a1cc0a26953de519530ac22cc481e82aa0d73 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Sat, 31 May 2025 14:09:00 +0800 Subject: [PATCH 29/31] Updated hf iclass legrec Updated hf iclass legrec to support pm3 button interrupt for user abort Cleaned up messaging to look more neat and tidy during the process --- armsrc/iclass.c | 41 +++++++++++++++++++++++++++------------- client/src/cmdhficlass.c | 12 ++++++++++++ 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/armsrc/iclass.c b/armsrc/iclass.c index 5d7159e8b..b95a174a8 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -2642,6 +2642,7 @@ void iClass_Recover(iclass_recover_req_t *msg) { int bits_found = -1; bool recovered = false; bool completed = false; + bool interrupted = false; uint8_t div_key2[8] = {0}; uint32_t eof_time = 0; uint32_t start_time = 0; @@ -2663,7 +2664,7 @@ void iClass_Recover(iclass_recover_req_t *msg) { }; LED_A_ON(); - DbpString(_RED_("Interrupting this process will render the card unusable!")); + DbpString(_RED_("Interrupting this process may render the card unusable!")); memcpy(div_key2, msg->nfa, 8); //START LOOP @@ -2684,10 +2685,22 @@ void iClass_Recover(iclass_recover_req_t *msg) { int status_message = 0; while (!card_select || !card_auth) { + + if (BUTTON_PRESS() || loops > msg->loop) { + if(loops > msg->loop){ + completed = true; + }else{ + interrupted = true; + } + goto out; + } + if (msg->test) { - Dbprintf(_YELLOW_("*Cycled Reader*") " ------------ TEST Index - Loops: "_YELLOW_("%3d / %3d") " *", loops, msg->loop); - } else { - Dbprintf(_YELLOW_("*Cycled Reader*") " ------------ Index: "_RED_("%3d")" Loops: "_YELLOW_("%3d / %3d") " *", index, loops, msg->loop); + Dbprintf(_YELLOW_("*Cycled Reader*") " TEST Index - Loops: "_YELLOW_("%3d / %3d") " *", loops, msg->loop); + }else if (msg->debug){ + Dbprintf(_YELLOW_("*Cycled Reader*") " Index: "_RED_("%3d")" Loops: "_YELLOW_("%3d / %3d") " *", index, loops, msg->loop); + }else{ + DbprintfEx(FLAG_INPLACE, "[" _BLUE_("#") "] Index: "_CYAN_("%3d")" Loops: "_YELLOW_("%3d / %3d")" ", index, loops, msg->loop); } Iso15693InitReader(); //has to be at the top as it starts tracing if (!msg->debug) { @@ -2720,6 +2733,7 @@ void iClass_Recover(iclass_recover_req_t *msg) { switch_off(); } if (reinit_tentatives == 5) { + DbpString(""); DbpString(_RED_("Unable to select or authenticate with card multiple times! Stopping.")); goto out; } @@ -2742,6 +2756,7 @@ void iClass_Recover(iclass_recover_req_t *msg) { priv_esc = true; } if (priv_esc_tries == 5) { + DbpString(""); DbpString(_RED_("Unable to complete privilege escalation! Stopping.")); goto out; } @@ -2776,10 +2791,12 @@ void iClass_Recover(iclass_recover_req_t *msg) { res = authenticate_iclass_tag(&msg->req, &hdr, &start_time, &eof_time, mac1); if (msg->test) { if (res) { + DbpString(""); DbpString(_GREEN_("*** CARD EPURSE IS LOUD! OK TO ATTEMPT KEY RETRIEVAL! RUN AGAIN WITH -notest ***")); completed = true; goto out; } else { + DbpString(""); DbpString(_RED_("*** CARD EPURSE IS SILENT! RISK OF BRICKING! DO NOT EXECUTE KEY UPDATES! SCAN IT ON READER FOR EPURSE UPDATE, COLLECT NEW TRACES AND TRY AGAIN! ***")); goto out; } @@ -2840,7 +2857,8 @@ void iClass_Recover(iclass_recover_req_t *msg) { revert_retries++; if (revert_retries >= 7) { //must always be an odd number! - DbpString("Wrote key: "); + DbpString(""); + DbpString(_CYAN_("Last Written Key: ")); Dbhexdump(8, genkeyblock, false); Dbprintf(_RED_("Attempted to restore original key for %3d times and failed. Stopping. Card is likely unusable."), revert_retries); goto out; @@ -2849,8 +2867,9 @@ void iClass_Recover(iclass_recover_req_t *msg) { } - if(msg->debug || msg->test){ + if(msg->debug){ if(status_message >= 1){ + DbpString(""); DbpString("Card Select:............."_GREEN_("Ok!")); } if(status_message >= 2){ @@ -2872,15 +2891,8 @@ void iClass_Recover(iclass_recover_req_t *msg) { if(status_message >= 7){ DbpString("Original Key Restore:...."_GREEN_("Verified!")); } - }else{ - Dbhexdump(8, genkeyblock, false); } - - if (loops >= msg->loop) { - completed = true; - goto out; - } if (write_error && (msg->debug || msg->test)) { //if there was a write error, re-run the loop for the same key index DbpString("Loop Error: "_RED_("Repeating Loop!")); }else{ @@ -2900,6 +2912,7 @@ restore: } //Print the bits decimal value + DbpString(""); DbpString(_RED_("--------------------------------------------------------")); Dbprintf("Decimal Value of last 3 bits: " _GREEN_("[%3d]"), bits_found); //Print the 24 bits found from k1 @@ -2916,6 +2929,8 @@ out: switch_off(); if (completed) { reply_ng(CMD_HF_ICLASS_RECOVER, PM3_EINVARG, NULL, 0); + } else if (interrupted){ + reply_ng(CMD_HF_ICLASS_RECOVER, PM3_EOPABORTED, NULL, 0); } else { reply_ng(CMD_HF_ICLASS_RECOVER, PM3_ESOFT, NULL, 0); } diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index a60df6ebb..71c58049d 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -4540,9 +4540,15 @@ static int iclass_recover(uint8_t key[8], uint32_t index_start, uint32_t loop, u WaitForResponse(CMD_HF_ICLASS_RECOVER, &resp); if (resp.status == PM3_SUCCESS) { + PrintAndLogEx(NORMAL, ""); PrintAndLogEx(SUCCESS, "iCLASS Key Bits Recovery: " _GREEN_("completed!")); repeat = false; + } else if (resp.status == PM3_EOPABORTED) { + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(WARNING, "iCLASS Key Bits Recovery: " _YELLOW_("user aborted")); + repeat = false; } else if (resp.status == PM3_ESOFT) { + PrintAndLogEx(NORMAL, ""); PrintAndLogEx(WARNING, "iCLASS Key Bits Recovery: " _RED_("failed/errors")); repeat = false; } else if (resp.status == PM3_EINVARG) { @@ -4887,8 +4893,14 @@ static int CmdHFiClassLegacyRecover(const char *Cmd) { return PM3_EINVARG; } + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "---------------------------------------"); + PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " to abort"); + PrintAndLogEx(INFO, "--------------- " _CYAN_("start") " -----------------\n"); + iclass_recover(macs, index, loop, no_first_auth, debug, test, allnight); + PrintAndLogEx(NORMAL, ""); PrintAndLogEx(WARNING, _YELLOW_("If the process completed successfully, you can now run 'hf iclass legbrute' with the partial key found.")); PrintAndLogEx(NORMAL, ""); From 91a16e4d9efed4fe7775c3fa7b50ddd10679e7e5 Mon Sep 17 00:00:00 2001 From: apply-science <106422483+apply-science@users.noreply.github.com> Date: Sun, 1 Jun 2025 00:14:23 +0200 Subject: [PATCH 30/31] Update Makefile Openssl@1.1 was disabled 2024-10-24 due to not being supported upstream and blocked from install. Updated to openssl@3.5 in order for compilation to be successful on machines installed after that date. Older machines is encouraged to update. Signed-off-by: apply-science <106422483+apply-science@users.noreply.github.com> --- tools/mfd_aes_brute/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/mfd_aes_brute/Makefile b/tools/mfd_aes_brute/Makefile index 6f8fa44a9..07330e077 100644 --- a/tools/mfd_aes_brute/Makefile +++ b/tools/mfd_aes_brute/Makefile @@ -34,8 +34,8 @@ endif # OS X needs linking to openssl ifeq ($(USE_BREW),1) - MYCFLAGS += -I$(BREW_PREFIX)/opt/openssl@3/include -I$(BREW_PREFIX)/opt/openssl@1.1/include - MYLDFLAGS += -L$(BREW_PREFIX)/opt/openssl@3/lib -L$(BREW_PREFIX)/opt/openssl@1.1/lib + MYCFLAGS += -I$(BREW_PREFIX)/opt/openssl@3/include -I$(BREW_PREFIX)/opt/openssl@3.5/include + MYLDFLAGS += -L$(BREW_PREFIX)/opt/openssl@3/lib -L$(BREW_PREFIX)/opt/openssl@3.5/lib endif ifeq ($(USE_MACPORTS),1) From 810eaeac250f35eca8819aa9c23cb57c5276b3e6 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sun, 1 Jun 2025 14:37:55 +0200 Subject: [PATCH 31/31] string trimming --- client/src/util.c | 14 ++++++++++++++ client/src/util.h | 2 ++ 2 files changed, 16 insertions(+) diff --git a/client/src/util.c b/client/src/util.c index 6a54387d2..f5c3735d9 100644 --- a/client/src/util.c +++ b/client/src/util.c @@ -1409,6 +1409,20 @@ void str_inverse_bin(char *buf, size_t len) { } } +void str_trim(char *s) { + if (s == NULL) { + return; + } + + // handle empty string + if (!*s) { + return; + } + + char *ptr; + for (ptr = s + strlen(s) - 1; (ptr >= s) && isspace(*ptr); --ptr); + ptr[1] = '\0'; +} /** * Converts a hex string to component "hi2", "hi" and "lo" 32-bit integers diff --git a/client/src/util.h b/client/src/util.h index d4c34eb46..0ca53591e 100644 --- a/client/src/util.h +++ b/client/src/util.h @@ -168,10 +168,12 @@ void str_creplace(char *buf, size_t len, char from, char to); void str_reverse(char *buf, size_t len); void str_inverse_hex(char *buf, size_t len); void str_inverse_bin(char *buf, size_t len); +void str_trim(char *s); char *str_dup(const char *src); char *str_ndup(const char *src, size_t len); size_t str_nlen(const char *src, size_t maxlen); + int hexstring_to_u96(uint32_t *hi2, uint32_t *hi, uint32_t *lo, const char *str); int binstring_to_u96(uint32_t *hi2, uint32_t *hi, uint32_t *lo, const char *str); int binarray_to_u96(uint32_t *hi2, uint32_t *hi, uint32_t *lo, const uint8_t *arr, int arrlen);