Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI: use one process to test many keys for mfkey32v2 #191

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-)
Expand Down
110 changes: 58 additions & 52 deletions software/script/chameleon_cli_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -1308,16 +1321,24 @@ 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

@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()
Expand All @@ -1341,44 +1362,29 @@ 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']
for type_, records in result_for_block.items():
# print(f" - {type_} record: { records }")
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']
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:")
for key in sorted(results):
print(f" - {self.disp_key(key)}")


@hf_mf.command('eload')
Expand Down
129 changes: 84 additions & 45 deletions software/src/mfkey32v2.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,41 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 <uid> <nt> <nr_0> <ar_0> <nt1> <nr_1> <ar_1>\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);
Expand All @@ -67,10 +47,69 @@ 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[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 <uid> <nt> <nr_0> <ar_0> <nt1> <nr_1> <ar_1>\n", argv[0]);
printf(" %s --server\n\n", argv[0]);
return 1;
}
}
Loading