Skip to content

Commit

Permalink
CLI: use one process to test many keys for mfkey32v2
Browse files Browse the repository at this point in the history
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      | RfidResearchGroup#187    | 6m13,513s  | 35m2,926s  | 0m22,038s |
| With item skipping        | RfidResearchGroup#189    | 2m42,491s  | 15m43,425s | 0m9,881s  |
| With `mfkey32v2 --server` | this PR | 1m55,160s  | 0m1,315s   | 0m0,250s  |
  • Loading branch information
p-l- committed Dec 31, 2023
1 parent fefcde5 commit 73dd141
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 65 deletions.
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-)
- 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)
Expand Down
57 changes: 37 additions & 20 deletions software/script/chameleon_cli_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand Down
137 changes: 92 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,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 <uid> <nt> <nr_0> <ar_0> <nt1> <nr_1> <ar_1>\n", argv[0]);
printf(" %s --server\n\n", argv[0]);
return 1;
}
}

0 comments on commit 73dd141

Please sign in to comment.