From e02e145fae1a016942f649e0d36ad843d53ea4fc Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Mon, 4 Dec 2017 19:36:26 +0100 Subject: [PATCH] draft for a Mifare classic NACK bug detection. the idea is to have a statistically solid conclusion if tag does or does not have the NACK bug. -in short, ref https://github.com/iceman1001/proxmark3/issues/141 NACK bug; when a tag responds with a NACK to a 8 byte nonce exchange during authentication when the bytes are wrong but the parity bits are correct. This is a strong oracle which is used in the darkside attack. --- armsrc/appmain.c | 14 +-- armsrc/iso14443a.c | 227 ++++++++++++++++++++++++++++++++++++++++++++- armsrc/iso14443a.h | 21 ++++- client/cmdhfmf.c | 31 +++++++ client/cmdhfmf.h | 1 + include/usb_cmd.h | 1 + 6 files changed, 281 insertions(+), 14 deletions(-) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 954287c4e..d1bd86579 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -881,21 +881,18 @@ void UsbPacketReceived(uint8_t *packet, int len) { case CMD_MIFARE_CIDENT: MifareCIdent(); break; - // mifare sniffer case CMD_MIFARE_SNIFFER: SniffMifare(c->arg[0]); break; - case CMD_MIFARE_SETMOD: MifareSetMod(c->arg[0], c->d.asBytes); break; - //mifare desfire - case CMD_MIFARE_DESFIRE_READBL: - break; - case CMD_MIFARE_DESFIRE_WRITEBL: - break; + case CMD_MIFARE_DESFIRE_READBL: + break; + case CMD_MIFARE_DESFIRE_WRITEBL: + break; case CMD_MIFARE_DESFIRE_AUTH1: MifareDES_Auth1(c->arg[0], c->arg[1], c->arg[2], c->d.asBytes); break; @@ -913,6 +910,9 @@ void UsbPacketReceived(uint8_t *packet, int len) { break; case CMD_MIFARE_COLLECT_NONCES: break; + case CMD_MIFARE_NACK_DETECT: + DetectNACKbug(); + break; #endif #ifdef WITH_ICLASS diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index f34ab6608..688227d6b 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -2297,7 +2297,6 @@ int32_t dist_nt(uint32_t nt1, uint32_t nt2) { // Cloning MiFare Classic Rail and Building Passes, Anywhere, Anytime" // (article by Nicolas T. Courtois, 2009) //----------------------------------------------------------------------------- - void ReaderMifare(bool first_try, uint8_t block, uint8_t keytype ) { uint8_t mf_auth[] = { keytype, block, 0x00, 0x00 }; @@ -2548,6 +2547,231 @@ void ReaderMifare(bool first_try, uint8_t block, uint8_t keytype ) { set_tracing(false); } +/* +* Mifare Classic NACK-bug detection +*/ +void DetectNACKbug() { + + uint8_t mf_auth[] = {0x60, 0, 0, 0}; + uint8_t mf_nr_ar[] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + uint8_t uid[10] = {0,0,0,0,0,0,0,0,0,0}; + uint8_t receivedAnswer[MAX_MIFARE_FRAME_SIZE] = {0x00}; + uint8_t receivedAnswerPar[MAX_MIFARE_PARITY_SIZE] = {0x00}; + uint8_t par[1] = {0}; // maximum 8 Bytes to be sent here, 1 byte parity is therefore enough + uint8_t nt_diff = 0; + uint32_t nt = 0; + uint32_t previous_nt = 0; + uint32_t cuid = 0; + + int32_t catch_up_cycles = 0; + int32_t last_catch_up = 0; + int32_t isOK = 0; + int32_t nt_distance = 0; + + uint16_t elapsed_prng_sequences = 1; + uint16_t consecutive_resyncs = 0; + uint16_t unexpected_random = 0; + uint16_t sync_tries = 0; + + // static variables here, is re-used in the next call + static uint32_t nt_attacked = 0; + static uint32_t sync_time = 0; + static uint32_t sync_cycles = 0; + static uint8_t par_low = 0; + + #define PRNG_SEQUENCE_LENGTH (1 << 16) + #define MAX_UNEXPECTED_RANDOM 4 // maximum number of unexpected (i.e. real) random numbers when trying to sync. Then give up. + #define MAX_SYNC_TRIES 32 + + AppendCrc14443a(mf_auth, 2); + + BigBuf_free(); BigBuf_Clear_ext(false); + clear_trace(); + set_tracing(false); + iso14443a_setup(FPGA_HF_ISO14443A_READER_MOD); + + sync_time = GetCountSspClk() & 0xfffffff8; + sync_cycles = PRNG_SEQUENCE_LENGTH; // Mifare Classic's random generator repeats every 2^16 cycles (and so do the nonces). + nt_attacked = 0; + + if (MF_DBGLEVEL >= 4) Dbprintf("Mifare::Sync %u", sync_time); + + par_low = 0; + + bool have_uid = false; + uint8_t cascade_levels = 0; + + LED_C_ON(); + uint16_t i; + for(i = 0; true; ++i) { + + WDT_HIT(); + + // Test if the action was cancelled + if(BUTTON_PRESS()) { + isOK = -1; + break; + } + + // this part is from Piwi's faster nonce collecting part in Hardnested. + if (!have_uid) { // 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)) { + if (MF_DBGLEVEL >= 4) Dbprintf("Mifare: Can't select card (ALL)"); + break; + } + 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_select_card(uid, NULL, &cuid, false, cascade_levels, true)) { + if (MF_DBGLEVEL >= 4) Dbprintf("Mifare: Can't select card (UID)"); + continue; + } + } + + // Sending timeslot of ISO14443a frame + sync_time = (sync_time & 0xfffffff8 ) + sync_cycles + catch_up_cycles; + catch_up_cycles = 0; + + // if we missed the sync time already, advance to the next nonce repeat + while( GetCountSspClk() > sync_time) { + ++elapsed_prng_sequences; + sync_time = (sync_time & 0xfffffff8 ) + sync_cycles; + } + + // Transmit MIFARE_CLASSIC_AUTH at synctime. Should result in returning the same tag nonce (== nt_attacked) + ReaderTransmit(mf_auth, sizeof(mf_auth), &sync_time); + + // Receive the (4 Byte) "random" nonce from TAG + if (!ReaderReceive(receivedAnswer, receivedAnswerPar)) + continue; + + previous_nt = nt; + nt = bytes_to_num(receivedAnswer, 4); + + // Transmit reader nonce with fake par + ReaderTransmitPar(mf_nr_ar, sizeof(mf_nr_ar), par, NULL); + + // we didn't calibrate our clock yet, + // iceman: has to be calibrated every time. + if (previous_nt && !nt_attacked) { + + nt_distance = dist_nt(previous_nt, nt); + + // if no distance between, then we are in sync. + if (nt_distance == 0) { + nt_attacked = nt; + } else { + if (nt_distance == -99999) { // invalid nonce received + unexpected_random++; + if (unexpected_random > MAX_UNEXPECTED_RANDOM) { + isOK = -3; // Card has an unpredictable PRNG. Give up + break; + } else { + if (sync_cycles <= 0) sync_cycles += PRNG_SEQUENCE_LENGTH; + LED_B_OFF(); + continue; // continue trying... + } + } + + if (++sync_tries > MAX_SYNC_TRIES) { + isOK = -4; // Card's PRNG runs at an unexpected frequency or resets unexpectedly + break; + } + + sync_cycles = (sync_cycles - nt_distance)/elapsed_prng_sequences; + + if (sync_cycles <= 0) + sync_cycles += PRNG_SEQUENCE_LENGTH; + + if (MF_DBGLEVEL >= 4) + Dbprintf("calibrating in cycle %d. nt_distance=%d, elapsed_prng_sequences=%d, new sync_cycles: %d\n", i, nt_distance, elapsed_prng_sequences, sync_cycles); + + LED_B_OFF(); + continue; + } + } + LED_B_OFF(); + + if ( (nt != nt_attacked) && nt_attacked) { + // we somehow lost sync. Try to catch up again... + catch_up_cycles = ABS(dist_nt(nt_attacked, nt)); + + if (catch_up_cycles == 99999) { + // invalid nonce received. Don't resync on that one. + catch_up_cycles = 0; + continue; + } + // average? + catch_up_cycles /= elapsed_prng_sequences; + + if (catch_up_cycles == last_catch_up) { + consecutive_resyncs++; + } else { + last_catch_up = catch_up_cycles; + consecutive_resyncs = 0; + } + + if (consecutive_resyncs < 3) { + if (MF_DBGLEVEL >= 4) + Dbprintf("Lost sync in cycle %d. nt_distance=%d. Consecutive Resyncs = %d. Trying one time catch up...\n", i, catch_up_cycles, consecutive_resyncs); + } else { + sync_cycles += catch_up_cycles; + + if (MF_DBGLEVEL >= 4) + Dbprintf("Lost sync in cycle %d for the fourth time consecutively (nt_distance = %d). Adjusting sync_cycles to %d.\n", i, catch_up_cycles, sync_cycles); + + last_catch_up = 0; + catch_up_cycles = 0; + consecutive_resyncs = 0; + } + continue; + } + + // Receive answer. This will be a 4 Bit NACK when the 8 parity bits are OK after decoding + if (ReaderReceive(receivedAnswer, receivedAnswerPar)) { + catch_up_cycles = 8; // the PRNG is delayed by 8 cycles due to the NAC (4Bits = 0x05 encrypted) transfer + + if (nt_diff == 0) + par_low = par[0] & 0xE0; // there is no need to check all parities for other nt_diff. Parity Bits for mf_nr_ar[0..2] won't change + + // Test if the information is complete + + nt_diff = (nt_diff + 1) & 0x07; + mf_nr_ar[3] = (mf_nr_ar[3] & 0x1F) | (nt_diff << 5); + par[0] = par_low; + + } else { + // No NACK. + if (nt_diff == 0) { + par[0]++; + if (par[0] == 0x00) { // tried all 256 possible parities without success. Card doesn't send NACK. + isOK = -2; + break; + } + } else { + // Why this? + par[0] = ((par[0] & 0x1F) + 1) | par_low; + } + } + + // reset the resyncs since we got a complete transaction on right time. + consecutive_resyncs = 0; + } // end for loop + + if (MF_DBGLEVEL >= 4) Dbprintf("Number of sent auth requestes: %u", i); + + cmd_send(CMD_ACK, isOK, 0, 0, 0, 0 ); + + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + LEDsoff(); + set_tracing(false); +} /** *MIFARE 1K simulate. @@ -3155,7 +3379,6 @@ void Mifare1ksim(uint8_t flags, uint8_t exitAfterNReads, uint8_t arg2, uint8_t * set_tracing(false); } - //----------------------------------------------------------------------------- // MIFARE sniffer. // diff --git a/armsrc/iso14443a.h b/armsrc/iso14443a.h index bae9ad772..edaf402cf 100644 --- a/armsrc/iso14443a.h +++ b/armsrc/iso14443a.h @@ -16,7 +16,8 @@ #ifdef __cplusplus extern "C" { #endif - + +#include "usb_cmd.h" #include "cmd.h" #include "apps.h" #include "util.h" @@ -27,6 +28,7 @@ extern "C" { #include "mifareutil.h" #include "parity.h" #include "random.h" +#include "mifare.h" // structs typedef struct { enum { @@ -82,10 +84,13 @@ typedef struct { uint8_t *parity; } tUart; - extern void GetParity(const uint8_t *pbtCmd, uint16_t len, uint8_t *par); extern void AppendCrc14443a(uint8_t *data, int len); +// iso14443a.h +extern void RAMFUNC SniffIso14443a(uint8_t param); +extern void SimulateIso14443aTag(int tagType, int flags, uint8_t *data); +extern void ReaderIso14443a(UsbCommand *c); extern void ReaderTransmit(uint8_t *frame, uint16_t len, uint32_t *timing); extern void ReaderTransmitBitsPar(uint8_t *frame, uint16_t bits, uint8_t *par, uint32_t *timing); extern void ReaderTransmitPar(uint8_t *frame, uint16_t len, uint8_t *par, uint32_t *timing); @@ -97,14 +102,20 @@ extern int iso14443a_select_card(uint8_t *uid_ptr, iso14a_card_select_t *resp_da extern int iso14443a_fast_select_card(uint8_t *uid_ptr, uint8_t num_cascades); extern void iso14a_set_trigger(bool enable); -int EmSendCmd14443aRaw(uint8_t *resp, uint16_t respLen); -int EmSend4bit(uint8_t resp); +extern int EmSendCmd14443aRaw(uint8_t *resp, uint16_t respLen); +extern int EmSend4bit(uint8_t resp); extern int EmSendCmd(uint8_t *resp, uint16_t respLen); extern int EmGetCmd(uint8_t *received, uint16_t *len, uint8_t *parity); -int EmSendCmdPar(uint8_t *resp, uint16_t respLen, uint8_t *par); +extern int EmSendCmdPar(uint8_t *resp, uint16_t respLen, uint8_t *par); +extern int EmSendPrecompiledCmd(tag_response_info_t *response_info); + bool EmLogTrace(uint8_t *reader_data, uint16_t reader_len, uint32_t reader_StartTime, uint32_t reader_EndTime, uint8_t *reader_Parity, uint8_t *tag_data, uint16_t tag_len, uint32_t tag_StartTime, uint32_t tag_EndTime, uint8_t *tag_Parity); +//extern bool prepare_allocated_tag_modulation(tag_response_info_t *response_info, uint8_t **buffer, size_t *buffer_size); + +void ReaderMifare(bool first_try, uint8_t block, uint8_t keytype ); +void DetectNACKbug(); #ifdef __cplusplus } diff --git a/client/cmdhfmf.c b/client/cmdhfmf.c index 65838c09b..9048d2f1b 100644 --- a/client/cmdhfmf.c +++ b/client/cmdhfmf.c @@ -2848,6 +2848,36 @@ int CmdHf14AMfSetMod(const char *Cmd) { return 0; } +// Mifare NACK bug detection +int CmdHf14AMfNack(const char *Cmd) { + + UsbCommand c = {CMD_MIFARE_NACK_DETECT, {0, 0, 0}}; + clearCommandBuffer(); + SendCommand(&c); + UsbCommand resp; + + while (true) { + + printf("."); + fflush(stdout); + if (ukbhit()) { + int gc = getchar(); (void)gc; + return -1; + break; + } + + if (WaitForResponseTimeout(CMD_ACK, &resp, 2000)) { + uint8_t ok = resp.arg[0] & 0xff; + PrintAndLog("isOk:%02x", ok); + if (!ok) { + PrintAndLog("Failed."); + } + break; + } + } + return 0; +} + int CmdHF14AMfice(const char *Cmd) { uint8_t blockNo = 0; @@ -2957,6 +2987,7 @@ static command_t CommandTable[] = { {"decrypt", CmdHf14AMfDecryptBytes, 1, "[nt] [ar_enc] [at_enc] [data] - to decrypt snoop or trace"}, {"setmod", CmdHf14AMfSetMod, 0, "Set MIFARE Classic EV1 load modulation strength"}, {"ice", CmdHF14AMfice, 0, "collect Mifare Classic nonces to file"}, + {"nack", CmdHf14AMfNack, 0, "Test for Mifare NACK bug"}, {NULL, NULL, 0, NULL} }; diff --git a/client/cmdhfmf.h b/client/cmdhfmf.h index 0c59ae9e3..a25041af1 100644 --- a/client/cmdhfmf.h +++ b/client/cmdhfmf.h @@ -63,6 +63,7 @@ extern int CmdHF14AMfCLoad(const char* cmd); extern int CmdHF14AMfCSave(const char* cmd); extern int CmdHf14MfDecryptBytes(const char *Cmd); extern int CmdHf14AMfSetMod(const char *Cmd); +extern int CmdHf14AMfNack(const char *Cmd); void showSectorTable(void); void readerAttack(nonces_t data, bool setEmulatorMem, bool verbose); diff --git a/include/usb_cmd.h b/include/usb_cmd.h index 271954413..9604ea29f 100644 --- a/include/usb_cmd.h +++ b/include/usb_cmd.h @@ -231,6 +231,7 @@ typedef struct{ #define CMD_MIFARE_DESFIRE 0x072e #define CMD_MIFARE_COLLECT_NONCES 0x072f +#define CMD_MIFARE_NACK_DETECT 0x0730 #define CMD_HF_SNIFFER 0x0800