From 213c134bdcffae8180c57872d79da6a73c48c0a6 Mon Sep 17 00:00:00 2001 From: dxl <64101226@qq.com> Date: Wed, 24 Dec 2025 20:02:43 +0800 Subject: [PATCH 1/2] staticnested faild to find a KeyB Fixes #2938 --- armsrc/appmain.c | 3 +- armsrc/mifarecmd.c | 22 ++++++++-- armsrc/mifarecmd.h | 2 +- client/src/cmdhfmf.c | 74 ++++++++++++++++++++++------------ client/src/mifare/mifarehost.c | 4 +- client/src/mifare/mifarehost.h | 2 +- 6 files changed, 74 insertions(+), 33 deletions(-) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 8e947bd08..88ce070a8 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -1945,10 +1945,11 @@ static void PacketReceived(PacketCommandNG *packet) { uint8_t keytype; uint8_t target_block; uint8_t target_keytype; + uint8_t force_detect_dist; uint8_t key[6]; } PACKED; struct p *payload = (struct p *) packet->data.asBytes; - MifareStaticNested(payload->block, payload->keytype, payload->target_block, payload->target_keytype, payload->key); + MifareStaticNested(payload->block, payload->keytype, payload->target_block, payload->target_keytype, payload->key, payload->force_detect_dist); break; } case CMD_HF_MIFARE_CHKKEYS: { diff --git a/armsrc/mifarecmd.c b/armsrc/mifarecmd.c index 3eac557f1..2ea1a9bc9 100644 --- a/armsrc/mifarecmd.c +++ b/armsrc/mifarecmd.c @@ -1586,7 +1586,7 @@ void MifareNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo, uint8 set_tracing(false); } -void MifareStaticNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo, uint8_t targetKeyType, uint8_t *key) { +void MifareStaticNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo, uint8_t targetKeyType, uint8_t *key, uint8_t forceDetectDist) { LEDsoff(); @@ -1653,8 +1653,24 @@ void MifareStaticNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo, continue; }; - // pre-generate nonces - if (targetKeyType == 1 && nt1 == 0x009080A2) { + // ST types: + // --- + // NT1 is 0x01200145: + // NT2 case: + // A type dist is 160, B type dist is 160, Can double NT2 to fast decrypt. + // A type dist is 160, B type dist is 160, No double NT2 to fast decrypt. + // --- + // NT1 is 0x009080A2: + // NT2 case: + // A type dist is 160, B type dist is 160, Can double NT2 to fast decrypt. + // A type dist is 160, B type dist is 161, Can double NT2 to fast decrypt. + // --- + // NT1 is any: + // NT2 case: random, but is static(static encrypted nested). + + // pre-generate nonces: the distance value is related to the key type, so if the key type is the same, + // we can directly use the measured distance value. (Experience) + if (targetKeyType == 1 && nt1 == 0x009080A2 && forceDetectDist == 0) { target_nt[0] = prng_successor(nt1, 161); target_nt[1] = prng_successor(nt1, 321); } else { diff --git a/armsrc/mifarecmd.h b/armsrc/mifarecmd.h index d15ab183f..721a5cce3 100644 --- a/armsrc/mifarecmd.h +++ b/armsrc/mifarecmd.h @@ -35,7 +35,7 @@ void MifareUWriteBlockCompat(mful_writeblock_t *packet); void MifareUWriteBlock(mful_writeblock_t *packet); void MifareNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo, uint8_t targetKeyType, bool calibrate, uint8_t *key); -void MifareStaticNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo, uint8_t targetKeyType, uint8_t *key); +void MifareStaticNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo, uint8_t targetKeyType, uint8_t *key, uint8_t forceDetectDist); void MifareAcquireEncryptedNonces(uint32_t arg0, uint32_t arg1, uint32_t flags, uint8_t *datain); int MifareAcquireStaticEncryptedNonces(uint32_t flags, const uint8_t *key, bool reply, uint8_t first_block_no, uint8_t first_key_type); diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index c7ffab56c..140f89ae9 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -2495,15 +2495,20 @@ static int CmdHF14AMfNestedStatic(const char *Cmd) { PrintAndLogEx(SUCCESS, "Time in check keys " _YELLOW_("%.0f") " seconds\n", (float)t2 / 1000.0); PrintAndLogEx(SUCCESS, "--- " _CYAN_("Enter static nested key recovery") " --------------"); + // Decryption backup logic for special card 0x009080A2(keyB NT1 dist is 160 & 320, not 161 & 321). + bool forceDetectDist; + // nested sectors for (trgKeyType = MF_KEY_A; trgKeyType <= MF_KEY_B; ++trgKeyType) { for (uint8_t sectorNo = 0; sectorNo < SectorsCnt; ++sectorNo) { - for (int i = 0; i < 1; i++) { + forceDetectDist = 0; // Fist decrypt, auto detect dist for NT2_1 & NT2_2. + + for (int i = 0; i < 2; i++) { if (e_sector[sectorNo].foundKey[trgKeyType]) continue; - - int16_t isOK = mf_static_nested(blockNo, keyType, key, mfFirstBlockOfSector(sectorNo), trgKeyType, keyBlock); + + int16_t isOK = mf_static_nested(blockNo, keyType, key, mfFirstBlockOfSector(sectorNo), trgKeyType, keyBlock, forceDetectDist); switch (isOK) { case PM3_ETIMEOUT : PrintAndLogEx(ERR, "command execution time out"); @@ -2512,11 +2517,15 @@ static int CmdHF14AMfNestedStatic(const char *Cmd) { PrintAndLogEx(WARNING, "aborted via keyboard."); break; case PM3_ESOFT : + // No any key found? + // Try to force decryption using measured nonce instead of automatic detection (some card types may misjudge) + forceDetectDist = 1; + PrintAndLogEx(WARNING, "No key found, next try..."); continue; case PM3_SUCCESS : e_sector[sectorNo].foundKey[trgKeyType] = 1; e_sector[sectorNo].Key[trgKeyType] = bytes_to_num(keyBlock, 6); - + i = 2; // Key found, no next retry. // mf_check_keys_fast(SectorsCnt, true, true, 2, 1, keyBlock, e_sector, false, false); continue; default : @@ -3018,6 +3027,10 @@ static int CmdHF14AMfAutoPWN(const char *Cmd) { uint64_t key64 = 0; bool calibrate = true; + // staticNested parameter + bool force_detect_dist; + int static_nested_retry_i = 0; + // Attack key storage variables uint8_t *keyBlock = NULL; uint32_t key_cnt = 0; @@ -3694,28 +3707,37 @@ tryStaticnested: (current_key_type_i == MF_KEY_B) ? 'B' : 'A'); } - isOK = mf_static_nested(mfFirstBlockOfSector(sectorno), keytype, key, mfFirstBlockOfSector(current_sector_i), current_key_type_i, tmp_key); - DropField(); - switch (isOK) { - case PM3_ETIMEOUT: { - PrintAndLogEx(ERR, "\nError: No response from Proxmark3"); - free(e_sector); - free(fptr); - return isOK; - } - case PM3_EOPABORTED: { - PrintAndLogEx(WARNING, "\nButton pressed, user aborted"); - free(e_sector); - free(fptr); - return isOK; - } - case PM3_SUCCESS: { - e_sector[current_sector_i].Key[current_key_type_i] = bytes_to_num(tmp_key, MIFARE_KEY_SIZE); - e_sector[current_sector_i].foundKey[current_key_type_i] = 'C'; - break; - } - default: { - break; + force_detect_dist = 0; // First time to decrypt staticnested tag, we can auto detect dist by tag type. + for (static_nested_retry_i = 0; static_nested_retry_i < 2; static_nested_retry_i++) { + isOK = mf_static_nested(mfFirstBlockOfSector(sectorno), keytype, key, mfFirstBlockOfSector(current_sector_i), current_key_type_i, tmp_key, force_detect_dist); + DropField(); + switch (isOK) { + case PM3_ETIMEOUT: { + PrintAndLogEx(ERR, "\nError: No response from Proxmark3"); + free(e_sector); + free(fptr); + return isOK; + } + case PM3_EOPABORTED: { + PrintAndLogEx(WARNING, "\nButton pressed, user aborted"); + free(e_sector); + free(fptr); + return isOK; + } + case PM3_ESOFT: { + PrintAndLogEx(WARNING, "No key found, next try..."); + force_detect_dist = 1; + continue; + } + case PM3_SUCCESS: { + e_sector[current_sector_i].Key[current_key_type_i] = bytes_to_num(tmp_key, MIFARE_KEY_SIZE); + e_sector[current_sector_i].foundKey[current_key_type_i] = 'C'; + static_nested_retry_i = 2; // Key found, no next retry. + break; + } + default: { + break; + } } } } diff --git a/client/src/mifare/mifarehost.c b/client/src/mifare/mifarehost.c index d8f3a04ed..c88b60f32 100644 --- a/client/src/mifare/mifarehost.c +++ b/client/src/mifare/mifarehost.c @@ -725,7 +725,7 @@ out: return PM3_ESOFT; } -int mf_static_nested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t *resultKey) { +int mf_static_nested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t *resultKey, bool forceDetectDist) { uint32_t uid = 0; StateList_t statelists[2]; @@ -736,12 +736,14 @@ int mf_static_nested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trg uint8_t keytype; uint8_t target_block; uint8_t target_keytype; + uint8_t force_detect_dist; uint8_t key[6]; } PACKED payload; payload.block = blockNo; payload.keytype = keyType; payload.target_block = trgBlockNo; payload.target_keytype = trgKeyType; + payload.force_detect_dist = forceDetectDist; memcpy(payload.key, key, sizeof(payload.key)); PacketResponseNG resp; diff --git a/client/src/mifare/mifarehost.h b/client/src/mifare/mifarehost.h index 02c0422f0..a46ddf105 100644 --- a/client/src/mifare/mifarehost.h +++ b/client/src/mifare/mifarehost.h @@ -72,7 +72,7 @@ typedef struct { int mf_dark_side(uint8_t blockno, uint8_t key_type, uint64_t *key); int mf_nested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t *resultKey, bool calibrate); -int mf_static_nested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t *resultKey); +int mf_static_nested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t *resultKey, bool forceDetectDist); int mf_check_keys(uint8_t blockNo, uint8_t keyType, bool clear_trace, uint8_t keycnt, uint8_t *keyBlock, uint64_t *key); int mf_check_keys_fast(uint8_t sectorsCnt, uint8_t firstChunk, uint8_t lastChunk, uint8_t strategy, uint32_t size, uint8_t *keyBlock, sector_t *e_sector, From 69d6834a61185aaf4026bd05a9f95f5a654af6d5 Mon Sep 17 00:00:00 2001 From: dxl <64101226@qq.com> Date: Wed, 24 Dec 2025 20:07:21 +0800 Subject: [PATCH 2/2] Changelog updated --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 486a173a3..e378ef8a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Added ATR fingerprinting to `hf 14a/14b info` (@doegox) - Added `Verkada 40-bit` format (@aaronmaxlevy) - Added `hf seos write` command (@aaronjamt) +- Fix `hf mf staticnested` faild to find a KeyB (@xianglin1998) ## [Phrack.4.20728][2025-09-11] - Added `unofficial desfire bible` document (@mistial-dev)