sort mfc tools

This commit is contained in:
Philippe Teuwen 2024-08-01 16:43:25 +02:00
commit c47578c048
49 changed files with 143 additions and 175 deletions

10
tools/mfc/card_reader/.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
mfkey32
mfkey32v2
mfkey64
mf_nonce_brute
mf_trace_brute
mfkey32.exe
mfkey32v2.exe
mfkey64.exe
mf_nonce_brute.exe
mf_trace_brute.exe

View file

@ -0,0 +1,33 @@
ROOTPATH = ../../..
MYSRCPATHS = $(ROOTPATH)/common $(ROOTPATH)/common/crapto1
MYSRCS = crypto1.c crapto1.c bucketsort.c iso14443crc.c sleep.c util_posix.c
MYINCLUDES = -I$(ROOTPATH)/include -I$(ROOTPATH)/common
MYCFLAGS = -O3
MYDEFS =
MYLDLIBS =
ifneq ($(SKIPPTHREAD),1)
MYLDLIBS += -lpthread
endif
BINS = mfkey32 mfkey32v2 mfkey64 mf_nonce_brute mf_trace_brute
INSTALLTOOLS = $(BINS)
include $(ROOTPATH)/Makefile.host
# checking platform can be done only after Makefile.host
ifneq (,$(findstring MINGW,$(platform)))
# Mingw uses by default Microsoft printf, we want the GNU printf (e.g. for %z)
# and setting _ISOC99_SOURCE sets internally __USE_MINGW_ANSI_STDIO=1
CFLAGS += -D_ISOC99_SOURCE
endif
# macOS doesn't like these compiler params
ifneq ($(platform),Darwin)
MYCFLAGS += --param max-completely-peeled-insns=1000 --param max-completely-peel-times=10000
endif
mfkey32 : $(OBJDIR)/mfkey32.o $(MYOBJS)
mfkey32v2 : $(OBJDIR)/mfkey32v2.o $(MYOBJS)
mfkey64 : $(OBJDIR)/mfkey64.o $(MYOBJS)
mf_nonce_brute : $(OBJDIR)/mf_nonce_brute.o $(MYOBJS)
mf_trace_brute : $(OBJDIR)/mf_trace_brute.o $(MYOBJS)

View file

@ -0,0 +1,45 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// ISO14443 CRC calculation code.
//-----------------------------------------------------------------------------
#include "iso14443crc.h"
static unsigned short UpdateCrc14443(unsigned char ch, unsigned short *lpwCrc) {
ch = (ch ^ (unsigned char)((*lpwCrc) & 0x00FF));
ch = (ch ^ (ch << 4));
*lpwCrc = (*lpwCrc >> 8) ^ ((unsigned short) ch << 8) ^
((unsigned short) ch << 3) ^ ((unsigned short) ch >> 4);
return (*lpwCrc);
}
void ComputeCrc14443(int CrcType,
const unsigned char *Data, int Length,
unsigned char *TransmitFirst,
unsigned char *TransmitSecond) {
unsigned short wCrc = CrcType;
do {
unsigned char chBlock = *Data++;
UpdateCrc14443(chBlock, &wCrc);
} while (--Length);
if (CrcType == CRC_14443_B)
wCrc = ~wCrc; /* ISO/IEC 13239 (formerly ISO/IEC 3309) */
*TransmitFirst = (unsigned char)(wCrc & 0xFF);
*TransmitSecond = (unsigned char)((wCrc >> 8) & 0xFF);
return;
}
int CheckCrc14443(int CrcType, const unsigned char *Data, int Length) {
unsigned char b1;
unsigned char b2;
if (Length < 3) return 0;
ComputeCrc14443(CrcType, Data, Length - 2, &b1, &b2);
if ((b1 == Data[Length - 2]) && (b2 == Data[Length - 1])) return 1;
return 0;
}

View file

@ -0,0 +1,26 @@
//-----------------------------------------------------------------------------
// 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.
//-----------------------------------------------------------------------------
// ISO14443 CRC calculation code.
//-----------------------------------------------------------------------------
#ifndef __ISO14443CRC_H
#define __ISO14443CRC_H
//-----------------------------------------------------------------------------
// Routines to compute the CRCs (two different flavours, just for confusion)
// required for ISO 14443, swiped directly from the spec.
//-----------------------------------------------------------------------------
#define CRC_14443_A 0x6363 /* ITU-V.41 */
#define CRC_14443_B 0xFFFF /* ISO/IEC 13239 (formerly ISO/IEC 3309) */
#define CRC_ICLASS 0xE012 /* ICLASS PREFIX */
void ComputeCrc14443(int CrcType,
const unsigned char *Data, int Length,
unsigned char *TransmitFirst,
unsigned char *TransmitSecond);
int CheckCrc14443(int CrcType, const unsigned char *Data, int Length);
#endif

View file

@ -0,0 +1,847 @@
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include "crapto1/crapto1.h"
#include "protocol.h"
#include "iso14443crc.h"
#include "util_posix.h"
#define AEND "\x1b[0m"
#define _RED_(s) "\x1b[31m" s AEND
#define _GREEN_(s) "\x1b[32m" s AEND
#define _YELLOW_(s) "\x1b[33m" s AEND
#define _CYAN_(s) "\x1b[36m" s AEND
#define odd_parity(i) (( (i) ^ (i)>>1 ^ (i)>>2 ^ (i)>>3 ^ (i)>>4 ^ (i)>>5 ^ (i)>>6 ^ (i)>>7 ^ 1) & 0x01)
#define ARRAYLEN(x) (sizeof(x) / sizeof((x)[0]))
// a global mutex to prevent interlaced printing from different threads
pthread_mutex_t print_lock;
//--------------------- define options here
uint32_t uid = 0; // serial number
uint32_t nt_enc = 0; // Encrypted tag nonce
uint32_t nr_enc = 0; // encrypted reader challenge
uint32_t ar_enc = 0; // encrypted reader response
uint32_t at_enc = 0; // encrypted tag response
uint32_t cmd_enc = 0; // next encrypted command to sector
uint32_t nt_par_err = 0;
uint32_t ar_par_err = 0;
uint32_t at_par_err = 0;
typedef struct thread_args {
uint16_t xored;
int thread;
int idx;
bool ev1;
} 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[8][2] = {
{ISO14443A_CMD_READBLOCK, 18},
{ISO14443A_CMD_WRITEBLOCK, 18},
{MIFARE_AUTH_KEYA, 0},
{MIFARE_AUTH_KEYB, 0},
{MIFARE_CMD_INC, 6},
{MIFARE_CMD_DEC, 6},
{MIFARE_CMD_RESTORE, 6},
{MIFARE_CMD_TRANSFER, 0}
};
static const uint64_t g_mifare_default_keys[] = {
0xffffffffffff, // Default key (first key used by program if no user defined key)
0xa0a1a2a3a4a5, // NFCForum MAD key
0xd3f7d3f7d3f7, // NDEF public key
0x4b791bea7bcc, // MFC EV1 Signature 17 B
0x5C8FF9990DA2, // MFC EV1 Signature 16 A
0xD01AFEEB890A, // MFC EV1 Signature 16 B
0x75CCB59C9BED, // MFC EV1 Signature 17 A
0xfc00018778f7, // Public Transport
0x6471a5ef2d1a, // SimonsVoss
0x4E3552426B32, // ID06
0x6A1987C40A21, // Salto
0xef1232ab18a0, // Schlage
0x3B7E4FD575AD, //
0xb7bf0c13066e, // Gallagher
0x135b88a94b8b, // Saflok
0x2A2C13CC242A, // Dorma Kaba
0x5a7a52d5e20d, // Bosch
0x314B49474956, // VIGIK1 A
0x564c505f4d41, // VIGIK1 B
0x021209197591, // BTCINO
0x484558414354, // Intratone
0xEC0A9B1A9E06, // Vingcard
0x66b31e64ca4b, // Vingcard
0x97F5DA640B18, // Bangkok metro key
0xA8844B0BCA06, // Metro Valencia key
0xE4410EF8ED2D, // Armenian metro
0x857464D3AAD1, // HTC Eindhoven key
0x08B386463229, // troika
0xe00000000000, // icopy
0x199404281970, // NSP A
0x199404281998, // NSP B
0x6A1987C40A21, // SALTO
0x7F33625BC129, // SALTO
0x484944204953, // HID
0x204752454154, // HID
0x3B7E4FD575AD, // HID
0x11496F97752A, // HID
0x3E65E4FB65B3, // Gym
0x000000000000, // Blank key
0xb0b1b2b3b4b5,
0xaabbccddeeff,
0x1a2b3c4d5e6f,
0x123456789abc,
0x010203040506,
0x123456abcdef,
0xabcdef123456,
0x4d3a99c351dd,
0x1a982c7e459a,
0x714c5c886e97,
0x587ee5f9350f,
0xa0478cc39091,
0x533cb6c723f6,
0x8fd0a4f256e9,
0x0000014b5c31,
0xb578f38a5c61,
0x96a301bce267,
};
//static int global_counter = 0;
static int global_found = 0;
static int global_found_candidate = 0;
static uint64_t global_candidate_key = 0;
static int 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 don't 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_base = (char *)buf;
char *tmp = tmp_base;
size_t i;
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) {
snprintf(tmp, hex_max_len - (tmp - tmp_base), (uppercase) ? "%02X" : "%02x", (unsigned int) hex_data[i]);
for (size_t j = 0; j < spaces_between; j++)
snprintf(tmp + 2 + j, hex_max_len - (2 + j + (tmp - tmp_base)), " ");
}
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)
snprintf(tmp, hex_max_len - (tmp - tmp_base), " ");
// remove last space
*tmp = '\0';
}
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) {
uint16_t par = 0;
par |= odd_parity((data >> 24) & 0xFF) ^ ((par_err >> 12) & 1);
par <<= 4;
par |= odd_parity((data >> 16) & 0xFF) ^ ((par_err >> 8) & 1);
par <<= 4;
par |= odd_parity((data >> 8) & 0xFF) ^ ((par_err >> 4) & 1);
par <<= 4;
par |= odd_parity(data & 0xFF) ^ (par_err & 1);
return par;
}
static uint16_t xored_bits(uint16_t nt_par, uint32_t ntenc, uint16_t ar_par, uint32_t arenc, uint16_t at_par, uint32_t atenc) {
uint16_t xored = 0;
uint8_t par;
//1st (1st nt)
par = (nt_par >> 12) & 1;
xored |= par ^ ((ntenc >> 16) & 1);
xored <<= 1;
//2nd (2nd nt)
par = (nt_par >> 8) & 1;
xored |= par ^ ((ntenc >> 8) & 1);
xored <<= 1;
//3rd (3rd nt)
par = (nt_par >> 4) & 1;
xored |= par ^ (ntenc & 1);
xored <<= 1;
//4th (1st ar)
par = (ar_par >> 12) & 1;
xored |= par ^ ((arenc >> 16) & 1);
xored <<= 1;
//5th (2nd ar)
par = (ar_par >> 8) & 1;
xored |= par ^ ((arenc >> 8) & 1);
xored <<= 1;
//6th (3rd ar)
par = (ar_par >> 4) & 1;
xored |= par ^ (arenc & 1);
xored <<= 1;
//7th (4th ar)
par = ar_par & 1;
xored |= par ^ ((atenc >> 24) & 1);
xored <<= 1;
//8th (1st at)
par = (at_par >> 12) & 1;
xored |= par ^ ((atenc >> 16) & 1);
xored <<= 1;
//9th (2nd at)
par = (at_par >> 8) & 1;
xored |= par ^ ((atenc >> 8) & 1);
xored <<= 1;
//10th (3rd at)
par = (at_par >> 4) & 1;
xored |= par ^ (atenc & 1);
return xored;
}
static bool candidate_nonce(uint32_t xored, uint32_t nt, bool ev1) {
uint8_t byte;
if (!ev1) {
// 1st (1st nt)
byte = (nt >> 24) & 0xFF;
if (odd_parity(byte) ^ ((nt >> 16) & 1) ^ ((xored >> 9) & 1)) {
return false;
}
// 2nd (2nd nt)
byte = (nt >> 16) & 0xFF;
if (odd_parity(byte) ^ ((nt >> 8) & 1) ^ ((xored >> 8) & 1)) {
return false;
}
}
// 3rd (3rd nt)
byte = (nt >> 8) & 0xFF;
if (odd_parity(byte) ^ (nt & 1) ^ ((xored >> 7) & 1)) {
return false;
}
uint32_t ar = prng_successor(nt, 64);
// 4th (1st ar)
byte = (ar >> 24) & 0xFF;
if (odd_parity(byte) ^ ((ar >> 16) & 1) ^ ((xored >> 6) & 1)) {
return false;
}
// 5th (2nd ar)
byte = (ar >> 16) & 0x0FF;
if (odd_parity(byte) ^ ((ar >> 8) & 1) ^ ((xored >> 5) & 1)) {
return false;
}
// 6th (3rd ar)
byte = (ar >> 8) & 0xFF;
if (odd_parity(byte) ^ (ar & 1) ^ ((xored >> 4) & 1)) {
return false;
}
uint32_t at = prng_successor(nt, 96);
// 7th (4th ar)
byte = ar & 0xFF;
if (odd_parity(byte) ^ ((at >> 24) & 1) ^ ((xored >> 3) & 1)) {
return false;
}
// 8th (1st at)
byte = (at >> 24) & 0xFF;
if (odd_parity(byte) ^ ((at >> 16) & 1) ^ ((xored >> 2) & 1)) {
return false;
}
// 9th (2nd at)
byte = (at >> 16) & 0xFF;
if (odd_parity(byte) ^ ((at >> 8) & 1) ^ ((xored >> 1) & 1)) {
return false;
}
// 10th (3rd at)
byte = (at >> 8) & 0xFF;
if (odd_parity(byte) ^ (at & 1) ^ (xored & 1)) {
return false;
}
return true;
}
static bool checkValidCmd(uint32_t decrypted) {
uint8_t cmd = (decrypted >> 24) & 0xFF;
for (int i = 0; i < 8; ++i) {
if (cmd == cmds[i][0]) {
return true;
}
}
return false;
}
static bool checkValidCmdByte(uint8_t *cmd, uint16_t n) {
// if we don't have enough data then this might be a false positive
if (cmd == NULL) {
return false;
}
for (int i = 0; i < 8; ++i) {
if (cmd[0] == cmds[i][0]) {
int res = 0;
if (n >= 4) {
res = CheckCrc14443(CRC_14443_A, cmd, 4);
}
if (res == 0 && cmds[i][1] > 0 && n >= cmds[i][1]) {
res = CheckCrc14443(CRC_14443_A, cmd, cmds[i][1]);
}
if (res) {
return true;
}
}
}
return false;
}
static bool checkCRC(uint32_t decrypted) {
uint8_t data[] = {
(decrypted >> 24) & 0xFF,
(decrypted >> 16) & 0xFF,
(decrypted >> 8) & 0xFF,
decrypted & 0xFF
};
return CheckCrc14443(CRC_14443_A, data, sizeof(data));
}
static void *check_default_keys(void *arguments) {
struct thread_key_args *args = (struct thread_key_args *) arguments;
uint8_t local_enc[args->enc_len];
memcpy(local_enc, args->enc, args->enc_len);
for (uint8_t i = 0; i < ARRAYLEN(g_mifare_default_keys); i++) {
uint64_t key = g_mifare_default_keys[i];
// Init cipher with key
struct Crypto1State *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 bytes
uint8_t dec[args->enc_len];
for (int j = 0; j < args->enc_len; j++) {
dec[j] = crypto1_byte(pcs, 0x00, 0) ^ local_enc[j];
}
crypto1_destroy(pcs);
// check if cmd exists
bool res = checkValidCmdByte(dec, args->enc_len);
if (args->enc_len > 4) {
res |= checkValidCmdByte(dec + 4, args->enc_len - 4);
}
if (res == false) {
continue;
}
__sync_fetch_and_add(&global_found, 1);
pthread_mutex_lock(&print_lock);
printf("\nFound a default key!\n");
printf("enc: %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 [ " _GREEN_("%012" PRIx64) " ]\n\n", key);
pthread_mutex_unlock(&print_lock);
break;
}
free(args);
return NULL;
}
static void *brute_thread(void *arguments) {
struct thread_args *args = (struct thread_args *) arguments;
struct Crypto1State *revstate = NULL;
uint64_t key; // recovered key candidate
uint32_t ks2; // keystream used to encrypt reader response
uint32_t ks3; // keystream used to encrypt tag response
uint32_t ks4; // keystream used to encrypt next command
uint32_t nt; // current tag nonce
uint32_t p64 = 0;
// TC == 4 (
// threads calls 0 ev1 == false
// threads calls 0,1,2 ev1 == true
for (uint32_t count = args->idx; count <= 0xFFFF; count += thread_count) {
if (__atomic_load_n(&global_found, __ATOMIC_ACQUIRE) == 1) {
break;
}
nt = count << 16 | prng_successor(count, 16);
if (candidate_nonce(args->xored, nt, args->ev1) == false) {
continue;
}
p64 = prng_successor(nt, 64);
ks2 = ar_enc ^ p64;
ks3 = at_enc ^ prng_successor(p64, 32);
revstate = lfsr_recovery64(ks2, ks3);
ks4 = crypto1_word(revstate, 0, 0);
if (ks4 == 0) {
free(revstate);
continue;
}
// lock this section to avoid interlacing prints from different threats
pthread_mutex_lock(&print_lock);
if (args->ev1) {
printf("\n---> " _YELLOW_(" Possible key candidate")" <---\n");
}
#if 0
printf("thread #%d idx %d %s\n", args->thread, args->idx, (args->ev1) ? "(Ev1)" : "");
printf("current nt(%08x) ar_enc(%08x) at_enc(%08x)\n", nt, ar_enc, at_enc);
printf("ks2:%08x\n", ks2);
printf("ks3:%08x\n", ks3);
printf("ks4:%08x\n", ks4);
#endif
if (cmd_enc) {
uint32_t decrypted = ks4 ^ cmd_enc;
printf("CMD enc( %08x )\n", cmd_enc);
printf(" dec( %08x ) ", decrypted);
// check if cmd exists
uint8_t isOK = checkValidCmd(decrypted);
if (isOK == false) {
printf(_RED_("<-- not a valid cmd\n"));
pthread_mutex_unlock(&print_lock);
free(revstate);
continue;
}
// Add a crc-check.
isOK = checkCRC(decrypted);
if (isOK == false) {
printf(_RED_("<-- not a valid crc\n"));
pthread_mutex_unlock(&print_lock);
free(revstate);
continue;
}
printf("<-- " _GREEN_("valid cmd") "\n");
}
lfsr_rollback_word(revstate, 0, 0);
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);
free(revstate);
if (args->ev1) {
// if it was EV1, we know for sure xxxAAAAAAAA recovery
printf("\nKey candidate [ " _YELLOW_("....%08" PRIx64)" ]\n\n", key & 0xFFFFFFFF);
__sync_fetch_and_add(&global_found_candidate, 1);
} else {
printf("\nKey candidate [ " _GREEN_("....%08" PRIx64) " ]", key & 0xFFFFFFFF);
printf("\nKey candidate [ " _GREEN_("%12" PRIx64) " ]\n\n", key);
__sync_fetch_and_add(&global_found, 1);
}
// release lock
pthread_mutex_unlock(&print_lock);
__sync_fetch_and_add(&global_candidate_key, key);
break;
}
free(args);
return NULL;
}
// Bruteforce the upper 16 bits of the key
static void *brute_key_thread(void *arguments) {
struct thread_key_args *args = (struct thread_key_args *) arguments;
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) {
uint64_t key = args->part_key | (count << 32);
// Init cipher with key
struct Crypto1State *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_destroy(pcs);
// check if cmd exists
if (checkValidCmdByte(dec, args->enc_len) == false) {
continue;
}
__sync_fetch_and_add(&global_found_candidate, 1);
// 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));
if (key == global_candidate_key) {
printf("\nValid Key found [ " _GREEN_("%012" PRIx64) " ] - " _YELLOW_("matches candidate") "\n\n", key);
} else {
printf("\nValid Key found [ " _GREEN_("%012" PRIx64) " ]\n\n", key);
}
pthread_mutex_unlock(&print_lock);
}
free(args);
return NULL;
}
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("how to convert trace data to needed input:\n");
printf(" nt in trace = 8c! 42 e6! 4e!\n");
printf(" nt = 8c42e64e\n");
printf(" nt_par_err = 1011\n\n");
printf("samples:\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: [....ffffffff]\n");
printf("Too few next cmd bytes, skipping phase 2\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;
}
int main(int argc, const char *argv[]) {
printf("\nMifare classic nested auth key recovery\n\n");
if (argc < 9) return usage();
sscanf(argv[1], "%x", &uid);
sscanf(argv[2], "%x", &nt_enc);
sscanf(argv[3], "%x", &nt_par_err);
sscanf(argv[4], "%x", &nr_enc);
sscanf(argv[5], "%x", &ar_enc);
sscanf(argv[6], "%x", &ar_par_err);
sscanf(argv[7], "%x", &at_enc);
sscanf(argv[8], "%x", &at_par_err);
// next encrypted command + a full read/write
int enc_len = 0;
uint8_t enc[ENC_LEN] = {0};
if (argc > 9) {
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("----------- " _CYAN_("information") " ------------------------\n");
printf("uid.................. %08x\n", uid);
printf("nt encrypted......... %08x\n", nt_enc);
printf("nt parity err........ %04x\n", nt_par_err);
printf("nr encrypted......... %08x\n", nr_enc);
printf("ar encrypted......... %08x\n", ar_enc);
printf("ar parity err........ %04x\n", ar_par_err);
printf("at encrypted......... %08x\n", at_enc);
printf("at parity err........ %04x\n", at_par_err);
if (argc > 9) {
printf("next encrypted cmd... %s\n", sprint_hex_inrow_ex(enc, enc_len, 0));
}
uint64_t t1 = msclock();
uint16_t nt_par = parity_from_err(nt_enc, nt_par_err);
uint16_t ar_par = parity_from_err(ar_enc, ar_par_err);
uint16_t at_par = parity_from_err(at_enc, at_par_err);
// calc (parity XOR corresponding nonce bit encoded with the same keystream bit)
uint16_t xored = xored_bits(nt_par, nt_enc, ar_par, ar_enc, at_par, at_enc);
#if !defined(_WIN32) || !defined(__WIN32__)
thread_count = sysconf(_SC_NPROCESSORS_CONF);
if (thread_count < 2)
thread_count = 2;
#endif /* _WIN32 */
printf("\nBruteforce using " _YELLOW_("%d") " threads\n\n", thread_count);
pthread_t threads[thread_count];
// create a mutex to avoid interlacing print commands from our different threads
pthread_mutex_init(&print_lock, NULL);
// if we have 4 or more bytes, look for a default key
if (enc_len > 3) {
printf("----------- " _CYAN_("Phase 1 pre-processing") " ------------------------\n");
printf("Testing default keys using NESTED authentication...\n");
struct thread_key_args *def = calloc(1, sizeof(struct thread_key_args));
def->thread = 0;
def->idx = 0;
def->uid = uid;
def->nt_enc = nt_enc;
def->nr_enc = nr_enc;
def->enc_len = enc_len;
memcpy(def->enc, enc, enc_len);
pthread_create(&threads[0], NULL, check_default_keys, (void *)def);
pthread_join(threads[0], NULL);
if (global_found) {
goto out;
}
}
printf("\n----------- " _CYAN_("Phase 2 examine") " -------------------------------\n");
printf("Looking for the last bytes of the encrypted tagnonce\n");
printf("\nTarget old MFC...\n");
// the rest of available threads to EV1 scenario
for (int i = 0; i < thread_count; ++i) {
struct thread_args *a = calloc(1, sizeof(struct thread_args));
a->xored = xored;
a->thread = i;
a->idx = i;
a->ev1 = false;
pthread_create(&threads[i], NULL, brute_thread, (void *)a);
}
// wait for threads to terminate:
for (int i = 0; i < thread_count; ++i) {
pthread_join(threads[i], NULL);
}
t1 = msclock() - t1;
printf("execution time " _YELLOW_("%.2f") " sec\n", (float)t1 / 1000.0);
if (!global_found && !global_found_candidate) {
printf("\nTarget MFC Ev1...\n");
t1 = msclock();
// the rest of available threads to EV1 scenario
for (int i = 0; i < thread_count; ++i) {
struct thread_args *a = calloc(1, sizeof(struct thread_args));
a->xored = xored;
a->thread = i;
a->idx = i;
a->ev1 = true;
pthread_create(&threads[i], NULL, brute_thread, (void *)a);
}
// wait for threads to terminate:
for (int i = 0; i < thread_count; ++i) {
pthread_join(threads[i], NULL);
}
t1 = msclock() - t1;
printf("execution time " _YELLOW_("%.2f") " sec\n", (float)t1 / 1000.0);
if (!global_found && !global_found_candidate) {
printf("\nFailed to find a key\n\n");
goto out;
}
}
if (enc_len < 4) {
printf("Too few next cmd bytes, skipping phase 3\n\n");
goto out;
}
// reset thread signals
global_found_candidate = 0;
printf("\n----------- " _CYAN_("Phase 3 validating") " ----------------------------\n");
printf("uid.................. %08x\n", uid);
printf("partial key.......... %08x\n", (uint32_t)(global_candidate_key & 0xFFFFFFFF));
printf("possible key......... %012" PRIx64 "\n", global_candidate_key);
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("\nLooking for the upper 16 bits of the key\n");
fflush(stdout);
// threads
for (int i = 0; i < thread_count; ++i) {
struct thread_key_args *b = calloc(1, sizeof(struct thread_key_args));
b->thread = i;
b->idx = i;
b->uid = uid;
b->part_key = (uint32_t)(global_candidate_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:
for (int i = 0; i < thread_count; ++i) {
pthread_join(threads[i], NULL);
}
if (global_found_candidate > 1) {
printf("Key recovery ( " _GREEN_("ok") " )\n");
printf("Found " _GREEN_("%d") " possible keys\n", global_found_candidate);
printf(_YELLOW_("You need to test them manually, start with the one matching the candidate\n\n"));
} else if (global_found_candidate == 1) {
printf("Key recovery ( " _GREEN_("ok") " )\n\n");
} else {
printf("Key recovery ( " _RED_("fail") " )\n\n");
}
out:
// clean up mutex
pthread_mutex_destroy(&print_lock);
return 0;
}

View file

@ -0,0 +1,205 @@
mf_nonce_brute
==============
Nested autenticated sector key recovery tool
-----------------------------------------------
Compatible tags:
* Mifare Classic 1k (4k)
* Mifare Plus in SL1 mode
To recover keys to nested autenticated sectors you need a reader-card communication log. To get it use
hardware tools that able to sniff communication (for example Proxmark3 or HydraNFC).
This enhanced version:
First 2 bytes should be bruteforced in phase 2 with mf_key_brute tool that interacts with a card.
Sample trace:
```
93 70 fd ac f6 d8 7f 21 4f // select card with UID fdacf6d8
TAG 08 b6 dd // sak
60 04 d1 3d // wanna auth block 0x04 with A key
TAG ed 12 9c 74 // 1st auth clear text nt
55 53 9f cc 41 8d e8 f3 // nr', ar' (nr^ks1, ar^ks2 )
TAG 05 49 e1 65 // at' ( at^ks3 )
03 24 26 56 // wanna read block 0x04
TAG ac 69 ef 58 45 e1 c2 1d a9 47 a5 94 54 ef 5d c7 1e a9 // block 0x04 content
d4 3e a8 aa
TAG 8e 8e e3 e6 e9 e2 5f dd f6 08 ce fb 02 6a db 75 94 2f
79 77 68 3c
TAG e0 00 00 80 80 08 cc 80 08 9c 82 e0 68 64 60 30 91 60 // 18 bytes = 16 byte content + 2 bytes crc
ea 88 c3 c2 // 4 byte read cmd
TAG a3 76 dc df c1 42 e0 ee c6 75 a4 ca eb 0c da eb 46 a0 // 18 bytes = 16 byte content + 2 bytes crc ks8 + crc
2d 27 ab 6f // wanna auth to 0x04 block with key B
-------Until this line we can recover key or decrypt communication with no troubles (see mfkey64 tool)--------------------------------
TAG 52 6e af 8b // nested auth encrypted tag nonce that we don't know
8e 21 3a 29 a4 80 7e 02 // nr_enc = nr^ks1, ar_enc = ar^ks2
TAG b9 43 74 8d // at_enc = at^ks3
e2 25 f8 32 // probably next command (actually is read block cmd, but we don't know it yet)
TAG 1f 26 82 8d 12 21 dd 42 c2 84 3e d0 26 7f 6b 2a 81 a9 // probably data
ba 85 1d 36 // probably read cmd
TAG 62 a8 78 69 ee 36 22 16 1c ff 4b 4e 69 cb 27 c2 e8 7e // probably data
a7 b1 c8 da // probably read cmd
TAG b2 fc 6c 65 60 ec 35 83 87 56 e3 7e 3c bf 38 b8 73 21 // probably data
99 92 13 55 // probably read cmd
TAG 93 5b 65 a3 1d 8c 75 b8 3a 63 e2 31 f0 d0 a9 24 9a f6 // probably data
```
Phase 1
-------
Syntax:
`mf_nonce_brute <uid> <{nt}> <nt_par_err> <{nr}> <{ar}> <ar_par_err> <{at}> <at_par_err> [<{next_command}>]`
Example: if `nt` in trace is `8c! 42 e6! 4e!`, then `nt` is `8c42e64e` and `nt_par_err` is `1011`
Example with parity (from this trace http://www.proxmark.org/forum/viewtopic.php?pid=550#p550) :
```
+ 561882: 1 : 26
+ 64: 2 : TAG 04 00
+ 10217: 2 : 93 20
+ 64: 5 : TAG 9c 59 9b 32 6c UID
+ 12313: 9 : 93 70 9c 59 9b 32 6c 6b 30
+ 64: 3 : TAG 08 b6 dd
+ 923318: 4 : 60 00 f5 7b AUTH Block 0
+ 112: 4 : TAG 82 a4 16 6c Nonce Tag (NT)
+ 6985: 8 : a1 e4! 58 ce! 6e ea! 41 e0! NR , AR
+ 64: 4 : TAG 5c! ad f4 39! AT
+ 811513: 4 : 8e 0e! 5d! b9 AUTH Block 0 (nested)
+ 112: 4 : TAG 5a! 92 0d! 85! Nonce Tag (NT)
+ 6946: 8 : 98! d7 6b! 77 d6 c6 e8 70 NR , AR
+ 64: 4 : TAG ca 7e! 0b! 63! AT
+ 670868: 4 : 3e! 70 9c! 8a
+ 112: 4 : TAG 36! 41 24! 79
+ 9505: 8 : 1b! 8c 3a! 48! 83 5a 4a! 27
+ 64: 4 : TAG 40! 6a! 99! 4b
+ 905612: 4 : c9 7c 64! 13! !crc
+ 112: 4 : TAG b5! ab! 1d! 2b
+ 6936: 8 : 7e! d2 5c! ca! 4b! 50! 88! c4 !crc
+ 64: 4 : TAG bf dd 01 be!
+ 987853: 4 : 56 98 49 d6! !crc
```
=>
```
./mf_nonce_brute 9c599b32 82a4166c 0000 a1e458ce 6eea41e0 0101 5cadf439 1001 8e0e5db9
| | | | | | | | |
+UID +nt_enc | +nr_enc +ar_enc | +at_enc | +encrypted next cmd
+nt_par_err +at_par_err +at_par_err
```
These two taken from above use the plaintext tagnonce `nt`=`82a4166c`, they still find a possible key candidate.
```
./mf_nonce_brute 9c599b32 82a4166c 0000 a1e458ce 6eea41e0 0101 5cadf439 1001
./mf_nonce_brute 9c599b32 82a4166c 0000 98d76b77 d6c6e870 0000 ca7e0b63 0111
```
This one uses the encrypted tagnonce `nt`=`5a920d85`, it finds a valid key.
```
./mf_nonce_brute 9c599b32 5a920d85 1011 98d76b77 d6c6e870 0000 ca7e0b63 0111
```
This one uses the encrypted tagnonce `nt`=`5a920d85` and the encrypted cmd `3e709c8a` to validate , it finds a valid key.
```
./mf_nonce_brute 9c599b32 5a920d85 1011 98d76b77 d6c6e870 0000 ca7e0b63 0111 3e709c8a
```
Full output:
```
./mf_nonce_brute 9c599b32 5a920d85 1011 98d76b77 d6c6e870 0000 ca7e0b63 0111 3e709c8a
Mifare classic nested auth key recovery. Phase 1.
-------------------------------------------------
uid: 9c599b32
nt encrypted: 5a920d85
nt parity err: 1011
nr encrypted: 98d76b77
ar encrypted: d6c6e870
ar parity err: 0000
at encrypted: ca7e0b63
at parity err: 0111
next cmd enc: 3e709c8a
Starting 4 threads to bruteforce encrypted tag nonce last bytes
CMD enc(3e709c8a)
dec(6000f57b) <-- Valid cmd
Valid Key found: [ffffffffffff]
Time in mf_nonce_brute (Phase 1): 1763 ticks 2.0 seconds
```
[2024-07-11]
There is an odd case where we find multiple valid MIFARE Classic protocol commands with a valid ISO14443-A CRC when decrypting four bytes and are bruteforcing the last upper 16 bit of keyspace in phase 3.
The command has been updated to give a more informative text in order to help the user understanding and what to do next.
```
./mf_nonce_brute fcf77b54 1b456bdd 1110 f215b6 f9eb95e9 0011 bf55d0b1 0000 AAD4126B
```
When running you get the following full output
```
./mf_nonce_brute$ ./mf_nonce_brute fcf77b54 1b456bdd 1110 f215b6 f9eb95e9 0011 bf55d0b1 0000 AAD4126B
Mifare classic nested auth key recovery
----------- information ------------------------
uid.................. fcf77b54
nt encrypted......... 1b456bdd
nt parity err........ 1110
nr encrypted......... 00f215b6
ar encrypted......... f9eb95e9
ar parity err........ 0011
at encrypted......... bf55d0b1
at parity err........ 0000
next encrypted cmd... AAD4126B
Bruteforce using 8 threads
----------- Phase 1 pre-processing ------------------------
Testing default keys using NESTED authentication...
----------- Phase 2 examine -------------------------------
Looking for the last bytes of the encrypted tagnonce
Target old MFC...
CMD enc( aad4126b )
dec( 302424cf ) <-- valid cmd
Key candidate [ ....37afcc2b ]
Key candidate [ a70d37afcc2b ]
execution time 0.47 sec
----------- Phase 3 validating ----------------------------
uid.................. fcf77b54
partial key.......... 37afcc2b
possible key......... a70d37afcc2b
nt enc............... 1b456bdd
nr enc............... 00f215b6
next encrypted cmd... AAD4126B
Looking for the upper 16 bits of the key
enc: AAD4126B
dec: 610BFEDC
Valid Key found [ 7c2337afcc2b ]
enc: AAD4126B
dec: 302424CF
Valid Key found [ a70d37afcc2b ] - matches candidate
Odd case but we found 2 possible keys
You need to test all of them manually, start with the one matching the candidate
```

View file

@ -0,0 +1,319 @@
//
// bruteforce the upper 16bits of a partial key recovered from mf_nonce_brute.
// J-run's original idea was a two part recovery vector with first a offline trace and then online for 2 bytes.
//
// This idea is two use only offline, to recover a nested authentication key.
// Assumption, we get a read/write command after a nested auth, we need 22 bytes of data.
// Iceman, 2021,
//
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include "ctype.h"
#include "crapto1/crapto1.h"
#include "protocol.h"
#include "iso14443crc.h"
#include <util_posix.h>
#define AEND "\x1b[0m"
#define _RED_(s) "\x1b[31m" s AEND
#define _GREEN_(s) "\x1b[32m" s AEND
#define _YELLOW_(s) "\x1b[33m" s AEND
#define _CYAN_(s) "\x1b[36m" s AEND
// a global mutex to prevent interlaced printing from different threads
pthread_mutex_t print_lock;
#define ENC_LEN (4 + 16 + 2)
//--------------------- define options here
typedef struct thread_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;
//------------------------------------------------------------------
uint8_t cmds[8][2] = {
{ISO14443A_CMD_READBLOCK, 18},
{ISO14443A_CMD_WRITEBLOCK, 18},
{MIFARE_AUTH_KEYA, 0},
{MIFARE_AUTH_KEYB, 0},
{MIFARE_CMD_INC, 6},
{MIFARE_CMD_DEC, 6},
{MIFARE_CMD_RESTORE, 6},
{MIFARE_CMD_TRANSFER, 0}
};
static int global_found = 0;
static int 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 don't 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_base = (char *)buf;
char *tmp = tmp_base;
size_t i;
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) {
snprintf(tmp, hex_max_len - (tmp - tmp_base), (uppercase) ? "%02X" : "%02x", (unsigned int) hex_data[i]);
for (size_t j = 0; j < spaces_between; j++)
snprintf(tmp + 2 + j, hex_max_len - (2 + j + (tmp - tmp_base)), " ");
}
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)
snprintf(tmp, hex_max_len - (tmp - tmp_base), " ");
// remove last space
*tmp = '\0';
}
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 bool checkValidCmdByte(uint8_t *cmd, uint16_t n) {
bool ok = false;
if (cmd == NULL)
return 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 void *brute_thread(void *arguments) {
struct thread_args *args = (struct thread_args *) arguments;
uint64_t key = args->part_key;
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) {
if (__atomic_load_n(&global_found, __ATOMIC_ACQUIRE) == 1) {
break;
}
key |= count << 32;
// Init cipher with key
struct Crypto1State *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_destroy(pcs);
if (checkValidCmdByte(dec, args->enc_len) == false) {
continue;
}
__sync_fetch_and_add(&global_found, 1);
// 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 [ " _GREEN_("%012" PRIx64) " ]\n\n", key);
pthread_mutex_unlock(&print_lock);
break;
}
free(args);
return NULL;
}
static int usage(void) {
printf(" syntax: mf_trace_brute <uid> <partial key> <nt enc> <nr enc> [<next_command + 18 bytes>]\n\n");
return 1;
}
int main(int argc, const char *argv[]) {
printf("Mifare classic nested auth key recovery Phase 2\n");
if (argc < 3) return usage();
uint32_t uid = 0; // serial number
uint32_t part_key = 0; // last 4 keys of key
uint32_t nt_enc = 0; // noncce tag
uint32_t nr_enc = 0; // nonce reader encrypted
sscanf(argv[1], "%x", &uid);
sscanf(argv[2], "%x", &part_key);
sscanf(argv[3], "%x", &nt_enc);
sscanf(argv[4], "%x", &nr_enc);
int enc_len = 0;
uint8_t enc[ENC_LEN] = {0}; // next encrypted command + a full read/write
param_gethex_to_eol(argv[5], 0, enc, sizeof(enc), &enc_len);
printf("-------------------------------------------------\n");
printf("uid.................. %08x\n", uid);
printf("partial key.......... %08x\n", part_key);
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));
uint64_t t1 = msclock();
#if !defined(_WIN32) || !defined(__WIN32__)
thread_count = sysconf(_SC_NPROCESSORS_CONF);
if (thread_count < 2)
thread_count = 2;
#endif /* _WIN32 */
printf("\nBruteforce using %d threads to find upper 16bits of key\n", thread_count);
pthread_t threads[thread_count];
// create a mutex to avoid interlacing print commands from our different threads
pthread_mutex_init(&print_lock, NULL);
// threads
for (int i = 0; i < thread_count; ++i) {
struct thread_args *a = calloc(1, sizeof(struct thread_args));
a->thread = i;
a->idx = i;
a->uid = uid;
a->part_key = part_key;
a->nt_enc = nt_enc;
a->nr_enc = nr_enc;
a->enc_len = enc_len;
memcpy(a->enc, enc, enc_len);
pthread_create(&threads[i], NULL, brute_thread, (void *)a);
}
// wait for threads to terminate:
for (int i = 0; i < thread_count; ++i)
pthread_join(threads[i], NULL);
if (global_found == false) {
printf("\nFailed to find a key\n\n");
}
t1 = msclock() - t1;
if (t1 > 0)
printf("execution time " _YELLOW_("%.2f") " sec\n", (float)t1 / 1000.0);
// clean up mutex
pthread_mutex_destroy(&print_lock);
return 0;
}

View file

@ -0,0 +1,70 @@
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include "crapto1/crapto1.h"
#include "util_posix.h"
int main(int argc, char *argv[]) {
struct Crypto1State *s, *t;
uint64_t key; // recovered key
uint32_t uid; // serial number
uint32_t nt; // tag challenge
uint32_t nr0_enc; // first encrypted reader challenge
uint32_t ar0_enc; // first encrypted reader response
uint32_t nr1_enc; // second encrypted reader challenge
uint32_t ar1_enc; // second encrypted reader response
uint32_t ks2; // keystream used to encrypt reader response
printf("MIFARE Classic key recovery - based on 32 bits of keystream\n");
printf("Recover key from two 32-bit reader authentication answers only!\n\n");
if (argc < 7) {
printf(" syntax: %s <uid> <nt> <nr_0> <ar_0> <nr_1> <ar_1>\n\n", argv[0]);
return 1;
}
sscanf(argv[1], "%x", &uid);
sscanf(argv[2], "%x", &nt);
sscanf(argv[3], "%x", &nr0_enc);
sscanf(argv[4], "%x", &ar0_enc);
sscanf(argv[5], "%x", &nr1_enc);
sscanf(argv[6], "%x", &ar1_enc);
printf("Recovering key for:\n");
printf(" uid: %08x\n", uid);
printf(" nt: %08x\n", nt);
printf(" {nr_0}: %08x\n", nr0_enc);
printf(" {ar_0}: %08x\n", ar0_enc);
printf(" {nr_1}: %08x\n", nr1_enc);
printf(" {ar_1}: %08x\n", ar1_enc);
// Generate lfsr successors of the tag challenge
printf("\nLFSR successors of the tag challenge:\n");
uint32_t p64 = prng_successor(nt, 64);
printf(" nt': %08x\n", p64);
printf(" nt'': %08x\n", prng_successor(p64, 32));
// Extract the keystream from the messages
printf("\nKeystream used to generate {ar} and {at}:\n");
ks2 = ar0_enc ^ p64;
printf(" ks2: %08x\n", ks2);
s = lfsr_recovery32(ar0_enc ^ p64, 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) ^ p64)) {
printf("\nFound Key: [%012" PRIx64 "]\n\n", key);
break;
}
}
free(s);
return 0;
}

View file

@ -0,0 +1,77 @@
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include "crapto1/crapto1.h"
#include "util_posix.h"
int main(int argc, char *argv[]) {
struct Crypto1State *s, *t;
uint64_t key; // recovered key
uint32_t uid; // serial number
uint32_t nt0; // tag challenge first
uint32_t nt1; // tag challenge second
uint32_t nr0_enc; // first encrypted reader challenge
uint32_t ar0_enc; // first encrypted reader response
uint32_t nr1_enc; // second encrypted reader challenge
uint32_t ar1_enc; // second encrypted reader response
uint32_t ks2; // keystream used to encrypt reader response
printf("MIFARE Classic key recovery - based 32 bits of keystream VERSION2\n");
printf("Recover key from two 32-bit reader authentication answers only\n");
printf("This version implements Moebius two different nonce solution (like the supercard)\n\n");
if (argc < 8) {
printf("syntax: %s <uid> <nt> <nr_0> <ar_0> <nt1> <nr_1> <ar_1>\n\n", argv[0]);
return 1;
}
sscanf(argv[1], "%x", &uid);
sscanf(argv[2], "%x", &nt0);
sscanf(argv[3], "%x", &nr0_enc);
sscanf(argv[4], "%x", &ar0_enc);
sscanf(argv[5], "%x", &nt1);
sscanf(argv[6], "%x", &nr1_enc);
sscanf(argv[7], "%x", &ar1_enc);
printf("Recovering key for:\n");
printf(" uid: %08x\n", uid);
printf(" nt_0: %08x\n", nt0);
printf(" {nr_0}: %08x\n", nr0_enc);
printf(" {ar_0}: %08x\n", ar0_enc);
printf(" nt_1: %08x\n", nt1);
printf(" {nr_1}: %08x\n", nr1_enc);
printf(" {ar_1}: %08x\n", ar1_enc);
// Generate lfsr successors of the tag challenge
printf("\nLFSR successors of the tag challenge:\n");
uint32_t p64 = prng_successor(nt0, 64);
uint32_t p64b = prng_successor(nt1, 64);
printf(" nt': %08x\n", p64);
printf(" nt'': %08x\n", prng_successor(p64, 32));
// Extract the keystream from the messages
printf("\nKeystream used to generate {ar} and {at}:\n");
ks2 = ar0_enc ^ p64;
printf(" ks2: %08x\n", ks2);
s = lfsr_recovery32(ar0_enc ^ p64, 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) ^ p64b)) {
printf("\nFound Key: [%012" PRIx64 "]\n\n", key);
break;
}
}
free(s);
return 0;
}

View file

@ -0,0 +1,102 @@
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "crapto1/crapto1.h"
#include "util_posix.h"
int main(int argc, char *argv[]) {
struct Crypto1State *revstate;
uint64_t key; // recovered key
uint32_t uid; // serial number
uint32_t nt; // tag challenge
uint32_t nr_enc; // encrypted reader challenge
uint32_t ar_enc; // encrypted reader response
uint32_t at_enc; // encrypted tag response
uint32_t ks2; // keystream used to encrypt reader response
uint32_t ks3; // keystream used to encrypt tag response
printf("MIFARE Classic key recovery - based 64 bits of keystream\n");
printf("Recover key from only one complete authentication!\n\n");
if (argc < 6) {
printf(" syntax: %s <uid> <nt> <{nr}> <{ar}> <{at}> [enc...]\n\n", argv[0]);
return 1;
}
int encc = argc - 6;
int enclen[encc];
uint8_t enc[encc][120];
sscanf(argv[1], "%x", &uid);
sscanf(argv[2], "%x", &nt);
sscanf(argv[3], "%x", &nr_enc);
sscanf(argv[4], "%x", &ar_enc);
sscanf(argv[5], "%x", &at_enc);
for (int i = 0; i < encc; i++) {
enclen[i] = strlen(argv[i + 6]) / 2;
for (int i2 = 0; i2 < enclen[i]; i2++) {
sscanf(argv[i + 6] + i2 * 2, "%2hhx", &enc[i][i2]);
}
}
printf("Recovering key for:\n");
printf(" uid: %08x\n", uid);
printf(" nt: %08x\n", nt);
printf(" {nr}: %08x\n", nr_enc);
printf(" {ar}: %08x\n", ar_enc);
printf(" {at}: %08x\n", at_enc);
for (int i = 0; i < encc; i++) {
printf("{enc%d}: ", i);
for (int i2 = 0; i2 < enclen[i]; i2++) {
printf("%02x", enc[i][i2]);
}
printf("\n");
}
// Generate lfsr successors of the tag challenge
printf("\nLFSR successors of the tag challenge:\n");
uint32_t p64 = prng_successor(nt, 64);
printf(" nt': %08x\n", p64);
printf(" nt'': %08x\n", prng_successor(p64, 32));
// Extract the keystream from the messages
printf("\nKeystream used to generate {ar} and {at}:\n");
ks2 = ar_enc ^ p64;
ks3 = at_enc ^ prng_successor(p64, 32);
printf(" ks2: %08x\n", ks2);
printf(" ks3: %08x\n", ks3);
revstate = lfsr_recovery64(ks2, ks3);
// Decrypting communication using keystream if presented
if (argc > 6) {
printf("\nDecrypted communication:\n");
uint8_t ks4;
int rollb = 0;
for (int i = 0; i < encc; i++) {
printf("{dec%d}: ", i);
for (int i2 = 0; i2 < enclen[i]; i2++) {
ks4 = crypto1_byte(revstate, 0, 0);
printf("%02x", ks4 ^ enc[i][i2]);
rollb += 1;
}
printf("\n");
}
for (int i = 0; i < rollb; i++)
lfsr_rollback_byte(revstate, 0, 0);
}
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);
printf("\nFound Key: [%012" PRIx64 "]\n\n", key);
crypto1_destroy(revstate);
return 0;
}

View file

@ -0,0 +1,105 @@
## Sample trace
```
+ 50422: : 26
+ 64: 0: TAG 04 00
+ 944: : 93 20
+ 64: 0: TAG 9c 59 9b 32 6c
+ 1839: : 93 70 9c 59 9b 32 6c 6b 30
+ 64: 0: TAG 08 b6 dd
+ 3783: : 60 32 64 69
+ 113: 0: TAG 82 a4 16 6c
+ 1287: : a1 e4 58 ce 6e ea 41 e0
+ 64: 0: TAG 5c ad f4 39
```
Usage with sample trace:
`./mfkey64 9C599B32 82A4166C A1E458CE 6EEA41E0 5CADF439`
## Other examples
For mfkey32, you want to get two different NR_0/NR_1 values.
```
# <uid> <nt> <nr_0> <ar_0> <nr_1> <ar_1>
./mfkey32 52B0F519 5417D1F8 4D545EA7 E15AC8C2 DAC1A7F4 5AE5C37F
```
For mfkey32v2 (moebius), you want to get two different NT/NT1 values. (like in the SIM commands)
```
# <uid> <nt> <nr_0> <ar_0> <nt1> <nr_1> <ar_1>
./mfkey32v2 12345678 1AD8DF2B 1D316024 620EF048 30D6CB07 C52077E2 837AC61A
./mfkey32v2 52B0F519 5417D1F8 4D545EA7 E15AC8C2 A1BA88C6 DAC1A7F4 5AE5C37F
```
For mfkey64, you want to have the AT response from tag.
```
# <uid> <nt> <nr> <ar> <at>
./mfkey64 9C599B32 82A4166C A1E458CE 6EEA41E0 5CADF439
./mfkey64 52B0F519 5417D1F8 4D545EA7 E15AC8C2 5056E41B
```
### Communication decryption
A new functionality from @zhovner
Example: given the following trace
```
RDR 26
TAG 04 00
RDR 93 20
TAG 14 57 9f 69 b5
RDR 93 70 14 57 9f 69 b5 2e 51
TAG 08 b6 dd
RDR 60 14 50 2d
TAG ce 84 42 61
RDR f8 04 9c cb 05 25 c8 4f
TAG 94 31 cc 40
RDR 70 93 df 99
TAG 99 72 42 8c e2 e8 52 3f 45 6b 99 c8 31 e7 69 dc ed 09
RDR 8c a6 82 7b
TAG ab 79 7f d3 69 e8 b9 3a 86 77 6b 40 da e3 ef 68 6e fd
RDR c3 c3 81 ba
TAG 49 e2 c9 de f4 86 8d 17 77 67 0e 58 4c 27 23 02 86 f4
RDR fb dc d7 c1
TAG 4a bd 96 4b 07 d3 56 3a a0 66 ed 0a 2e ac 7f 63 12 bf
RDR 9f 91 49 ea
```
`./mfkey64 14579f69 ce844261 f8049ccb 0525c84f 9431cc40 7093df99 9972428ce2e8523f456b99c831e769dced09 8ca6827b ab797fd369e8b93a86776b40dae3ef686efd c3c381ba 49e2c9def4868d1777670e584c27230286f4 fbdcd7c1 4abd964b07d3563aa066ed0a2eac7f6312bf 9f9149ea`
```
Recovering key for:
uid: 14579f69
nt: ce844261
{nr}: f8049ccb
{ar}: 0525c84f
{at}: 9431cc40
{enc0}: 7093df99
{enc1}: 9972428ce2e8523f456b99c831e769dced09
{enc2}: 8ca6827b
{enc3}: ab797fd369e8b93a86776b40dae3ef686efd
{enc4}: c3c381ba
{enc5}: 49e2c9def4868d1777670e584c27230286f4
{enc6}: fbdcd7c1
{enc7}: 4abd964b07d3563aa066ed0a2eac7f6312bf
{enc8}: 9f9149ea
LFSR successors of the tag challenge:
nt': 76d4468d
nt'': d5f3c476
Keystream used to generate {ar} and {at}:
ks2: 73f18ec2
ks3: 41c20836
Decrypted communication:
{dec0}: 3014a7fe
{dec1}: c26935cfdb95c4b4a27a84b8217ae9e48217
{dec2}: 30152eef
{dec3}: 493167c536c30f8e220b09675687067d4b31
{dec4}: 3016b5dd
{dec5}: 493167c536c30f8e220b09675687067d4b31
{dec6}: 30173ccc
{dec7}: 0000000000007e178869000000000000c4f2
{dec8}: 61148834
Found Key: [091e639cb715]
```

View file

@ -0,0 +1,20 @@
#ifndef PROTOCOL_H
#define PROTOCOL_H
#define ISO14443A_CMD_READBLOCK 0x30
#define ISO14443A_CMD_WRITEBLOCK 0xA0
#define MIFARE_AUTH_KEYA 0x60
#define MIFARE_AUTH_KEYB 0x61
#define MIFARE_CMD_DEC 0xC0
#define MIFARE_CMD_INC 0xC1
#define MIFARE_CMD_RESTORE 0xC2
#define MIFARE_CMD_TRANSFER 0xB0
// mifare 4bit card answers
#define CARD_ACK 0x0A // 1010 - ACK
#define CARD_NACK_NA 0x04 // 0100 - NACK, not allowed (command not allowed)
#define CARD_NACK_TR 0x05 // 0101 - NACK, transmission error
#endif
// PROTOCOL_H

View file

@ -0,0 +1,28 @@
//-----------------------------------------------------------------------------
// Copyright (C) 2010 iZsh <izsh at fail0verflow.com>
//
// 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.
//-----------------------------------------------------------------------------
// platform-independant sleep macros
//-----------------------------------------------------------------------------
#ifndef _WIN32
#define _POSIX_C_SOURCE 199309L
#include "sleep.h"
#include <time.h>
#include <stdio.h>
#include <sys/time.h>
#include <errno.h>
void nsleep(uint64_t n) {
struct timespec timeout;
timeout.tv_sec = n / 1000000000;
timeout.tv_nsec = n % 1000000000;
while (nanosleep(&timeout, &timeout) && errno == EINTR);
}
#endif // _WIN32

View file

@ -0,0 +1,27 @@
//-----------------------------------------------------------------------------
// Copyright (C) 2010 iZsh <izsh at fail0verflow.com>
//
// 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.
//-----------------------------------------------------------------------------
// platform-independant sleep macros
//-----------------------------------------------------------------------------
#ifndef SLEEP_H__
#define SLEEP_H__
#ifdef _WIN32
# include <windows.h>
# define sleep(n) Sleep(1000 * n)
# define msleep(n) Sleep(n)
#else
# include <inttypes.h>
# include <unistd.h>
void nsleep(uint64_t n);
# define msleep(n) nsleep(1000000 * n)
# define usleep(n) nsleep(1000 * n)
#endif // _WIN32
#endif // SLEEP_H__

View file

@ -0,0 +1,137 @@
//-----------------------------------------------------------------------------
// Copyright (C) 2010 iZsh <izsh at fail0verflow.com>
//
// 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.
//-----------------------------------------------------------------------------
// utilities requiring Posix library functions
//-----------------------------------------------------------------------------
// ensure availability even with -std=c99; must be included before
#if !defined(_WIN32)
//#define _POSIX_C_SOURCE 199309L // need nanosleep()
#define _POSIX_C_SOURCE 200112L // need localtime_r()
#else
#include <windows.h>
#endif
#include "util_posix.h"
#include <stdint.h>
#include <time.h>
// Timer functions
#if !defined (_WIN32)
#include <errno.h>
static void nsleep(uint64_t n) {
struct timespec timeout;
timeout.tv_sec = n / 1000000000;
timeout.tv_nsec = n % 1000000000;
while (nanosleep(&timeout, &timeout) && errno == EINTR);
}
void msleep(uint32_t n) {
nsleep(1000000 * (uint64_t)n);
}
#endif // _WIN32
#ifdef __APPLE__
#ifndef CLOCK_MONOTONIC
#define CLOCK_MONOTONIC (1)
#endif
#ifndef CLOCK_REALTIME
#define CLOCK_REALTIME (2)
#endif
#include <sys/time.h>
#include <mach/clock.h>
#include <mach/mach.h>
#include <mach/mach_time.h>
/* clock_gettime is not implemented on OSX prior to 10.12 */
int _civet_clock_gettime(int clk_id, struct timespec *t);
int _civet_clock_gettime(int clk_id, struct timespec *t) {
memset(t, 0, sizeof(*t));
if (clk_id == CLOCK_REALTIME) {
struct timeval now;
int rv = gettimeofday(&now, NULL);
if (rv) {
return rv;
}
t->tv_sec = now.tv_sec;
t->tv_nsec = now.tv_usec * 1000;
return 0;
} else if (clk_id == CLOCK_MONOTONIC) {
static uint64_t clock_start_time = 0;
static mach_timebase_info_data_t timebase_info = {0, 0};
uint64_t now = mach_absolute_time();
if (clock_start_time == 0) {
mach_timebase_info(&timebase_info);
clock_start_time = now;
}
now = (uint64_t)((double)(now - clock_start_time)
* (double)timebase_info.numer
/ (double)timebase_info.denom);
t->tv_sec = now / 1000000000;
t->tv_nsec = now % 1000000000;
return 0;
}
return -1; // EINVAL - Clock ID is unknown
}
/* if clock_gettime is declared, then __CLOCK_AVAILABILITY will be defined */
#ifdef __CLOCK_AVAILABILITY
/* If we compiled with Mac OSX 10.12 or later, then clock_gettime will be declared
* but it may be NULL at runtime. So we need to check before using it. */
int _civet_safe_clock_gettime(int clk_id, struct timespec *t);
int _civet_safe_clock_gettime(int clk_id, struct timespec *t) {
if (clock_gettime) {
return clock_gettime(clk_id, t);
}
return _civet_clock_gettime(clk_id, t);
}
#define clock_gettime _civet_safe_clock_gettime
#else
#define clock_gettime _civet_clock_gettime
#endif
#endif
// a milliseconds timer for performance measurement
uint64_t msclock(void) {
#if defined(_WIN32)
#include <sys/types.h>
// WORKAROUND FOR MinGW (some versions - use if normal code does not compile)
// It has no _ftime_s and needs explicit inclusion of timeb.h
#include <sys/timeb.h>
struct _timeb t;
_ftime(&t);
return 1000 * (uint64_t)t.time + t.millitm;
// NORMAL CODE (use _ftime_s)
//struct _timeb t;
//if (_ftime_s(&t)) {
// return 0;
//} else {
// return 1000 * t.time + t.millitm;
//}
#else
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (1000 * (uint64_t)t.tv_sec + t.tv_nsec / 1000000);
#endif
}

View file

@ -0,0 +1,26 @@
//-----------------------------------------------------------------------------
// Copyright (C) 2010 iZsh <izsh at fail0verflow.com>
//
// 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.
//-----------------------------------------------------------------------------
// utilities requiring Posix library functions
//-----------------------------------------------------------------------------
#ifndef UTIL_POSIX_H__
#define UTIL_POSIX_H__
#include "common.h"
#ifdef _WIN32
# include <windows.h>
# define sleep(n) Sleep(1000 *(n))
# define msleep(n) Sleep((n))
#else
void msleep(uint32_t n); // sleep n milliseconds
#endif // _WIN32
uint64_t msclock(void); // a milliseconds clock
#endif