From c2fb77a2f2407bf162c880a42e80457759940e51 Mon Sep 17 00:00:00 2001 From: Pierre Lalet Date: Thu, 21 Dec 2023 19:54:25 +0100 Subject: [PATCH 1/4] CLI: use one process to test many keys for mfkey32v2 It is done by adding a "server" mode (on STDIN/STDOUT) to `mfkey32v2` using `--server`. This improves the speed of the decrypt process, as it prevents a lot of Python calls to `subprocess.Popen()` (and of course, a lot of fork/execve syscalls). On my laptop, with the same logs (37 records for one block and 37 records for another block), here are the performances, as measuerd using a simple command: ```bash time echo -e "hw connect\nhf mf elog --decrypt\nhw disconnect" | ./chameleon_cli_main.py ``` | | | real | user | sys | |---------------------------|---------|------------|------------|-----------| | Before parallelisation | 05ea03d | 14m59,277s | 14m47,995s | 0m8,490s | | With parallelisation | #187 | 6m13,513s | 35m2,926s | 0m22,038s | | With item skipping | #189 | 2m42,491s | 15m43,425s | 0m9,881s | | With `mfkey32v2 --server` | this PR | 1m55,160s | 0m1,315s | 0m0,250s | --- CHANGELOG.md | 1 + software/script/chameleon_cli_unit.py | 57 +++++++---- software/src/mfkey32v2.c | 137 +++++++++++++++++--------- 3 files changed, 130 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 398b0b31..52a17f77 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-) - Added command to check keys of multiple sectors at once (@taichunmin) - Fixed unused target key type parameter for nested (@petepriority) - Skip already used items `hf mf elog --decrypt` (@p-l-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 6bcf9e9b..9e5cf670 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1217,26 +1217,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: @@ -1308,13 +1321,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; + } } From b0388108692ec1d51a7982ed52bdb2bea79982c3 Mon Sep 17 00:00:00 2001 From: Pierre Lalet Date: Thu, 21 Dec 2023 19:55:59 +0100 Subject: [PATCH 2/4] CLI: clean-up "elog --decrypt" code --- software/script/chameleon_cli_unit.py | 47 +++++++++------------------ 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 9e5cf670..82f546e4 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1358,44 +1358,27 @@ def on_exec(self, args: argparse.Namespace): # classify result_maps = {} for item in result_list: - uid = item['uid'] - if uid not in result_maps: - result_maps[uid] = {} - block = item['block'] - if block not in result_maps[uid]: - result_maps[uid][block] = {} - type = item['type'] - if type not in result_maps[uid][block]: - result_maps[uid][block][type] = [] - - result_maps[uid][block][type].append(item) - - for uid in result_maps.keys(): + ( + result_maps.setdefault(item["uid"], {}) + .setdefault(item["block"], {}) + .setdefault(item["type"], []) + .append(item) + ) + + for uid, result_maps_for_uid in result_maps.items(): print(f" - Detection log for uid [{uid.upper()}]") - result_maps_for_uid = result_maps[uid] - for block in result_maps_for_uid: + for block, result_for_block in result_maps_for_uid.items(): print(f" > Block {block} detect log decrypting...") - if 'A' in result_maps_for_uid[block]: - # print(f" - A record: { result_maps[block]['A'] }") - records = result_maps_for_uid[block]['A'] - if len(records) > 1: - result_maps[uid][block]['A'] = self.decrypt_by_list(records) - else: - print(f" > {len(records)} record") - if 'B' in result_maps_for_uid[block]: - # print(f" - B record: { result_maps[block]['B'] }") - records = result_maps_for_uid[block]['B'] + for type_, records in result_for_block.items(): + # print(f" - {type_} record: { records }") if len(records) > 1: - result_maps[uid][block]['B'] = self.decrypt_by_list(records) + result_for_block[type_] = self.decrypt_by_list(records) else: print(f" > {len(records)} record") print(" > Result ---------------------------") - for block in result_maps_for_uid.keys(): - if 'A' in result_maps_for_uid[block]: - print(f" > Block {block}, A key result: {result_maps_for_uid[block]['A']}") - if 'B' in result_maps_for_uid[block]: - print(f" > Block {block}, B key result: {result_maps_for_uid[block]['B']}") - return + for block, result_for_block in result_maps_for_uid.items(): + for type_, results in result_for_block.items(): + print(f" > Block {block}, {type_} key result: {results}") @hf_mf.command('eload') From 8e80e314bc7ade6c27c2165902b923985bf55c14 Mon Sep 17 00:00:00 2001 From: Pierre Lalet Date: Thu, 4 Jan 2024 17:25:00 +0100 Subject: [PATCH 3/4] CLI: improve keys display in "elog --decrypt" --- software/script/chameleon_cli_unit.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 82f546e4..89b5daaa 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1335,6 +1335,10 @@ def decrypt_by_list(self, rs: list): print() return gen.keys + @staticmethod + def disp_key(key: str): + return f"{key} [{''.join(chr(c) if 31 < c < 127 else '.' for c in bytes.fromhex(key))}]" + def on_exec(self, args: argparse.Namespace): if not args.decrypt: count = self.cmd.mf1_get_detection_count() @@ -1378,7 +1382,9 @@ def on_exec(self, args: argparse.Namespace): print(" > Result ---------------------------") for block, result_for_block in result_maps_for_uid.items(): for type_, results in result_for_block.items(): - print(f" > Block {block}, {type_} key result: {results}") + print(f" > Block {block}, {type_} key result:") + for key in sorted(results): + print(f" - {self.disp_key(key)}") @hf_mf.command('eload') From 732c894a51d8516214b565e7258446bdfc9f09e3 Mon Sep 17 00:00:00 2001 From: Pierre Lalet Date: Tue, 20 Aug 2024 22:34:37 +0200 Subject: [PATCH 4/4] CLI: make mfkey32v2 Windows friendly --- software/src/mfkey32v2.c | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/software/src/mfkey32v2.c b/software/src/mfkey32v2.c index a2308200..567a2d3c 100644 --- a/software/src/mfkey32v2.c +++ b/software/src/mfkey32v2.c @@ -101,19 +101,11 @@ int main(int argc, char *argv[]) { 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; - } - } + char line[63]; + while(fgets(line, sizeof(line), stdin)) { + 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 { printf("syntax: %s \n", argv[0]);