mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2025-08-19 21:03:48 -07:00
iclass chk / lookup / loclass - speedups
This commit is contained in:
parent
4f55b349df
commit
4bb63577c6
5 changed files with 543 additions and 189 deletions
|
@ -295,6 +295,7 @@ static void print_picopass_info(const picopass_hdr *hdr) {
|
|||
fuse_config(hdr);
|
||||
mem_app_config(hdr);
|
||||
}
|
||||
|
||||
static void print_picopass_header(const picopass_hdr *hdr) {
|
||||
PrintAndLogEx(INFO, "--------------------------- " _CYAN_("card") " ---------------------------");
|
||||
PrintAndLogEx(SUCCESS, " CSN: " _GREEN_("%s") " uid", sprint_hex(hdr->csn, sizeof(hdr->csn)));
|
||||
|
@ -741,6 +742,7 @@ static int CmdHFiClassELoad(const char *Cmd) {
|
|||
PrintAndLogEx(SUCCESS, "sent %d bytes of data to device emulator memory", bytes_sent);
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int CmdHFiClassESave(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hf iclass esave",
|
||||
|
@ -858,6 +860,7 @@ static int CmdHFiClassEView(const char *Cmd) {
|
|||
free(dump);
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int CmdHFiClassDecrypt(const char *Cmd) {
|
||||
CLIParserContext *clictx;
|
||||
CLIParserInit(&clictx, "hf iclass decrypt",
|
||||
|
@ -2703,8 +2706,8 @@ static int CmdHFiClassCheckKeys(const char *Cmd) {
|
|||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hf iclass chk",
|
||||
"Checkkeys loads a dictionary text file with 8byte hex keys to test authenticating against a iClass tag",
|
||||
"hf iclass chk -f dictionaries/iclass_default_keys.dic\n"
|
||||
"hf iclass chk -f dictionaries/iclass_default_keys.dic --elite");
|
||||
"hf iclass chk -f iclass_default_keys.dic\n"
|
||||
"hf iclass chk -f iclass_default_keys.dic --elite");
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
|
@ -2883,14 +2886,15 @@ out:
|
|||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
// this method tries to identify in which configuration mode a iCLASS / iCLASS SE reader is in.
|
||||
// Standard or Elite / HighSecurity mode. It uses a default key dictionary list in order to work.
|
||||
static int CmdHFiClassLookUp(const char *Cmd) {
|
||||
CLIParserContext *ctx;
|
||||
CLIParserInit(&ctx, "hf iclass lookup",
|
||||
"Lookup keys takes some sniffed trace data and tries to verify what key was used against a dictionary file",
|
||||
"hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b -f dictionaries/iclass_default_keys.dic\n"
|
||||
"hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b -f dictionaries/iclass_default_keys.dic --elite");
|
||||
"hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b -f iclass_default_keys.dic\n"
|
||||
"hf iclass lookup --csn 9655a400f8ff12e0 --epurse f0ffffffffffffff --macs 0000000089cb984b -f iclass_default_keys.dic --elite");
|
||||
|
||||
void *argtable[] = {
|
||||
arg_param_begin,
|
||||
|
@ -2952,14 +2956,10 @@ static int CmdHFiClassLookUp(const char *Cmd) {
|
|||
uint8_t CCNR[12];
|
||||
uint8_t MAC_TAG[4] = { 0, 0, 0, 0 };
|
||||
|
||||
iclass_prekey_t *prekey = NULL;
|
||||
|
||||
// time
|
||||
uint64_t t1 = msclock();
|
||||
|
||||
// stupid copy.. CCNR is a combo of epurse and reader nonce
|
||||
memcpy(CCNR, epurse, 8);
|
||||
memcpy(CCNR + 8, macs, 4);
|
||||
memcpy(MAC_TAG, macs + 4, 4);
|
||||
|
||||
PrintAndLogEx(SUCCESS, " CSN: " _GREEN_("%s"), sprint_hex(csn, sizeof(csn)));
|
||||
PrintAndLogEx(SUCCESS, " Epurse: %s", sprint_hex(epurse, sizeof(epurse)));
|
||||
|
@ -2967,6 +2967,9 @@ static int CmdHFiClassLookUp(const char *Cmd) {
|
|||
PrintAndLogEx(SUCCESS, " CCNR: " _GREEN_("%s"), sprint_hex(CCNR, sizeof(CCNR)));
|
||||
PrintAndLogEx(SUCCESS, "TAG MAC: %s", sprint_hex(MAC_TAG, sizeof(MAC_TAG)));
|
||||
|
||||
// run time
|
||||
uint64_t t1 = msclock();
|
||||
|
||||
uint8_t *keyBlock = NULL;
|
||||
uint32_t keycount = 0;
|
||||
|
||||
|
@ -2978,7 +2981,7 @@ static int CmdHFiClassLookUp(const char *Cmd) {
|
|||
}
|
||||
|
||||
//iclass_prekey_t
|
||||
prekey = calloc(keycount, sizeof(iclass_prekey_t));
|
||||
iclass_prekey_t *prekey = calloc(keycount, sizeof(iclass_prekey_t));
|
||||
if (!prekey) {
|
||||
free(keyBlock);
|
||||
return PM3_EMALLOC;
|
||||
|
@ -3011,7 +3014,7 @@ static int CmdHFiClassLookUp(const char *Cmd) {
|
|||
}
|
||||
|
||||
t1 = msclock() - t1;
|
||||
PrintAndLogEx(SUCCESS, "time in iclass lookup " _YELLOW_("%.0f") " seconds", (float)t1 / 1000.0);
|
||||
PrintAndLogEx(SUCCESS, "time in iclass lookup " _YELLOW_("%.3f") " seconds", (float)t1 / 1000.0);
|
||||
|
||||
free(prekey);
|
||||
free(keyBlock);
|
||||
|
@ -3019,43 +3022,150 @@ static int CmdHFiClassLookUp(const char *Cmd) {
|
|||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
// precalc diversified keys and their MAC
|
||||
void GenerateMacFrom(uint8_t *CSN, uint8_t *CCNR, bool use_raw, bool use_elite, uint8_t *keys, uint32_t keycnt, iclass_premac_t *list) {
|
||||
typedef struct {
|
||||
uint8_t thread_idx;
|
||||
uint8_t use_raw;
|
||||
uint8_t use_elite;
|
||||
uint32_t keycnt;
|
||||
uint8_t csn[8];
|
||||
uint8_t cc_nr[12];
|
||||
uint8_t *keys;
|
||||
union {
|
||||
iclass_premac_t *premac;
|
||||
iclass_prekey_t *prekey;
|
||||
} list;
|
||||
} PACKED iclass_thread_arg_t;
|
||||
|
||||
static size_t iclass_tc = 1;
|
||||
|
||||
static void* bf_generate_mac(void *thread_arg) {
|
||||
|
||||
iclass_thread_arg_t *targ = (iclass_thread_arg_t *)thread_arg;
|
||||
const uint8_t idx = targ->thread_idx;
|
||||
const uint8_t use_raw = targ->use_raw;
|
||||
const uint8_t use_elite = targ->use_elite;
|
||||
const uint32_t keycnt = targ->keycnt;
|
||||
|
||||
uint8_t *keys = targ->keys;
|
||||
iclass_premac_t *list = targ->list.premac;
|
||||
|
||||
uint8_t csn[8];
|
||||
uint8_t cc_nr[12];
|
||||
memcpy(csn, targ->csn, sizeof(csn));
|
||||
memcpy(cc_nr, targ->cc_nr, sizeof(cc_nr));
|
||||
|
||||
uint8_t key[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
uint8_t div_key[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
//iceman: threading
|
||||
for (uint32_t i = 0; i < keycnt; i++) {
|
||||
for (uint32_t i = idx; i < keycnt; i += iclass_tc) {
|
||||
|
||||
memcpy(key, keys + 8 * i, 8);
|
||||
|
||||
if (use_raw)
|
||||
memcpy(div_key, key, 8);
|
||||
else
|
||||
HFiClassCalcDivKey(CSN, key, div_key, use_elite);
|
||||
HFiClassCalcDivKey(csn, key, div_key, use_elite);
|
||||
|
||||
doMAC(CCNR, div_key, list[i].mac);
|
||||
doMAC(cc_nr, div_key, list[i].mac);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// precalc diversified keys and their MAC
|
||||
void GenerateMacFrom(uint8_t *CSN, uint8_t *CCNR, bool use_raw, bool use_elite, uint8_t *keys, uint32_t keycnt, iclass_premac_t *list) {
|
||||
|
||||
iclass_tc = num_CPUs();
|
||||
pthread_t threads[iclass_tc];
|
||||
iclass_thread_arg_t args[iclass_tc];
|
||||
// init thread arguments
|
||||
for (uint8_t i = 0; i < iclass_tc; i++) {
|
||||
args[i].thread_idx = i;
|
||||
args[i].use_raw = use_raw;
|
||||
args[i].use_elite = use_elite;
|
||||
args[i].keycnt = keycnt;
|
||||
args[i].keys = keys;
|
||||
args[i].list.premac = list;
|
||||
|
||||
memcpy(args[i].csn, CSN, sizeof(args[i].csn) );
|
||||
memcpy(args[i].cc_nr, CCNR, sizeof(args[i].cc_nr) );
|
||||
}
|
||||
|
||||
for (int i = 0; i < iclass_tc; i++) {
|
||||
int res = pthread_create(&threads[i], NULL, bf_generate_mac, (void *)&args[i]);
|
||||
if (res) {
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(WARNING, "Failed to create pthreads. Quitting");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < iclass_tc; i++)
|
||||
pthread_join(threads[i], NULL);
|
||||
}
|
||||
|
||||
static void* bf_generate_mackey(void *thread_arg) {
|
||||
|
||||
iclass_thread_arg_t *targ = (iclass_thread_arg_t *)thread_arg;
|
||||
const uint8_t idx = targ->thread_idx;
|
||||
const uint8_t use_raw = targ->use_raw;
|
||||
const uint8_t use_elite = targ->use_elite;
|
||||
const uint32_t keycnt = targ->keycnt;
|
||||
|
||||
uint8_t *keys = targ->keys;
|
||||
iclass_prekey_t *list = targ->list.prekey;
|
||||
|
||||
uint8_t csn[8];
|
||||
uint8_t cc_nr[12];
|
||||
memcpy(csn, targ->csn, sizeof(csn));
|
||||
memcpy(cc_nr, targ->cc_nr, sizeof(cc_nr));
|
||||
|
||||
uint8_t div_key[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
for (uint32_t i = idx; i < keycnt; i += iclass_tc) {
|
||||
|
||||
memcpy(list[i].key, keys + 8 * i, 8);
|
||||
|
||||
if (use_raw)
|
||||
memcpy(div_key, list[i].key, 8);
|
||||
else
|
||||
HFiClassCalcDivKey(csn, list[i].key, div_key, use_elite);
|
||||
|
||||
doMAC(cc_nr, div_key, list[i].mac);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void GenerateMacKeyFrom(uint8_t *CSN, uint8_t *CCNR, bool use_raw, bool use_elite, uint8_t *keys, uint32_t keycnt, iclass_prekey_t *list) {
|
||||
|
||||
uint8_t div_key[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
iclass_tc = num_CPUs();
|
||||
pthread_t threads[iclass_tc];
|
||||
iclass_thread_arg_t args[iclass_tc];
|
||||
// init thread arguments
|
||||
for (uint8_t i = 0; i < iclass_tc; i++) {
|
||||
args[i].thread_idx = i;
|
||||
args[i].use_raw = use_raw;
|
||||
args[i].use_elite = use_elite;
|
||||
args[i].keycnt = keycnt;
|
||||
args[i].keys = keys;
|
||||
args[i].list.prekey = list;
|
||||
|
||||
//iceman: threading
|
||||
for (uint32_t i = 0; i < keycnt; i++) {
|
||||
|
||||
memcpy(list[i].key, keys + 8 * i, 8);
|
||||
|
||||
// generate diversifed key
|
||||
if (use_raw)
|
||||
memcpy(div_key, list[i].key, 8);
|
||||
else
|
||||
HFiClassCalcDivKey(CSN, list[i].key, div_key, use_elite);
|
||||
|
||||
// generate MAC
|
||||
doMAC(CCNR, div_key, list[i].mac);
|
||||
memcpy(args[i].csn, CSN, sizeof(args[i].csn) );
|
||||
memcpy(args[i].cc_nr, CCNR, sizeof(args[i].cc_nr) );
|
||||
}
|
||||
|
||||
for (int i = 0; i < iclass_tc; i++) {
|
||||
int res = pthread_create(&threads[i], NULL, bf_generate_mackey, (void *)&args[i]);
|
||||
if (res) {
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(WARNING, "Failed to create pthreads. Quitting");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < iclass_tc; i++)
|
||||
pthread_join(threads[i], NULL);
|
||||
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
}
|
||||
|
||||
// print diversified keys
|
||||
|
@ -3250,7 +3360,7 @@ static command_t CommandTable[] = {
|
|||
{"encrypt", CmdHFiClassEncryptBlk, AlwaysAvailable, "[options..] Encrypt given block data"},
|
||||
{"decrypt", CmdHFiClassDecrypt, AlwaysAvailable, "[options..] Decrypt given block data or tag dump file" },
|
||||
{"managekeys", CmdHFiClassManageKeys, AlwaysAvailable, "[options..] Manage keys to use with iclass commands"},
|
||||
{"permute", CmdHFiClassPermuteKey, IfPm3Iclass, " Permute function from 'heart of darkness' paper"},
|
||||
{"permutekey", CmdHFiClassPermuteKey, IfPm3Iclass, " Permute function from 'heart of darkness' paper"},
|
||||
{"view", CmdHFiClassView, AlwaysAvailable, "[options..] Display content from tag dump file"},
|
||||
|
||||
{NULL, NULL, NULL, NULL}
|
||||
|
|
|
@ -39,13 +39,14 @@
|
|||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <pthread.h>
|
||||
#include <time.h>
|
||||
#include "cipherutils.h"
|
||||
#include "cipher.h"
|
||||
#include "ikeys.h"
|
||||
#include "elite_crack.h"
|
||||
#include "fileutils.h"
|
||||
#include "des.h"
|
||||
#include "mbedtls/des.h"
|
||||
#include "util_posix.h"
|
||||
|
||||
/**
|
||||
|
@ -70,8 +71,7 @@
|
|||
* @param dest
|
||||
*/
|
||||
void permutekey(uint8_t key[8], uint8_t dest[8]) {
|
||||
int i;
|
||||
for (i = 0 ; i < 8 ; i++) {
|
||||
for (uint8_t i = 0 ; i < 8 ; i++) {
|
||||
dest[i] = (((key[7] & (0x80 >> i)) >> (7 - i)) << 7) |
|
||||
(((key[6] & (0x80 >> i)) >> (7 - i)) << 6) |
|
||||
(((key[5] & (0x80 >> i)) >> (7 - i)) << 5) |
|
||||
|
@ -164,12 +164,16 @@ rk(x [0] . . . x [7] , n + 1) = rk(rl(x [0] ) . . . rl(x [7] ), n)
|
|||
**/
|
||||
static void rk(uint8_t *key, uint8_t n, uint8_t *outp_key) {
|
||||
memcpy(outp_key, key, 8);
|
||||
uint8_t j;
|
||||
while (n-- > 0) {
|
||||
for (j = 0; j < 8 ; j++)
|
||||
outp_key[j] = rl(outp_key[j]);
|
||||
outp_key[0] = rl(outp_key[0]);
|
||||
outp_key[1] = rl(outp_key[1]);
|
||||
outp_key[2] = rl(outp_key[2]);
|
||||
outp_key[3] = rl(outp_key[3]);
|
||||
outp_key[4] = rl(outp_key[4]);
|
||||
outp_key[5] = rl(outp_key[5]);
|
||||
outp_key[6] = rl(outp_key[6]);
|
||||
outp_key[7] = rl(outp_key[7]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
static mbedtls_des_context ctx_enc;
|
||||
|
@ -214,16 +218,22 @@ void hash2(uint8_t *key64, uint8_t *outp_keytable) {
|
|||
uint8_t z[8][8] = {{0}, {0}};
|
||||
uint8_t temp_output[8] = {0};
|
||||
//calculate complement of key
|
||||
int i;
|
||||
for (i = 0; i < 8; i++)
|
||||
key64_negated[i] = ~key64[i];
|
||||
key64_negated[0] = ~key64[0];
|
||||
key64_negated[1] = ~key64[1];
|
||||
key64_negated[2] = ~key64[2];
|
||||
key64_negated[3] = ~key64[3];
|
||||
key64_negated[4] = ~key64[4];
|
||||
key64_negated[5] = ~key64[5];
|
||||
key64_negated[6] = ~key64[6];
|
||||
key64_negated[7] = ~key64[7];
|
||||
|
||||
// Once again, key is on iclass-format
|
||||
desencrypt_iclass(key64, key64_negated, z[0]);
|
||||
|
||||
// PrintAndLogEx(NORMAL, "");
|
||||
// PrintAndLogEx(INFO, "High security custom key (Kcus):");
|
||||
// PrintAndLogEx(INFO, "z0 %s", sprint_hex(z[0],8));
|
||||
if (g_debugMode > 0) {
|
||||
PrintAndLogEx(DEBUG, "High security custom key (Kcus):");
|
||||
PrintAndLogEx(DEBUG, "z0 %s", sprint_hex(z[0],8));
|
||||
}
|
||||
|
||||
uint8_t y[8][8] = {{0}, {0}};
|
||||
|
||||
|
@ -232,7 +242,7 @@ void hash2(uint8_t *key64, uint8_t *outp_keytable) {
|
|||
desdecrypt_iclass(z[0], key64_negated, y[0]);
|
||||
// PrintAndLogEx(INFO, "y0 %s", sprint_hex(y[0],8));
|
||||
|
||||
for (i = 1; i < 8; i++) {
|
||||
for (uint8_t i = 1; i < 8; i++) {
|
||||
// z [i] = DES dec (rk(K cus , i), z [i−1] )
|
||||
rk(key64, i, temp_output);
|
||||
//y [i] = DES enc (rk(K cus , i), y [i−1] )
|
||||
|
@ -242,7 +252,7 @@ void hash2(uint8_t *key64, uint8_t *outp_keytable) {
|
|||
}
|
||||
|
||||
if (outp_keytable != NULL) {
|
||||
for (i = 0 ; i < 8 ; i++) {
|
||||
for (uint8_t i = 0 ; i < 8 ; i++) {
|
||||
memcpy(outp_keytable + i * 16, y[i], 8);
|
||||
memcpy(outp_keytable + 8 + i * 16, z[i], 8);
|
||||
}
|
||||
|
@ -280,25 +290,121 @@ static int _readFromDump(uint8_t dump[], dumpdata *item, uint8_t i) {
|
|||
return 0;
|
||||
}
|
||||
*/
|
||||
//static uint32_t startvalue = 0;
|
||||
/**
|
||||
* @brief Performs brute force attack against a dump-data item, containing csn, cc_nr and mac.
|
||||
*This method calculates the hash1 for the CSN, and determines what bytes need to be bruteforced
|
||||
*on the fly. If it finds that more than three bytes need to be bruteforced, it aborts.
|
||||
*It updates the keytable with the findings, also using the upper half of the 16-bit ints
|
||||
*to signal if the particular byte has been cracked or not.
|
||||
*
|
||||
* @param dump The dumpdata from iclass reader attack.
|
||||
* @param keytable where to write found values.
|
||||
* @return
|
||||
*/
|
||||
int bruteforceItem(dumpdata item, uint16_t keytable[]) {
|
||||
|
||||
int found = false;
|
||||
uint8_t key_sel_p[8] = {0};
|
||||
uint8_t div_key[8] = {0};
|
||||
typedef struct {
|
||||
int thread_idx;
|
||||
uint32_t endmask;
|
||||
uint8_t numbytes_to_recover;
|
||||
uint8_t bytes_to_recover[3];
|
||||
uint8_t key_index[8];
|
||||
uint16_t keytable[128];
|
||||
loclass_dumpdata_t item;
|
||||
} loclass_thread_arg_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t values[3];
|
||||
} loclass_thread_ret_t;
|
||||
|
||||
static size_t loclass_tc = 1;
|
||||
static int loclass_found = 0;
|
||||
|
||||
static void* bf_thread(void* thread_arg) {
|
||||
|
||||
loclass_thread_arg_t *targ = (loclass_thread_arg_t *)thread_arg;
|
||||
const uint32_t endmask = targ->endmask;
|
||||
const uint8_t numbytes_to_recover = targ->numbytes_to_recover;
|
||||
uint32_t brute = targ->thread_idx;
|
||||
|
||||
uint8_t csn[8];
|
||||
uint8_t cc_nr[12];
|
||||
uint8_t mac[4];
|
||||
uint8_t key_index[8];
|
||||
uint8_t bytes_to_recover[3];
|
||||
uint16_t keytable[128];
|
||||
|
||||
memcpy(csn, targ->item.csn, sizeof(csn));
|
||||
memcpy(cc_nr, targ->item.cc_nr, sizeof(cc_nr));
|
||||
memcpy(mac, targ->item.mac, sizeof(mac));
|
||||
memcpy(key_index, targ->key_index, sizeof(key_index));
|
||||
memcpy(bytes_to_recover, targ->bytes_to_recover, sizeof(bytes_to_recover));
|
||||
memcpy(keytable, targ->keytable, sizeof(keytable));
|
||||
|
||||
int found;
|
||||
while (!(brute & endmask)) {
|
||||
|
||||
found = __atomic_load_n (&loclass_found, __ATOMIC_SEQ_CST);
|
||||
|
||||
if (found != 0xFF) return NULL;
|
||||
|
||||
//Update the keytable with the brute-values
|
||||
for (uint8_t i = 0; i < numbytes_to_recover; i++) {
|
||||
keytable[bytes_to_recover[i]] &= 0xFF00;
|
||||
keytable[bytes_to_recover[i]] |= (brute >> (i * 8) & 0xFF);
|
||||
}
|
||||
|
||||
uint8_t key_sel[8] = {0};
|
||||
|
||||
// Piece together the key
|
||||
key_sel[0] = keytable[key_index[0]] & 0xFF;
|
||||
key_sel[1] = keytable[key_index[1]] & 0xFF;
|
||||
key_sel[2] = keytable[key_index[2]] & 0xFF;
|
||||
key_sel[3] = keytable[key_index[3]] & 0xFF;
|
||||
key_sel[4] = keytable[key_index[4]] & 0xFF;
|
||||
key_sel[5] = keytable[key_index[5]] & 0xFF;
|
||||
key_sel[6] = keytable[key_index[6]] & 0xFF;
|
||||
key_sel[7] = keytable[key_index[7]] & 0xFF;
|
||||
|
||||
// Permute from iclass format to standard format
|
||||
|
||||
uint8_t key_sel_p[8] = {0};
|
||||
permutekey_rev(key_sel, key_sel_p);
|
||||
|
||||
// Diversify
|
||||
uint8_t div_key[8] = {0};
|
||||
diversifyKey(csn, key_sel_p, div_key);
|
||||
|
||||
// Calc mac
|
||||
uint8_t calculated_MAC[4] = {0};
|
||||
doMAC(cc_nr, div_key, calculated_MAC);
|
||||
|
||||
// success
|
||||
if (memcmp(calculated_MAC, mac, 4) == 0) {
|
||||
|
||||
loclass_thread_ret_t *r = (loclass_thread_ret_t*)malloc(sizeof(loclass_thread_ret_t));
|
||||
|
||||
for (uint8_t i = 0 ; i < numbytes_to_recover; i++) {
|
||||
r->values[i] = keytable[bytes_to_recover[i]] & 0xFF;
|
||||
}
|
||||
__atomic_store_n(&loclass_found, targ->thread_idx, __ATOMIC_SEQ_CST);
|
||||
pthread_exit ((void*)r);
|
||||
}
|
||||
|
||||
brute += loclass_tc;
|
||||
|
||||
#define _CLR_ "\x1b[0K"
|
||||
|
||||
if (numbytes_to_recover == 3) {
|
||||
if ((brute > 0) && ((brute & 0xFFFF) == 0)) {
|
||||
PrintAndLogEx(INPLACE, "[ %02x %02x %02x ] %8u / %u", bytes_to_recover[0], bytes_to_recover[1], bytes_to_recover[2] , brute, 0xFFFFFF);
|
||||
}
|
||||
} else if (numbytes_to_recover == 2) {
|
||||
if ((brute > 0) && ((brute & 0x3F) == 0))
|
||||
PrintAndLogEx(INPLACE, "[ %02x %02x ] %5u / %u" _CLR_ , bytes_to_recover[0], bytes_to_recover[1], brute, 0xFFFF);
|
||||
} else {
|
||||
if ((brute > 0) && ((brute & 0x1F) == 0))
|
||||
PrintAndLogEx(INPLACE, "[ %02x ] %3u / %u" _CLR_, bytes_to_recover[0], brute, 0xFF);
|
||||
}
|
||||
}
|
||||
pthread_exit(NULL);
|
||||
|
||||
void* dummyptr = NULL;
|
||||
return dummyptr;
|
||||
}
|
||||
|
||||
int bruteforceItem(loclass_dumpdata_t item, uint16_t keytable[]) {
|
||||
|
||||
// reset thread signals
|
||||
loclass_found = 0xFF;
|
||||
|
||||
//Get the key index (hash1)
|
||||
uint8_t key_index[8] = {0};
|
||||
|
@ -317,13 +423,12 @@ int bruteforceItem(dumpdata item, uint16_t keytable[]) {
|
|||
* Only the lower eight bits correspond to the (hopefully cracked) key-value.
|
||||
**/
|
||||
uint8_t bytes_to_recover[3] = {0};
|
||||
uint8_t numbytes_to_recover = 0 ;
|
||||
int i;
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (keytable[key_index[i]] & (CRACKED | BEING_CRACKED)) continue;
|
||||
uint8_t numbytes_to_recover = 0;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
if (keytable[key_index[i]] & (LOCLASS_CRACKED | LOCLASS_BEING_CRACKED)) continue;
|
||||
|
||||
bytes_to_recover[numbytes_to_recover++] = key_index[i];
|
||||
keytable[key_index[i]] |= BEING_CRACKED;
|
||||
keytable[key_index[i]] |= LOCLASS_BEING_CRACKED;
|
||||
|
||||
if (numbytes_to_recover > 3) {
|
||||
PrintAndLogEx(FAILED, "The CSN requires > 3 byte bruteforce, not supported");
|
||||
|
@ -331,18 +436,141 @@ int bruteforceItem(dumpdata item, uint16_t keytable[]) {
|
|||
PrintAndLogEx(INFO, "HASH1 %s", sprint_hex(key_index, 8));
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
//Before we exit, reset the 'BEING_CRACKED' to zero
|
||||
keytable[bytes_to_recover[0]] &= ~BEING_CRACKED;
|
||||
keytable[bytes_to_recover[1]] &= ~BEING_CRACKED;
|
||||
keytable[bytes_to_recover[2]] &= ~BEING_CRACKED;
|
||||
keytable[bytes_to_recover[0]] &= ~LOCLASS_BEING_CRACKED;
|
||||
keytable[bytes_to_recover[1]] &= ~LOCLASS_BEING_CRACKED;
|
||||
keytable[bytes_to_recover[2]] &= ~LOCLASS_BEING_CRACKED;
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*A uint32 has room for 4 bytes, we'll only need 24 of those bits to bruteforce up to three bytes,
|
||||
if (numbytes_to_recover == 0) {
|
||||
PrintAndLogEx(INFO, "No bytes to recover, exiting");
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
loclass_thread_arg_t args[loclass_tc];
|
||||
// init thread arguments
|
||||
for (int i = 0; i < loclass_tc; i++) {
|
||||
args[i].thread_idx = i;
|
||||
args[i].numbytes_to_recover = numbytes_to_recover;
|
||||
args[i].endmask = 1 << 8 * numbytes_to_recover;
|
||||
|
||||
memcpy((void*)&args[i].item, (void*)&item, sizeof(loclass_dumpdata_t));
|
||||
memcpy(args[i].bytes_to_recover, bytes_to_recover, sizeof(args[i].bytes_to_recover) );
|
||||
memcpy(args[i].key_index, key_index, sizeof(args[i].key_index) );
|
||||
memcpy(args[i].keytable, keytable, sizeof(args[i].keytable));
|
||||
}
|
||||
|
||||
pthread_t threads[loclass_tc];
|
||||
// create threads
|
||||
for (int i = 0; i < loclass_tc; i++) {
|
||||
int res = pthread_create(&threads[i], NULL, bf_thread, (void *)&args[i]);
|
||||
if (res) {
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(WARNING, "Failed to create pthreads. Quitting");
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
}
|
||||
// wait for threads to terminate:
|
||||
void* ptrs[loclass_tc];
|
||||
for (int i = 0; i < loclass_tc; i++)
|
||||
pthread_join(threads[i], &ptrs[i]);
|
||||
|
||||
// was it a success?
|
||||
int res = PM3_SUCCESS;
|
||||
if (loclass_found == 0xFF) {
|
||||
res = PM3_ESOFT;
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(WARNING, "Failed to recover %d bytes using the following CSN", numbytes_to_recover);
|
||||
PrintAndLogEx(INFO, "CSN %s", sprint_hex(item.csn, 8));
|
||||
|
||||
//Before we exit, reset the 'BEING_CRACKED' to zero
|
||||
for (uint8_t i = 0; i < numbytes_to_recover; i++) {
|
||||
keytable[bytes_to_recover[i]] &= 0xFF;
|
||||
keytable[bytes_to_recover[i]] |= LOCLASS_CRACK_FAILED;
|
||||
}
|
||||
|
||||
} else {
|
||||
loclass_thread_ret_t ice = *((loclass_thread_ret_t *)ptrs[loclass_found]);
|
||||
|
||||
for (uint8_t i = 0; i < numbytes_to_recover; i++) {
|
||||
keytable[bytes_to_recover[i]] = ice.values[i];
|
||||
keytable[bytes_to_recover[i]] &= 0xFF;
|
||||
keytable[bytes_to_recover[i]] |= LOCLASS_CRACKED;
|
||||
}
|
||||
for (uint8_t i = 0; i < loclass_tc; i++) {
|
||||
free(ptrs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
memset(args, 0x00, sizeof(args));
|
||||
memset(threads, 0x00, sizeof(threads));
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Performs brute force attack against a dump-data item, containing csn, cc_nr and mac.
|
||||
*This method calculates the hash1 for the CSN, and determines what bytes need to be bruteforced
|
||||
*on the fly. If it finds that more than three bytes need to be bruteforced, it aborts.
|
||||
*It updates the keytable with the findings, also using the upper half of the 16-bit ints
|
||||
*to signal if the particular byte has been cracked or not.
|
||||
*
|
||||
* @param dump The dumpdata from iclass reader attack.
|
||||
* @param keytable where to write found values.
|
||||
* @return
|
||||
*/
|
||||
//uint32_t brute = startvalue;
|
||||
/*
|
||||
int bruteforceItem(loclass_dumpdata_t item, uint16_t keytable[]) {
|
||||
|
||||
//Get the key index (hash1)
|
||||
uint8_t key_index[8] = {0};
|
||||
hash1(item.csn, key_index);
|
||||
*/
|
||||
/*
|
||||
* Determine which bytes to retrieve. A hash is typically
|
||||
* 01010000454501
|
||||
* We go through that hash, and in the corresponding keytable, we put markers
|
||||
* on what state that particular index is:
|
||||
* - CRACKED (this has already been cracked)
|
||||
* - BEING_CRACKED (this is being bruteforced now)
|
||||
* - CRACK_FAILED (self-explaining...)
|
||||
*
|
||||
* The markers are placed in the high area of the 16 bit key-table.
|
||||
* Only the lower eight bits correspond to the (hopefully cracked) key-value.
|
||||
**/
|
||||
|
||||
|
||||
/*
|
||||
uint8_t bytes_to_recover[3] = {0};
|
||||
uint8_t numbytes_to_recover = 0 ;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
if (keytable[key_index[i]] & (LOCLASS_CRACKED | LOCLASS_BEING_CRACKED)) continue;
|
||||
|
||||
bytes_to_recover[numbytes_to_recover++] = key_index[i];
|
||||
keytable[key_index[i]] |= LOCLASS_BEING_CRACKED;
|
||||
|
||||
if (numbytes_to_recover > 3) {
|
||||
PrintAndLogEx(FAILED, "The CSN requires > 3 byte bruteforce, not supported");
|
||||
PrintAndLogEx(INFO, "CSN %s", sprint_hex(item.csn, 8));
|
||||
PrintAndLogEx(INFO, "HASH1 %s", sprint_hex(key_index, 8));
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
//Before we exit, reset the 'BEING_CRACKED' to zero
|
||||
keytable[bytes_to_recover[0]] &= ~LOCLASS_BEING_CRACKED;
|
||||
keytable[bytes_to_recover[1]] &= ~LOCLASS_BEING_CRACKED;
|
||||
keytable[bytes_to_recover[2]] &= ~LOCLASS_BEING_CRACKED;
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t key_sel_p[8] = {0};
|
||||
uint8_t div_key[8] = {0};
|
||||
uint8_t key_sel[8] = {0};
|
||||
uint8_t calculated_MAC[4] = {0};
|
||||
|
||||
|
||||
//A uint32 has room for 4 bytes, we'll only need 24 of those bits to bruteforce up to three bytes,
|
||||
uint32_t brute = 0;
|
||||
*/
|
||||
/*
|
||||
Determine where to stop the bruteforce. A 1-byte attack stops after 256 tries,
|
||||
(when brute reaches 0x100). And so on...
|
||||
|
@ -350,16 +578,17 @@ int bruteforceItem(dumpdata item, uint16_t keytable[]) {
|
|||
bytes_to_recover = 2 --> endmask = 0x000010000
|
||||
bytes_to_recover = 3 --> endmask = 0x001000000
|
||||
*/
|
||||
|
||||
/*
|
||||
uint32_t endmask = 1 << 8 * numbytes_to_recover;
|
||||
PrintAndLogEx(NORMAL, "----------------------------");
|
||||
for (i = 0 ; i < numbytes_to_recover && numbytes_to_recover > 1; i++)
|
||||
PrintAndLogEx(INFO, "Bruteforcing byte %d", bytes_to_recover[i]);
|
||||
for (uint8_t i = 0 ; i < numbytes_to_recover && numbytes_to_recover > 1; i++)
|
||||
PrintAndLogEx(INFO, "Bruteforcing %d", bytes_to_recover[i]);
|
||||
|
||||
bool found = false;
|
||||
while (!found && !(brute & endmask)) {
|
||||
|
||||
//Update the keytable with the brute-values
|
||||
for (i = 0; i < numbytes_to_recover; i++) {
|
||||
for (uint8_t i = 0; i < numbytes_to_recover; i++) {
|
||||
keytable[bytes_to_recover[i]] &= 0xFF00;
|
||||
keytable[bytes_to_recover[i]] |= (brute >> (i * 8) & 0xFF);
|
||||
}
|
||||
|
@ -376,16 +605,15 @@ int bruteforceItem(dumpdata item, uint16_t keytable[]) {
|
|||
|
||||
//Permute from iclass format to standard format
|
||||
permutekey_rev(key_sel, key_sel_p);
|
||||
//Diversify
|
||||
|
||||
diversifyKey(item.csn, key_sel_p, div_key);
|
||||
//Calc mac
|
||||
doMAC(item.cc_nr, div_key, calculated_MAC);
|
||||
|
||||
// success
|
||||
if (memcmp(calculated_MAC, item.mac, 4) == 0) {
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
for (i = 0 ; i < numbytes_to_recover; i++) {
|
||||
PrintAndLogEx(INFO, "%d: 0x%02x", bytes_to_recover[i], 0xFF & keytable[bytes_to_recover[i]]);
|
||||
for (uint8_t i = 0 ; i < numbytes_to_recover; i++) {
|
||||
PrintAndLogEx(SUCCESS, "%d: 0x%02x", bytes_to_recover[i], keytable[bytes_to_recover[i]] & 0xFF);
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
|
@ -393,9 +621,7 @@ int bruteforceItem(dumpdata item, uint16_t keytable[]) {
|
|||
|
||||
brute++;
|
||||
if ((brute & 0xFFFF) == 0) {
|
||||
PrintAndLogEx(NORMAL, "%3d," NOLF, (brute >> 16) & 0xFF);
|
||||
if (((brute >> 16) % 0x10) == 0)
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(INPLACE, "%3d", (brute >> 16) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -408,19 +634,20 @@ int bruteforceItem(dumpdata item, uint16_t keytable[]) {
|
|||
errors = PM3_ESOFT;
|
||||
|
||||
//Before we exit, reset the 'BEING_CRACKED' to zero
|
||||
for (i = 0; i < numbytes_to_recover; i++) {
|
||||
for (uint8_t i = 0; i < numbytes_to_recover; i++) {
|
||||
keytable[bytes_to_recover[i]] &= 0xFF;
|
||||
keytable[bytes_to_recover[i]] |= CRACK_FAILED;
|
||||
keytable[bytes_to_recover[i]] |= LOCLASS_CRACK_FAILED;
|
||||
}
|
||||
} else {
|
||||
//PrintAndLogEx(SUCCESS, "DES calcs: %u", brute);
|
||||
for (i = 0; i < numbytes_to_recover; i++) {
|
||||
for (uint8_t i = 0; i < numbytes_to_recover; i++) {
|
||||
keytable[bytes_to_recover[i]] &= 0xFF;
|
||||
keytable[bytes_to_recover[i]] |= CRACKED;
|
||||
keytable[bytes_to_recover[i]] |= LOCLASS_CRACKED;
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* From dismantling iclass-paper:
|
||||
|
@ -434,7 +661,7 @@ int bruteforceItem(dumpdata item, uint16_t keytable[]) {
|
|||
* @param master_key where to put the master key
|
||||
* @return 0 for ok, 1 for failz
|
||||
*/
|
||||
int calculateMasterKey(uint8_t first16bytes[], uint64_t master_key[]) {
|
||||
int calculateMasterKey(uint8_t first16bytes[], uint8_t kcus[]) {
|
||||
mbedtls_des_context ctx_e;
|
||||
|
||||
uint8_t z_0[8] = {0};
|
||||
|
@ -457,9 +684,14 @@ int calculateMasterKey(uint8_t first16bytes[], uint64_t master_key[]) {
|
|||
mbedtls_des_setkey_enc(&ctx_e, z_0_rev);
|
||||
mbedtls_des_crypt_ecb(&ctx_e, y_0, key64_negated);
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 8 ; i++)
|
||||
key64[i] = ~key64_negated[i];
|
||||
key64[0] = ~key64_negated[0];
|
||||
key64[1] = ~key64_negated[1];
|
||||
key64[2] = ~key64_negated[2];
|
||||
key64[3] = ~key64_negated[3];
|
||||
key64[4] = ~key64_negated[4];
|
||||
key64[5] = ~key64_negated[5];
|
||||
key64[6] = ~key64_negated[6];
|
||||
key64[7] = ~key64_negated[7];
|
||||
|
||||
// Can we verify that the key is correct?
|
||||
// Once again, key is on iclass-format
|
||||
|
@ -468,21 +700,20 @@ int calculateMasterKey(uint8_t first16bytes[], uint64_t master_key[]) {
|
|||
|
||||
mbedtls_des_setkey_enc(&ctx_e, key64_stdformat);
|
||||
mbedtls_des_crypt_ecb(&ctx_e, key64_negated, result);
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(SUCCESS, "-- High security custom key (Kcus) --");
|
||||
PrintAndLogEx(SUCCESS, "Standard format " _GREEN_("%s"), sprint_hex(key64_stdformat, 8));
|
||||
PrintAndLogEx(SUCCESS, "iClass format %s", sprint_hex(key64, 8));
|
||||
|
||||
if (master_key != NULL)
|
||||
memcpy(master_key, key64, 8);
|
||||
if (kcus != NULL)
|
||||
memcpy(kcus, key64, 8);
|
||||
|
||||
PrintAndLogEx(NORMAL, "\n");
|
||||
if (memcmp(z_0, result, 4) != 0) {
|
||||
PrintAndLogEx(WARNING, _RED_("Failed to verify") " calculated master key (k_cus)! Something is wrong.");
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
PrintAndLogEx(SUCCESS, _GREEN_("Key verified ok!"));
|
||||
PrintAndLogEx(SUCCESS, "----- " _CYAN_("High security custom key (Kcus)") " -----");
|
||||
PrintAndLogEx(SUCCESS, "Standard format %s", sprint_hex(key64_stdformat, 8));
|
||||
PrintAndLogEx(SUCCESS, "iCLASS format " _GREEN_("%s"), sprint_hex(key64, 8));
|
||||
PrintAndLogEx(SUCCESS, "Key verified (" _GREEN_("ok") ")");
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
/**
|
||||
|
@ -494,25 +725,29 @@ int calculateMasterKey(uint8_t first16bytes[], uint64_t master_key[]) {
|
|||
*/
|
||||
int bruteforceDump(uint8_t dump[], size_t dumpsize, uint16_t keytable[]) {
|
||||
uint8_t i;
|
||||
size_t itemsize = sizeof(dumpdata);
|
||||
uint64_t t1 = msclock();
|
||||
|
||||
dumpdata *attack = (dumpdata *) calloc(itemsize, sizeof(uint8_t));
|
||||
size_t itemsize = sizeof(loclass_dumpdata_t);
|
||||
loclass_dumpdata_t *attack = (loclass_dumpdata_t *) calloc(itemsize, sizeof(uint8_t));
|
||||
if (attack == NULL) {
|
||||
PrintAndLogEx(WARNING, "failed to allocate memory");
|
||||
return PM3_EMALLOC;
|
||||
}
|
||||
|
||||
loclass_tc = num_CPUs();
|
||||
PrintAndLogEx(INFO, "bruteforce using " _YELLOW_("%zu") " threads", loclass_tc);
|
||||
|
||||
int res = 0;
|
||||
|
||||
uint64_t t1 = msclock();
|
||||
for (i = 0 ; i * itemsize < dumpsize ; i++) {
|
||||
memcpy(attack, dump + i * itemsize, itemsize);
|
||||
res += bruteforceItem(*attack, keytable);
|
||||
res = bruteforceItem(*attack, keytable);
|
||||
if (res != PM3_SUCCESS)
|
||||
break;
|
||||
}
|
||||
free(attack);
|
||||
t1 = msclock() - t1;
|
||||
PrintAndLogEx(SUCCESS, "time: %" PRIu64 " seconds", t1 / 1000);
|
||||
PrintAndLogEx(NORMAL, "");
|
||||
PrintAndLogEx(SUCCESS, "time " _YELLOW_("%" PRIu64) " seconds", t1 / 1000);
|
||||
|
||||
if (res != PM3_SUCCESS) {
|
||||
PrintAndLogEx(ERR, "loclass exiting. Try run " _YELLOW_("`hf iclass sim -t 2`") " again and collect new data");
|
||||
|
@ -524,11 +759,10 @@ int bruteforceDump(uint8_t dump[], size_t dumpsize, uint16_t keytable[]) {
|
|||
// indicate crack-status. Those must be discarded for the
|
||||
// master key calculation
|
||||
uint8_t first16bytes[16] = {0};
|
||||
|
||||
for (i = 0 ; i < 16 ; i++) {
|
||||
first16bytes[i] = keytable[i] & 0xFF;
|
||||
|
||||
if (!(keytable[i] & CRACKED)) {
|
||||
if ((keytable[i] & LOCLASS_CRACKED) != LOCLASS_CRACKED) {
|
||||
PrintAndLogEx(WARNING, "Warning: we are missing byte %d, custom key calculation will fail...", i);
|
||||
return PM3_ESOFT;
|
||||
}
|
||||
|
@ -620,7 +854,7 @@ static int _test_iclass_key_permutation(void) {
|
|||
return PM3_ESOFT;
|
||||
}
|
||||
|
||||
PrintAndLogEx(SUCCESS, "Iclass key permutation (%s)", _GREEN_("OK"));
|
||||
PrintAndLogEx(SUCCESS, " Iclass key permutation (%s)", _GREEN_("ok"));
|
||||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -640,8 +874,8 @@ static int _testHash1(void) {
|
|||
}
|
||||
|
||||
int testElite(bool slowtests) {
|
||||
PrintAndLogEx(INFO, "Testing iClass Elite functinality...");
|
||||
PrintAndLogEx(INFO, "Testing hash2");
|
||||
PrintAndLogEx(INFO, "Testing iClass Elite functionality");
|
||||
PrintAndLogEx(INFO, "Testing hash2...");
|
||||
uint8_t k_cus[8] = {0x5B, 0x7C, 0x62, 0xC4, 0x91, 0xC1, 0x1B, 0x39};
|
||||
|
||||
/**
|
||||
|
@ -661,20 +895,19 @@ int testElite(bool slowtests) {
|
|||
*/
|
||||
uint8_t keytable[128] = {0};
|
||||
hash2(k_cus, keytable);
|
||||
printarr_human_readable("Hash2", keytable, 128);
|
||||
printarr_human_readable("---------------------- Hash2 ----------------------", keytable, sizeof(keytable));
|
||||
if (keytable[3] == 0xA1 && keytable[0x30] == 0xA3 && keytable[0x6F] == 0x95) {
|
||||
PrintAndLogEx(SUCCESS, " Hash2 (%s)", _GREEN_("ok"));
|
||||
PrintAndLogEx(SUCCESS, " hash2 (%s)", _GREEN_("ok"));
|
||||
}
|
||||
|
||||
int res = PM3_SUCCESS;
|
||||
PrintAndLogEx(INFO, "Testing hash1...");
|
||||
res += _testHash1();
|
||||
PrintAndLogEx(INFO, " hash1 (%s)", (res == PM3_SUCCESS) ? _GREEN_("ok") : _RED_("fail"));
|
||||
PrintAndLogEx((res == PM3_SUCCESS) ? SUCCESS : WARNING, " hash1 (%s)", (res == PM3_SUCCESS) ? _GREEN_("ok") : _RED_("fail") );
|
||||
|
||||
PrintAndLogEx(INFO, "Testing key diversification...");
|
||||
res += _test_iclass_key_permutation();
|
||||
if (res == PM3_SUCCESS)
|
||||
PrintAndLogEx(INFO, " key diversification (%s)", (res == PM3_SUCCESS) ? _GREEN_("ok") : _RED_("fail"));
|
||||
PrintAndLogEx((res == PM3_SUCCESS) ? SUCCESS : WARNING, " key diversification (%s)", (res == PM3_SUCCESS) ? _GREEN_("ok") : _RED_("fail") );
|
||||
|
||||
if (slowtests)
|
||||
res += _testBruteforce();
|
||||
|
|
|
@ -38,6 +38,22 @@
|
|||
|
||||
#ifndef ELITE_CRACK_H
|
||||
#define ELITE_CRACK_H
|
||||
|
||||
//Crack status, see below
|
||||
#define LOCLASS_CRACKED 0x0100
|
||||
#define LOCLASS_BEING_CRACKED 0x0200
|
||||
#define LOCLASS_CRACK_FAILED 0x0400
|
||||
|
||||
/**
|
||||
This is how we expect each 'entry' in a dumpfile to look
|
||||
**/
|
||||
typedef struct {
|
||||
uint8_t csn[8];
|
||||
uint8_t cc_nr[12];
|
||||
uint8_t mac[4];
|
||||
} loclass_dumpdata_t;
|
||||
|
||||
|
||||
void permutekey(uint8_t key[8], uint8_t dest[8]);
|
||||
/**
|
||||
* Permutes a key from iclass specific format to NIST format
|
||||
|
@ -46,10 +62,6 @@ void permutekey(uint8_t key[8], uint8_t dest[8]);
|
|||
* @param dest
|
||||
*/
|
||||
void permutekey_rev(uint8_t key[8], uint8_t dest[8]);
|
||||
//Crack status, see below
|
||||
#define CRACKED 0x0100
|
||||
#define BEING_CRACKED 0x0200
|
||||
#define CRACK_FAILED 0x0400
|
||||
|
||||
/**
|
||||
* Perform a bruteforce against a file which has been saved by pm3
|
||||
|
@ -69,7 +81,7 @@ int bruteforceFile(const char *filename, uint16_t keytable[]);
|
|||
*/
|
||||
int bruteforceFileNoKeys(const char *filename);
|
||||
/**
|
||||
* @brief Same as bruteforcefile, but uses a an array of dumpdata instead
|
||||
* @brief Same as bruteforcefile, but uses a an array of loclass_dumpdata_t instead
|
||||
* @param dump
|
||||
* @param dumpsize
|
||||
* @param keytable
|
||||
|
@ -77,15 +89,6 @@ int bruteforceFileNoKeys(const char *filename);
|
|||
*/
|
||||
int bruteforceDump(uint8_t dump[], size_t dumpsize, uint16_t keytable[]);
|
||||
|
||||
/**
|
||||
This is how we expect each 'entry' in a dumpfile to look
|
||||
**/
|
||||
typedef struct {
|
||||
uint8_t csn[8];
|
||||
uint8_t cc_nr[12];
|
||||
uint8_t mac[4];
|
||||
} dumpdata;
|
||||
|
||||
/**
|
||||
* @brief Performs brute force attack against a dump-data item, containing csn, cc_nr and mac.
|
||||
*This method calculates the hash1 for the CSN, and determines what bytes need to be bruteforced
|
||||
|
@ -93,11 +96,11 @@ typedef struct {
|
|||
*It updates the keytable with the findings, also using the upper half of the 16-bit ints
|
||||
*to signal if the particular byte has been cracked or not.
|
||||
*
|
||||
* @param dump The dumpdata from iclass reader attack.
|
||||
* @param loclass_dumpdata_t The dumpdata from iclass reader attack.
|
||||
* @param keytable where to write found values.
|
||||
* @return
|
||||
*/
|
||||
int bruteforceItem(dumpdata item, uint16_t keytable[]);
|
||||
int bruteforceItem(loclass_dumpdata_t item, uint16_t keytable[]);
|
||||
/**
|
||||
* Hash1 takes CSN as input, and determines what bytes in the keytable will be used
|
||||
* when constructing the K_sel.
|
||||
|
@ -118,7 +121,7 @@ void hash2(uint8_t *key64, uint8_t *outp_keytable);
|
|||
* @param master_key where to put the master key
|
||||
* @return 0 for ok, 1 for failz
|
||||
*/
|
||||
int calculateMasterKey(uint8_t first16bytes[], uint64_t master_key[]);
|
||||
int calculateMasterKey(uint8_t first16bytes[], uint8_t master_key[]);
|
||||
|
||||
/**
|
||||
* @brief Test function
|
||||
|
@ -141,5 +144,4 @@ int testElite(bool slowtests);
|
|||
|
||||
**/
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -70,7 +70,7 @@ From "Dismantling iclass":
|
|||
|
||||
#include "fileutils.h"
|
||||
#include "cipherutils.h"
|
||||
#include "des.h"
|
||||
#include "mbedtls/des.h"
|
||||
|
||||
uint8_t pi[35] = {
|
||||
0x0F, 0x17, 0x1B, 0x1D, 0x1E, 0x27, 0x2B, 0x2D,
|
||||
|
@ -80,9 +80,6 @@ uint8_t pi[35] = {
|
|||
0x72, 0x74, 0x78
|
||||
};
|
||||
|
||||
static mbedtls_des_context ctx_enc;
|
||||
static mbedtls_des_context ctx_dec;
|
||||
|
||||
/**
|
||||
* @brief The key diversification algorithm uses 6-bit bytes.
|
||||
* This implementation uses 64 bit uint to pack seven of them into one
|
||||
|
@ -94,16 +91,19 @@ static mbedtls_des_context ctx_dec;
|
|||
* @param n bitnumber
|
||||
* @return
|
||||
*/
|
||||
static uint8_t getSixBitByte(uint64_t c, int n) {
|
||||
#define getSixBitByte(c, n) ((uint8_t)(((c) >> (42 - 6 * (n))) & 0x3F))
|
||||
/*
|
||||
static inline uint8_t getSixBitByte(uint64_t c, int n) {
|
||||
return (c >> (42 - 6 * n)) & 0x3F;
|
||||
}
|
||||
|
||||
*/
|
||||
/**
|
||||
* @brief Puts back a six-bit 'byte' into a uint64_t.
|
||||
* @param c buffer
|
||||
* @param z the value to place there
|
||||
* @param n bitnumber.
|
||||
*/
|
||||
|
||||
static void pushbackSixBitByte(uint64_t *c, uint8_t z, int n) {
|
||||
//0x XXXX YYYY ZZZZ ZZZZ ZZZZ
|
||||
// ^z0 ^z7
|
||||
|
@ -210,14 +210,14 @@ static void permute(BitstreamIn *p_in, uint64_t z, int l, int r, BitstreamOut *o
|
|||
return;
|
||||
|
||||
bool pn = tailBit(p_in);
|
||||
if (pn) { // pn = 1
|
||||
if (pn) {
|
||||
// pn = 1
|
||||
uint8_t zl = getSixBitByte(z, l);
|
||||
|
||||
push6bits(out, zl + 1);
|
||||
permute(p_in, z, l + 1, r, out);
|
||||
} else { // otherwise
|
||||
} else {
|
||||
// otherwise
|
||||
uint8_t zr = getSixBitByte(z, r);
|
||||
|
||||
push6bits(out, zr);
|
||||
permute(p_in, z, l, r + 1, out);
|
||||
}
|
||||
|
@ -226,6 +226,7 @@ static void permute(BitstreamIn *p_in, uint64_t z, int l, int r, BitstreamOut *o
|
|||
static void printState(const char *desc, uint64_t c) {
|
||||
if (g_debugMode == 0)
|
||||
return;
|
||||
|
||||
char s[60] = {0};
|
||||
snprintf(s, sizeof(s), "%s : ", desc);
|
||||
|
||||
|
@ -254,8 +255,10 @@ static void printState(const char *desc, uint64_t c) {
|
|||
void hash0(uint64_t c, uint8_t k[8]) {
|
||||
c = swapZvalues(c);
|
||||
|
||||
if (g_debugMode > 0) {
|
||||
PrintAndLogEx(DEBUG, " | x| y|z0|z1|z2|z3|z4|z5|z6|z7|");
|
||||
printState("origin", c);
|
||||
}
|
||||
//These 64 bits are divided as c = x, y, z [0] , . . . , z [7]
|
||||
// x = 8 bits
|
||||
// y = 8 bits
|
||||
|
@ -266,9 +269,7 @@ void hash0(uint64_t c, uint8_t k[8]) {
|
|||
|
||||
for (int n = 0; n < 4 ; n++) {
|
||||
uint8_t zn = getSixBitByte(c, n);
|
||||
|
||||
uint8_t zn4 = getSixBitByte(c, n + 4);
|
||||
|
||||
uint8_t _zn = (zn % (63 - n)) + n;
|
||||
uint8_t _zn4 = (zn4 % (64 - n)) + n;
|
||||
|
||||
|
@ -276,17 +277,18 @@ void hash0(uint64_t c, uint8_t k[8]) {
|
|||
pushbackSixBitByte(&zP, _zn4, n + 4);
|
||||
}
|
||||
|
||||
printState("0|0|z'", zP);
|
||||
if (g_debugMode > 0) printState("0|0|z'", zP);
|
||||
|
||||
uint64_t zCaret = check(zP);
|
||||
printState("0|0|z^", zP);
|
||||
|
||||
if (g_debugMode > 0) printState("0|0|z^", zP);
|
||||
|
||||
uint8_t p = pi[x % 35];
|
||||
|
||||
if (x & 1) //Check if x7 is 1
|
||||
p = ~p;
|
||||
|
||||
PrintAndLogEx(DEBUG, "p: %02x", p);
|
||||
if (g_debugMode > 0) PrintAndLogEx(DEBUG, " p : %02x", p);
|
||||
|
||||
BitstreamIn p_in = { &p, 8, 0 };
|
||||
uint8_t outbuffer[] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
@ -301,9 +303,8 @@ void hash0(uint64_t c, uint8_t k[8]) {
|
|||
|
||||
zTilde >>= 16;
|
||||
|
||||
printState("0|0|z~", zTilde);
|
||||
if (g_debugMode > 0) printState("0|0|z~", zTilde);
|
||||
|
||||
// int zerocounter = 0 ;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
// the key on index i is first a bit from y
|
||||
// then six bits from z,
|
||||
|
@ -317,8 +318,6 @@ void hash0(uint64_t c, uint8_t k[8]) {
|
|||
// First, place y(7-i) leftmost in k
|
||||
k[i] |= (y << (7 - i)) & 0x80 ;
|
||||
|
||||
|
||||
|
||||
uint8_t zTilde_i = getSixBitByte(zTilde, i);
|
||||
// zTildeI is now on the form 00XXXXXX
|
||||
// with one leftshift, it'll be
|
||||
|
@ -342,9 +341,6 @@ void hash0(uint64_t c, uint8_t k[8]) {
|
|||
k[i] |= zTilde_i & 0x7E;
|
||||
k[i] |= (~p_i) & 1;
|
||||
}
|
||||
// if ((k[i] & 1) == 0) {
|
||||
// zerocounter++;
|
||||
// }
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
@ -354,18 +350,17 @@ void hash0(uint64_t c, uint8_t k[8]) {
|
|||
* @param div_key
|
||||
*/
|
||||
void diversifyKey(uint8_t *csn, uint8_t *key, uint8_t *div_key) {
|
||||
// Prepare the DES key
|
||||
mbedtls_des_setkey_enc(&ctx_enc, key);
|
||||
|
||||
uint8_t crypted_csn[8] = {0};
|
||||
|
||||
// Calculate DES(CSN, KEY)
|
||||
mbedtls_des_context ctx_enc;
|
||||
mbedtls_des_setkey_enc(&ctx_enc, key);
|
||||
mbedtls_des_crypt_ecb(&ctx_enc, csn, crypted_csn);
|
||||
mbedtls_des_free(&ctx_enc);
|
||||
|
||||
//Calculate HASH0(DES))
|
||||
uint64_t c_csn = x_bytes_to_num(crypted_csn, sizeof(crypted_csn));
|
||||
//uint64_t crypted_csn_swapped = swapZvalues(crypt_csn);
|
||||
|
||||
hash0(c_csn, div_key);
|
||||
}
|
||||
/*
|
||||
|
@ -420,28 +415,38 @@ typedef struct {
|
|||
uint8_t uid[8];
|
||||
uint8_t t_key[8];
|
||||
uint8_t div_key[8];
|
||||
} Testcase;
|
||||
} testcase_t;
|
||||
|
||||
static int testDES(Testcase testcase) {
|
||||
static int testDES(uint8_t *key, testcase_t testcase) {
|
||||
uint8_t des_encrypted_csn[8] = {0};
|
||||
uint8_t decrypted[8] = {0};
|
||||
uint8_t div_key[8] = {0};
|
||||
|
||||
mbedtls_des_context ctx_enc;
|
||||
mbedtls_des_context ctx_dec;
|
||||
|
||||
mbedtls_des_setkey_enc(&ctx_enc, key);
|
||||
mbedtls_des_setkey_dec(&ctx_dec, key);
|
||||
|
||||
int retval = mbedtls_des_crypt_ecb(&ctx_enc, testcase.uid, des_encrypted_csn);
|
||||
retval |= mbedtls_des_crypt_ecb(&ctx_dec, des_encrypted_csn, decrypted);
|
||||
|
||||
mbedtls_des_free(&ctx_enc);
|
||||
mbedtls_des_free(&ctx_dec);
|
||||
|
||||
if (memcmp(testcase.uid, decrypted, 8) != 0) {
|
||||
//Decryption fail
|
||||
PrintAndLogEx(FAILED, "Encryption <-> Decryption FAIL");
|
||||
printarr("Input", testcase.uid, 8);
|
||||
printarr("Decrypted", decrypted, 8);
|
||||
printarr(" input", testcase.uid, 8);
|
||||
printarr(" decrypted", decrypted, 8);
|
||||
retval = 1;
|
||||
}
|
||||
|
||||
if (memcmp(des_encrypted_csn, testcase.t_key, 8) != 0) {
|
||||
//Encryption fail
|
||||
PrintAndLogEx(FAILED, "Encryption != Expected result");
|
||||
printarr("Output", des_encrypted_csn, 8);
|
||||
printarr("Expected", testcase.t_key, 8);
|
||||
printarr(" output", des_encrypted_csn, 8);
|
||||
printarr(" expected", testcase.t_key, 8);
|
||||
retval = 1;
|
||||
}
|
||||
uint64_t crypted_csn = x_bytes_to_num(des_encrypted_csn, 8);
|
||||
|
@ -453,7 +458,7 @@ static int testDES(Testcase testcase) {
|
|||
printarr(" csn ", testcase.uid, 8);
|
||||
printarr("{csn} ", des_encrypted_csn, 8);
|
||||
printarr("hash0 ", div_key, 8);
|
||||
printarr("Expected", testcase.div_key, 8);
|
||||
printarr(" expected", testcase.div_key, 8);
|
||||
retval = 1;
|
||||
}
|
||||
return retval;
|
||||
|
@ -461,9 +466,12 @@ static int testDES(Testcase testcase) {
|
|||
static bool des_getParityBitFromKey(uint8_t key) {
|
||||
// The top 7 bits is used
|
||||
bool parity = ((key & 0x80) >> 7)
|
||||
^ ((key & 0x40) >> 6) ^ ((key & 0x20) >> 5)
|
||||
^ ((key & 0x10) >> 4) ^ ((key & 0x08) >> 3)
|
||||
^ ((key & 0x04) >> 2) ^ ((key & 0x02) >> 1);
|
||||
^ ((key & 0x40) >> 6)
|
||||
^ ((key & 0x20) >> 5)
|
||||
^ ((key & 0x10) >> 4)
|
||||
^ ((key & 0x08) >> 3)
|
||||
^ ((key & 0x04) >> 2)
|
||||
^ ((key & 0x02) >> 1);
|
||||
return !parity;
|
||||
}
|
||||
|
||||
|
@ -480,11 +488,11 @@ static void des_checkParity(uint8_t *key) {
|
|||
if (fails) {
|
||||
PrintAndLogEx(FAILED, "parity fails: %d", fails);
|
||||
} else {
|
||||
PrintAndLogEx(SUCCESS, "Key syntax is with parity bits inside each byte");
|
||||
PrintAndLogEx(SUCCESS, " Key syntax is with parity bits inside each byte (%s)", _GREEN_("ok"));
|
||||
}
|
||||
}
|
||||
|
||||
Testcase testcases[] = {
|
||||
testcase_t testcases[] = {
|
||||
|
||||
{{0x8B, 0xAC, 0x60, 0x1F, 0x53, 0xB8, 0xED, 0x11}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, {0x02, 0x04, 0x06, 0x08, 0x01, 0x03, 0x05, 0x07}},
|
||||
{{0xAE, 0x51, 0xE5, 0x62, 0xE7, 0x9A, 0x99, 0x39}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}, {0x04, 0x02, 0x06, 0x08, 0x01, 0x03, 0x05, 0x07}},
|
||||
|
@ -555,14 +563,14 @@ Testcase testcases[] = {
|
|||
{{0}, {0}, {0}}
|
||||
};
|
||||
|
||||
static int testKeyDiversificationWithMasterkeyTestcases(void) {
|
||||
static int testKeyDiversificationWithMasterkeyTestcases(uint8_t *key) {
|
||||
int i, error = 0;
|
||||
uint8_t empty[8] = {0};
|
||||
|
||||
PrintAndLogEx(INFO, "Testing encryption/decryption");
|
||||
|
||||
for (i = 0; memcmp(testcases + i, empty, 8); i++)
|
||||
error += testDES(testcases[i]);
|
||||
error += testDES(key, testcases[i]);
|
||||
|
||||
if (error)
|
||||
PrintAndLogEx(FAILED, "%d errors occurred (%d testcases)", error, i);
|
||||
|
@ -581,7 +589,7 @@ static int testCryptedCSN(uint64_t crypted_csn, uint64_t expected) {
|
|||
PrintAndLogEx(DEBUG, "");
|
||||
PrintAndLogEx(DEBUG, " {csn} %"PRIx64, crypted_csn);
|
||||
PrintAndLogEx(DEBUG, " {csn-revz} %"PRIx64, crypted_csn_swapped);
|
||||
PrintAndLogEx(DEBUG, " hash0 %"PRIx64 " (%s)", resultbyte, (resultbyte == expected) ? _GREEN_("OK") : _RED_("FAIL"));
|
||||
PrintAndLogEx(DEBUG, " hash0 %"PRIx64 " (%s)", resultbyte, (resultbyte == expected) ? _GREEN_("ok") : _RED_("fail"));
|
||||
|
||||
if (resultbyte != expected) {
|
||||
PrintAndLogEx(DEBUG, " expected " _YELLOW_("%"PRIx64), expected);
|
||||
|
@ -590,19 +598,22 @@ static int testCryptedCSN(uint64_t crypted_csn, uint64_t expected) {
|
|||
return PM3_SUCCESS;
|
||||
}
|
||||
|
||||
static int testDES2(uint64_t csn, uint64_t expected) {
|
||||
static int testDES2(uint8_t *key, uint64_t csn, uint64_t expected) {
|
||||
uint8_t result[8] = {0};
|
||||
uint8_t input[8] = {0};
|
||||
|
||||
PrintAndLogEx(DEBUG, " csn %"PRIx64, csn);
|
||||
x_num_to_bytes(csn, 8, input);
|
||||
|
||||
mbedtls_des_context ctx_enc;
|
||||
mbedtls_des_setkey_enc(&ctx_enc, key);
|
||||
mbedtls_des_crypt_ecb(&ctx_enc, input, result);
|
||||
mbedtls_des_free(&ctx_enc);
|
||||
|
||||
uint64_t crypt_csn = x_bytes_to_num(result, 8);
|
||||
|
||||
PrintAndLogEx(DEBUG, " {csn} %"PRIx64, crypt_csn);
|
||||
PrintAndLogEx(DEBUG, " expected %"PRIx64 " (%s)", expected, (expected == crypt_csn) ? _GREEN_("OK") : _RED_("FAIL"));
|
||||
PrintAndLogEx(DEBUG, " expected %"PRIx64 " (%s)", expected, (expected == crypt_csn) ? _GREEN_("ok") : _RED_("fail") );
|
||||
|
||||
if (expected != crypt_csn)
|
||||
return PM3_ESOFT;
|
||||
|
@ -619,8 +630,7 @@ static int doTestsWithKnownInputs(void) {
|
|||
PrintAndLogEx(INFO, "Testing DES encryption");
|
||||
uint8_t key[8] = {0x6c, 0x8d, 0x44, 0xf9, 0x2a, 0x2d, 0x01, 0xbf};
|
||||
|
||||
mbedtls_des_setkey_enc(&ctx_enc, key);
|
||||
testDES2(0xbbbbaaaabbbbeeee, 0xd6ad3ca619659e6b);
|
||||
testDES2(key, 0xbbbbaaaabbbbeeee, 0xd6ad3ca619659e6b);
|
||||
|
||||
PrintAndLogEx(INFO, "Testing hashing algorithm");
|
||||
|
||||
|
@ -681,11 +691,10 @@ int doKeyTests(void) {
|
|||
PrintAndLogEx(SUCCESS, "Key present");
|
||||
PrintAndLogEx(SUCCESS, "Checking key parity...");
|
||||
des_checkParity(key);
|
||||
mbedtls_des_setkey_enc(&ctx_enc, key);
|
||||
mbedtls_des_setkey_dec(&ctx_dec, key);
|
||||
|
||||
// Test hashing functions
|
||||
PrintAndLogEx(SUCCESS, "The following tests require the correct 8-byte master key");
|
||||
testKeyDiversificationWithMasterkeyTestcases();
|
||||
testKeyDiversificationWithMasterkeyTestcases(key);
|
||||
}
|
||||
}
|
||||
PrintAndLogEx(INFO, "Testing key diversification with non-sensitive keys...");
|
||||
|
|
|
@ -433,7 +433,7 @@ while true; do
|
|||
"Paradox - ID: 004209dea FC: 96 Card: 40426, Checksum: b2, Raw: 0f55555695596a6a9999a59a"; then break; fi
|
||||
if ! CheckExecute slow "lf T55 presco test" "$CLIENTBIN -c 'data load -f traces/lf_ATA5577_presco.pm3; lf search 1'" "Presco ID found"; then break; fi
|
||||
if ! CheckExecute slow "lf T55 presco test2" "$CLIENTBIN -c 'data load -f traces/lf_ATA5577_presco.pm3; lf presco demod'" \
|
||||
"Presco - Card: 1E8021D9, Raw: 10D0000000000000000000001E8021D9"; then break; fi
|
||||
"Presco Site code: 30 User code: 8665 Full code: 1E8021D9 Raw: 10D0000000000000000000001E8021D9"; then break; fi
|
||||
if ! CheckExecute slow "lf T55 pyramid test" "$CLIENTBIN -c 'data load -f traces/lf_ATA5577_pyramid.pm3; lf search 1'" "Pyramid ID found"; then break; fi
|
||||
if ! CheckExecute slow "lf T55 pyramid test2" "$CLIENTBIN -c 'data load -f traces/lf_ATA5577_pyramid.pm3; lf pyramid demod'" \
|
||||
"Pyramid - len: 26, FC: 123 Card: 11223 - Wiegand: 2f657ae, Raw: 00010101010101010101016eb35e5da4"; then break; fi
|
||||
|
@ -450,7 +450,7 @@ while true; do
|
|||
echo -e "\n${C_BLUE}Testing HF:${C_NC}"
|
||||
if ! CheckExecute "hf mf offline text" "$CLIENTBIN -c 'hf mf'" "at_enc"; then break; fi
|
||||
if ! CheckExecute slow retry ignore "hf mf hardnested long test" "$CLIENTBIN -c 'hf mf hardnested t 1 000000000000'" "found:"; then break; fi
|
||||
if ! CheckExecute slow "hf iclass long test" "$CLIENTBIN -c 'hf iclass loclass --long'" "verified ok"; then break; fi
|
||||
if ! CheckExecute slow "hf iclass long test" "$CLIENTBIN -c 'hf iclass loclass --long'" "verified (ok)"; then break; fi
|
||||
if ! CheckExecute slow "emv long test" "$CLIENTBIN -c 'emv test -l'" "Test(s) \[ ok"; then break; fi
|
||||
if ! $SLOWTESTS; then
|
||||
if ! CheckExecute "hf iclass test" "$CLIENTBIN -c 'hf iclass loclass --test'" "key diversification (ok)"; then break; fi
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue