diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d1c6ff89..4ae35b2f8 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 `tools\mfkeys\staticnested` - program to recover static nested keys (@iceman1001) - Added `pm3_gen_dictionary.py` - python script to extract and save all keys from MFC dump files. (@iceman1001) - Changed `hf mfu info` - now detect MIFARE Ultralight AES (@iceman1001) - Changed `hf mf autopwn` - now supports multiple user supplied keys (@iceman1001) diff --git a/tools/mfkey/Makefile b/tools/mfkey/Makefile index d1579a98b..113cd753d 100644 --- a/tools/mfkey/Makefile +++ b/tools/mfkey/Makefile @@ -1,10 +1,10 @@ MYSRCPATHS = ../../common ../../common/crapto1 -MYSRCS = crypto1.c crapto1.c bucketsort.c +MYSRCS = crypto1.c crapto1.c bucketsort.c nested_util.c MYINCLUDES = -I../../include -I../../common MYCFLAGS = -O3 MYDEFS = -BINS = mfkey32 mfkey32v2 mfkey64 +BINS = mfkey32 mfkey32v2 mfkey64 staticnested INSTALLTOOLS = $(BINS) include ../../Makefile.host @@ -24,3 +24,4 @@ endif mfkey32 : $(OBJDIR)/mfkey32.o $(MYOBJS) mfkey32v2 : $(OBJDIR)/mfkey32v2.o $(MYOBJS) mfkey64 : $(OBJDIR)/mfkey64.o $(MYOBJS) +staticnested : $(OBJDIR)/staticnested.o $(MYOBJS) diff --git a/tools/mfkey/nested_util.c b/tools/mfkey/nested_util.c new file mode 100644 index 000000000..d25cfd663 --- /dev/null +++ b/tools/mfkey/nested_util.c @@ -0,0 +1,257 @@ +#include +#include +#include +#include +#include +#include +#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; + + +static int compar_int(const void *a, const void *b) { + return (*(uint64_t *)b - * (uint64_t *)a); +} + +// 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) { + keys = malloc((*keyCount) * sizeof(uint64_t)); + if (keys != 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) { + 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) { + keys = tmp; + keys[*keyCount - 1] = ck[i].key; + } else { + printf("Cannot allocate memory for keys on merge."); + free(keys); + break; + } + } + } + } else { + printf("Cannot allocate memory for ck on uniqsort."); + } + } else { + printf("Cannot allocate memory to merge keys.\r\n"); + } + } + 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; +} diff --git a/tools/mfkey/nested_util.h b/tools/mfkey/nested_util.h new file mode 100644 index 000000000..fa0e1552d --- /dev/null +++ b/tools/mfkey/nested_util.h @@ -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 \ No newline at end of file diff --git a/tools/mfkey/staticnested.c b/tools/mfkey/staticnested.c new file mode 100644 index 000000000..4e68e5f94 --- /dev/null +++ b/tools/mfkey/staticnested.c @@ -0,0 +1,234 @@ +#include +#include +#include +#include +#include +#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("[ %d ] " _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 \n\n"); + printf("samples:\n"); + printf("\n"); + printf(" ./staticnested 461dce03 7eef3586 ffb02eda 322bc14d ffc875ca\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("[ %d ] " _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); +} \ No newline at end of file diff --git a/tools/mfkey/test_static.sh b/tools/mfkey/test_static.sh new file mode 100755 index 000000000..7fd180d8a --- /dev/null +++ b/tools/mfkey/test_static.sh @@ -0,0 +1,9 @@ +./staticnested 461dce03 7eef3586 ffb02eda 322bc14d ffc875ca; +./staticnested 461dce03 7eef3586 7f21594f 322bc14d 7f815fba; +./staticnested 461dce03 7eef3586 ff315fe7 322bc14d ffc1364d; +./staticnested 461dce03 7eef3586 d742a617 322bc14d d7f2f337; +./staticnested 461dce03 7eef3586 5e3e037c 322bc14d 5ef705c2; +./staticnested 461dce03 7eef3586 5fcaebc6 322bc14d 5f72de17; +./staticnested 461dce03 7eef3586 3fbcfb30 322bc14d 3fe4c47c; +./staticnested 461dce03 7eef3586 1fb6b496 322bc14d 1f4eebdd; +./staticnested 461dce03 7eef3586 7fa28c7e 322bc14d 7f62b3d6; diff --git a/tools/pm3_tests.sh b/tools/pm3_tests.sh index 0615a1447..6343fcfcd 100755 --- a/tools/pm3_tests.sh +++ b/tools/pm3_tests.sh @@ -283,13 +283,15 @@ while true; do if ! CheckFileExist "fpgacompress exists" "$FPGACPMPRESSBIN"; then break; fi fi if $TESTALL || $TESTMFKEY; then - echo -e "\n${C_BLUE}Testing mfkey:${C_NC} ${MFKEY32V2BIN:=./tools/mfkey/mfkey32v2} ${MFKEY64BIN:=./tools/mfkey/mfkey64}" + echo -e "\n${C_BLUE}Testing mfkey:${C_NC} ${MFKEY32V2BIN:=./tools/mfkey/mfkey32v2} ${MFKEY64BIN:=./tools/mfkey/mfkey64} ${STATICNESTEDBIN:=./tools/mfkey/staticnested}" if ! CheckFileExist "mfkey32v2 exists" "$MFKEY32V2BIN"; then break; fi if ! CheckFileExist "mfkey64 exists" "$MFKEY64BIN"; then break; fi + if ! CheckFileExist "staticnested exists" "$STATICNESTEDBIN"; then break; fi # Need a decent example for mfkey32... if ! CheckExecute "mfkey32v2 test" "$MFKEY32V2BIN 12345678 1AD8DF2B 1D316024 620EF048 30D6CB07 C52077E2 837AC61A" "Found Key: \[a0a1a2a3a4a5\]"; then break; fi if ! CheckExecute "mfkey64 test" "$MFKEY64BIN 9c599b32 82a4166c a1e458ce 6eea41e0 5cadf439" "Found Key: \[ffffffffffff\]"; then break; fi if ! CheckExecute "mfkey64 long trace test" "$MFKEY64BIN 14579f69 ce844261 f8049ccb 0525c84f 9431cc40 7093df99 9972428ce2e8523f456b99c831e769dced09 8ca6827b ab797fd369e8b93a86776b40dae3ef686efd c3c381ba 49e2c9def4868d1777670e584c27230286f4 fbdcd7c1 4abd964b07d3563aa066ed0a2eac7f6312bf 9f9149ea" "Found Key: \[091e639cb715\]"; then break; fi + if ! CheckExecute "staticnested test" "$STATICNESTEDBIN 461dce03 7eef3586 7fa28c7e 322bc14d 7f62b3d6" "\[ 2 \].*ffffffffff40.*"; then break; fi fi if $TESTALL || $TESTNONCE2KEY; then echo -e "\n${C_BLUE}Testing nonce2key:${C_NC} ${NONCE2KEYBIN:=./tools/nonce2key/nonce2key}"