diff --git a/CHANGELOG.md b/CHANGELOG.md index e3f731a6e..28fc5060a 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 support for collecting all fm11rf08s nT/{nT}/par_err at once (@doegox) - Fixed `hf mfu wrbl` - compatibility write only writes 4 bytes. Now handled correctly (@iceman1001) - Changed `hf mfu info` - better magic tag detection (@iceman1001) - Added ELECTRA pattern decoding in `lf search` (@CiRIP) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index e9b37be78..4464db77a 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -1750,6 +1750,10 @@ static void PacketReceived(PacketCommandNG *packet) { MifareAcquireEncryptedNonces(packet->oldarg[0], packet->oldarg[1], packet->oldarg[2], packet->data.asBytes); break; } + case CMD_HF_MIFARE_ACQ_STATIC_ENCRYPTED_NONCES: { + MifareAcquireStaticEncryptedNonces(packet->data.asBytes); + break; + } case CMD_HF_MIFARE_ACQ_NONCES: { MifareAcquireNonces(packet->oldarg[0], packet->oldarg[2]); break; diff --git a/armsrc/mifarecmd.c b/armsrc/mifarecmd.c index ec049edfb..6897cdb49 100644 --- a/armsrc/mifarecmd.c +++ b/armsrc/mifarecmd.c @@ -1032,6 +1032,140 @@ void MifareAcquireEncryptedNonces(uint32_t arg0, uint32_t arg1, uint32_t flags, } +//----------------------------------------------------------------------------- +// acquire static encrypted nonces in order to perform the attack described in +// Philippe Teuwen, "MIFARE Classic: exposing the static encrypted nonce variant" +//----------------------------------------------------------------------------- +void MifareAcquireStaticEncryptedNonces(uint8_t *key) { + + struct Crypto1State mpcs = {0, 0}; + struct Crypto1State *pcs; + pcs = &mpcs; + + uint8_t uid[10] = {0x00}; + uint8_t receivedAnswer[MAX_MIFARE_FRAME_SIZE] = {0x00}; + uint8_t par_enc[1] = {0x00}; + uint8_t buf[PM3_CMD_DATA_SIZE] = {0x00}; + + uint64_t ui64Key = bytes_to_num(key, 6); + uint32_t cuid = 0; + int16_t isOK = PM3_SUCCESS; + uint16_t num_nonces = 0; + uint8_t cascade_levels = 0; + bool have_uid = false; + uint8_t num_sectors = 16; + + LED_A_ON(); + LED_C_OFF(); + + BigBuf_free(); + BigBuf_Clear_ext(false); + clear_trace(); + set_tracing(false); + + iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN); + + LED_C_ON(); + + for (uint16_t sec = 0; sec < num_sectors; sec++) { + uint8_t blockNo = sec * 4; + for (uint8_t keyType = 0; keyType < 2; keyType++) { + // Test if the action was cancelled + if (BUTTON_PRESS()) { + isOK = PM3_EOPABORTED; + break; + } + if (have_uid == false) { // need a full select cycle to get the uid first + iso14a_card_select_t card_info; + if (iso14443a_select_card(uid, &card_info, &cuid, true, 0, true) == 0) { + if (g_dbglevel >= DBG_ERROR) Dbprintf("AcquireStaticEncryptedNonces: Can't select card (ALL)"); + continue; + } + switch (card_info.uidlen) { + case 4 : + cascade_levels = 1; + break; + case 7 : + cascade_levels = 2; + break; + case 10: + cascade_levels = 3; + break; + default: + break; + } + have_uid = true; + } else { // no need for anticollision. We can directly select the card + if (iso14443a_fast_select_card(uid, cascade_levels) == 0) { + if (g_dbglevel >= DBG_ERROR) Dbprintf("AcquireStaticEncryptedNonces: Can't select card (UID)"); + continue; + } + } + + uint32_t nt1 = 0; + if (mifare_classic_authex_cmd(pcs, cuid, blockNo, MIFARE_AUTH_KEYA + keyType + 4, ui64Key, AUTH_FIRST, &nt1, NULL, NULL, NULL, false, false)) { + if (g_dbglevel >= DBG_ERROR) Dbprintf("AcquireStaticEncryptedNonces: Auth1 error"); + isOK = PM3_ESOFT; + goto out; + }; + // nested authentication + uint16_t len = mifare_sendcmd_short(pcs, AUTH_NESTED, MIFARE_AUTH_KEYA + keyType + 4, blockNo, receivedAnswer, par_enc, NULL); + if (len != 4) { + if (g_dbglevel >= DBG_ERROR) Dbprintf("AcquireStaticEncryptedNonces: Auth2 error len=%d", len); + isOK = PM3_ESOFT; + goto out; + } + uint32_t nt_enc = bytes_to_num(receivedAnswer, 4); + crypto1_init(pcs, ui64Key); + uint32_t nt = crypto1_word(pcs, nt_enc ^ cuid, 1) ^ nt_enc; + // Dbprintf("Sec %2i key %i nT=%08x", sec, keyType + 4, nt); + num_to_bytes(nt, 4, buf + (((sec * 2) + keyType) * 9)); + // send some crap to fail auth + uint8_t nack[] = {0x04}; + ReaderTransmit(nack, sizeof(nack), NULL); + + if (iso14443a_fast_select_card(uid, cascade_levels) == 0) { + if (g_dbglevel >= DBG_ERROR) Dbprintf("AcquireStaticEncryptedNonces: Can't select card (UID)"); + continue; + } + if (mifare_classic_authex_cmd(pcs, cuid, blockNo, MIFARE_AUTH_KEYA + keyType + 4, ui64Key, AUTH_FIRST, &nt1, NULL, NULL, NULL, false, false)) { + if (g_dbglevel >= DBG_ERROR) Dbprintf("AcquireStaticEncryptedNonces: Auth1 error"); + isOK = PM3_ESOFT; + goto out; + }; + + // nested authentication on regular keytype + len = mifare_sendcmd_short(pcs, AUTH_NESTED, MIFARE_AUTH_KEYA + keyType, blockNo, receivedAnswer, par_enc, NULL); + if (len != 4) { + if (g_dbglevel >= DBG_ERROR) Dbprintf("AcquireStaticEncryptedNonces: Auth2 error len=%d", len); + isOK = PM3_ESOFT; + goto out; + } + memcpy(buf + (((sec * 2) + keyType) * 9) + 4, receivedAnswer, 4); + nt_enc = bytes_to_num(receivedAnswer, 4); + uint8_t nt_par_err = ((((par_enc[0] >> 7) & 1) ^ oddparity8((nt_enc >> 24) & 0xFF)) << 3 | + (((par_enc[0] >> 6) & 1) ^ oddparity8((nt_enc >> 16) & 0xFF)) << 2 | + (((par_enc[0] >> 5) & 1) ^ oddparity8((nt_enc >> 8) & 0xFF)) << 1 | + (((par_enc[0] >> 4) & 1) ^ oddparity8((nt_enc >> 0) & 0xFF))); + // Dbprintf("Sec %2i key %i {nT}=%02x%02x%02x%02x perr=%x", sec, keyType, receivedAnswer[0], receivedAnswer[1], receivedAnswer[2], receivedAnswer[3], nt_par_err); + buf[(((sec * 2) + keyType) * 9) + 8] = nt_par_err; + // send some crap to fail auth + ReaderTransmit(nack, sizeof(nack), NULL); + } + } +out: + LED_C_OFF(); + crypto1_deinit(pcs); + LED_B_ON(); + reply_old(CMD_ACK, isOK, cuid, num_nonces, buf, sizeof(buf)); + LED_B_OFF(); + + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + LEDsoff(); + set_tracing(false); +} + + //----------------------------------------------------------------------------- // MIFARE nested authentication. // diff --git a/armsrc/mifarecmd.h b/armsrc/mifarecmd.h index 874cae341..fde47d224 100644 --- a/armsrc/mifarecmd.h +++ b/armsrc/mifarecmd.h @@ -37,6 +37,7 @@ void MifareNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo, uint8 void MifareStaticNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo, uint8_t targetKeyType, uint8_t *key); void MifareAcquireEncryptedNonces(uint32_t arg0, uint32_t arg1, uint32_t flags, uint8_t *datain); +void MifareAcquireStaticEncryptedNonces(uint8_t *key); void MifareAcquireNonces(uint32_t arg0, uint32_t flags); void MifareChkKeys(uint8_t *datain, uint8_t reserved_mem); void MifareChkKeys_fast(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint8_t *datain); diff --git a/client/pyscripts/fm11rf08s_recovery.py b/client/pyscripts/fm11rf08s_recovery.py index 493fd058f..9f6e79a06 100755 --- a/client/pyscripts/fm11rf08s_recovery.py +++ b/client/pyscripts/fm11rf08s_recovery.py @@ -18,6 +18,7 @@ import sys import time import subprocess import argparse +import json import pm3 # optional color support try: @@ -101,44 +102,14 @@ if args.init_check: found_keys[sec][1] = res[4] print_key(sec, 1, found_keys[sec][1]) -nt = [["", ""] for _ in range(NUM_SECTORS)] -nt_enc = [["", ""] for _ in range(NUM_SECTORS)] -par_err = [["", ""] for _ in range(NUM_SECTORS)] print("Getting nonces...") -for sec in range(NUM_SECTORS): - blk = sec * 4 - if found_keys[sec][0] == "" or found_keys[sec][1] == "": - # Even if one key already found, we'll need both nt - for key_type in [0, 1]: - cmd = f"hf mf isen -n1 --blk {blk} -c {key_type+4} --key {BACKDOOR_RF08S}" - p.console(cmd) - cmd += f" --c2 {key_type}" - p.console(cmd) -print("Processing traces...") -for line in p.grabbed_output.split('\n'): - if "nested cmd: 64" in line or "nested cmd: 65" in line: - sec = int(line[24:26], 16)//4 - key_type = int(line[21:23], 16) - 0x64 - data = line[65:73] - nt[sec][key_type] = data - if "nested cmd: 60" in line or "nested cmd: 61" in line: - sec = int(line[24:26], 16)//4 - key_type = int(line[21:23], 16) - 0x60 - data = line[108:116] - nt_enc[sec][key_type] = data - data = line[128:136] - par_err[sec][key_type] = data - -# Check if we got all nonces, else abort. -# TODO: retry instead... -for sec in range(NUM_SECTORS): - if found_keys[sec][0] == "" or found_keys[sec][1] == "": - for key_type in [0, 1]: - if (nt[sec][key_type] == "" or - nt_enc[sec][key_type] == "" or - par_err[sec][key_type] == ""): - print("Error, could not collect all nonces, try again.") - exit() +cmd = f"hf mf isen --collect_fm11rf08s --key {BACKDOOR_RF08S}" +p.console(cmd) +try: + nt, nt_enc, par_err = json.loads(p.grabbed_output) +except json.decoder.JSONDecodeError: + print("Error getting nonces, abort.") + exit() if os.path.isfile(DICT_DEF_PATH): print(f"Loading {DICT_DEF}") diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 657497102..44fd8f905 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -9727,6 +9727,7 @@ static int CmdHF14AMfISEN(const char *Cmd) { arg_lit0(NULL, "incblk2", "auth(blk)-auth(blk2)-auth(blk2+4)-..."), arg_lit0(NULL, "corruptnrar", "corrupt {nR}{aR}, but with correct parity"), arg_lit0(NULL, "corruptnrarparity", "correct {nR}{aR}, but with corrupted parity"), + arg_lit0(NULL, "collect_fm11rf08s", "correct all nT/{nT}/par_err of FM11RF08S in JSON. Option to be used only with -k."), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); @@ -9792,6 +9793,7 @@ static int CmdHF14AMfISEN(const char *Cmd) { bool incblk2 = arg_get_lit(ctx, 16); bool corruptnrar = arg_get_lit(ctx, 17); bool corruptnrarparity = arg_get_lit(ctx, 18); + bool collect_fm11rf08s = arg_get_lit(ctx, 19); CLIParserFree(ctx); uint8_t dbg_curr = DBG_NONE; @@ -9837,6 +9839,47 @@ static int CmdHF14AMfISEN(const char *Cmd) { PrintAndLogEx(INFO, "Card doesn't support standard iso14443-3 anticollision"); } + if (collect_fm11rf08s) { + SendCommandNG(CMD_HF_MIFARE_ACQ_STATIC_ENCRYPTED_NONCES, key, sizeof(key)); + if (WaitForResponseTimeout(CMD_HF_MIFARE_STATIC_ENCRYPTED_NONCE, &resp, 1000)) { + if (resp.status == PM3_ESOFT) { + return NONCE_FAIL; + } + } + uint8_t num_sectors = 16; + // TODO: get nonces and display + PrintAndLogEx(NORMAL, "[\n ["); + for (uint8_t sec = 0; sec < num_sectors; sec++) { + PrintAndLogEx(NORMAL, " [\"%08x\", \"%08x\"]%s", + bytes_to_num(resp.data.asBytes + ((sec * 2) * 9), 4), + bytes_to_num(resp.data.asBytes + (((sec * 2) + 1) * 9), 4), + sec < num_sectors - 1 ? "," : ""); + } + PrintAndLogEx(NORMAL, " ],\n ["); + for (uint8_t sec = 0; sec < num_sectors; sec++) { + PrintAndLogEx(NORMAL, " [\"%08x\", \"%08x\"]%s", + bytes_to_num(resp.data.asBytes + ((sec * 2) * 9) + 4, 4), + bytes_to_num(resp.data.asBytes + (((sec * 2) + 1) * 9) + 4, 4), + sec < num_sectors - 1 ? "," : ""); + } + PrintAndLogEx(NORMAL, " ],\n ["); + for (uint8_t sec = 0; sec < num_sectors; sec++) { + uint8_t p0 = resp.data.asBytes[((sec * 2) * 9) + 8]; + uint8_t p1 = resp.data.asBytes[(((sec * 2) + 1) * 9) + 8]; + PrintAndLogEx(NORMAL, " [\"%i%i%i%i\", \"%i%i%i%i\"]%s", + (p0 >> 3) & 1, (p0 >> 2) & 1, (p0 >> 1) & 1, p0 & 1, + (p1 >> 3) & 1, (p1 >> 2) & 1, (p1 >> 1) & 1, p1 & 1, + sec < num_sectors - 1 ? "," : ""); + } + PrintAndLogEx(NORMAL, " ]\n]"); + + // PrintAndLogEx(SUCCESS, " nT: " _GREEN_("%s"), sprint_hex(resp.data.asBytes + (((sec * 2) + keyType) * 9), 4)); + // PrintAndLogEx(SUCCESS, " {nT}: " _GREEN_("%s"), sprint_hex(resp.data.asBytes + (((sec * 2) + keyType) * 9) + 4, 4)); + // // TODO: wrong par: + // PrintAndLogEx(SUCCESS, " par: " _GREEN_("%02x"), resp.data.asBytes[(((sec * 2) + keyType) * 9) + 8]); + return PM3_SUCCESS; + } + PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "--- " _CYAN_("ISO14443-a Information") " ---------------------"); PrintAndLogEx(SUCCESS, " UID: " _GREEN_("%s"), sprint_hex(card.uid, card.uidlen)); diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index bb459f3fb..15caa9299 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -677,6 +677,7 @@ typedef struct { #define CMD_HF_MIFARE_ACQ_NONCES 0x0614 #define CMD_HF_MIFARE_STATIC_NESTED 0x0615 #define CMD_HF_MIFARE_STATIC_ENC 0x0616 +#define CMD_HF_MIFARE_ACQ_STATIC_ENCRYPTED_NONCES 0x0617 #define CMD_HF_MIFARE_READBL 0x0620 #define CMD_HF_MIFARE_READBL_EX 0x0628