From 5a03ea993fea2573f0e4f6002c0bca8b2cda003c Mon Sep 17 00:00:00 2001
From: uzlonewolf <github_com@hacker-nin.com>
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.5