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