From 5a03ea993fea2573f0e4f6002c0bca8b2cda003c Mon Sep 17 00:00:00 2001 From: uzlonewolf Date: Mon, 23 Dec 2019 07:08:23 -0800 Subject: [PATCH] Nested loop fix for static nonces * add detection of static tag nonces * add tag nonce to error message * modify mfCheckKeys() to pass button press events upstream * don't abort nested when a static nonce is encountered * modify nested to try multiple keys in a single operation * Print keys remaining only every 10 seconds, and add estimated worst-case time --- armsrc/mifarecmd.c | 42 +++++++++----- client/cmdhfmf.c | 12 ++-- client/mifare/mifarehost.c | 112 ++++++++++++++++++++++++++++++++----- 3 files changed, 132 insertions(+), 34 deletions(-) diff --git a/armsrc/mifarecmd.c b/armsrc/mifarecmd.c index bed2f076..89afe06a 100644 --- a/armsrc/mifarecmd.c +++ b/armsrc/mifarecmd.c @@ -779,6 +779,7 @@ void MifareNested(uint32_t arg0, uint32_t arg1, uint32_t calibrate, uint8_t *dat uint32_t cuid, nt1, nt2, nttmp, nttest, ks1; uint8_t par[1]; uint32_t target_nt[2], target_ks[2]; + uint8_t target_nt_duplicate_count = 0; uint8_t par_array[4]; uint16_t ncount = 0; @@ -814,12 +815,6 @@ void MifareNested(uint32_t arg0, uint32_t arg1, uint32_t calibrate, uint8_t *dat for (rtr = 0; rtr < 17; rtr++) { - // Test if the action was cancelled - if(BUTTON_PRESS()) { - isOK = -2; - break; - } - // prepare next select. No need to power down the card. if(mifare_classic_halt(pcs, cuid)) { if (MF_DBGLEVEL >= 1) Dbprintf("Nested: Halt error"); @@ -827,6 +822,12 @@ void MifareNested(uint32_t arg0, uint32_t arg1, uint32_t calibrate, uint8_t *dat continue; } + // Test if the action was cancelled + if(BUTTON_PRESS()) { + isOK = -2; + break; + } + if(!iso14443a_select_card(uid, NULL, &cuid, true, 0, true)) { if (MF_DBGLEVEL >= 1) Dbprintf("Nested: Can't select card"); rtr--; @@ -893,7 +894,7 @@ void MifareNested(uint32_t arg0, uint32_t arg1, uint32_t calibrate, uint8_t *dat for(i=0; i < 2 && !isOK; i++) { // look for exactly two different nonces target_nt[i] = 0; - while(target_nt[i] == 0) { // continue until we have an unambiguous nonce + while(target_nt[i] == 0 && !isOK) { // continue until we have an unambiguous nonce // prepare next select. No need to power down the card. if(mifare_classic_halt(pcs, cuid)) { @@ -901,16 +902,22 @@ void MifareNested(uint32_t arg0, uint32_t arg1, uint32_t calibrate, uint8_t *dat continue; } + // break out of the loop on button press + if(BUTTON_PRESS()) { + isOK = -2; + break; + } + if(!iso14443a_select_card(uid, NULL, &cuid, true, 0, true)) { if (MF_DBGLEVEL >= 1) Dbprintf("Nested: Can't select card"); continue; - }; + } auth1_time = 0; if(mifare_classic_authex(pcs, cuid, blockNo, keyType, ui64Key, AUTH_FIRST, &nt1, &auth1_time)) { if (MF_DBGLEVEL >= 1) Dbprintf("Nested: Auth1 error"); continue; - }; + } // nested authentication auth2_time = auth1_time + delta_time; @@ -918,7 +925,7 @@ void MifareNested(uint32_t arg0, uint32_t arg1, uint32_t calibrate, uint8_t *dat if (len != 4) { if (MF_DBGLEVEL >= 1) Dbprintf("Nested: Auth2 error len=%d", len); continue; - }; + } nt2 = bytes_to_num(receivedAnswer, 4); if (MF_DBGLEVEL >= 3) Dbprintf("Nonce#%d: Testing nt1=%08x nt2enc=%08x nt2par=%02x", i+1, nt1, nt2, par[0]); @@ -944,7 +951,12 @@ void MifareNested(uint32_t arg0, uint32_t arg1, uint32_t calibrate, uint8_t *dat target_ks[i] = ks1; ncount++; if (i == 1 && target_nt[1] == target_nt[0]) { // we need two different nonces - target_nt[i] = 0; + if( ++target_nt_duplicate_count >= NESTED_MAX_TRIES ) { // unable to get a 2nd nonce after NESTED_MAX_TRIES tries, probably a fixed nonce + if (MF_DBGLEVEL >= 2) Dbprintf("Nonce#2: cannot get nonce that != nonce#1, continuing anyway with single nonce! ntdist=%d", j); + break; + } + + target_nt[1] = 0; if (MF_DBGLEVEL >= 3) Dbprintf("Nonce#2: dismissed (= nonce#1), ntdist=%d", j); break; } @@ -1013,17 +1025,17 @@ void MifareChkKeys(uint16_t arg0, uint16_t arg1, uint8_t arg2, uint8_t *datain) int res = MifareMultisectorChk(datain, keyCount, sectorCnt, keyType, OLD_MF_DBGLEVEL, &keyIndex); if (res >= 0) { - cmd_send(CMD_ACK, 1, 0, 0, keyIndex, 80); + cmd_send(CMD_ACK, 1, res, 0, keyIndex, 80); } else { - cmd_send(CMD_ACK, 0, 0, 0, NULL, 0); + cmd_send(CMD_ACK, 0, res, 0, NULL, 0); } } else { int res = MifareChkBlockKeys(datain, keyCount, blockNo, keyType, OLD_MF_DBGLEVEL); if (res > 0) { - cmd_send(CMD_ACK, 1, 0, 0, datain + (res - 1) * 6, 6); + cmd_send(CMD_ACK, 1, res, 0, datain + (res - 1) * 6, 6); } else { - cmd_send(CMD_ACK, 0, 0, 0, NULL, 0); + cmd_send(CMD_ACK, 0, res, 0, NULL, 0); } } diff --git a/client/cmdhfmf.c b/client/cmdhfmf.c index 9ecf99fb..2fe74705 100644 --- a/client/cmdhfmf.c +++ b/client/cmdhfmf.c @@ -707,17 +707,17 @@ int CmdHF14AMfNested(const char *Cmd) if (cmdp == 'o') { // ------------------------------------ one sector working PrintAndLog("--target block no:%3d, target key type:%c ", trgBlockNo, trgKeyType?'B':'A'); int16_t isOK = mfnested(blockNo, keyType, key, trgBlockNo, trgKeyType, keyBlock, true); - if (isOK) { + if (isOK < 0) { switch (isOK) { case -1 : PrintAndLog("Error: No response from Proxmark.\n"); break; case -2 : PrintAndLog("Button pressed. Aborted.\n"); break; case -3 : PrintAndLog("Tag isn't vulnerable to Nested Attack (random numbers are not predictable).\n"); break; - default : PrintAndLog("Unknown Error.\n"); + default : PrintAndLog("Unknown Error (%d)\n", isOK); } return 2; } key64 = bytes_to_num(keyBlock, 6); - if (key64) { + if (!isOK) { PrintAndLog("Found valid key:%012" PRIx64, key64); // transfer key to the emulator @@ -792,12 +792,12 @@ int CmdHF14AMfNested(const char *Cmd) if (e_sector[sectorNo].foundKey[trgKeyType]) continue; PrintAndLog("-----------------------------------------------"); int16_t isOK = mfnested(blockNo, keyType, key, FirstBlockOfSector(sectorNo), trgKeyType, keyBlock, calibrate); - if(isOK) { + if(isOK < 0) { switch (isOK) { case -1 : PrintAndLog("Error: No response from Proxmark.\n"); break; case -2 : PrintAndLog("Button pressed. Aborted.\n"); break; case -3 : PrintAndLog("Tag isn't vulnerable to Nested Attack (random numbers are not predictable).\n"); break; - default : PrintAndLog("Unknown Error.\n"); + default : PrintAndLog("Unknown Error (%d)\n", isOK); } free(e_sector); return 2; @@ -808,7 +808,7 @@ int CmdHF14AMfNested(const char *Cmd) iterations++; key64 = bytes_to_num(keyBlock, 6); - if (key64) { + if (!isOK) { PrintAndLog("Found valid key:%012" PRIx64, key64); e_sector[sectorNo].foundKey[trgKeyType] = 1; e_sector[sectorNo].Key[trgKeyType] = key64; diff --git a/client/mifare/mifarehost.c b/client/mifare/mifarehost.c index 90c69878..3a961ef7 100644 --- a/client/mifare/mifarehost.c +++ b/client/mifare/mifarehost.c @@ -23,6 +23,7 @@ #include "parity.h" #include "util.h" #include "iso14443crc.h" +#include "util_posix.h" #include "mifare.h" #include "mifare4.h" @@ -236,8 +237,16 @@ int mfCheckKeys (uint8_t blockNo, uint8_t keyType, bool clear_trace, uint8_t key SendCommand(&c); UsbCommand resp; - if (!WaitForResponseTimeout(CMD_ACK,&resp,3000)) return 1; - if ((resp.arg[0] & 0xff) != 0x01) return 2; + if (!WaitForResponseTimeout(CMD_ACK,&resp,3000)) + return 1; + + if ((resp.arg[0] & 0xff) != 0x01) { + if (((int)resp.arg[1]) < 0) + return (int)resp.arg[1]; + + return 2; + } + *key = bytes_to_num(resp.d.asBytes, 6); return 0; } @@ -321,13 +330,25 @@ __attribute__((force_align_arg_pointer)) int mfnested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t *resultKey, bool calibrate) { - uint16_t i; + uint32_t i, j; uint32_t uid; UsbCommand resp; + int num_unique_nonces; + StateList_t statelists[2]; struct Crypto1State *p1, *p2, *p3, *p4; + uint8_t *keyBlock = NULL; + uint64_t key64; + + int isOK = 1; + + uint64_t next_print_time = 0; + uint64_t start_time; + float brute_force_time; + float brute_force_per_second; + // flush queue (void)WaitForResponseTimeout(CMD_ACK,NULL,100); @@ -335,7 +356,11 @@ int mfnested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, memcpy(c.d.asBytes, key, 6); SendCommand(&c); - if (!WaitForResponseTimeout(CMD_ACK, &resp, 1500)) { + if (!WaitForResponseTimeout(CMD_ACK, &resp, 2500)) { + // some cards can cause it to get stuck in a loop, so break out of it + UsbCommand c = {CMD_PING}; + SendCommand(&c); + (void)WaitForResponseTimeout(CMD_ACK,NULL,500); return -1; } @@ -354,6 +379,11 @@ int mfnested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, memcpy(&statelists[i].ks1, (void *)(resp.d.asBytes + 4 + i * 8 + 4), 4); } + if (statelists[0].nt == statelists[1].nt && statelists[0].ks1 == statelists[1].ks1) + num_unique_nonces = 1; + else + num_unique_nonces = 2; + // calc keys pthread_t thread_id[2]; @@ -404,30 +434,86 @@ int mfnested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, statelists[0].tail.sltail=--p3; statelists[1].tail.sltail=--p4; + for (i = 0; i < 2; i++) { + PrintAndLog("statelist %d: length:%d block:%02d keytype:%d nt:%08X ks1:%08X", i, statelists[i].len, statelists[i].blockNo, statelists[i].keyType, statelists[i].nt, statelists[i].ks1); + } + // the statelists now contain possible keys. The key we are searching for must be in the // intersection of both lists. Create the intersection: qsort(statelists[0].head.keyhead, statelists[0].len, sizeof(uint64_t), compare_uint64); - qsort(statelists[1].head.keyhead, statelists[1].len, sizeof(uint64_t), compare_uint64); - statelists[0].len = intersection(statelists[0].head.keyhead, statelists[1].head.keyhead); + + if (num_unique_nonces > 1) { + qsort(statelists[1].head.keyhead, statelists[1].len, sizeof(uint64_t), compare_uint64); + statelists[0].len = intersection(statelists[0].head.keyhead, statelists[1].head.keyhead); + } + else { + PrintAndLog("Nonce 1 and 2 are the same!"); + } + + if (statelists[0].len > 100) { + PrintAndLog("We have %d keys to check. This will take a very long time!", statelists[0].len); + PrintAndLog("Press button to abort."); + } + else if (statelists[0].len < 1) { + PrintAndLog("No candidate keys to check!"); + } + else { + PrintAndLog("We have %d key(s) to check.", statelists[0].len); + } + + uint32_t max_keys = (statelists[0].len > (USB_CMD_DATA_SIZE / 6)) ? (USB_CMD_DATA_SIZE / 6) : statelists[0].len; + keyBlock = calloc(max_keys, 6); + + if (keyBlock == NULL) { + free(statelists[0].head.slhead); + free(statelists[1].head.slhead); + return -4; + } memset(resultKey, 0, 6); + start_time = msclock(); + next_print_time = start_time + 1 * 1000; // The list may still contain several key candidates. Test each of them with mfCheckKeys - for (i = 0; i < statelists[0].len; i++) { - uint8_t keyBlock[6]; - uint64_t key64; - crypto1_get_lfsr(statelists[0].head.slhead + i, &key64); - num_to_bytes(key64, 6, keyBlock); + for (i = 0; i < statelists[0].len; i+=max_keys) { + if (next_print_time <= msclock()) { + brute_force_per_second = ((float)i) / (((float)(msclock() - start_time)) / 1000.0); + brute_force_time = ((float)(statelists[0].len - i)) / brute_force_per_second; + next_print_time = msclock() + 10 * 1000; + PrintAndLog(" %8d keys left | %5.1f keys/sec | worst case %6.1f seconds remaining", statelists[0].len - i, brute_force_per_second, brute_force_time); + } + + if ((i+max_keys) >= statelists[0].len) + max_keys = statelists[0].len - i; + + for (j = 0; j < max_keys; j++) { + crypto1_get_lfsr(statelists[0].head.slhead + i + j, &key64); + num_to_bytes(key64, 6, keyBlock+(j*6)); + } + key64 = 0; - if (!mfCheckKeys(statelists[0].blockNo, statelists[0].keyType, false, 1, keyBlock, &key64)) { + isOK = mfCheckKeys(statelists[0].blockNo, statelists[0].keyType, true, max_keys, keyBlock, &key64); + + if (isOK == 1) { // timeout + isOK = -1; + break; + } + else if (isOK < 0) { // -2 is button pressed + break; + } + else if (!isOK) { num_to_bytes(key64, 6, resultKey); break; } } + if (isOK == 0 && statelists[0].len != 1) + PrintAndLog("Key found in %0.2f seconds after checking %d keys\n", ((float)(msclock() - start_time)) / 1000.0, i+max_keys); + free(statelists[0].head.slhead); free(statelists[1].head.slhead); + free(keyBlock); - return 0; + return isOK; } // MIFARE -- 2.39.2