From 7ad3f6eaf2768b1c8cd6315990df36bd9bae7125 Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Sat, 5 Jul 2025 13:25:18 +0800 Subject: [PATCH 1/5] Updated hf iclass wrbl replay replay behavior to use privilege escalation instead of having to generate specific block/content macs for hf iclass wrbl --- CHANGELOG.md | 1 + armsrc/iclass.c | 101 +++++++++++++++++++++------------------ client/src/cmdhficlass.c | 2 +- 3 files changed, 56 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c1083e3d..e1e2b5994 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 wrbl` - replay behavior to use privilege escalation instead of having to generate specific block/content macs(@antiklesys) - Changed `hf iclass restore` - it now supports privilege escalation to restore card content using replay (@antiklesys) - Fixed `hf 15 dump` - now reads sysinfo response correct (@iceman1001) - Changed `make clean` - it now removes all __pycache__ folders (@iceman1001) diff --git a/armsrc/iclass.c b/armsrc/iclass.c index 2bbde9dae..191d0194f 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -1857,8 +1857,39 @@ static bool iclass_writeblock_sp(uint8_t blockno, uint8_t *data, uint8_t *mac, b return true; } +uint8_t credit_key[8] = {0xFD, 0xCB, 0x5A, 0x52, 0xEA, 0x8F, 0x30, 0x90}; + +static bool do_privilege_escalation(uint8_t *read_check_cc, size_t cc_len, uint32_t *eof_time) { + + int priv_esc_tries = 5; + + while (priv_esc_tries--) { + + uint16_t resp_len = 0; + uint8_t resp[10] = {0}; + //The privilege escalation is done with a readcheck and not just a normal read! + uint32_t start_time = *eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; + + iclass_send_as_reader(read_check_cc, cc_len, &start_time, eof_time, false); + // expect a 8-byte response here + int res = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time, false, true, &resp_len); + if (res == PM3_SUCCESS && resp_len == 8) { + return true; + } + } + + if (g_dbglevel == DBG_INFO) { + DbpString(""); + DbpString(_RED_("Unable to complete privilege escalation! Stopping.")); + } + return false; +} + // turn off afterwards void iClass_WriteBlock(uint8_t *msg) { + bool priv_esc = false; + uint8_t read_check_cc[] = { 0x10 | ICLASS_CMD_READCHECK, 0x18 }; + uint8_t div_cc[8] = {0}; LED_A_ON(); @@ -1878,6 +1909,9 @@ void iClass_WriteBlock(uint8_t *msg) { goto out; } + iclass_calc_div_key(hdr.csn, credit_key, div_cc, false); + read_check_cc[1] = hdr.conf.app_limit + 1; //first block of AA2 + uint32_t start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; uint8_t mac[4] = {0}; @@ -1904,21 +1938,24 @@ void iClass_WriteBlock(uint8_t *msg) { write_len -= 2; } else { - if (payload->req.use_replay) { - memcpy(write + 10, payload->mac, sizeof(payload->mac)); - } else { - // Secure tags uses MAC - uint8_t wb[9]; - wb[0] = payload->req.blockno; - memcpy(wb + 1, payload->data, PICOPASS_BLOCK_SIZE); + // Secure tags uses MAC + uint8_t wb[9]; + wb[0] = payload->req.blockno; + memcpy(wb + 1, payload->data, PICOPASS_BLOCK_SIZE); - if (payload->req.use_credit_key) - doMAC_N(wb, sizeof(wb), hdr.key_c, mac); - else - doMAC_N(wb, sizeof(wb), hdr.key_d, mac); - - memcpy(write + 10, mac, sizeof(mac)); + if (payload->req.use_credit_key){ + doMAC_N(wb, sizeof(wb), hdr.key_c, mac); + } else if (payload->req.use_replay) { + priv_esc = do_privilege_escalation(read_check_cc, sizeof(read_check_cc), &eof_time); + if (priv_esc == false) { + goto out; + } + doMAC_N(wb, sizeof(wb), div_cc, mac); + }else{ + doMAC_N(wb, sizeof(wb), hdr.key_d, mac); } + memcpy(write + 10, mac, sizeof(mac)); + } start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; @@ -2552,36 +2589,9 @@ out: } } -static bool do_privilege_escalation(uint8_t *read_check_cc, size_t cc_len, uint32_t *eof_time) { - - int priv_esc_tries = 5; - - while (priv_esc_tries--) { - - uint16_t resp_len = 0; - uint8_t resp[10] = {0}; - //The privilege escalation is done with a readcheck and not just a normal read! - uint32_t start_time = *eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; - - iclass_send_as_reader(read_check_cc, cc_len, &start_time, eof_time, false); - // expect a 8-byte response here - int res = GetIso15693AnswerFromTag(resp, sizeof(resp), ICLASS_READER_TIMEOUT_OTHERS, eof_time, false, true, &resp_len); - if (res == PM3_SUCCESS && resp_len == 8) { - return true; - } - } - - if (g_dbglevel == DBG_INFO) { - DbpString(""); - DbpString(_RED_("Unable to complete privilege escalation! Stopping.")); - } - return false; -} - void iClass_Restore(iclass_restore_req_t *msg) { bool priv_esc = false; uint8_t read_check_cc[] = { 0x10 | ICLASS_CMD_READCHECK, 0x18 }; - uint8_t credit_key[8] = {0xFD, 0xCB, 0x5A, 0x52, 0xEA, 0x8F, 0x30, 0x90}; uint8_t div_cc[8] = {0}; // sanitation @@ -2626,13 +2636,6 @@ void iClass_Restore(iclass_restore_req_t *msg) { } } - if (msg->req.use_replay) { - priv_esc = do_privilege_escalation(read_check_cc, sizeof(read_check_cc), &eof_time); - if (priv_esc == false) { - goto out; - } - } - // main loop bool use_mac; for (uint8_t i = 0; i < msg->item_cnt; i++) { @@ -2653,6 +2656,10 @@ void iClass_Restore(iclass_restore_req_t *msg) { if (msg->req.use_credit_key) { doMAC_N(wb, sizeof(wb), hdr.key_c, mac); } else if (msg->req.use_replay) { + priv_esc = do_privilege_escalation(read_check_cc, sizeof(read_check_cc), &eof_time); + if (priv_esc == false) { + goto out; + } doMAC_N(wb, sizeof(wb), div_cc, mac); } else { doMAC_N(wb, sizeof(wb), hdr.key_d, mac); diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index b4d32ef6f..584edf662 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -2329,7 +2329,7 @@ static int CmdHFiClass_WriteBlock(const char *Cmd) { arg_lit0(NULL, "credit", "key is assumed to be the credit key"), arg_lit0(NULL, "elite", "elite computations applied to key"), arg_lit0(NULL, "raw", "no computations applied to key"), - arg_lit0(NULL, "nr", "replay of NR/MAC"), + arg_lit0(NULL, "nr", "replay of NR/MAC using privilege escalation"), arg_lit0("v", "verbose", "verbose output"), arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"), arg_param_end From 33c3988a94c1302cb4da76ccb17e1701d0baf62c Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Sat, 5 Jul 2025 19:26:22 +0800 Subject: [PATCH 2/5] Fix broken older functionality Updated to still maintain older functionality when the macs field is passed --- CHANGELOG.md | 2 +- armsrc/iclass.c | 6 +++++- client/src/cmdhficlass.c | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1e2b5994..73dcf3e25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +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 wrbl` - replay behavior to use privilege escalation instead of having to generate specific block/content macs(@antiklesys) +- Changed `hf iclass wrbl` - replay behavior to use privilege escalation if the macs field is not passed empty(@antiklesys) - Changed `hf iclass restore` - it now supports privilege escalation to restore card content using replay (@antiklesys) - Fixed `hf 15 dump` - now reads sysinfo response correct (@iceman1001) - Changed `make clean` - it now removes all __pycache__ folders (@iceman1001) diff --git a/armsrc/iclass.c b/armsrc/iclass.c index 191d0194f..522dfdfe9 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -1938,6 +1938,9 @@ void iClass_WriteBlock(uint8_t *msg) { write_len -= 2; } else { + if (payload->req.use_replay && sizeof(payload->mac) > 0) { + memcpy(write + 10, payload->mac, sizeof(payload->mac)); + } else { // Secure tags uses MAC uint8_t wb[9]; wb[0] = payload->req.blockno; @@ -1954,8 +1957,9 @@ void iClass_WriteBlock(uint8_t *msg) { }else{ doMAC_N(wb, sizeof(wb), hdr.key_d, mac); } - memcpy(write + 10, mac, sizeof(mac)); + memcpy(write + 10, mac, sizeof(mac)); + } } start_time = eof_time + DELAY_ICLASS_VICC_TO_VCD_READER; diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 584edf662..f305c33e5 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -2329,7 +2329,7 @@ static int CmdHFiClass_WriteBlock(const char *Cmd) { arg_lit0(NULL, "credit", "key is assumed to be the credit key"), arg_lit0(NULL, "elite", "elite computations applied to key"), arg_lit0(NULL, "raw", "no computations applied to key"), - arg_lit0(NULL, "nr", "replay of NR/MAC using privilege escalation"), + arg_lit0(NULL, "nr", "replay of NR/MAC block write or use privilege escalation if mac is empty"), arg_lit0("v", "verbose", "verbose output"), arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"), arg_param_end From 75c3ce61dd50f81991339ed43ecd40aa937502ae Mon Sep 17 00:00:00 2001 From: Antiklesys Date: Sat, 5 Jul 2025 19:35:41 +0800 Subject: [PATCH 3/5] Update iclass.c Fixed correctly, in the previous fix I'm checking the length of the mac, but the mac is always 4 0 bytes (set from client side as part of the variable size) and the only actual check happens on client side. I'll have to check for the mac value to be != from 00000000 --- armsrc/iclass.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/armsrc/iclass.c b/armsrc/iclass.c index 522dfdfe9..3837d6931 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -1938,7 +1938,7 @@ void iClass_WriteBlock(uint8_t *msg) { write_len -= 2; } else { - if (payload->req.use_replay && sizeof(payload->mac) > 0) { + if (payload->req.use_replay && (memcmp(payload->mac, "\x00\x00\x00\x00", 4) != 0)) { memcpy(write + 10, payload->mac, sizeof(payload->mac)); } else { // Secure tags uses MAC From 7717dfc04dfff873f97f706ac872d07df4d9e6e7 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sun, 6 Jul 2025 20:12:13 +0200 Subject: [PATCH 4/5] text --- armsrc/iclass.c | 30 +++++++++++++++--------------- client/src/flash.c | 16 +++++++++++++++- doc/commands.json | 4 ++-- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/armsrc/iclass.c b/armsrc/iclass.c index 3837d6931..3eb9df188 100644 --- a/armsrc/iclass.c +++ b/armsrc/iclass.c @@ -1941,24 +1941,24 @@ void iClass_WriteBlock(uint8_t *msg) { if (payload->req.use_replay && (memcmp(payload->mac, "\x00\x00\x00\x00", 4) != 0)) { memcpy(write + 10, payload->mac, sizeof(payload->mac)); } else { - // Secure tags uses MAC - uint8_t wb[9]; - wb[0] = payload->req.blockno; - memcpy(wb + 1, payload->data, PICOPASS_BLOCK_SIZE); + // Secure tags uses MAC + uint8_t wb[9]; + wb[0] = payload->req.blockno; + memcpy(wb + 1, payload->data, PICOPASS_BLOCK_SIZE); - if (payload->req.use_credit_key){ - doMAC_N(wb, sizeof(wb), hdr.key_c, mac); - } else if (payload->req.use_replay) { - priv_esc = do_privilege_escalation(read_check_cc, sizeof(read_check_cc), &eof_time); - if (priv_esc == false) { - goto out; + if (payload->req.use_credit_key) { + doMAC_N(wb, sizeof(wb), hdr.key_c, mac); + } else if (payload->req.use_replay) { + priv_esc = do_privilege_escalation(read_check_cc, sizeof(read_check_cc), &eof_time); + if (priv_esc == false) { + goto out; + } + doMAC_N(wb, sizeof(wb), div_cc, mac); + } else { + doMAC_N(wb, sizeof(wb), hdr.key_d, mac); } - doMAC_N(wb, sizeof(wb), div_cc, mac); - }else{ - doMAC_N(wb, sizeof(wb), hdr.key_d, mac); - } - memcpy(write + 10, mac, sizeof(mac)); + memcpy(write + 10, mac, sizeof(mac)); } } diff --git a/client/src/flash.c b/client/src/flash.c index 1672a4497..25edb7c0f 100644 --- a/client/src/flash.c +++ b/client/src/flash.c @@ -698,8 +698,13 @@ int flash_write(flash_file_t *ctx) { PrintAndLogEx(SUCCESS, " 0x%08x..0x%08x [0x%x / %u blocks]", seg->start, end - 1, length, blocks); if (is_loaded) { - fprintf(stdout, "\n\n"); + if (blocks < 50) { + PrintAndLogEx(SUCCESS, "" NOLF); + } else { + fprintf(stdout, "\n\n"); + } } + fflush(stdout); int block = 0; uint8_t *data = seg->data; @@ -721,6 +726,15 @@ int flash_write(flash_file_t *ctx) { length -= block_size; block++; + // small files, like bootrom + if (blocks < 50) { + fprintf(stdout, "."); + len++; + fflush(stdout); + continue; + } + + // large fullimage write if (is_loaded) { if (len < ice3len) { fprintf(stdout, "%c", ice3[len++]); diff --git a/doc/commands.json b/doc/commands.json index 8bc8b21f2..bcc254785 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -3810,7 +3810,7 @@ "--credit key is assumed to be the credit key", "--elite elite computations applied to key", "--raw no computations applied to key", - "--nr replay of NR/MAC", + "--nr replay of NR/MAC block write or use privilege escalation if mac is empty", "-v, --verbose verbose output", "--shallow use shallow (ASK) reader modulation instead of OOK" ], @@ -13377,6 +13377,6 @@ "metadata": { "commands_extracted": 768, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-07-04T10:19:21" + "extracted_on": "2025-07-06T18:10:18" } } From ed504a76ca9e5e348066a9a95829923b1b41df71 Mon Sep 17 00:00:00 2001 From: Lucifer Voeltner Date: Tue, 8 Jul 2025 00:15:43 +0700 Subject: [PATCH 5/5] Fix incorrect formula for calculating Saflok years --- client/src/cmdhfmf.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 29eefbf0e..63229caf9 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -267,7 +267,7 @@ static void ParseAndPrintSaflokData(const sector_t *sector0_info, const sector_t 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; + uint16_t creation_year = (creation_year_high_bits | (decodedBA[11] >> 4)) + SAFLOK_YEAR_OFFSET; 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); @@ -329,7 +329,7 @@ static void ParseAndPrintSaflokData(const sector_t *sector0_info, const sector_t bool checksum_valid = (checksum_calculated == checksum); PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(INFO, "--- " _CYAN_("SAFLOK details")); + 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);