From 079689628be623b72ef4f2f1ee7bb9468a2a5c00 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Wed, 16 Oct 2024 19:54:03 +0200 Subject: [PATCH] hf mf sim: add nested reader attack (needs data & rf08s nonces) --- CHANGELOG.md | 1 + armsrc/iso14443a.c | 2 +- armsrc/mifarecmd.c | 2 +- armsrc/mifaresim.c | 105 +++++++++++++++++++++++++++++--------- client/src/cmdhfmf.c | 47 ++++++++++++++--- client/src/mifare/mfkey.c | 32 ++++++++++++ client/src/mifare/mfkey.h | 1 + include/mifare.h | 13 +++-- include/pm3_cmd.h | 1 + 9 files changed, 166 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81d2dc636..ca576b542 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] +- Changed `hf mf sim` - support data-first and nested reader attacks (@doegox) - Fixed em4x50_read() - `lf search` and `lf em 4x50 rdbl -b ` does not coredump reading EM4450 tag (@ANTodorov) - Fixed flashing - client doesnt fail every other flash attempt (@iceman1001) - Changed `pref show` - add option to dump as JSON (@doegox) diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 9572074b8..16ba37c65 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -1580,7 +1580,7 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *data, uint8_ } } - switch (ar_nr_nonces[index].state) { + switch ((nonce_state)ar_nr_nonces[index].state) { case EMPTY: { // first nonce collect ar_nr_nonces[index].cuid = cuid; diff --git a/armsrc/mifarecmd.c b/armsrc/mifarecmd.c index c6efda5f0..96f615c64 100644 --- a/armsrc/mifarecmd.c +++ b/armsrc/mifarecmd.c @@ -1183,7 +1183,7 @@ int MifareAcquireStaticEncryptedNonces(uint32_t flags, const uint8_t *key, bool // 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); // store nt_par_err buf[(keyType * 8) + 2] = nt_par_err; - buf[(keyType * 8) + 3] = 0xAA; // flag to tell we don't know the key yet + buf[(keyType * 8) + 3] = 0xAA; // extra check to tell we have nt/nt_enc/par_err emlSetMem_xt(buf, (CARD_MEMORY_RF08S_OFFSET / MIFARE_BLOCK_SIZE) + sec, 1, MIFARE_BLOCK_SIZE); // send some crap to fail auth ReaderTransmit(nack, sizeof(nack), NULL); diff --git a/armsrc/mifaresim.c b/armsrc/mifaresim.c index 00d3119c3..77e321a09 100644 --- a/armsrc/mifaresim.c +++ b/armsrc/mifaresim.c @@ -45,6 +45,7 @@ #include "crc16.h" #include "dbprint.h" #include "ticks.h" +#include "parity.h" static bool IsKeyBReadable(uint8_t blockNo) { uint8_t sector_trailer[16]; @@ -459,8 +460,9 @@ static bool MifareSimInit(uint16_t flags, uint8_t *datain, uint16_t atqa, uint8_ * FLAG_7B_UID_IN_DATA - means that there is a 7-byte UID in the data-section, we're expected to use that * FLAG_10B_UID_IN_DATA - use 10-byte UID in the data-section not finished * FLAG_NR_AR_ATTACK - means we should collect NR_AR responses for bruteforcing later +* FLAG_NESTED_AUTH_ATTACK - means that we support nested authentication attack *@param exitAfterNReads, exit simulation after n blocks have been read, 0 is infinite ... -* (unless reader attack mode enabled then it runs util it gets enough nonces to recover all keys attmpted) +* (unless reader attack mode enabled then it runs util it gets enough nonces to recover all keys attempted) */ void Mifare1ksim(uint16_t flags, uint8_t exitAfterNReads, uint8_t *datain, uint16_t atqa, uint8_t sak) { tag_response_info_t *responses; @@ -539,6 +541,7 @@ void Mifare1ksim(uint16_t flags, uint8_t exitAfterNReads, uint8_t *datain, uint1 int counter = 0; bool finished = false; + bool running_nested_auth_attack = false; bool button_pushed = BUTTON_PRESS(); while ((button_pushed == false) && (finished == false)) { @@ -788,7 +791,7 @@ void Mifare1ksim(uint16_t flags, uint8_t exitAfterNReads, uint8_t *datain, uint1 // Load key into crypto crypto1_init(pcs, emlGetKey(cardAUTHSC, cardAUTHKEY)); - + running_nested_auth_attack = false; if (!encrypted_data) { // Receive Cmd in clear txt // Update crypto state (UID ^ NONCE) @@ -812,9 +815,38 @@ void Mifare1ksim(uint16_t flags, uint8_t exitAfterNReads, uint8_t *datain, uint1 ans = nonce ^ crypto1_word(pcs, cuid ^ nonce, 0); num_to_bytes(ans, 4, rAUTH_AT); */ - // rAUTH_NT, rAUTH_NT_keystream contains prepared nonce and keystream for nested authentication - // we need calculate parity bits for non-encrypted sequence - mf_crypto1_encryptEx(pcs, rAUTH_NT, rAUTH_NT_keystream, response, 4, response_par); + + // if key not known and FLAG_NESTED_AUTH_ATTACK and we have nt/nt_enc/parity, send recorded nt_enc and parity + if ((flags & FLAG_NESTED_AUTH_ATTACK) == FLAG_NESTED_AUTH_ATTACK) { + if (emlGetKey(cardAUTHSC, cardAUTHKEY) == 0) { + uint8_t buf[16] = {0}; + emlGetMem(buf, (CARD_MEMORY_RF08S_OFFSET / MIFARE_BLOCK_SIZE) + cardAUTHSC, 1); + if (buf[(cardAUTHKEY * 8) + 3] == 0xAA) { // extra check to tell we have nt/nt_enc/par_err + running_nested_auth_attack = true; + // nt + nonce = bytes_to_num(buf + (cardAUTHKEY * 8), 2); + nonce = nonce << 16 | prng_successor(nonce, 16); + // nt_enc + memcpy(response, buf + (cardAUTHKEY * 8) + 4, 4); + uint8_t nt_par_err = buf[(cardAUTHKEY * 8) + 2]; + uint32_t nt_enc = bytes_to_num(response, 4); + response_par[0] = ((((nt_par_err >> 3) & 1) ^ oddparity8((nt_enc >> 24) & 0xFF)) << 7 | + (((nt_par_err >> 2) & 1) ^ oddparity8((nt_enc >> 16) & 0xFF)) << 6 | + (((nt_par_err >> 1) & 1) ^ oddparity8((nt_enc >> 8) & 0xFF)) << 5 | + (((nt_par_err >> 0) & 1) ^ oddparity8((nt_enc >> 0) & 0xFF)) << 4); + ar_nr_resp[0].cuid = cuid; + ar_nr_resp[0].sector = cardAUTHSC; + ar_nr_resp[0].keytype = cardAUTHKEY; + ar_nr_resp[0].nonce = nonce; + ar_nr_resp[0].nonce2 = nt_enc; + }; + } + } + if (running_nested_auth_attack == false) { + // rAUTH_NT, rAUTH_NT_keystream contains prepared nonce and keystream for nested authentication + // we need calculate parity bits for non-encrypted sequence + mf_crypto1_encryptEx(pcs, rAUTH_NT, rAUTH_NT_keystream, response, 4, response_par); + } EmSendCmdPar(response, 4, response_par); FpgaDisableTracing(); @@ -1145,6 +1177,12 @@ void Mifare1ksim(uint16_t flags, uint8_t exitAfterNReads, uint8_t *datain, uint1 // test if auth KO if (cardRr != prng_successor(nonce, 64)) { // Collect AR/NR per keytype & sector + if (running_nested_auth_attack) { + ar_nr_resp[0].nr = nr; + ar_nr_resp[0].ar = ar; + ar_nr_resp[0].state = NESTED; + finished = true; + } if ((flags & FLAG_NR_AR_ATTACK) == FLAG_NR_AR_ATTACK) { for (uint8_t i = 0; i < ATTACK_KEY_COUNT; i++) { @@ -1319,26 +1357,45 @@ void Mifare1ksim(uint16_t flags, uint8_t exitAfterNReads, uint8_t *datain, uint1 FpgaDisableTracing(); - // NR AR ATTACK uint8_t index = 0; - if ((flags & FLAG_NR_AR_ATTACK) == FLAG_NR_AR_ATTACK) { - for (uint8_t i = 0; i < ATTACK_KEY_COUNT; i++) { - if (ar_nr_resp[i].state == SECOND) { - index = i; - if (g_dbglevel >= DBG_INFO) { - Dbprintf("Collected two pairs of AR/NR which can be used to extract sector %d " _YELLOW_("%s") - , ar_nr_resp[i].sector - , (ar_nr_resp[i].keytype == AUTHKEYA) ? "key A" : "key B" - ); - Dbprintf("../tools/mfc/card_reader/mfkey32v2 %08x %08x %08x %08x %08x %08x %08x", - ar_nr_resp[i].cuid, //UID - ar_nr_resp[i].nonce, //NT - ar_nr_resp[i].nr, //NR1 - ar_nr_resp[i].ar, //AR1 - ar_nr_resp[i].nonce2,//NT2 - ar_nr_resp[i].nr2, //NR2 - ar_nr_resp[i].ar2 //AR2 - ); + if (running_nested_auth_attack) { + if ((nonce_state)ar_nr_resp[0].state == NESTED) { + running_nested_auth_attack = false; + if (g_dbglevel >= DBG_INFO) { + Dbprintf("Collected nested AR/NR which can be used to extract sector %d " _YELLOW_("%s") + , ar_nr_resp[0].sector + , (ar_nr_resp[0].keytype == AUTHKEYA) ? "key A" : "key B" + ); + Dbprintf("../tools/mfc/card_reader/mfkey32nested %08x %08x %08x %08x %08x", + ar_nr_resp[0].cuid, //UID + ar_nr_resp[0].nonce, //NT + ar_nr_resp[0].nonce2,//NT_ENC + ar_nr_resp[0].nr, //NR1 + ar_nr_resp[0].ar //AR1 + ); + } + } + } else { + // NR AR ATTACK + if ((flags & FLAG_NR_AR_ATTACK) == FLAG_NR_AR_ATTACK) { + for (uint8_t i = 0; i < ATTACK_KEY_COUNT; i++) { + if ((nonce_state)ar_nr_resp[i].state == SECOND) { + index = i; + if (g_dbglevel >= DBG_INFO) { + Dbprintf("Collected two pairs of AR/NR which can be used to extract sector %d " _YELLOW_("%s") + , ar_nr_resp[i].sector + , (ar_nr_resp[i].keytype == AUTHKEYA) ? "key A" : "key B" + ); + Dbprintf("../tools/mfc/card_reader/mfkey32v2 %08x %08x %08x %08x %08x %08x %08x", + ar_nr_resp[i].cuid, //UID + ar_nr_resp[i].nonce, //NT + ar_nr_resp[i].nr, //NR1 + ar_nr_resp[i].ar, //AR1 + ar_nr_resp[i].nonce2,//NT2 + ar_nr_resp[i].nr2, //NR2 + ar_nr_resp[i].ar2 //AR2 + ); + } } } } diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 00cd80eef..8b7e218b0 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -4044,7 +4044,13 @@ void readerAttack(sector_t *k_sector, size_t k_sectors_cnt, nonces_t data, bool } uint64_t key = 0; - if (mfkey32_moebius(&data, &key)) { + bool found = false; + if ((nonce_state)data.state == SECOND) { + found = mfkey32_moebius(&data, &key); + } else if ((nonce_state)data.state == NESTED) { + found = mfkey32_nested(&data, &key); + } + if (found) { uint8_t sector = data.sector; uint8_t keytype = data.keytype; @@ -4094,6 +4100,7 @@ static int CmdHF14AMfSim(const char *Cmd) { "hf mf sim --1k -u 11223344 -i -x --> Perform reader attack in interactive mode\n" "hf mf sim --2k --> MIFARE 2k\n" "hf mf sim --4k --> MIFARE 4k" + "hf mf sim --1k -x -e --> Keep simulation running and populate with found reader keys\n" ); void *argtable[] = { @@ -4107,8 +4114,9 @@ static int CmdHF14AMfSim(const char *Cmd) { arg_str0(NULL, "sak", "", "Provide explicit SAK (1 bytes, overrides option t)"), arg_int0("n", "num", " ", "Automatically exit simulation after blocks have been read by reader. 0 = infinite"), arg_lit0("i", "interactive", "Console will not be returned until simulation finishes or is aborted"), - arg_lit0("x", NULL, "Performs the 'reader attack', nr/ar attack against a reader"), - arg_lit0("e", "emukeys", "Fill simulator keys from found keys"), + arg_lit0("x", NULL, "Performs the 'reader attack', nr/ar attack against a reader."), + arg_lit0("y", NULL, "Performs the nested 'reader attack'. This requires preloading nt & nt_enc in emulator memory. Implies -x."), + arg_lit0("e", "emukeys", "Fill simulator keys from found keys. Requires -x or -y. Implies -i. Simulation will restart automatically."), arg_lit0("v", "verbose", "verbose output"), arg_lit0(NULL, "cve", "trigger CVE 2021_0430"), arg_param_end @@ -4166,10 +4174,15 @@ static int CmdHF14AMfSim(const char *Cmd) { flags |= FLAG_NR_AR_ATTACK; } - bool setEmulatorMem = arg_get_lit(ctx, 11); - bool verbose = arg_get_lit(ctx, 12); + if (arg_get_lit(ctx, 11)) { + flags |= FLAG_NESTED_AUTH_ATTACK; + } - if (arg_get_lit(ctx, 13)) { + bool setEmulatorMem = arg_get_lit(ctx, 12); + + bool verbose = arg_get_lit(ctx, 13); + + if (arg_get_lit(ctx, 14)) { flags |= FLAG_CVE21_0430; } CLIParserFree(ctx); @@ -4225,6 +4238,24 @@ static int CmdHF14AMfSim(const char *Cmd) { return PM3_EINVARG; } + if ((flags & FLAG_NESTED_AUTH_ATTACK) == FLAG_NESTED_AUTH_ATTACK) { + if ((flags & FLAG_NR_AR_ATTACK) != FLAG_NR_AR_ATTACK) { + PrintAndLogEx(INFO, "Note: option -y implies -x"); + flags |= FLAG_NR_AR_ATTACK; + } + } + + if (setEmulatorMem) { + if ((flags & FLAG_INTERACTIVE) != FLAG_INTERACTIVE) { + PrintAndLogEx(INFO, "Note: option -e implies -i"); + flags |= FLAG_INTERACTIVE; + } + if ((flags & FLAG_NR_AR_ATTACK) != FLAG_NR_AR_ATTACK) { + PrintAndLogEx(WARNING, "Option -e requires -x or -y"); + return PM3_EINVARG; + } + } + PrintAndLogEx(INFO, _YELLOW_("MIFARE %s") " | %s UID " _YELLOW_("%s") "" , csize , uidsize @@ -4281,7 +4312,9 @@ static int CmdHF14AMfSim(const char *Cmd) { const nonces_t *data = (nonces_t *)resp.data.asBytes; readerAttack(k_sector, k_sectors_cnt, data[0], setEmulatorMem, verbose); - cont = true; + if (setEmulatorMem) { + cont = true; + } break; } if (keypress) { diff --git a/client/src/mifare/mfkey.c b/client/src/mifare/mfkey.c index 181818978..33c79b185 100644 --- a/client/src/mifare/mfkey.c +++ b/client/src/mifare/mfkey.c @@ -160,6 +160,38 @@ bool mfkey32_moebius(nonces_t *data, uint64_t *outputkey) { return isSuccess; } +// recover key from 2 reader responses on 2 different tag challenges +// skip "several found keys". Only return true if ONE key is found +bool mfkey32_nested(nonces_t *data, uint64_t *outputkey) { + struct Crypto1State *s, *t; + uint64_t key = 0; // recovered key + bool isSuccess = false; + + uint32_t uid = data->cuid; + uint32_t nt = data->nonce; + uint32_t nt_enc = data->nonce2; + uint32_t ar = prng_successor(nt, 64); + uint32_t nr_enc = data->nr; + uint32_t ar_enc = data->ar; + uint32_t ks0 = nt_enc ^ nt; + uint32_t ks2 = ar_enc ^ ar; + s = lfsr_recovery32(ks0, uid ^ nt); + for (t = s; t->odd | t->even; ++t) { + crypto1_word(t, nr_enc, 1); + if (ks2 == crypto1_word(t, 0, 0)) { + lfsr_rollback_word(t, 0, 0); + lfsr_rollback_word(t, nr_enc, 1); + lfsr_rollback_word(t, uid ^ nt, 0); + crypto1_get_lfsr(t, &key); + isSuccess = true; + break; + } + } + *outputkey = (isSuccess) ? key : 0; + crypto1_destroy(s); + return isSuccess; +} + // recover key from reader response and tag response of one authentication sequence int mfkey64(nonces_t *data, uint64_t *outputkey) { uint64_t key = 0; // recovered key diff --git a/client/src/mifare/mfkey.h b/client/src/mifare/mfkey.h index 22ee47836..18e87d038 100644 --- a/client/src/mifare/mfkey.h +++ b/client/src/mifare/mfkey.h @@ -26,6 +26,7 @@ uint32_t nonce2key(uint32_t uid, uint32_t nt, uint32_t nr, uint32_t ar, uint64_t par_info, uint64_t ks_info, uint64_t **keys); bool mfkey32(nonces_t *data, uint64_t *outputkey); bool mfkey32_moebius(nonces_t *data, uint64_t *outputkey); +bool mfkey32_nested(nonces_t *data, uint64_t *outputkey); int mfkey64(nonces_t *data, uint64_t *outputkey); int compare_uint64(const void *a, const void *b); diff --git a/include/mifare.h b/include/mifare.h index c9685e29e..4082b174e 100644 --- a/include/mifare.h +++ b/include/mifare.h @@ -167,6 +167,13 @@ typedef enum { //----------------------------------------------------------------------------- // "hf 14a sim -x", "hf mf sim -x" attacks //----------------------------------------------------------------------------- +typedef enum { + EMPTY, + FIRST, + SECOND, + NESTED +} nonce_state; + typedef struct { uint32_t cuid; uint32_t nonce; @@ -178,11 +185,7 @@ typedef struct { uint32_t nr2; uint8_t sector; uint8_t keytype; - enum { - EMPTY, - FIRST, - SECOND, - } state; + uint8_t state; } PACKED nonces_t; #endif // _MIFARE_H_ diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index af7b68d9d..e3eb64978 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -780,6 +780,7 @@ typedef struct { #define FLAG_FORCED_SAK 0x1000 #define FLAG_CVE21_0430 0x2000 #define FLAG_RATS_IN_DATA 0x4000 +#define FLAG_NESTED_AUTH_ATTACK 0x8000 #define MODE_SIM_CSN 0