mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2025-08-21 05:43:48 -07:00
added the possibility to automatically bruteforce the key if four or more bytes of encrypted next command is available
This commit is contained in:
parent
e08d6e5ca5
commit
43d45b0331
1 changed files with 304 additions and 35 deletions
|
@ -14,6 +14,7 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include <ctype.h>
|
||||||
#include "crapto1/crapto1.h"
|
#include "crapto1/crapto1.h"
|
||||||
#include "protocol.h"
|
#include "protocol.h"
|
||||||
#include "iso14443crc.h"
|
#include "iso14443crc.h"
|
||||||
|
@ -42,24 +43,150 @@ typedef struct thread_args {
|
||||||
bool ev1;
|
bool ev1;
|
||||||
} targs;
|
} targs;
|
||||||
|
|
||||||
|
#define ENC_LEN (200)
|
||||||
|
typedef struct thread_key_args {
|
||||||
|
int thread;
|
||||||
|
int idx;
|
||||||
|
uint32_t uid;
|
||||||
|
uint32_t part_key;
|
||||||
|
uint32_t nt_enc;
|
||||||
|
uint32_t nr_enc;
|
||||||
|
uint16_t enc_len;
|
||||||
|
uint8_t enc[ENC_LEN]; // next encrypted command + a full read/write
|
||||||
|
} targs_key;
|
||||||
|
|
||||||
//------------------------------------------------------------------
|
//------------------------------------------------------------------
|
||||||
uint8_t cmds[] = {
|
|
||||||
ISO14443A_CMD_READBLOCK,
|
uint8_t cmds[8][2] = {
|
||||||
ISO14443A_CMD_WRITEBLOCK,
|
{ISO14443A_CMD_READBLOCK, 18},
|
||||||
MIFARE_AUTH_KEYA,
|
{ISO14443A_CMD_WRITEBLOCK, 18},
|
||||||
MIFARE_AUTH_KEYB,
|
{MIFARE_AUTH_KEYA, 0},
|
||||||
MIFARE_CMD_INC,
|
{MIFARE_AUTH_KEYB, 0},
|
||||||
MIFARE_CMD_DEC,
|
{MIFARE_CMD_INC, 6},
|
||||||
MIFARE_CMD_RESTORE,
|
{MIFARE_CMD_DEC, 6},
|
||||||
MIFARE_CMD_TRANSFER
|
{MIFARE_CMD_RESTORE, 6},
|
||||||
|
{MIFARE_CMD_TRANSFER, 0}
|
||||||
};
|
};
|
||||||
|
|
||||||
int global_counter = 0;
|
int global_counter = 0;
|
||||||
int global_fin_flag = 0;
|
int global_fin_flag = 0;
|
||||||
int global_found = 0;
|
int global_found = 0;
|
||||||
int global_found_candidate = 0;
|
int global_found_candidate = 0;
|
||||||
|
uint64_t global_candiate_key = 0;
|
||||||
size_t thread_count = 2;
|
size_t thread_count = 2;
|
||||||
|
|
||||||
|
static int param_getptr(const char *line, int *bg, int *en, int paramnum) {
|
||||||
|
int i;
|
||||||
|
int len = strlen(line);
|
||||||
|
|
||||||
|
*bg = 0;
|
||||||
|
*en = 0;
|
||||||
|
|
||||||
|
// skip spaces
|
||||||
|
while (line[*bg] == ' ' || line[*bg] == '\t')(*bg)++;
|
||||||
|
if (*bg >= len) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < paramnum; i++) {
|
||||||
|
while (line[*bg] != ' ' && line[*bg] != '\t' && line[*bg] != '\0')(*bg)++;
|
||||||
|
while (line[*bg] == ' ' || line[*bg] == '\t')(*bg)++;
|
||||||
|
|
||||||
|
if (line[*bg] == '\0') return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
*en = *bg;
|
||||||
|
while (line[*en] != ' ' && line[*en] != '\t' && line[*en] != '\0')(*en)++;
|
||||||
|
|
||||||
|
(*en)--;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int param_gethex_to_eol(const char *line, int paramnum, uint8_t *data, int maxdatalen, int *datalen) {
|
||||||
|
int bg, en;
|
||||||
|
uint32_t temp;
|
||||||
|
char buf[5] = {0};
|
||||||
|
|
||||||
|
if (param_getptr(line, &bg, &en, paramnum)) return 1;
|
||||||
|
|
||||||
|
*datalen = 0;
|
||||||
|
|
||||||
|
int indx = bg;
|
||||||
|
while (line[indx]) {
|
||||||
|
if (line[indx] == '\t' || line[indx] == ' ') {
|
||||||
|
indx++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isxdigit(line[indx])) {
|
||||||
|
buf[strlen(buf) + 1] = 0x00;
|
||||||
|
buf[strlen(buf)] = line[indx];
|
||||||
|
} else {
|
||||||
|
// if we have symbols other than spaces and hex
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*datalen >= maxdatalen) {
|
||||||
|
// if we dont have space in buffer and have symbols to translate
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(buf) >= 2) {
|
||||||
|
sscanf(buf, "%x", &temp);
|
||||||
|
data[*datalen] = (uint8_t)(temp & 0xff);
|
||||||
|
*buf = 0;
|
||||||
|
(*datalen)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
indx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(buf) > 0)
|
||||||
|
//error when not completed hex bytes
|
||||||
|
return 3;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hex_to_buffer(const uint8_t *buf, const uint8_t *hex_data, const size_t hex_len, const size_t hex_max_len,
|
||||||
|
const size_t min_str_len, const size_t spaces_between, bool uppercase) {
|
||||||
|
|
||||||
|
if (buf == NULL) return;
|
||||||
|
|
||||||
|
char *tmp = (char *)buf;
|
||||||
|
size_t i;
|
||||||
|
memset(tmp, 0x00, hex_max_len);
|
||||||
|
|
||||||
|
size_t max_len = (hex_len > hex_max_len) ? hex_max_len : hex_len;
|
||||||
|
|
||||||
|
for (i = 0; i < max_len; ++i, tmp += 2 + spaces_between) {
|
||||||
|
sprintf(tmp, (uppercase) ? "%02X" : "%02x", (unsigned int) hex_data[i]);
|
||||||
|
|
||||||
|
for (size_t j = 0; j < spaces_between; j++)
|
||||||
|
sprintf(tmp + 2 + j, " ");
|
||||||
|
}
|
||||||
|
|
||||||
|
i *= (2 + spaces_between);
|
||||||
|
|
||||||
|
size_t mlen = min_str_len > i ? min_str_len : 0;
|
||||||
|
if (mlen > hex_max_len)
|
||||||
|
mlen = hex_max_len;
|
||||||
|
|
||||||
|
for (; i < mlen; i++, tmp += 1)
|
||||||
|
sprintf(tmp, " ");
|
||||||
|
|
||||||
|
// remove last space
|
||||||
|
*tmp = '\0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *sprint_hex_inrow_ex(const uint8_t *data, const size_t len, const size_t min_str_len) {
|
||||||
|
static char buf[100] = {0};
|
||||||
|
hex_to_buffer((uint8_t *)buf, data, len, sizeof(buf) - 1, min_str_len, 0, true);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
static uint16_t parity_from_err(uint32_t data, uint16_t par_err) {
|
static uint16_t parity_from_err(uint32_t data, uint16_t par_err) {
|
||||||
|
|
||||||
uint16_t par = 0;
|
uint16_t par = 0;
|
||||||
|
@ -196,13 +323,33 @@ static bool candidate_nonce(uint32_t xored, uint32_t nt, bool ev1) {
|
||||||
|
|
||||||
static bool checkValidCmd(uint32_t decrypted) {
|
static bool checkValidCmd(uint32_t decrypted) {
|
||||||
uint8_t cmd = (decrypted >> 24) & 0xFF;
|
uint8_t cmd = (decrypted >> 24) & 0xFF;
|
||||||
for (int i = 0; i < sizeof(cmds); ++i) {
|
for (int i = 0; i < 8; ++i) {
|
||||||
if (cmd == cmds[i])
|
if (cmd == cmds[i][0])
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool checkValidCmdByte(uint8_t *cmd, uint16_t n) {
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
if (cmd[0] == cmds[i][0]) {
|
||||||
|
|
||||||
|
if (n >= 4)
|
||||||
|
ok = CheckCrc14443(CRC_14443_A, cmd, 4);
|
||||||
|
|
||||||
|
if (cmds[i][1] > 0 && n >= cmds[i][1])
|
||||||
|
ok = CheckCrc14443(CRC_14443_A, cmd + 4, cmds[i][1]);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static bool checkCRC(uint32_t decrypted) {
|
static bool checkCRC(uint32_t decrypted) {
|
||||||
uint8_t data[] = {
|
uint8_t data[] = {
|
||||||
(decrypted >> 24) & 0xFF,
|
(decrypted >> 24) & 0xFF,
|
||||||
|
@ -293,11 +440,13 @@ static void *brute_thread(void *arguments) {
|
||||||
free(revstate);
|
free(revstate);
|
||||||
|
|
||||||
if (args->ev1) {
|
if (args->ev1) {
|
||||||
printf("\nKey candidate: [%012" PRIx64 "]\n\n", key);
|
printf("\nKey candidate [ %012" PRIx64 " ]\n\n", key);
|
||||||
__sync_fetch_and_add(&global_found_candidate, 1);
|
__sync_fetch_and_add(&global_found_candidate, 1);
|
||||||
|
__sync_fetch_and_add(&global_candiate_key, key);
|
||||||
} else {
|
} else {
|
||||||
printf("\nValid Key found: [%012" PRIx64 "]\n\n", key);
|
printf("\nValid Key found [ %012" PRIx64 " ]\n\n", key);
|
||||||
__sync_fetch_and_add(&global_found, 1);
|
__sync_fetch_and_add(&global_found, 1);
|
||||||
|
__sync_fetch_and_add(&global_candiate_key, key);
|
||||||
}
|
}
|
||||||
//release lock
|
//release lock
|
||||||
pthread_mutex_unlock(&print_lock);
|
pthread_mutex_unlock(&print_lock);
|
||||||
|
@ -307,18 +456,85 @@ static void *brute_thread(void *arguments) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void *brute_key_thread(void *arguments) {
|
||||||
|
|
||||||
|
struct thread_key_args *args = (struct thread_key_args *) arguments;
|
||||||
|
|
||||||
|
uint64_t key; // recovered key candidate
|
||||||
|
int found = 0;
|
||||||
|
struct Crypto1State mpcs = {0, 0};
|
||||||
|
struct Crypto1State *pcs = &mpcs;
|
||||||
|
|
||||||
|
uint8_t local_enc[args->enc_len];
|
||||||
|
memcpy(local_enc, args->enc, args->enc_len);
|
||||||
|
|
||||||
|
for (uint64_t count = args->idx; count < 0xFFFF; count += thread_count) {
|
||||||
|
|
||||||
|
found = global_found;
|
||||||
|
if (found) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
key = (count << 32 | args->part_key);
|
||||||
|
|
||||||
|
// Init cipher with key
|
||||||
|
pcs = crypto1_create(key);
|
||||||
|
|
||||||
|
// NESTED decrypt nt with help of new key
|
||||||
|
crypto1_word(pcs, args->nt_enc ^ args->uid, 1);
|
||||||
|
crypto1_word(pcs, args->nr_enc, 1);
|
||||||
|
crypto1_word(pcs, 0, 0);
|
||||||
|
crypto1_word(pcs, 0, 0);
|
||||||
|
|
||||||
|
// decrypt 22 bytes
|
||||||
|
uint8_t dec[args->enc_len];
|
||||||
|
for (int i = 0; i < args->enc_len; i++)
|
||||||
|
dec[i] = crypto1_byte(pcs, 0x00, 0) ^ local_enc[i];
|
||||||
|
|
||||||
|
crypto1_deinit(pcs);
|
||||||
|
|
||||||
|
// check if cmd exists
|
||||||
|
uint8_t isOK = checkValidCmdByte(dec, args->enc_len);
|
||||||
|
if (isOK == false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// lock this section to avoid interlacing prints from different threats
|
||||||
|
pthread_mutex_lock(&print_lock);
|
||||||
|
printf("\nenc: %s\n", sprint_hex_inrow_ex(local_enc, args->enc_len, 0));
|
||||||
|
printf("dec: %s\n", sprint_hex_inrow_ex(dec, args->enc_len, 0));
|
||||||
|
printf("\nValid Key found [ %012" PRIx64 " ]\n\n", key);
|
||||||
|
pthread_mutex_unlock(&print_lock);
|
||||||
|
__sync_fetch_and_add(&global_found, 1);
|
||||||
|
}
|
||||||
|
free(args);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static int usage(void) {
|
static int usage(void) {
|
||||||
|
printf("\n");
|
||||||
printf("syntax: mf_nonce_brute <uid> <nt> <nt_par_err> <nr> <ar> <ar_par_err> <at> <at_par_err> [<next_command>]\n\n");
|
printf("syntax: mf_nonce_brute <uid> <nt> <nt_par_err> <nr> <ar> <ar_par_err> <at> <at_par_err> [<next_command>]\n\n");
|
||||||
printf(" example: nt in trace = 8c! 42 e6! 4e!\n");
|
printf("how to convert trace data to needed input:\n");
|
||||||
|
printf(" nt in trace = 8c! 42 e6! 4e!\n");
|
||||||
printf(" nt = 8c42e64e\n");
|
printf(" nt = 8c42e64e\n");
|
||||||
printf(" nt_par_err = 1011\n\n");
|
printf(" nt_par_err = 1011\n\n");
|
||||||
printf("\n expected outcome:\n");
|
printf("samples:\n");
|
||||||
printf(" KEY 0xFFFFFFFFFFFF == fa247164 fb47c594 0000 71909d28 0c254817 1000 0dc7cfbd 1110\n");
|
printf("\n");
|
||||||
|
printf(" ./mf_nonce_brute fa247164 fb47c594 0000 71909d28 0c254817 1000 0dc7cfbd 1110\n");
|
||||||
|
printf("\n");
|
||||||
|
printf("**** Possible key candidate ****\n");
|
||||||
|
printf("Key candidate: [ffffffffffff]\n");
|
||||||
|
printf("\n");
|
||||||
|
printf(" ./mf_nonce_brute 96519578 d7e3c6ac 0011 cd311951 9da49e49 0010 2bb22e00 0100 a4f7f398ebdb4e484d1cb2b174b939d18b469f3fa5d9caab\n");
|
||||||
|
printf("\n");
|
||||||
|
printf("enc: A4F7F398EBDB4E484D1CB2B174B939D18B469F3FA5D9CAABBFA018EC7E0CC5721DE2E590F64BD0A5B4EFCE71\n");
|
||||||
|
printf("dec: 30084A24302F8102F44CA5020500A60881010104763930084A24302F8102F44CA5020500A608810101047639\n");
|
||||||
|
printf("Valid Key found: [3b7e4fd575ad]\n\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
printf("Mifare classic nested auth key recovery. Phase 1.\n");
|
printf("\nMifare classic nested auth key recovery\n\n");
|
||||||
|
|
||||||
if (argc < 9) return usage();
|
if (argc < 9) return usage();
|
||||||
|
|
||||||
|
@ -331,21 +547,28 @@ int main(int argc, char *argv[]) {
|
||||||
sscanf(argv[7], "%x", &at_enc);
|
sscanf(argv[7], "%x", &at_enc);
|
||||||
sscanf(argv[8], "%x", &at_par_err);
|
sscanf(argv[8], "%x", &at_par_err);
|
||||||
|
|
||||||
if (argc > 9)
|
int enc_len = 0;
|
||||||
sscanf(argv[9], "%x", &cmd_enc);
|
uint8_t enc[ENC_LEN] = {0}; // next encrypted command + a full read/write
|
||||||
|
if (argc > 9) {
|
||||||
|
// sscanf(argv[9], "%x", &cmd_enc);
|
||||||
|
param_gethex_to_eol(argv[9], 0, enc, sizeof(enc), &enc_len);
|
||||||
|
cmd_enc = (enc[0] << 24 | enc[1] << 16 | enc[2] << 8 | enc[3]);
|
||||||
|
}
|
||||||
|
|
||||||
printf("-------------------------------------------------\n");
|
printf("----------- Phase 1 ------------------------\n");
|
||||||
printf("uid:\t\t%08x\n", uid);
|
printf("uid............. %08x\n", uid);
|
||||||
printf("nt encrypted:\t%08x\n", nt_enc);
|
printf("nt encrypted.... %08x\n", nt_enc);
|
||||||
printf("nt parity err:\t%04x\n", nt_par_err);
|
printf("nt parity err... %04x\n", nt_par_err);
|
||||||
printf("nr encrypted:\t%08x\n", nr_enc);
|
printf("nr encrypted.... %08x\n", nr_enc);
|
||||||
printf("ar encrypted:\t%08x\n", ar_enc);
|
printf("ar encrypted.... %08x\n", ar_enc);
|
||||||
printf("ar parity err:\t%04x\n", ar_par_err);
|
printf("ar parity err... %04x\n", ar_par_err);
|
||||||
printf("at encrypted:\t%08x\n", at_enc);
|
printf("at encrypted.... %08x\n", at_enc);
|
||||||
printf("at parity err:\t%04x\n", at_par_err);
|
printf("at parity err... %04x\n", at_par_err);
|
||||||
|
|
||||||
if (argc > 9)
|
if (argc > 9) {
|
||||||
printf("next cmd enc:\t%08x\n\n", cmd_enc);
|
// printf("next cmd enc:\t%08x\n\n", cmd_enc);
|
||||||
|
printf("next encrypted cmd: %s\n", sprint_hex_inrow_ex(enc, enc_len ,0));
|
||||||
|
}
|
||||||
|
|
||||||
clock_t t1 = clock();
|
clock_t t1 = clock();
|
||||||
uint16_t nt_par = parity_from_err(nt_enc, nt_par_err);
|
uint16_t nt_par = parity_from_err(nt_enc, nt_par_err);
|
||||||
|
@ -386,6 +609,52 @@ int main(int argc, char *argv[]) {
|
||||||
pthread_create(&threads[i + 1], NULL, brute_thread, (void *)b);
|
pthread_create(&threads[i + 1], NULL, brute_thread, (void *)b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wait for threads to terminate:
|
||||||
|
for (int i = 0; i < thread_count; ++i)
|
||||||
|
pthread_join(threads[i], NULL);
|
||||||
|
|
||||||
|
t1 = clock() - t1;
|
||||||
|
printf("execution time %.0f ticks\n", (float)t1);
|
||||||
|
|
||||||
|
|
||||||
|
if (!global_found && !global_found_candidate) {
|
||||||
|
printf("\nFailed to find a key\n\n");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enc_len < 4) {
|
||||||
|
printf("skipping phase 2\n");
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset thread signals
|
||||||
|
__sync_fetch_and_add(&global_found, 0);
|
||||||
|
__sync_fetch_and_add(&global_found_candidate, 0);
|
||||||
|
t1 = clock();
|
||||||
|
|
||||||
|
printf("\n----------- Phase 2 ------------------------\n");
|
||||||
|
printf("uid.......... %08x\n", uid);
|
||||||
|
printf("partial key.. %08x\n", (uint32_t)(global_candiate_key & 0xFFFFFFFF));
|
||||||
|
printf("nt enc....... %08x\n", nt_enc);
|
||||||
|
printf("nr enc....... %08x\n", nr_enc);
|
||||||
|
printf("next encrypted cmd: %s\n", sprint_hex_inrow_ex(enc, enc_len ,0));
|
||||||
|
printf("\nBruteforce using %zu threads to find upper 16 bits of key\n", thread_count);
|
||||||
|
fflush(stdout);
|
||||||
|
|
||||||
|
// threads
|
||||||
|
for (int i = 0; i < thread_count; ++i) {
|
||||||
|
struct thread_key_args *b = malloc(sizeof(struct thread_key_args));
|
||||||
|
b->thread = i;
|
||||||
|
b->idx = i;
|
||||||
|
b->uid = uid;
|
||||||
|
b->part_key = (uint32_t)(global_candiate_key & 0xFFFFFFFF);
|
||||||
|
b->nt_enc = nt_enc;
|
||||||
|
b->nr_enc = nr_enc;
|
||||||
|
b->enc_len = enc_len;
|
||||||
|
memcpy(b->enc, enc, enc_len);
|
||||||
|
pthread_create(&threads[i], NULL, brute_key_thread, (void *)b);
|
||||||
|
}
|
||||||
|
|
||||||
// wait for threads to terminate:
|
// wait for threads to terminate:
|
||||||
for (int i = 0; i < thread_count; ++i)
|
for (int i = 0; i < thread_count; ++i)
|
||||||
pthread_join(threads[i], NULL);
|
pthread_join(threads[i], NULL);
|
||||||
|
@ -395,9 +664,9 @@ int main(int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t1 = clock() - t1;
|
t1 = clock() - t1;
|
||||||
if (t1 > 0)
|
printf("execution time %.0f ticks\n", (float)t1);
|
||||||
printf("Execution time: %.0f ticks\n", (float)t1);
|
|
||||||
|
|
||||||
|
out:
|
||||||
// clean up mutex
|
// clean up mutex
|
||||||
pthread_mutex_destroy(&print_lock);
|
pthread_mutex_destroy(&print_lock);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue