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

13
tools/mfc/card_only/.gitignore vendored Normal file
View file

@ -0,0 +1,13 @@
nonce2key
staticnested_0nt
staticnested_1nt
staticnested_2nt
staticnested_2x1nt_rf08s_1key
staticnested_2x1nt_rf08s
nonce2key.exe
staticnested_0nt.exe
staticnested_1nt.exe
staticnested_2nt.exe
staticnested_2x1nt_rf08s_1key.exe
staticnested_2x1nt_rf08s.exe
keys*.dic

View file

@ -0,0 +1,36 @@
ROOTPATH = ../../..
MYSRCPATHS = $(ROOTPATH)/common $(ROOTPATH)/common/crapto1
MYSRCS = crypto1.c crapto1.c bucketsort.c nested_util.c
MYINCLUDES = -I$(ROOTPATH)/include -I$(ROOTPATH)/common
MYCFLAGS = -O3
MYDEFS =
BINS = nonce2key staticnested_0nt staticnested_1nt staticnested_2nt staticnested_2x1nt_rf08s_1key staticnested_2x1nt_rf08s
INSTALLTOOLS = $(BINS)
include $(ROOTPATH)/Makefile.host
# nested_util.c needs pthread support. Older glibc needs it externally
ifneq ($(SKIPPTHREAD),1)
MYLDLIBS += -lpthread
endif
# 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
nonce2key : $(OBJDIR)/nonce2key.o $(MYOBJS)
staticnested_0nt : $(OBJDIR)/staticnested_0nt.o $(MYOBJS)
staticnested_1nt : $(OBJDIR)/staticnested_1nt.o $(MYOBJS)
staticnested_2nt : $(OBJDIR)/staticnested_2nt.o $(MYOBJS)
staticnested_2x1nt_rf08s_1key : $(OBJDIR)/staticnested_2x1nt_rf08s_1key.o $(MYOBJS)
staticnested_2x1nt_rf08s : $(OBJDIR)/staticnested_2x1nt_rf08s.o $(MYOBJS)

View file

@ -0,0 +1,271 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <inttypes.h>
#include <ctype.h>
#include "parity.h"
#ifdef __WIN32
#include "windows.h"
#else
#include "unistd.h"
#endif
#include "pthread.h"
#include "nested_util.h"
#define MEM_CHUNK 10000
#define TRY_KEYS 50
typedef struct {
uint64_t key;
int count;
} countKeys;
typedef struct {
NtpKs1 *pNK;
uint32_t authuid;
uint64_t *keys;
uint32_t keyCount;
uint32_t startPos;
uint32_t endPos;
} RecPar;
inline static int compar_int(const void *a, const void *b) {
if (*(uint64_t *)b == *(uint64_t *)a) return 0;
if (*(uint64_t *)b < * (uint64_t *)a) return 1;
return -1;
}
// Compare countKeys structure
static int compar_special_int(const void *a, const void *b) {
return (((countKeys *)b)->count - ((countKeys *)a)->count);
}
// keys qsort and unique.
static countKeys *uniqsort(uint64_t *possibleKeys, uint32_t size) {
unsigned int i, j = 0;
int count = 0;
countKeys *our_counts;
qsort(possibleKeys, size, sizeof(uint64_t), compar_int);
our_counts = calloc(size, sizeof(countKeys));
if (our_counts == NULL) {
printf("Memory allocation error for our_counts");
exit(EXIT_FAILURE);
}
for (i = 0; i < size; i++) {
if (possibleKeys[i + 1] == possibleKeys[i]) {
count++;
} else {
our_counts[j].key = possibleKeys[i];
our_counts[j].count = count;
j++;
count = 0;
}
}
qsort(our_counts, j, sizeof(countKeys), compar_special_int);
return (our_counts);
}
// nested decrypt
static void *nested_revover(void *args) {
struct Crypto1State *revstate, * revstate_start = NULL;
uint64_t lfsr = 0;
uint32_t i, kcount = 0;
bool is_ok = true;
RecPar *rp = (RecPar *)args;
rp->keyCount = 0;
rp->keys = NULL;
//printf("Start pos is %d, End pos is %d\r\n", rp->startPos, rp->endPos);
for (i = rp->startPos; i < rp->endPos; i++) {
uint32_t nt_probe = rp->pNK[i].ntp ^ rp->authuid;
uint32_t ks1 = rp->pNK[i].ks1;
/*
printf(" ntp = %"PRIu32"\r\n", nt_probe);
printf(" ks1 = %"PRIu32"\r\n", ks1);
printf("\r\n");
*/
// And finally recover the first 32 bits of the key
revstate = lfsr_recovery32(ks1, nt_probe);
if (revstate_start == NULL) {
revstate_start = revstate;
}
while ((revstate->odd != 0x0) || (revstate->even != 0x0)) {
lfsr_rollback_word(revstate, nt_probe, 0);
crypto1_get_lfsr(revstate, &lfsr);
if (((kcount % MEM_CHUNK) == 0) || (kcount >= rp->keyCount)) {
rp->keyCount += MEM_CHUNK;
// printf("New chunk by %d, sizeof %lu\n", kcount, rp->keyCount * sizeof(uint64_t));
void *tmp = realloc(rp->keys, rp->keyCount * sizeof(uint64_t));
if (tmp == NULL) {
printf("Memory allocation error for pk->possibleKeys");
rp->keyCount = 0;
is_ok = false;
break;
}
rp->keys = (uint64_t *)tmp;
}
rp->keys[kcount] = lfsr;
kcount++;
revstate++;
}
--kcount;
free(revstate_start);
revstate_start = NULL;
if (!is_ok) {
break;
}
}
if (is_ok) {
if (kcount != 0) {
rp->keyCount = kcount;
void *tmp = (uint64_t *)realloc(rp->keys, rp->keyCount * sizeof(uint64_t));
if (tmp == NULL) {
printf("Memory allocation error for pk->possibleKeys");
rp->keyCount = 0;
free(rp->keys);
} else {
rp->keys = tmp;
}
}
} else {
rp->keyCount = 0;
free(rp->keys);
}
return NULL;
}
uint64_t *nested(NtpKs1 *pNK, uint32_t sizePNK, uint32_t authuid, uint32_t *keyCount) {
#define THREAD_MAX 4
*keyCount = 0;
uint32_t i, j, manyThread;
uint64_t *keys = (uint64_t *)NULL;
manyThread = THREAD_MAX;
if (manyThread > sizePNK) {
manyThread = sizePNK;
}
// pthread handle
pthread_t *threads = calloc(sizePNK, sizeof(pthread_t));
if (threads == NULL) return NULL;
// Param
RecPar *pRPs = calloc(sizePNK, sizeof(RecPar));
if (pRPs == NULL) {
free(threads);
return NULL;
}
uint32_t average = sizePNK / manyThread;
uint32_t modules = sizePNK % manyThread;
// Assign tasks
for (i = 0, j = 0; i < manyThread; i++, j += average) {
pRPs[i].pNK = pNK;
pRPs[i].authuid = authuid;
pRPs[i].startPos = j;
pRPs[i].endPos = j + average;
pRPs[i].keys = NULL;
// last thread can decrypt more pNK
if (i == (manyThread - 1) && modules > 0) {
(pRPs[i].endPos) += modules;
}
pthread_create(&threads[i], NULL, nested_revover, &(pRPs[i]));
}
for (i = 0; i < manyThread; i++) {
// wait thread exit...
pthread_join(threads[i], NULL);
*keyCount += pRPs[i].keyCount;
}
free(threads);
if (*keyCount == 0) {
printf("Didn't recover any keys.\r\n");
free(pRPs);
return NULL;
}
keys = calloc((*keyCount) * sizeof(uint64_t), sizeof(uint8_t));
if (keys == NULL) {
printf("Cannot allocate memory to merge keys.\r\n");
free(pRPs);
return NULL;
}
for (i = 0, j = 0; i < manyThread; i++) {
if (pRPs[i].keyCount > 0) {
// printf("The thread %d recover %d keys.\r\n", i, pRPs[i].keyCount);
if (pRPs[i].keys != NULL) {
memcpy(
keys + j,
pRPs[i].keys,
pRPs[i].keyCount * sizeof(uint64_t)
);
j += pRPs[i].keyCount;
free(pRPs[i].keys);
}
}
}
countKeys *ck = uniqsort(keys, *keyCount);
free(keys);
keys = (uint64_t *)NULL;
*keyCount = 0;
if (ck == NULL) {
printf("Cannot allocate memory for ck on uniqsort.");
free(ck);
free(pRPs);
return NULL;
}
for (i = 0; i < TRY_KEYS; i++) {
// We don't known this key, try to break it
// This key can be found here two or more times
if (ck[i].count > 0) {
*keyCount += 1;
void *tmp = realloc(keys, sizeof(uint64_t) * (*keyCount));
if (tmp == NULL) {
printf("Cannot allocate memory for keys on merge.");
free(ck);
free(keys);
free(pRPs);
return NULL;
}
keys = tmp;
keys[*keyCount - 1] = ck[i].key;
}
}
free(ck);
free(pRPs);
return keys;
}
// Return 1 if the nonce is invalid else return 0
uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity) {
return (
(oddparity8((Nt >> 24) & 0xFF) == ((parity[0]) ^ oddparity8((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) && \
(oddparity8((Nt >> 16) & 0xFF) == ((parity[1]) ^ oddparity8((NtEnc >> 16) & 0xFF) ^ BIT(Ks1, 8))) && \
(oddparity8((Nt >> 8) & 0xFF) == ((parity[2]) ^ oddparity8((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0)))
) ? 1 : 0;
}

View file

@ -0,0 +1,15 @@
#ifndef NESTED_H__
#define NESTED_H__
#include "crapto1/crapto1.h"
typedef struct {
uint32_t ntp;
uint32_t ks1;
} NtpKs1;
uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity);
uint64_t *nested(NtpKs1 *pNK, uint32_t sizePNK, uint32_t authuid, uint32_t *keyCount);
#endif

View file

@ -0,0 +1,58 @@
#include "crapto1/crapto1.h"
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#include <stdio.h>
int main(const int argc, const char *argv[]) {
struct Crypto1State *state;
uint32_t pos, uid, nt, nr, rr;
uint8_t ks3x[8], par[8][8];
uint64_t key_recovered;
uint64_t par_info;
uint64_t ks_info;
nr = rr = 0;
if (argc < 5) {
printf("\nsyntax: %s <uid> <nt> <par> <ks>\n", argv[0]);
printf("example: %s 92c0456b 73294ab7 a3fbfb537343eb7b 070608090e060a02\n\n", argv[0]);
return 1;
}
sscanf(argv[1], "%08x", &uid);
sscanf(argv[2], "%08x", &nt);
sscanf(argv[3], "%016" SCNx64, &par_info);
sscanf(argv[4], "%016" SCNx64, &ks_info);
// Reset the last three significant bits of the reader nonce
nr &= 0xffffff1f;
printf("\nuid(%08x) nt(%08x) par(%016" PRIx64 ") ks(%016" PRIx64 ")\n\n", uid, nt, par_info, ks_info);
for (pos = 0; pos < 8; pos++) {
ks3x[7 - pos] = (ks_info >> (pos * 8)) & 0x0f;
uint8_t bt = (par_info >> (pos * 8)) & 0xff;
for (uint8_t i = 0; i < 8; i++) {
par[7 - pos][i] = (bt >> i) & 0x01;
}
}
printf("|diff|{nr} |ks3|ks3^5|parity |\n");
printf("+----+--------+---+-----+---------------+\n");
for (uint8_t i = 0; i < 8; i++) {
uint32_t nr_diff = nr | i << 5;
printf("| %02x |%08x| %01x | %01x |", i << 5, nr_diff, ks3x[i], ks3x[i] ^ 5);
for (pos = 0; pos < 7; pos++)
printf("%01x,", par[i][pos]);
printf("%01x|\n", par[i][7]);
}
printf("+----+--------+---+-----+---------------+\n");
state = lfsr_common_prefix(nr, rr, ks3x, par, false);
lfsr_rollback_word(state, uid ^ nt, 0);
crypto1_get_lfsr(state, &key_recovered);
printf("\nkey recovered: %012" PRIx64 "\n\n", key_recovered);
crypto1_destroy(state);
return 0;
}

View file

@ -0,0 +1,416 @@
// Reused Keys Nested Attack against Fudan FM11RF08S tags
//
// Attack conditions:
// * Know a first key, to be able to activate the nested authentication protocol
// * The card must reuse some keys across several sectors. Or several cards of an infrastructure share the same key
//
// Strategy:
// * Find all possible key candidates for one reference sector, and check on-the-fly if they are compatible with any other sector we want to compare with
//
// Doegox, 2024
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <pthread.h>
#include "common.h"
#include "crapto1/crapto1.h"
#include "parity.h"
#define NUM_THREADS 20
#define BATCH_SIZE (8192 / NUM_THREADS)
// oversized just in case...
#define KEY_SPACE_SIZE ((1 << 16) * 4)
// we expect intersection to be about 250-500 keys. Oversized just in case...
#define KEY_SPACE_SIZE_STEP2 2000
#define MAX_NR_NONCES 32
typedef struct {
uint32_t ntp;
uint32_t ks1;
} NtpKs1;
typedef struct {
uint32_t authuid;
NtpKs1 *pNK;
uint32_t sizeNK;
uint32_t nt_enc;
uint8_t nt_par_enc;
} NtData;
typedef struct {
NtData NtDataList[MAX_NR_NONCES];
uint32_t nr_nonces;
} NtpKs1List;
// Struct for thread data
typedef struct {
NtpKs1List *pNKL;
uint32_t startPos;
uint32_t endPos;
uint32_t *keyCount[MAX_NR_NONCES];
uint64_t *result_keys[MAX_NR_NONCES];
uint32_t thread_id;
pthread_mutex_t *keyCount_mutex[MAX_NR_NONCES];
uint32_t num_nonces;
} thread_data_t;
static uint32_t hex_to_uint32(const char *hex_str) {
return (uint32_t)strtoul(hex_str, NULL, 16);
}
static int bin_to_uint8_arr(const char *bin_str, uint8_t bit_arr[], uint8_t arr_size) {
if (strlen(bin_str) != arr_size) {
fprintf(stderr, "Error: Binary string (%s) length does not match array size (%i).\n", bin_str, arr_size);
return 1;
}
for (uint8_t i = 0; i < arr_size; i++) {
if (bin_str[i] == '0') {
bit_arr[i] = 0;
} else if (bin_str[i] == '1') {
bit_arr[i] = 1;
} else {
fprintf(stderr, "Error: Invalid character '%c' in binary string.\n", bin_str[i]);
return 1;
}
}
return 0;
}
static uint8_t valid_nonce(uint32_t Nt, uint32_t ks1, uint8_t nt_par_enc) {
return (oddparity8((Nt >> 24) & 0xFF) == (((nt_par_enc >> 3) & 1) ^ BIT(ks1, 16))) &&
(oddparity8((Nt >> 16) & 0xFF) == (((nt_par_enc >> 2) & 1) ^ BIT(ks1, 8))) &&
(oddparity8((Nt >> 8) & 0xFF) == (((nt_par_enc >> 1) & 1) ^ BIT(ks1, 0)));
}
static bool search_match(NtData *pND, NtData *pND0, uint64_t key) {
bool ret = 0;
struct Crypto1State *s;
s = crypto1_create(0);
if (s == NULL) {
fprintf(stderr, "\nMalloc error in search_match!\n");
return 0;
}
crypto1_init(s, key);
uint32_t authuid = pND->authuid;
uint32_t nt_enc = pND->nt_enc;
uint8_t nt_par_enc = pND->nt_par_enc;
uint32_t nt = crypto1_word(s, nt_enc ^ authuid, 1) ^ nt_enc;
// filter: we know nt should be valid, no need to spend time on other ones
if (validate_prng_nonce(nt)) {
// look for same nt
for (uint32_t k = 0; k < pND->sizeNK; k++) {
if (nt == pND->pNK[k].ntp) {
// Possible match
// filter: check the full 4 bits of parity (actually only the last one might be wrong)
uint32_t ks1, ks2;
uint8_t par1, par2, ksp;
par1 = (oddparity8((nt >> 24) & 0xFF) << 3) | (oddparity8((nt >> 16) & 0xFF) << 2) | (oddparity8((nt >> 8) & 0xFF) << 1) | (oddparity8(nt & 0xFF));
ks1 = nt ^ nt_enc;
ks2 = crypto1_word(s, 0, 0);
ksp = (((ks1 >> 16) & 1) << 3) | (((ks1 >> 8) & 1) << 2) | (((ks1 >> 0) & 1) << 1) | ((ks2 >> 24) & 1);
par2 = nt_par_enc ^ ksp;
if (par1 != par2) {
continue;
}
// filter: same check on the full 4 bits of parity of initial nonce
// check is slow so we do it only now
crypto1_init(s, key);
authuid = pND0->authuid;
nt_enc = pND0->nt_enc;
nt_par_enc = pND0->nt_par_enc;
nt = crypto1_word(s, nt_enc ^ authuid, 1) ^ nt_enc;
par1 = (oddparity8((nt >> 24) & 0xFF) << 3) | (oddparity8((nt >> 16) & 0xFF) << 2) | (oddparity8((nt >> 8) & 0xFF) << 1) | (oddparity8(nt & 0xFF));
ks1 = nt ^ nt_enc;
ks2 = crypto1_word(s, 0, 0);
ksp = (((ks1 >> 16) & 1) << 3) | (((ks1 >> 8) & 1) << 2) | (((ks1 >> 0) & 1) << 1) | ((ks2 >> 24) & 1);
par2 = nt_par_enc ^ ksp;
if (par1 != par2) {
continue;
}
k = pND->sizeNK;
ret = 1;
}
}
}
crypto1_destroy(s);
return ret;
}
static void *generate_and_intersect_keys(void *threadarg) {
thread_data_t *data = (thread_data_t *)threadarg;
NtpKs1List *pNKL = data->pNKL;
uint32_t startPos = data->startPos;
uint32_t endPos = data->endPos;
uint32_t thread_id = data->thread_id;
uint32_t num_nonces = data->num_nonces;
struct Crypto1State *revstate, *revstate_start = NULL;
uint64_t lfsr = 0;
uint32_t authuid = pNKL->NtDataList[0].authuid;
for (uint32_t i = startPos; i < endPos; i++) {
uint32_t ntp = pNKL->NtDataList[0].pNK[i].ntp;
uint32_t ks1 = pNKL->NtDataList[0].pNK[i].ks1;
uint32_t nt_probe = ntp ^ authuid;
revstate = lfsr_recovery32(ks1, nt_probe);
if (revstate == NULL) {
fprintf(stderr, "\nMalloc error in generate_and_intersect_keys!\n");
pthread_exit(NULL);
}
if (revstate_start == NULL) {
revstate_start = revstate;
}
uint32_t keyCount0 = 0;
while ((revstate->odd != 0x0) || (revstate->even != 0x0)) {
lfsr_rollback_word(revstate, nt_probe, 0);
crypto1_get_lfsr(revstate, &lfsr);
keyCount0++;
for (uint32_t nonce_index = 1; nonce_index < num_nonces; nonce_index++) {
if (search_match(&pNKL->NtDataList[nonce_index], &pNKL->NtDataList[0], lfsr)) {
pthread_mutex_lock(data->keyCount_mutex[nonce_index]);
data->result_keys[nonce_index][*data->keyCount[nonce_index]] = lfsr;
(*data->keyCount[nonce_index])++;
pthread_mutex_unlock(data->keyCount_mutex[nonce_index]);
if (*data->keyCount[nonce_index] == KEY_SPACE_SIZE_STEP2) {
fprintf(stderr, "No space left on result_keys[%d], abort!\n", nonce_index);
i = endPos;
break;
}
}
}
revstate++;
}
free(revstate_start);
revstate_start = NULL;
pthread_mutex_lock(data->keyCount_mutex[0]);
(*data->keyCount[0]) += keyCount0;
keyCount0 = 0;
pthread_mutex_unlock(data->keyCount_mutex[0]);
if ((i != startPos) && ((i - startPos) % ((endPos - startPos) / 20) == 0)) { // every 5%
printf("\rThread %3i %3i%%", thread_id, (i - startPos) * 100 / (endPos - startPos));
printf(" keys[%d]:%9i", 0, *data->keyCount[0]);
for (uint32_t nonce_index = 1; nonce_index < num_nonces; nonce_index++) {
printf(" keys[%d]:%5i", nonce_index, *data->keyCount[nonce_index]);
}
fflush(stdout);
}
}
pthread_exit(NULL);
}
static uint64_t **unpredictable_nested(NtpKs1List *pNKL, uint32_t keyCounts[]) {
pthread_t threads[NUM_THREADS];
thread_data_t thread_data[NUM_THREADS];
pthread_mutex_t keyCount_mutex[MAX_NR_NONCES];
uint64_t **result_keys = (uint64_t **)malloc(MAX_NR_NONCES * sizeof(uint64_t *));
// no result_keys[0] stored, would be too large
for (uint32_t i = 1; i < MAX_NR_NONCES; i++) {
result_keys[i] = (uint64_t *)malloc(KEY_SPACE_SIZE_STEP2 * sizeof(uint64_t));
keyCounts[i] = 0;
pthread_mutex_init(&keyCount_mutex[i], NULL);
}
uint32_t average = pNKL->NtDataList[0].sizeNK / NUM_THREADS;
uint32_t modulo = pNKL->NtDataList[0].sizeNK % NUM_THREADS;
for (uint32_t t = 0, j = 0; t < NUM_THREADS; t++, j += average) {
thread_data[t].pNKL = pNKL;
thread_data[t].startPos = j;
thread_data[t].endPos = j + average;
for (uint32_t i = 0; i < MAX_NR_NONCES; i++) {
thread_data[t].result_keys[i] = result_keys[i];
thread_data[t].keyCount[i] = &keyCounts[i];
thread_data[t].keyCount_mutex[i] = &keyCount_mutex[i];
}
thread_data[t].thread_id = t;
thread_data[t].num_nonces = pNKL->nr_nonces;
// last thread can decrypt more pNK
if (t == (NUM_THREADS - 1) && modulo > 0) {
thread_data[t].endPos += modulo;
}
pthread_create(&threads[t], NULL, generate_and_intersect_keys, (void *)&thread_data[t]);
}
printf("All threads spawn...\n");
for (uint32_t t = 0; t < NUM_THREADS; t++) {
pthread_join(threads[t], NULL);
}
// no result_keys[0]
for (uint32_t i = 1; i < MAX_NR_NONCES; i++) {
if (keyCounts[i] == 0) {
free(result_keys[i]);
result_keys[i] = NULL;
}
}
return result_keys;
}
// Function to compare keys and keep track of their occurrences
static void analyze_keys(uint64_t **keys, uint32_t keyCounts[MAX_NR_NONCES], uint32_t nr_nonces) {
// Assuming the maximum possible keys
#define MAX_KEYS (MAX_NR_NONCES * KEY_SPACE_SIZE_STEP2)
uint64_t combined_keys[MAX_KEYS] = {0};
uint32_t combined_counts[MAX_KEYS] = {0};
uint32_t combined_length = 0;
printf("Analyzing keys...\n");
for (uint32_t i = 0; i < nr_nonces; i++) {
if (i==0) {
printf("nT(%i): %i key candidates\n", i, keyCounts[i]);
continue;
} else {
printf("nT(%i): %i key candidates matching nT(0)\n", i, keyCounts[i]);
}
for (uint32_t j = 0; j < keyCounts[i]; j++) {
uint64_t key = keys[i][j];
// Check if key is already in combined_keys
bool found = false;
for (uint32_t k = 0; k < combined_length; k++) {
if (combined_keys[k] == key) {
combined_counts[k]++;
found = true;
break;
}
}
// If key not found, add it to combined_keys
if (!found) {
combined_keys[combined_length] = key;
combined_counts[combined_length] = 1;
combined_length++;
}
}
}
for (uint32_t i = 0; i < combined_length; i++) {
if (combined_counts[i] > 1) {
printf("Key %012" PRIx64 " found in %d arrays: 0", combined_keys[i], combined_counts[i]+1);
for (uint32_t ii = 1; ii < nr_nonces; ii++) {
for (uint32_t j = 0; j < keyCounts[ii]; j++) {
if (combined_keys[i] == keys[ii][j]) {
printf(", %2i", ii);
}
}
}
printf("\n");
}
}
}
int main(int argc, char *const argv[]) {
if (argc < 2) {
int cmdlen = strlen(argv[0]);
printf("Usage:\n %s <uid1> <nt_enc1> <nt_par_err1> <uid2> <nt_enc2> <nt_par_err2> ...\n", argv[0]);
printf(" UID placeholder: if uid(n)==uid(n-1) you can use '.' as uid(n+1) placeholder\n");
printf(" parity example: if nt in trace is 7b! fc! 7a! 5b , then nt_enc is 7bfc7a5b and nt_par_err is 1110\n");
printf("Example:\n");
printf(" %*s a13e4902 2e9e49fc 1111 . 7bfc7a5b 1110 a17e4902 50f2abc2 1101\n", cmdlen, argv[0]);
printf(" %*s +uid1 | | +uid2=uid1 | +uid3 | |\n", cmdlen, "");
printf(" %*s +nt_enc1 | +nt_enc2 | +nt_enc3 |\n", cmdlen, "");
printf(" %*s +nt_par_err1 +nt_par_err2 +nt_par_err3\n", cmdlen, "");
return 1;
}
if (argc < 1 + 2 * 3) {
fprintf(stderr, "Too few nonces, abort. Need 2 nonces min.\n");
return 1;
}
if (argc > 1 + MAX_NR_NONCES * 3) {
fprintf(stderr, "Too many nonces, abort. Choose max %i nonces.\n", MAX_NR_NONCES);
return 1;
}
NtpKs1List NKL = {0};
uint64_t **keys = NULL;
uint32_t keyCounts[MAX_NR_NONCES] = {0};
uint32_t authuid = hex_to_uint32(argv[1]);
// process all args.
printf("Generating nonce candidates...\n");
for (uint32_t i = 1; i < argc; i += 3) {
// uid + ntEnc + parEnc
if (strcmp(argv[i], ".") != 0) {
authuid = hex_to_uint32(argv[i]);
}
uint32_t nt_enc = hex_to_uint32(argv[i + 1]);
uint8_t nt_par_err_arr[4];
if (bin_to_uint8_arr(argv[i + 2], nt_par_err_arr, 4)) {
return 1;
}
uint8_t nt_par_enc = ((nt_par_err_arr[0] ^ oddparity8((nt_enc >> 24) & 0xFF)) << 3) |
((nt_par_err_arr[1] ^ oddparity8((nt_enc >> 16) & 0xFF)) << 2) |
((nt_par_err_arr[2] ^ oddparity8((nt_enc >> 8) & 0xFF)) << 1) |
((nt_par_err_arr[3] ^ oddparity8((nt_enc >> 0) & 0xFF)) << 0);
NtData *pNtData = &NKL.NtDataList[NKL.nr_nonces];
// Try to recover the keystream1
uint32_t nttest = prng_successor(1, 16); // a first valid nonce
pNtData->pNK = (NtpKs1 *)malloc(sizeof(NtpKs1) * 8192); // 2**16 filtered with 3 parity bits => 2**13
if (pNtData->pNK == NULL) {
return 1;
}
uint32_t j = 0;
for (uint16_t m = 1; m; m++) {
uint32_t ks1 = nt_enc ^ nttest;
if (valid_nonce(nttest, ks1, nt_par_enc)) {
pNtData->pNK[j].ntp = nttest;
pNtData->pNK[j].ks1 = ks1;
j++;
}
nttest = prng_successor(nttest, 1);
}
printf("uid=%08x nt_enc=%08x nt_par_err=%i%i%i%i nt_par_enc=%i%i%i%i %i/%i: %d\n", authuid, nt_enc,
nt_par_err_arr[0], nt_par_err_arr[1], nt_par_err_arr[2], nt_par_err_arr[3],
(nt_par_enc >> 3)&1, (nt_par_enc >> 2)&1, (nt_par_enc >> 1)&1, nt_par_enc&1,
NKL.nr_nonces + 1, (argc - 1) / 3, j);
pNtData->authuid = authuid;
pNtData->sizeNK = j;
pNtData->nt_enc = nt_enc;
pNtData->nt_par_enc = nt_par_enc;
NKL.nr_nonces++;
}
printf("Finding key candidates...\n");
keys = unpredictable_nested(&NKL, keyCounts);
printf("\n\nFinding phase complete.\n");
for (uint32_t k = 0; k < NKL.nr_nonces; k++)
free(NKL.NtDataList[k].pNK);
analyze_keys(keys, keyCounts, NKL.nr_nonces);
FILE* fptr;
// opening the file in read mode
fptr = fopen("keys.dic", "w");
if (fptr != NULL) {
for (uint32_t i = 1; i < NKL.nr_nonces; i++) {
if (keyCounts[i] > 0) {
for (uint32_t j = 0; j < keyCounts[i]; j++) {
fprintf(fptr, "%012" PRIx64 "\n", keys[i][j]);
}
}
}
fclose(fptr);
} else {
fprintf(stderr, "Warning: Cannot save keys in keys.dic\n");
}
for (uint32_t i = 1; i < NKL.nr_nonces; i++) {
if (keys[i] != NULL) {
free(keys[i]);
}
}
if (keys != NULL) {
free(keys);
}
return 0;
}

View file

@ -0,0 +1,164 @@
// Backdoored Nested Attack against Fudan FM11RF08S tags
//
// Attack conditions:
// * Backdoor
//
// Strategy:
// * Use backdoor on the targeted sector to get the clear static nested nT
// * Enumerate key candidates based on clear and encrypted nT
// * Use the resulting dictionary to bruteforce the key
//
// Doegox, 2024
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include "common.h"
#include "crapto1/crapto1.h"
#include "parity.h"
#define KEY_SPACE_SIZE (1 << 18)
typedef struct {
uint32_t authuid;
uint32_t nt;
uint32_t nt_enc;
uint8_t nt_par_enc;
} NtData;
static uint32_t hex_to_uint32(const char *hex_str) {
return (uint32_t)strtoul(hex_str, NULL, 16);
}
static int bin_to_uint8_arr(const char *bin_str, uint8_t bit_arr[], uint8_t arr_size) {
if (strlen(bin_str) != arr_size) {
fprintf(stderr, "Error: Binary string (%s) length does not match array size (%i).\n", bin_str, arr_size);
return 1;
}
for (uint8_t i = 0; i < arr_size; i++) {
if (bin_str[i] == '0') {
bit_arr[i] = 0;
} else if (bin_str[i] == '1') {
bit_arr[i] = 1;
} else {
fprintf(stderr, "Error: Invalid character '%c' in binary string.\n", bin_str[i]);
return 1;
}
}
return 0;
}
static uint64_t *generate_keys(uint64_t authuid, uint32_t nt, uint32_t nt_enc, uint32_t nt_par_enc, uint32_t *keyCount) {
uint64_t *result_keys = (uint64_t *)malloc(KEY_SPACE_SIZE * sizeof(uint64_t));
if (result_keys == NULL) {
fprintf(stderr, "\nMalloc error in generate_and_intersect_keys!\n");
return NULL;
}
struct Crypto1State *revstate, *revstate_start = NULL, *s = NULL;
uint64_t lfsr = 0;
uint32_t ks1 = nt ^ nt_enc;
revstate = lfsr_recovery32(ks1, nt ^ authuid);
if (revstate == NULL) {
fprintf(stderr, "\nMalloc error in generate_keys!\n");
free(result_keys);
return NULL;
}
if (revstate_start == NULL) {
revstate_start = revstate;
}
s = crypto1_create(0);
if (s == NULL) {
fprintf(stderr, "\nMalloc error in generate_keys!\n");
free(result_keys);
crypto1_destroy(revstate_start);
return 0;
}
while ((revstate->odd != 0x0) || (revstate->even != 0x0)) {
lfsr_rollback_word(revstate, nt ^ authuid, 0);
crypto1_get_lfsr(revstate, &lfsr);
// only filtering possibility: last parity bit ks in ks2
uint32_t ks2;
uint8_t lastpar1, lastpar2, kslastp;
crypto1_init(s, lfsr);
crypto1_word(s, nt ^ authuid, 0);
ks2 = crypto1_word(s, 0, 0);
lastpar1 = oddparity8(nt & 0xFF);
kslastp = (ks2 >> 24) & 1;
lastpar2 = (nt_par_enc & 1) ^ kslastp;
if (lastpar1 == lastpar2) {
result_keys[(*keyCount)++] = lfsr;
if (*keyCount == KEY_SPACE_SIZE) {
fprintf(stderr, "No space left on result_keys, abort! Increase KEY_SPACE_SIZE\n");
break;
}
}
revstate++;
}
crypto1_destroy(s);
crypto1_destroy(revstate_start);
revstate_start = NULL;
return result_keys;
}
int main(int argc, char *const argv[]) {
if (argc != 6) {
int cmdlen = strlen(argv[0]);
printf("Usage:\n %s <uid:hex> <sector:dec> <nt:hex> <nt_enc:hex> <nt_par_err:bin>\n"
" parity example: if for block 63 == sector 15, nt in trace is 7b! fc! 7a! 5b\n"
" then nt_enc is 7bfc7a5b and nt_par_err is 1110\n"
"Example:\n"
" %*s a13e4902 15 d14191b3 2e9e49fc 1111\n"
" %*s +uid +s +nt +nt_enc +nt_par_err\n",
argv[0], cmdlen, argv[0], cmdlen, "");
return 1;
}
uint64_t *keys = NULL;
uint32_t keyCount = 0;
uint32_t authuid = hex_to_uint32(argv[1]);
uint32_t sector = hex_to_uint32(argv[2]);
uint32_t nt = hex_to_uint32(argv[3]);
uint32_t nt_enc = hex_to_uint32(argv[4]);
uint8_t nt_par_err_arr[4];
if (bin_to_uint8_arr(argv[5], nt_par_err_arr, 4)) {
return 1;
}
uint8_t nt_par_enc = ((nt_par_err_arr[0] ^ oddparity8((nt_enc >> 24) & 0xFF)) << 3) |
((nt_par_err_arr[1] ^ oddparity8((nt_enc >> 16) & 0xFF)) << 2) |
((nt_par_err_arr[2] ^ oddparity8((nt_enc >> 8) & 0xFF)) << 1) |
((nt_par_err_arr[3] ^ oddparity8((nt_enc >> 0) & 0xFF)) << 0);
printf("uid=%08x nt=%08x nt_enc=%08x nt_par_err=%i%i%i%i nt_par_enc=%i%i%i%i ks1=%08x\n", authuid, nt, nt_enc,
nt_par_err_arr[0], nt_par_err_arr[1], nt_par_err_arr[2], nt_par_err_arr[3],
(nt_par_enc >> 3)&1, (nt_par_enc >> 2)&1, (nt_par_enc >> 1)&1, nt_par_enc&1,
nt ^ nt_enc);
printf("Finding key candidates...\n");
keys = generate_keys(authuid, nt, nt_enc, nt_par_enc, &keyCount);
printf("Finding phase complete, found %i keys\n", keyCount);
FILE* fptr;
char filename[30];
snprintf(filename, sizeof(filename), "keys_%08x_%02i_%08x.dic", authuid, sector, nt);
fptr = fopen(filename, "w");
if (fptr != NULL) {
if (keyCount > 0) {
for (uint32_t j = 0; j < keyCount; j++) {
fprintf(fptr, "%012" PRIx64 "\n", keys[j]);
}
}
fclose(fptr);
} else {
fprintf(stderr, "Warning: Cannot save keys in %s\n", filename);
}
if (keys != NULL) {
free(keys);
}
return 0;
}

View file

@ -0,0 +1,236 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <pthread.h>
#include "common.h"
#include "nested_util.h"
#include "crapto1/crapto1.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
typedef struct {
union {
struct Crypto1State *slhead;
uint64_t *keyhead;
} head;
union {
struct Crypto1State *sltail;
uint64_t *keytail;
} tail;
uint32_t len;
uint32_t uid;
uint32_t blockNo;
uint32_t keyType;
uint32_t nt_enc;
uint32_t ks1;
} StateList_t;
inline static int compare_uint64(const void *a, const void *b) {
if (*(uint64_t *)b == *(uint64_t *)a) return 0;
if (*(uint64_t *)b < * (uint64_t *)a) return 1;
return -1;
}
// Compare 16 Bits out of cryptostate
inline static int compare16Bits(const void *a, const void *b) {
if ((*(uint64_t *)b & 0x00ff000000ff0000) == (*(uint64_t *)a & 0x00ff000000ff0000)) return 0;
if ((*(uint64_t *)b & 0x00ff000000ff0000) > (*(uint64_t *)a & 0x00ff000000ff0000)) return 1;
return -1;
}
// create the intersection (common members) of two sorted lists. Lists are terminated by -1. Result will be in list1. Number of elements is returned.
static uint32_t intersection(uint64_t *listA, uint64_t *listB) {
if (listA == NULL || listB == NULL)
return 0;
uint64_t *p1, *p2, *p3;
p1 = p3 = listA;
p2 = listB;
while (*p1 != UINT64_C(-1) && *p2 != UINT64_C(-1)) {
if (compare_uint64(p1, p2) == 0) {
*p3++ = *p1++;
p2++;
} else {
while (compare_uint64(p1, p2) < 0) ++p1;
while (compare_uint64(p1, p2) > 0) ++p2;
}
}
*p3 = UINT64_C(-1);
return p3 - listA;
}
// wrapper function for multi-threaded lfsr_recovery32
static void
#ifdef __has_attribute
#if __has_attribute(force_align_arg_pointer)
__attribute__((force_align_arg_pointer))
#endif
#endif
*nested_worker_thread(void *arg) {
struct Crypto1State *p1;
StateList_t *statelist = arg;
statelist->head.slhead = lfsr_recovery32(statelist->ks1, statelist->nt_enc ^ statelist->uid);
for (p1 = statelist->head.slhead; p1->odd | p1->even; p1++) {};
statelist->len = p1 - statelist->head.slhead;
statelist->tail.sltail = --p1;
qsort(statelist->head.slhead, statelist->len, sizeof(uint64_t), compare16Bits);
return statelist->head.slhead;
}
static void pm3_staticnested(uint32_t uid, uint32_t nt1, uint32_t ks1, uint32_t nt2, uint32_t ks2) {
StateList_t statelists[2];
struct Crypto1State *p1, * p2, * p3, * p4;
for (uint8_t i = 0; i < 2; i++) {
statelists[i].uid = uid;
}
statelists[0].nt_enc = nt1;
statelists[0].ks1 = ks1;
statelists[1].nt_enc = nt2;
statelists[1].ks1 = ks2;
// calc keys
pthread_t thread_id[2];
// create and run worker threads
for (uint8_t i = 0; i < 2; i++)
pthread_create(thread_id + i, NULL, nested_worker_thread, &statelists[i]);
// wait for threads to terminate:
for (uint8_t i = 0; i < 2; i++)
pthread_join(thread_id[i], (void *)&statelists[i].head.slhead);
// the first 16 Bits of the cryptostate already contain part of our key.
// Create the intersection of the two lists based on these 16 Bits and
// roll back the cryptostate
p1 = p3 = statelists[0].head.slhead;
p2 = p4 = statelists[1].head.slhead;
while (p1 <= statelists[0].tail.sltail && p2 <= statelists[1].tail.sltail) {
if (compare16Bits(p1, p2) == 0) {
struct Crypto1State savestate;
savestate = *p1;
while (compare16Bits(p1, &savestate) == 0 && p1 <= statelists[0].tail.sltail) {
*p3 = *p1;
lfsr_rollback_word(p3, statelists[0].nt_enc ^ statelists[0].uid, 0);
p3++;
p1++;
}
savestate = *p2;
while (compare16Bits(p2, &savestate) == 0 && p2 <= statelists[1].tail.sltail) {
*p4 = *p2;
lfsr_rollback_word(p4, statelists[1].nt_enc ^ statelists[1].uid, 0);
p4++;
p2++;
}
} else {
while (compare16Bits(p1, p2) == -1) p1++;
while (compare16Bits(p1, p2) == 1) p2++;
}
}
p3->odd = -1;
p3->even = -1;
p4->odd = -1;
p4->even = -1;
statelists[0].len = p3 - statelists[0].head.slhead;
statelists[1].len = p4 - statelists[1].head.slhead;
statelists[0].tail.sltail = --p3;
statelists[1].tail.sltail = --p4;
// the statelists now contain possible keys. The key we are searching for must be in the
// intersection of both lists
qsort(statelists[0].head.keyhead, statelists[0].len, sizeof(uint64_t), compare_uint64);
qsort(statelists[1].head.keyhead, statelists[1].len, sizeof(uint64_t), compare_uint64);
// Create the intersection
statelists[0].len = intersection(statelists[0].head.keyhead, statelists[1].head.keyhead);
uint32_t keycnt = statelists[0].len;
if (keycnt) {
printf("PM3 Static nested --> Found " _YELLOW_("%u") " key candidates\n", keycnt);
for (uint32_t k = 0; k < keycnt; k++) {
uint64_t key64 = 0;
crypto1_get_lfsr(statelists[0].head.slhead + k, &key64);
printf("[ %u ] " _GREEN_("%012" PRIx64) "\n", k + 1, key64);
}
}
}
static int usage(void) {
printf("\n");
printf("\nProgram tries to recover keys from static encrypted nested MFC cards\n");
printf("using two different implementations, Chameleon Ultra (CU) and Proxmark3.\n");
printf("It uses the nonce, keystream sent from pm3 device to client.\n");
printf("ie: NOT the CU data which is data in the trace.\n");
printf("\n");
printf("syntax: staticnested <uid> <nt1> <ks1> <nt2> <ks2>\n\n");
printf("samples:\n");
printf("\n");
printf(" ./staticnested 461dce03 7eef3586 ffb02eda 322bc14d ffc875ca\n");
printf(" ./staticnested 461dce03 7eef3586 1fb6b496 322bc14d 1f4eebdd\n");
printf(" ./staticnested 461dce03 7eef3586 7fa28c7e 322bc14d 7f62b3d6\n");
printf("\n");
return 1;
}
int main(int argc, char *const argv[]) {
printf("\nMIFARE Classic static nested key recovery\n\n");
if (argc < 5) return usage();
printf("Init...\n");
NtpKs1 *pNK = calloc(2, sizeof(NtpKs1));
if (pNK == NULL) {
goto error;
}
uint32_t uid = 0;
sscanf(argv[1], "%x", &uid);
sscanf(argv[2], "%x", &pNK[0].ntp);
sscanf(argv[3], "%x", &pNK[0].ks1);
sscanf(argv[4], "%x", &pNK[1].ntp);
sscanf(argv[5], "%x", &pNK[1].ks1);
printf("uid... %08x\n", uid);
printf("nt1... %08x\n", pNK[0].ntp);
printf("ks1... %08x\n", pNK[0].ks1);
printf("nt2... %08x\n", pNK[1].ntp);
printf("ks2... %08x\n", pNK[1].ks1);
// process all args.
printf("Recovery...\n");
uint32_t key_count = 0;
uint64_t *keys = nested(pNK, 2, uid, &key_count);
if (key_count) {
printf("Ultra Static nested --> Found " _YELLOW_("%u") " key candidates\n", key_count);
for (uint32_t k = 0; k < key_count; k++) {
printf("[ %u ] " _GREEN_("%012" PRIx64) "\n", k + 1, keys[k]);
}
}
pm3_staticnested(uid, pNK[0].ntp, pNK[0].ks1, pNK[1].ntp, pNK[1].ks1);
fflush(stdout);
free(keys);
exit(EXIT_SUCCESS);
error:
exit(EXIT_FAILURE);
}

View file

@ -0,0 +1,9 @@
./staticnested_2nt 461dce03 7eef3586 ffb02eda 322bc14d ffc875ca;
./staticnested_2nt 461dce03 7eef3586 7f21594f 322bc14d 7f815fba;
./staticnested_2nt 461dce03 7eef3586 ff315fe7 322bc14d ffc1364d;
./staticnested_2nt 461dce03 7eef3586 d742a617 322bc14d d7f2f337;
./staticnested_2nt 461dce03 7eef3586 5e3e037c 322bc14d 5ef705c2;
./staticnested_2nt 461dce03 7eef3586 5fcaebc6 322bc14d 5f72de17;
./staticnested_2nt 461dce03 7eef3586 3fbcfb30 322bc14d 3fe4c47c;
./staticnested_2nt 461dce03 7eef3586 1fb6b496 322bc14d 1f4eebdd;
./staticnested_2nt 461dce03 7eef3586 7fa28c7e 322bc14d 7f62b3d6;

View file

@ -0,0 +1,243 @@
// Faster Backdoored Nested Attack against Fudan FM11RF08S tags
//
// Attack conditions:
// * Backdoor
// * keyA and keyB are different for the targeted sector
//
// Strategy:
// * Use backdoor on the targeted sector to get the clear static nested nT for keyA and for keyB
// * Generate 2 lists of key candidates based on clear and encrypted nT
// * Search couples of keyA/keyB satisfying some obscure relationship
// * Use the resulting dictionary to bruteforce the keyA (and staticnested_2x1nt_rf08s_1key for keyB)
//
// Doegox, 2024
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
uint16_t i_lfsr16[1 << 16] = {0};
uint16_t s_lfsr16[1 << 16] = {0};
static void init_lfsr16_table(void) {
uint16_t x = 1;
for (uint16_t i=1; i; ++i) {
i_lfsr16[(x & 0xff) << 8 | x >> 8] = i;
s_lfsr16[i] = (x & 0xff) << 8 | x >> 8;
x = x >> 1 | (x ^ x >> 2 ^ x >> 3 ^ x >> 5) << 15;
}
}
// static uint16_t next_lfsr16(uint16_t nonce) {
// return s_lfsr16[(i_lfsr16[nonce]+1) % 65535];
// }
static uint16_t prev_lfsr16(uint16_t nonce) {
return s_lfsr16[(i_lfsr16[nonce]-1) % 65535];
}
static uint16_t compute_seednt16_nt32(uint32_t nt32, uint64_t key) {
uint8_t a[] = {0, 8, 9, 4, 6, 11, 1, 15, 12, 5, 2, 13, 10, 14, 3, 7};
uint8_t b[] = {0, 13, 1, 14, 4, 10, 15, 7, 5, 3, 8, 6, 9, 2, 12, 11};
uint16_t nt = nt32 >> 16;
uint8_t prev = 14;
for (uint8_t i=0; i<prev; i++) {
nt = prev_lfsr16(nt);
}
uint8_t prevoff = 8;
bool odd = 1;
for (uint8_t i=0; i<6*8; i+=8) {
if (odd) {
nt ^= (a[(key >> i) & 0xF]);
nt ^= (b[(key >> i >> 4) & 0xF]) << 4;
} else {
nt ^= (b[(key >> i) & 0xF]);
nt ^= (a[(key >> i >> 4) & 0xF]) << 4;
}
odd ^= 1;
prev += prevoff;
for (uint8_t j=0; j<prevoff; j++) {
nt = prev_lfsr16(nt);
}
}
return nt;
}
int main(int argc, char *const argv[]) {
if (argc != 3) {
printf("Usage:\n %s keys_<uid:08x>_<sector:02>_<nt1:08x>.dic keys_<uid:08x>_<sector:02>_<nt2:08x>.dic\n"
" where both dict files are produced by staticnested_1nt *for the same UID and same sector*\n",
argv[0]);
return 1;
}
uint32_t uid1, sector1, nt1, uid2, sector2, nt2;
char *filename1 = argv[1], *filename2= argv[2];
int result;
result = sscanf(filename1, "keys_%8x_%2d_%8x.dic", &uid1, &sector1, &nt1);
if (result != 3) {
fprintf(stderr, "Error: Failed to parse the filename %s.\n", filename1);
return 1;
}
result = sscanf(filename2, "keys_%8x_%2d_%8x.dic", &uid2, &sector2, &nt2);
if (result != 3) {
fprintf(stderr, "Error: Failed to parse the filename %s.\n", filename2);
return 1;
}
if (uid1 != uid2) {
fprintf(stderr, "Error: Files must belong to the same UID.\n");
return 1;
}
if (sector1 != sector2) {
fprintf(stderr, "Error: Files must belong to the same sector.\n");
return 1;
}
if (nt1 == nt2) {
fprintf(stderr, "Error: Files must belong to different nonces.\n");
return 1;
}
init_lfsr16_table();
uint32_t keycount1 = 0;
uint64_t* keys1 = NULL;
uint8_t* filter_keys1 = NULL;
uint16_t* seednt1 = NULL;
uint32_t keycount2 = 0;
uint64_t* keys2 = NULL;
uint8_t* filter_keys2 = NULL;
FILE* fptr;
fptr = fopen(filename1, "r");
if (fptr != NULL) {
uint64_t buffer;
while (fscanf(fptr, "%012" PRIx64, &buffer) == 1) {
keycount1++;
}
keys1 = (uint64_t*)malloc(keycount1 * sizeof(uint64_t));
filter_keys1 = (uint8_t*)calloc(keycount1, sizeof(uint8_t));
if ((keys1 == NULL)||(filter_keys1 == NULL)) {
perror("Failed to allocate memory");
fclose(fptr);
goto end;
}
rewind(fptr);
for (uint32_t i = 0; i < keycount1; i++) {
if (fscanf(fptr, "%012" PRIx64, &keys1[i]) != 1) {
perror("Failed to read key");
fclose(fptr);
goto end;
}
}
fclose(fptr);
} else {
fprintf(stderr, "Warning: Cannot open %s\n", filename1);
goto end;
}
fptr = fopen(filename2, "r");
if (fptr != NULL) {
uint64_t buffer;
while (fscanf(fptr, "%012" PRIx64, &buffer) == 1) {
keycount2++;
}
keys2 = (uint64_t*)malloc(keycount2 * sizeof(uint64_t));
filter_keys2 = (uint8_t*)calloc(keycount1, sizeof(uint8_t));
if ((keys2 == NULL)||(filter_keys2 == NULL)) {
perror("Failed to allocate memory");
fclose(fptr);
goto end;
}
rewind(fptr);
for (uint32_t i = 0; i < keycount2; i++) {
if (fscanf(fptr, "%012" PRIx64, &keys2[i]) != 1) {
perror("Failed to read key");
fclose(fptr);
goto end;
}
}
fclose(fptr);
} else {
fprintf(stderr, "Warning: Cannot open %s\n", filename2);
goto end;
}
printf("%s: %i keys loaded\n", filename1, keycount1);
printf("%s: %i keys loaded\n", filename2, keycount2);
seednt1 = (uint16_t*)malloc(keycount1 * sizeof(uint16_t));
if (seednt1 == NULL) {
perror("Failed to allocate memory");
goto end;
}
for (uint32_t i = 0; i < keycount1; i++) {
seednt1[i] = compute_seednt16_nt32(nt1, keys1[i]);
}
for (uint32_t j = 0; j < keycount2; j++) {
uint16_t seednt2 = compute_seednt16_nt32(nt2, keys2[j]);
for (uint32_t i = 0; i < keycount1; i++) {
if (seednt2 == seednt1[i]) {
// printf("MATCH: key1=%012" PRIx64 " key2=%012" PRIx64 "\n", keys1[i], keys2[j]);
filter_keys1[i] = 1;
filter_keys2[j] = 1;
}
}
}
char filter_filename1[40];
uint32_t filter_keycount1 = 0;
snprintf(filter_filename1, sizeof(filter_filename1), "keys_%08x_%02i_%08x_filtered.dic", uid1, sector1, nt1);
fptr = fopen(filter_filename1, "w");
if (fptr != NULL) {
for (uint32_t j = 0; j < keycount1; j++) {
if (filter_keys1[j]) {
filter_keycount1++;
fprintf(fptr, "%012" PRIx64 "\n", keys1[j]);
}
}
fclose(fptr);
} else {
fprintf(stderr, "Warning: Cannot save keys in %s\n", filter_filename1);
}
char filter_filename2[40];
uint32_t filter_keycount2 = 0;
snprintf(filter_filename2, sizeof(filter_filename2), "keys_%08x_%02i_%08x_filtered.dic", uid2, sector2, nt2);
fptr = fopen(filter_filename2, "w");
if (fptr != NULL) {
for (uint32_t j = 0; j < keycount2; j++) {
if (filter_keys2[j]) {
filter_keycount2++;
fprintf(fptr, "%012" PRIx64 "\n", keys2[j]);
}
}
fclose(fptr);
} else {
fprintf(stderr, "Warning: Cannot save keys in %s\n", filter_filename2);
}
printf("%s: %i keys saved\n", filter_filename1, filter_keycount1);
printf("%s: %i keys saved\n", filter_filename2, filter_keycount2);
end:
if (keys1 != NULL)
free(keys1);
if (keys2 != NULL)
free(keys2);
if (filter_keys1 != NULL)
free(filter_keys1);
if (filter_keys2 != NULL)
free(filter_keys2);
if (seednt1 != NULL)
free(seednt1);
return 0;
}

View file

@ -0,0 +1,151 @@
// Faster Backdoored Nested Attack against Fudan FM11RF08S tags, part 2
//
// Attack conditions:
// * Backdoor
// * keyA and keyB are different for the targeted sector
//
// Strategy:
// * Use f08s_nested_known_collision to crack keyA
// * If keyB not readable, find keyB in its dictionary based on the obscure relationship between keyA, keyB and their nT
//
// Doegox, 2024
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
static uint32_t hex_to_uint32(const char *hex_str) {
return (uint32_t)strtoul(hex_str, NULL, 16);
}
uint16_t i_lfsr16[1 << 16] = {0};
uint16_t s_lfsr16[1 << 16] = {0};
static void init_lfsr16_table(void) {
uint16_t x = 1;
for (uint16_t i=1; i; ++i) {
i_lfsr16[(x & 0xff) << 8 | x >> 8] = i;
s_lfsr16[i] = (x & 0xff) << 8 | x >> 8;
x = x >> 1 | (x ^ x >> 2 ^ x >> 3 ^ x >> 5) << 15;
}
}
// static uint16_t next_lfsr16(uint16_t nonce) {
// return s_lfsr16[(i_lfsr16[nonce]+1) % 65535];
// }
static uint16_t prev_lfsr16(uint16_t nonce) {
return s_lfsr16[(i_lfsr16[nonce]-1) % 65535];
}
static uint16_t compute_seednt16_nt32(uint32_t nt32, uint64_t key) {
uint8_t a[] = {0, 8, 9, 4, 6, 11, 1, 15, 12, 5, 2, 13, 10, 14, 3, 7};
uint8_t b[] = {0, 13, 1, 14, 4, 10, 15, 7, 5, 3, 8, 6, 9, 2, 12, 11};
uint16_t nt = nt32 >> 16;
uint8_t prev = 14;
for (uint8_t i=0; i<prev; i++) {
nt = prev_lfsr16(nt);
}
uint8_t prevoff = 8;
bool odd = 1;
for (uint8_t i=0; i<6*8; i+=8) {
if (odd) {
nt ^= (a[(key >> i) & 0xF]);
nt ^= (b[(key >> i >> 4) & 0xF]) << 4;
} else {
nt ^= (b[(key >> i) & 0xF]);
nt ^= (a[(key >> i >> 4) & 0xF]) << 4;
}
odd ^= 1;
prev += prevoff;
for (uint8_t j=0; j<prevoff; j++) {
nt = prev_lfsr16(nt);
}
}
return nt;
}
int main(int argc, char *const argv[]) {
if (argc != 4) {
printf("Usage:\n %s <nt1:08x> <key1:012x> keys_<uid:08x>_<sector:02>_<nt2:08x>.dic\n"
" where dict file is produced by rf08s_nested_known *for the same UID and same sector* as provided nt and key\n",
argv[0]);
return 1;
}
uint32_t nt1 = hex_to_uint32(argv[1]);
uint64_t key1 = 0;
if (sscanf(argv[2], "%012" PRIx64, &key1) != 1) {
fprintf(stderr, "Failed to parse key: %s", argv[2]);
return 1;
}
char *filename= argv[3];
uint32_t uid, sector, nt2;
int result;
result = sscanf(filename, "keys_%8x_%2d_%8x.dic", &uid, &sector, &nt2);
if (result != 3) {
fprintf(stderr, "Error: Failed to parse the filename %s.\n", filename);
return 1;
}
if (nt1 == nt2) {
fprintf(stderr, "Error: File must belong to different nonce.\n");
return 1;
}
init_lfsr16_table();
uint32_t keycount2 = 0;
uint64_t* keys2 = NULL;
FILE* fptr = fopen(filename, "r");
if (fptr != NULL) {
uint64_t buffer;
while (fscanf(fptr, "%012" PRIx64, &buffer) == 1) {
keycount2++;
}
keys2 = (uint64_t*)malloc(keycount2 * sizeof(uint64_t));
if (keys2 == NULL) {
perror("Failed to allocate memory");
fclose(fptr);
goto end;
}
rewind(fptr);
for (uint32_t i = 0; i < keycount2; i++) {
if (fscanf(fptr, "%012" PRIx64, &keys2[i]) != 1) {
perror("Failed to read key");
fclose(fptr);
goto end;
}
}
fclose(fptr);
} else {
fprintf(stderr, "Warning: Cannot open %s\n", filename);
goto end;
}
printf("%s: %i keys loaded\n", filename, keycount2);
uint32_t found = 0;
uint16_t seednt1 = compute_seednt16_nt32(nt1, key1);
for (uint32_t i = 0; i < keycount2; i++) {
if (seednt1 == compute_seednt16_nt32(nt2, keys2[i])) {
printf("MATCH: key2=%012" PRIx64 "\n", keys2[i]);
found++;
}
}
if (!found) {
printf("No key found :(\n");
}
end:
if (keys2 != NULL)
free(keys2);
return 0;
}

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

31
tools/mfc/pm3_eml2lower.sh Executable file
View file

@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Andrei Costin <zveriu@gmail.com>, 2011
# pm3_eml2lower.sh
# Converts PM3 Mifare Classic emulator EML file to lower case (for easier comparison in some text-comparison tools)
# http://www.linuxquestions.org/questions/programming-9/bash-script-parsing-optional-parameters-621728/
# show program usage
show_usage()
{
echo
echo "Usage:"
echo "${0##/} input.eml output.eml"
exit
}
# Minimum number of arguments needed by this program
MINARGS=2
# get the number of command-line arguments given
ARGC=$#
# check to make sure enough arguments were given or exit
if [[ $ARGC -lt $MINARGS ]] ; then
echo "Too few arguments given (Minimum:$MINARGS)"
echo
show_usage
fi
tr '[:upper:]' '[:lower:]' < $1 > $2

27
tools/mfc/pm3_eml2mfd.py Executable file
View file

@ -0,0 +1,27 @@
#!/usr/bin/env python3
'''
# Andrei Costin <zveriu@gmail.com>, 2011
# pm3_eml2mfd.py
# Converts PM3 Mifare Classic emulator EML text file to MFD binary dump file
'''
import sys
import binascii
def main(argv):
argc = len(argv)
if argc < 3:
print('Usage:', argv[0], 'input.eml output.mfd')
sys.exit(1)
with open(argv[1], "r") as file_inp, open(argv[2], "wb") as file_out:
for line in file_inp:
line = line.rstrip('\n').rstrip('\r')
print(line)
data = binascii.unhexlify(line)
file_out.write(data)
if __name__ == '__main__':
main(sys.argv)

31
tools/mfc/pm3_eml2upper.sh Executable file
View file

@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Andrei Costin <zveriu@gmail.com>, 2011
# pm3_eml2upper.sh
# Converts PM3 Mifare Classic emulator EML file to UPPER case (for easier comparison in some text-comparison tools)
# http://www.linuxquestions.org/questions/programming-9/bash-script-parsing-optional-parameters-621728/
# show program usage
show_usage()
{
echo
echo "Usage:"
echo "${0##/} input.eml output.eml"
exit
}
# Minimum number of arguments needed by this program
MINARGS=2
# get the number of command-line arguments given
ARGC=$#
# check to make sure enough arguments were given or exit
if [[ $ARGC -lt $MINARGS ]] ; then
echo "Too few arguments given (Minimum:$MINARGS)"
echo
show_usage
fi
tr '[:lower:]' '[:upper:]' < $1 > $2

53
tools/mfc/pm3_eml_mfd_test.py Executable file
View file

@ -0,0 +1,53 @@
#!/usr/bin/env python3
from tempfile import mkdtemp
from shutil import rmtree
from string import hexdigits
import unittest, os
import pm3_eml2mfd, pm3_mfd2eml
class TestEmlMfd(unittest.TestCase):
def setUp(self):
self.tmpdir = mkdtemp()
def tearDown(self):
rmtree(self.tmpdir)
EML2MFD_TESTCASES = [
('', ''),
("41424344\r\n45464748\n494A4B4C\n", "ABCDEFGHIJKL")
]
def test_eml2mfd(self):
self.three_argument_test(pm3_eml2mfd.main, self.EML2MFD_TESTCASES)
def test_mfd2eml(self):
self.three_argument_test(pm3_mfd2eml.main,
map(reversed, self.EML2MFD_TESTCASES), c14n=hex_c14n)
def three_argument_test(self, operation, cases, c14n=str):
for case_input, case_output in cases:
try:
inp_name = os.path.join(self.tmpdir, 'input')
out_name = os.path.join(self.tmpdir, 'output')
with open(inp_name, 'w') as in_file:
in_file.write(case_input)
operation(['', inp_name, out_name])
with open(out_name, 'r') as out_file:
self.assertEqual(c14n(case_output), c14n(out_file.read()))
finally:
for file_name in inp_name, out_name:
if os.path.exists(file_name):
os.remove(file_name)
def hex_c14n(inp):
"""
Canonicalizes the input string by removing non-hexadecimal
characters and making everything uppercase
"""
return ''.join(c.upper() for c in inp if c in hexdigits)
if __name__ == '__main__':
unittest.main()

154
tools/mfc/pm3_gen_dictionary.py Executable file
View file

@ -0,0 +1,154 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pexpect
from colors import color
import re
import argparse
import os
import fnmatch
'''
# pm3_gen_dictionary.py
# Christian Herrmann, Iceman, <iceman@icesql.se> 2023
# version = 'v1.0.0'
#
# This code is copyright (c) Christian Herrmann, 2023, All rights reserved.
# For non-commercial use only, the following terms apply - for all other
# uses, please contact the author:
#
# This code is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This code is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
#
# Dependencies:
#
# pip3 install pexpect ansicolors
#
# Usage:
#
# ./pm3_gen_dictionary.py --path folder --fn mydictionary.dic -v
#
# Info:
# Will search all dump files files in given folder and all its subfolders
# With the option to save found keys to a text file.
#
'''
def escape_ansi(line):
ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]')
return ansi_escape.sub('', str(line)).lower()
def parse_keys(line):
"""
Parse keys from a line and return them as a set.
Keys must be 12 hex characters long
:param line: string containing keys.
:return: A set of keys read from the line
"""
keys = set()
key_regex = re.compile('[0-9a-fA-F]{12}')
key = key_regex.findall(line)
if not key:
return []
try:
keys.add(key[0])
keys.add(key[1])
except AttributeError:
pass
return keys
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--path", help="Path to folder")
parser.add_argument("--fn", help="Dictionary file name")
parser.add_argument("-v", help="verbose output", action="store_true")
args = parser.parse_args()
path = args.path
verbose = args.v
# Check if the directory exists
if not os.path.isdir(path):
print("The provided directory does not exist.")
return
# start pm3
child = pexpect.spawnu('./pm3 -o')
i = child.expect('pm3 --> ')
print("[+] Proxmark3 client open")
# MIFARE CLASSIC dumps
pattern = 'hf-mf-*-dump*'
print(f'[+] Iterating all dumpfiles in... ', color(f'{path}', fg='cyan'))
# Walk through the directory
keys = set()
for root, dirs, files in os.walk(path):
for file in files:
# Check if the file name starts with the given prefix
if fnmatch.fnmatch(file, pattern):
if ":Zone.Identifier" in file:
continue
if ":OECustomProperty" in file:
continue
f = os.path.join(root, file)
cmd = f'hf mf view -v -f {f}'
if verbose:
print(cmd)
# Send proxmark3 commnad
child.sendline(cmd)
i = child.expect('pm3 --> ')
msg = escape_ansi(str(child.before))
# extract key table from msg
found = False
for line in msg.splitlines():
if found == False:
key_row = line.find('000 | 003')
if key_row > -1:
found = True
if found:
foo = parse_keys(line)
if not foo:
found = False
continue
# append found set
keys |= foo
# shut down proxmark3 client connection
child.sendline('quit')
child.expect(pexpect.EOF)
print("[+] Proxmark3 client closed")
# print all found keys
if verbose:
for k in keys:
print(f'{k}')
print("")
# save keys
if args.fn:
print(f'[+] Writing keys to dictionary file... ', color(f'{args.fn}', fg='cyan'))
with open(args.fn, 'w') as f:
for k in keys:
f.write(f'{k}\n')
return 0
if __name__ == "__main__":
main()

35
tools/mfc/pm3_gen_mfsim.sh Executable file
View file

@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Andrei Costin <zveriu@gmail.com>, 2011
# gen_pm3mfsim_script.sh
# Generates Mifare Classic emulation script file that will load a given EML dump into PM3 and start emulation automagically
# http://www.linuxquestions.org/questions/programming-9/bash-script-parsing-optional-parameters-621728/
# show program usage
show_usage()
{
echo
echo "Usage:"
echo "${0##/} input_eml_without_extension output.cmd"
exit
}
# Minimum number of arguments needed by this program
MINARGS=2
# get the number of command-line arguments given
ARGC=$#
# check to make sure enough arguments were given or exit
if [[ $ARGC -lt $MINARGS ]] ; then
echo "Too few arguments given (Minimum:$MINARGS)"
echo
show_usage
fi
rm $2
echo "hf mf eclr" >> $2
echo "hf mf eload -f " $1 >> $2
echo "hf mf ekeyprn" >> $2
echo "hf mf sim --1k -u" `cat $1.eml | (read -n 8 uid; echo $uid)` >> $2

80
tools/mfc/pm3_key_file_diff.py Executable file
View file

@ -0,0 +1,80 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
########################################################################
#
# Copyright 2018 Gerhard Klostermeier
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
########################################################################
#
# Usage: ./key-file-diff.py <file A> <file B>
#
########################################################################
#
# Info:
# - Read two key files and show the keys that are in file B but not in A.
# - Keys must be 12 hex characters long and at the beginning of a line.
#
########################################################################
import re
def main(args):
"""
Read two key files and show the keys that are in file B but not in A.
:param args: Path to two key files A and B (positional shell parameters).
:return: 0 if everything went fine.
"""
key_file_a = args[1]
key_file_b = args[2]
with open(key_file_a, 'r') as f:
keys_a = parse_keys(f)
with open(key_file_b, 'r') as f:
keys_b = parse_keys(f)
# Show all keys that are in B but not in A.
keys_diff = keys_b.difference(keys_a)
for key in keys_diff:
print(key)
return 0
def parse_keys(file):
"""
Parse keys from a file and return them as a set.
Keys must be 12 hex characters long and at the beginning of a line.
:param file: Path to a file containing keys.
:return: A set of keys read from the file.
"""
keys = set()
key_regex = re.compile('^[0-9a-fA-F]{12}')
for line in file:
key = key_regex.match(line)
try:
key = key.group(0).upper()
keys.add(key)
except AttributeError:
pass
return keys
if __name__ == '__main__':
import sys
sys.exit(main(sys.argv))

147
tools/mfc/pm3_mf7b_wipe.py Executable file
View file

@ -0,0 +1,147 @@
#! /usr/bin/env python3.6
# -*- coding: utf-8 -*-
#
# VULNERS OPENSOURCE
# __________________
#
# Vulners Project [https://vulners.com]
# All Rights Reserved.
#
# Author: Kir [isox@vulners.com]
# Credits: Dennis Goh [dennis@rfidresearchgroup.com]
#
# This helper script is made for wiping S50 7byte UID cards with Gen2 magic commands from restored state to blank one.
#
# Scenario:
# You want to clone 7byte Mifare 1k card using RfidResearchGroup Proxmark3 RDV4.0
#
# Step 1: Dumping original card and making a Mifare 7byte UID clone using S50 7byte UID
#
# Place original card to the reader.
# Dump data and recover keys
#
# hf mf autopwn
#
# You will get data, EML and key file. Backup this file, you will need them to wipe the card back to blank state.
# Place blank S50 card to the reader.
#
# Get first line from EML file (block0) and write it down using command
#
# Place it here
# |
# |
# v
# hf mf wrbl --blk 0 -b -k FFFFFFFFFFFF -d 046E46AAA53480084400120111003113
#
# Now restore all the data using built-in restore command
#
# hf mf restore
#
# Step 2: Recovering S50 7byte UID card to the blank state
#
# Find current card data files from Step 1 in your backup or if you lost them create them again using 'hf mf autopwn' command.
# Place them in current working directory.
#
# Read hf-mf-CARD_UID-data.eml file and copy it content with CTRL-C.
# Place it to the eml variable in this script.
#
# Check execution command and check device and command name: 'proxmark3 -c "%s" /dev/tty.usbmodemiceman1'
#
# Run script and review key blocks returning to default FFFFFFFFFFFF state.
# Be patient! It is executing aprox 3 minutes.
# Success one result looks like:
#
# Block 0: Success: isOk:01
# Block 3: Success: isOk:01
# Block 7: Success: isOk:01
# Block 11: Success: isOk:01
# Block 15: Success: isOk:01
# Block 19: Success: isOk:01
# Block 23: Success: isOk:01
# Block 27: Success: isOk:01
# Block 31: Success: isOk:01
# Block 35: Success: isOk:01
# Block 39: Success: isOk:01
# Block 43: Success: isOk:01
# Block 47: Success: isOk:01
# Block 51: Success: isOk:01
# Block 55: Success: isOk:01
# Block 59: Success: isOk:01
# Block 63: Success: isOk:01
#
# That's it! Your S50 7byte UID card is wiped back. Now you can return back to Step 1 of this manual.
#
#
import subprocess
# EML data var te get keys of
EML_FILE_DATA = """PLACE RAW hf-mf-CARD_UID-dump.eml FILE CONTENT OF CURRENTLY LOADED CARD HERE"""
# Change your device name here if it differs from the default Proxmark3 RDV4.0
PROXMARK_BIN_EXEC_STRING = './pm3 -c "%s"'
# Constants
DEFAULT_ACCESS_BLOCK = "FFFFFFFFFFFFFF078000FFFFFFFFFFFF"
F12_KEY = "FFFFFFFFFFFF"
def exec_proxmark_cmd(command, retry = 2, input=""):
exec_ok = False
retry_c = 0
while not exec_ok and retry_c < retry:
sh_command = PROXMARK_BIN_EXEC_STRING % command
rst = subprocess.run(sh_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, input=input.encode("utf-8"))
proxmark_reply = rst.stdout.decode("utf-8")
proxmark_status = proxmark_reply.splitlines()[-1:][0].strip()
if proxmark_status == "ok":
return True, "Success: " + proxmark_status
retry_c += 1
return False, "Error: %s , status %s" % (proxmark_reply.splitlines()[-2:][0], proxmark_status)
def chunk(iterable,n):
"""assumes n is an integer>0
"""
iterable=iter(iterable)
while True:
result=[]
for i in range(n):
try:
a=next(iterable)
except StopIteration:
break
else:
result.append(a)
if result:
yield result
else:
break
sector_array = [sector for sector in chunk(EML_FILE_DATA.splitlines(), 4)]
block = 0
block_success = {}
for sector in sector_array:
key_A = sector[3][:12]
key_B = sector[3][-12:]
for _block in range(0,4):
if sector_array.index(sector) == 0 and block == 0:
write_status, verbose = exec_proxmark_cmd("hf mf wrbl --blk %s -b -k %s -d %s" % (block, key_B, sector[0]))
if not write_status:
write_status, verbose = exec_proxmark_cmd("hf mf wrbl --blk %s -a -k %s -d %s" % (block, key_A, sector[0]))
if not write_status:
write_status, verbose = exec_proxmark_cmd("hf mf wrbl --blk %s -a -k %s -d %s" % (block, F12_KEY, sector[0]))
block_success[block] = verbose
elif _block == 3:
write_status, verbose = exec_proxmark_cmd("hf mf wrbl --blk %s -b -k %s -d %s" % (block, key_B, DEFAULT_ACCESS_BLOCK))
if not write_status:
write_status, verbose = exec_proxmark_cmd("hf mf wrbl --blk %s -a -k %s -d %s" % (block, key_A, DEFAULT_ACCESS_BLOCK))
if not write_status:
write_status, verbose = exec_proxmark_cmd("hf mf wrbl --blk %s -a -k %s -d %s" % (block, F12_KEY, DEFAULT_ACCESS_BLOCK))
block_success[block] = verbose
_block += 1
block += 1
for block in block_success:
print("Block %s: %s" % (block ,block_success[block]))

30
tools/mfc/pm3_mfd2eml.py Executable file
View file

@ -0,0 +1,30 @@
#!/usr/bin/env python3
'''
# Andrei Costin <zveriu@gmail.com>, 2011
# pm3_eml2mfd.py
# Converts PM3 Mifare Classic MFD binary dump file to emulator EML text file
'''
import sys
READ_BLOCKSIZE = 16
def main(argv):
argc = len(argv)
if argc < 3:
print('Usage:', argv[0], 'input.mfd output.eml')
sys.exit(1)
with open(argv[1], "rb") as file_inp, open(argv[2], "w") as file_out:
while True:
byte_s = file_inp.read(READ_BLOCKSIZE)
if not byte_s:
break
hex_char_repr = byte_s.hex()
file_out.write(hex_char_repr)
file_out.write("\n")
if __name__ == '__main__':
main(sys.argv)

266
tools/mfc/pm3_mfdread.py Executable file
View file

@ -0,0 +1,266 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# mfdread.py - Mifare dumps parser in human readable format
# Pavel Zhovner <pavel@zhovner.com>
# https://github.com/zhovner/mfdread
#
#Dependencies:
# easy_install bitstring
#or
# pip install bitstring
#Usage:
# pm3_mfdread.py ./dump.mfd
#
import codecs
import copy
import sys
from collections import defaultdict
try:
from bitstring import BitArray
except ModuleNotFoundError:
print("Please install bitstring module first.")
sys.exit(1)
class Options:
FORCE_1K = False
if len(sys.argv) == 1:
sys.exit('''
------------------
Usage: pm3_mfdread.py ./dump.mfd
Mifare dumps reader.
''')
def decode(bytes):
decoded = codecs.decode(bytes, "hex")
try:
return str(decoded, "utf-8").rstrip(b'\0')
except:
return ""
class bashcolors:
BLUE = '\033[34m'
RED = '\033[91m'
GREEN = '\033[32m'
WARNING = '\033[93m'
ENDC = '\033[0m'
def accbits_for_blocknum(accbits_str, blocknum):
'''
Decodes the access bit string for block "blocknum".
Returns the three access bits for the block or False if the
inverted bits do not match the access bits.
'''
bits = BitArray([0])
inverted = BitArray([0])
# Block 0 access bits
if blocknum == 0:
bits = BitArray([accbits_str[11], accbits_str[23], accbits_str[19]])
inverted = BitArray([accbits_str[7], accbits_str[3], accbits_str[15]])
# Block 0 access bits
elif blocknum == 1:
bits = BitArray([accbits_str[10], accbits_str[22], accbits_str[18]])
inverted = BitArray([accbits_str[6], accbits_str[2], accbits_str[14]])
# Block 0 access bits
elif blocknum == 2:
bits = BitArray([accbits_str[9], accbits_str[21], accbits_str[17]])
inverted = BitArray([accbits_str[5], accbits_str[1], accbits_str[13]])
# Sector trailer / Block 3 access bits
elif blocknum in (3, 15):
bits = BitArray([accbits_str[8], accbits_str[20], accbits_str[16]])
inverted = BitArray([accbits_str[4], accbits_str[0], accbits_str[12]])
# Check the decoded bits
inverted.invert()
if bits.bin == inverted.bin:
return bits
else:
return False
def accbits_to_permission_sector(accbits):
permissions = {
'000': "- A | A - | A A [read B]",
'010': "- - | A - | A - [read B]",
'100': "- B | A/B - | - B",
'110': "- - | A/B - | - -",
'001': "- A | A A | A A [transport]",
'011': "- B | A/B B | - B",
'101': "- - | A/B B | - -",
'111': "- - | A/B - | - -",
}
if isinstance(accbits, BitArray):
return permissions.get(accbits.bin, "unknown")
else:
return ""
def accbits_to_permission_data(accbits):
permissions = {
'000': "A/B | A/B | A/B | A/B [transport]",
'010': "A/B | - | - | - [r/w]",
'100': "A/B | B | - | - [r/w]",
'110': "A/B | B | B | A/B [value]",
'001': "A/B | - | - | A/B [value]",
'011': " B | B | - | - [r/w]",
'101': " B | - | - | - [r/w]",
'111': " - | - | - | - [r/w]",
}
if isinstance(accbits, BitArray):
return permissions.get(accbits.bin, "unknown")
else:
return ""
def accbit_info(accbits, sector_size):
'''
Returns a dictionary of a access bits for all three blocks in a sector.
If the access bits for block could not be decoded properly, the value is set to False.
'''
access_bits = defaultdict(lambda: False)
if sector_size == 15:
access_bits[sector_size] = accbits_for_blocknum(accbits, sector_size)
return access_bits
# Decode access bits for all 4 blocks of the sector
for i in range(0, 4):
access_bits[i] = accbits_for_blocknum(accbits, i)
return access_bits
def print_info(data):
blocksmatrix = []
blockrights = {}
block_number = 0
data_size = len(data)
if data_size not in {4096, 1024}:
sys.exit("Wrong file size: %d bytes.\nOnly 1024 or 4096 allowed." % len(data))
if Options.FORCE_1K:
data_size = 1024
# read all sectors
sector_number = 0
start = 0
end = 64
while True:
sector = data[start:end]
sector = codecs.encode(sector, 'hex')
if not isinstance(sector, str):
sector = str(sector, 'ascii')
sectors = [sector[x:x + 32] for x in range(0, len(sector), 32)]
blocksmatrix.append(sectors)
# after 32 sectors each sector has 16 blocks instead of 4
sector_number += 1
if sector_number < 32:
start += 64
end += 64
elif sector_number == 32:
start += 64
end += 256
else:
start += 256
end += 256
if start == data_size:
break
blocksmatrix_clear = copy.deepcopy(blocksmatrix)
# add colors for each keyA, access bits, KeyB
for c in range(0, len(blocksmatrix)):
sector_size = len(blocksmatrix[c]) - 1
# Fill in the access bits
blockrights[c] = accbit_info(BitArray('0x' + blocksmatrix[c][sector_size][12:20]), sector_size)
# Prepare colored output of the sector trailor
keyA = bashcolors.RED + blocksmatrix[c][sector_size][0:12] + bashcolors.ENDC
accbits = bashcolors.GREEN + blocksmatrix[c][sector_size][12:20] + bashcolors.ENDC
keyB = bashcolors.BLUE + blocksmatrix[c][sector_size][20:32] + bashcolors.ENDC
blocksmatrix[c][sector_size] = keyA + accbits + keyB
print("File size: %d bytes. Expected %d sectors" % (len(data), sector_number))
print("\n\tUID: " + blocksmatrix[0][0][0:8])
print("\tBCC: " + blocksmatrix[0][0][8:10])
print("\tSAK: " + blocksmatrix[0][0][10:12])
print("\tATQA: " + blocksmatrix[0][0][12:14])
print(" %sKey A%s %sAccess Bits%s %sKey B%s" % (
bashcolors.RED, bashcolors.ENDC, bashcolors.GREEN, bashcolors.ENDC, bashcolors.BLUE, bashcolors.ENDC))
print("╔═════════╦═══════╦══════════════════════════════════╦════════╦═════════════════════════════════════╗")
print("║ Sector ║ Block ║ Data ║ Access ║ A | Acc. | B ║")
print("║ ║ ║ ║ ║ r w | r w | r w [info] ║")
print("║ ║ ║ ║ ║ r | w | i | d/t/r ║")
for q in range(0, len(blocksmatrix)):
print("╠═════════╬═══════╬══════════════════════════════════╬════════╬═════════════════════════════════════╣")
n_blocks = len(blocksmatrix[q])
# z is the block in each sector
for z in range(0, len(blocksmatrix[q])):
# Format the access bits. Print ERR in case of an error
if isinstance(blockrights[q][z], BitArray):
accbits = bashcolors.GREEN + blockrights[q][z].bin + bashcolors.ENDC
else:
accbits = bashcolors.WARNING + "ERR" + bashcolors.ENDC
if q == 0 and z == 0:
permissions = "-"
elif z == n_blocks - 1:
permissions = accbits_to_permission_sector(blockrights[q][z])
else:
permissions = accbits_to_permission_data(blockrights[q][z])
# Print the sector number in the second third row
if z == 2:
qn = q
else:
qn = ""
print("%-5s%-3d%s%s%-35s%s" % (qn, block_number, blocksmatrix[q][z],
accbits, permissions,
decode(blocksmatrix_clear[q][z])))
block_number += 1
print("╚═════════╩═══════╩══════════════════════════════════╩════════╩═════════════════════════════════════╝")
def main(args):
if args[0] == '-n':
args.pop(0)
bashcolors.BLUE = ""
bashcolors.RED = ""
bashcolors.GREEN = ""
bashcolors.WARNING = ""
bashcolors.ENDC = ""
if args[0] == '-1':
args.pop(0)
Options.FORCE_1K = True
filename = args[0]
with open(filename, "rb") as f:
data = f.read()
print_info(data)
if __name__ == "__main__":
main(sys.argv[1:])