diff --git a/CHANGELOG.md b/CHANGELOG.md index 6781907a..34309b71 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] + - Implement a "server" mode in mfkey32v2 to avoid spawning one process per combination (@p-l-) - Skip already used items `hf mf elog --decrypt` (@p-l-) - Parallelize mfkey32v2 processes called from CLI (@p-l-) - Added support for mifare classic value block operations (@taichunmin) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index c42b80d1..812139ee 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1026,26 +1026,39 @@ def res_value(self, src_blk, src_type, src_key, dst_blk, dst_type, dst_key): _KEY = re.compile("[a-fA-F0-9]{12}", flags=re.MULTILINE) +class Mfkey32v2Runner: + + def __init__(self): + self.proc = subprocess.Popen( + [default_cwd / (f"mfkey32v2{'.exe' if sys.platform == 'win32' else ''}"), "--server"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + bufsize=1, + encoding="ascii", + ) + + def close(self): + self.proc.stdin.close() + self.proc.wait() + + def check(self, items): + if not items: + self.close() + return None + self.proc.stdin.write(f"{items[0]['uid']} {items[0]['nt']} {items[0]['nr']} {items[0]['ar']} {items[1]['nt']} {items[1]['nr']} {items[1]['ar']}\n") + answer = self.proc.stdout.readline().strip() + if answer: + return answer, items + return None + + +def _init_mfkey32v2_run(): + global _MFKEY32V2_RUNNER + _MFKEY32V2_RUNNER = Mfkey32v2Runner() + + def _run_mfkey32v2(items): - output_str = subprocess.run( - [ - default_cwd / ("mfkey32v2.exe" if sys.platform == "win32" else "mfkey32v2"), - items[0]["uid"], - items[0]["nt"], - items[0]["nr"], - items[0]["ar"], - items[1]["nt"], - items[1]["nr"], - items[1]["ar"], - ], - capture_output=True, - check=True, - encoding="ascii", - ).stdout - sea_obj = _KEY.search(output_str) - if sea_obj is not None: - return sea_obj[0], items - return None + return _MFKEY32V2_RUNNER.check(items) class ItemGenerator: @@ -1117,13 +1130,17 @@ def decrypt_by_list(self, rs: list): msg3 = " key(s) found" n = 1 gen = ItemGenerator(rs) - with Pool(cpu_count()) as pool: + with Pool( + processes=cpu_count(), + initializer=_init_mfkey32v2_run, + ) as pool: for result in pool.imap(_run_mfkey32v2, gen): # TODO: if some keys already recovered, test them on item before running mfkey32 on item if result is not None: gen.key_found(*result) print(f"{msg1}{n}{msg2}{len(gen.keys)}{msg3}\r", end="") n += 1 + pool.imap(_run_mfkey32v2, [[]] * cpu_count()) print() return gen.keys diff --git a/software/src/mfkey32v2.c b/software/src/mfkey32v2.c index 11e36231..a2308200 100644 --- a/software/src/mfkey32v2.c +++ b/software/src/mfkey32v2.c @@ -3,61 +3,41 @@ #include #include #include +#include #include "crapto1.h" -int main(int argc, char *argv[]) { +void attack( + uint32_t uid, // serial number + uint32_t nt0, // tag challenge first + uint32_t nr0_enc, // first encrypted reader challenge + uint32_t ar0_enc, // first encrypted reader response + uint32_t nt1, // tag challenge second + uint32_t nr1_enc, // second encrypted reader challenge + uint32_t ar1_enc, // second encrypted reader response + bool verbose +) { 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 \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"); + if (verbose) { + 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)); + if (verbose) { + printf(" nt': %08x\n", p64); + printf(" nt'': %08x\n", prng_successor(p64, 32)); + printf("\nKeystream used to generate {ar} and {at}:\n"); + } + bool found = false; // Extract the keystream from the messages - printf("\nKeystream used to generate {ar} and {at}:\n"); ks2 = ar0_enc ^ p64; - printf(" ks2: %08x\n", ks2); - + if (verbose) { + 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); @@ -67,10 +47,77 @@ int main(int argc, char *argv[]) { 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); + if (verbose) { + printf("\nFound Key: [%012" PRIx64 "]\n\n", key); + } + else { + printf("%012" PRIx64 "\n", key); + } + found = true; break; } } free(s); - return 0; + if (! (found || verbose)) + printf("\n"); + fflush(stdout); +} + +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 + + if (argc == 8) { + 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"); + + 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); + + attack(uid, nt0, nr0_enc, ar0_enc, nt1, nr1_enc, ar1_enc, true); + return 0; + } + else if (argc == 2 && ! strncmp(argv[1], "--server", 9)) { + char *line =NULL; + size_t len = 0; + ssize_t line_size; + while(true) { + line_size = getline(&line, &len, stdin); + if (line_size > 1) { + sscanf(line, "%x %x %x %x %x %x %x\n", &uid, &nt0, &nr0_enc, &ar0_enc, &nt1, &nr1_enc, &ar1_enc); + attack(uid, nt0, nr0_enc, ar0_enc, nt1, nr1_enc, ar1_enc, false); + } + else { + break; + } + } + } + else { + printf("syntax: %s \n", argv[0]); + printf(" %s --server\n\n", argv[0]); + return 1; + } }