From 7fec0d693c1eafa121e57783422bdcce0c644cc1 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Tue, 30 Jul 2024 16:41:00 +0200 Subject: [PATCH] Added few tools to tackle FM11RF08S cards --- .gitignore | 5 + CHANGELOG.md | 1 + tools/mf_fudan_rf08s/Makefile | 33 ++ tools/mf_fudan_rf08s/rf08s_nested.c | 406 ++++++++++++++++++ tools/mf_fudan_rf08s/rf08s_nested_known.c | 153 +++++++ .../rf08s_nested_known_collision.c | 230 ++++++++++ .../mf_fudan_rf08s/rf08s_nested_known_match.c | 140 ++++++ 7 files changed, 968 insertions(+) create mode 100644 tools/mf_fudan_rf08s/Makefile create mode 100644 tools/mf_fudan_rf08s/rf08s_nested.c create mode 100644 tools/mf_fudan_rf08s/rf08s_nested_known.c create mode 100644 tools/mf_fudan_rf08s/rf08s_nested_known_collision.c create mode 100644 tools/mf_fudan_rf08s/rf08s_nested_known_match.c diff --git a/.gitignore b/.gitignore index 58057497b..c76ccfe27 100644 --- a/.gitignore +++ b/.gitignore @@ -82,6 +82,11 @@ tools/cryptorf/cm tools/cryptorf/sm tools/cryptorf/sma tools/cryptorf/sma_multi +tools/mf_fudan_rf08s/rf08s_nested +tools/mf_fudan_rf08s/rf08s_nested_known +tools/mf_fudan_rf08s/rf08s_nested_known_collision +tools/mf_fudan_rf08s/rf08s_nested_known_match +tools/mf_fudan_rf08s/keys* tools/mf_nonce_brute/mf_nonce_brute tools/mf_nonce_brute/mf_trace_brute tools/jtag_openocd/openocd_configuration diff --git a/CHANGELOG.md b/CHANGELOG.md index bb1900445..1e143e9c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] +- Added few tools to tackle FM11RF08S cards (@doegox) - Changed standalone mode HF_MATTYRUN - support more card sizes, user dictionaries, improved emulation (@michaelroland) - Changed `hf iclass dump --ns` - now supports the nosave flag (@iceman1001) - Fixed write check in hitag2crack2 buildtables (@mwalker33) diff --git a/tools/mf_fudan_rf08s/Makefile b/tools/mf_fudan_rf08s/Makefile new file mode 100644 index 000000000..a8a888291 --- /dev/null +++ b/tools/mf_fudan_rf08s/Makefile @@ -0,0 +1,33 @@ +MYSRCPATHS = ../../common ../../common/crapto1 +MYSRCS = crypto1.c crapto1.c bucketsort.c +MYINCLUDES = -I../../include -I../../common +MYCFLAGS = -O3 +MYDEFS = + +BINS = rf08s_nested rf08s_nested_known rf08s_nested_known_collision rf08s_nested_known_match + +INSTALLTOOLS = $(BINS) + +include ../../Makefile.host + +# rf08s_nested.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 + +rf08s_nested : $(OBJDIR)/rf08s_nested.o $(MYOBJS) +rf08s_nested_known : $(OBJDIR)/rf08s_nested_known.o $(MYOBJS) +rf08s_nested_known_collision : $(OBJDIR)/rf08s_nested_known_collision.o $(MYOBJS) +rf08s_nested_known_match : $(OBJDIR)/rf08s_nested_known_match.o $(MYOBJS) diff --git a/tools/mf_fudan_rf08s/rf08s_nested.c b/tools/mf_fudan_rf08s/rf08s_nested.c new file mode 100644 index 000000000..82e0e5f1b --- /dev/null +++ b/tools/mf_fudan_rf08s/rf08s_nested.c @@ -0,0 +1,406 @@ + +#include +#include +#include +#include +#include +#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 ...\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; +} \ No newline at end of file diff --git a/tools/mf_fudan_rf08s/rf08s_nested_known.c b/tools/mf_fudan_rf08s/rf08s_nested_known.c new file mode 100644 index 000000000..7c068e7e3 --- /dev/null +++ b/tools/mf_fudan_rf08s/rf08s_nested_known.c @@ -0,0 +1,153 @@ + +#include +#include +#include +#include +#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 \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; +} diff --git a/tools/mf_fudan_rf08s/rf08s_nested_known_collision.c b/tools/mf_fudan_rf08s/rf08s_nested_known_collision.c new file mode 100644 index 000000000..97b8aab57 --- /dev/null +++ b/tools/mf_fudan_rf08s/rf08s_nested_known_collision.c @@ -0,0 +1,230 @@ + +#include +#include +#include +#include +#include + +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> 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__.dic keys___.dic\n" + " where both dict files are produced by rf08s_nested_known *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; +} diff --git a/tools/mf_fudan_rf08s/rf08s_nested_known_match.c b/tools/mf_fudan_rf08s/rf08s_nested_known_match.c new file mode 100644 index 000000000..f4298ee51 --- /dev/null +++ b/tools/mf_fudan_rf08s/rf08s_nested_known_match.c @@ -0,0 +1,140 @@ + +#include +#include +#include +#include +#include + +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> 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 keys___.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; +}