Skip to content

Commit 73dd141

Browse files
committed
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 |
1 parent fefcde5 commit 73dd141

File tree

3 files changed

+130
-65
lines changed

3 files changed

+130
-65
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
33
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...
44

55
## [unreleased][unreleased]
6+
- Implement a "server" mode in mfkey32v2 to avoid spawning one process per combination (@p-l-)
67
- Skip already used items `hf mf elog --decrypt` (@p-l-)
78
- Parallelize mfkey32v2 processes called from CLI (@p-l-)
89
- Added support for mifare classic value block operations (@taichunmin)

software/script/chameleon_cli_unit.py

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,26 +1026,39 @@ def res_value(self, src_blk, src_type, src_key, dst_blk, dst_type, dst_key):
10261026
_KEY = re.compile("[a-fA-F0-9]{12}", flags=re.MULTILINE)
10271027

10281028

1029+
class Mfkey32v2Runner:
1030+
1031+
def __init__(self):
1032+
self.proc = subprocess.Popen(
1033+
[default_cwd / (f"mfkey32v2{'.exe' if sys.platform == 'win32' else ''}"), "--server"],
1034+
stdin=subprocess.PIPE,
1035+
stdout=subprocess.PIPE,
1036+
bufsize=1,
1037+
encoding="ascii",
1038+
)
1039+
1040+
def close(self):
1041+
self.proc.stdin.close()
1042+
self.proc.wait()
1043+
1044+
def check(self, items):
1045+
if not items:
1046+
self.close()
1047+
return None
1048+
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")
1049+
answer = self.proc.stdout.readline().strip()
1050+
if answer:
1051+
return answer, items
1052+
return None
1053+
1054+
1055+
def _init_mfkey32v2_run():
1056+
global _MFKEY32V2_RUNNER
1057+
_MFKEY32V2_RUNNER = Mfkey32v2Runner()
1058+
1059+
10291060
def _run_mfkey32v2(items):
1030-
output_str = subprocess.run(
1031-
[
1032-
default_cwd / ("mfkey32v2.exe" if sys.platform == "win32" else "mfkey32v2"),
1033-
items[0]["uid"],
1034-
items[0]["nt"],
1035-
items[0]["nr"],
1036-
items[0]["ar"],
1037-
items[1]["nt"],
1038-
items[1]["nr"],
1039-
items[1]["ar"],
1040-
],
1041-
capture_output=True,
1042-
check=True,
1043-
encoding="ascii",
1044-
).stdout
1045-
sea_obj = _KEY.search(output_str)
1046-
if sea_obj is not None:
1047-
return sea_obj[0], items
1048-
return None
1061+
return _MFKEY32V2_RUNNER.check(items)
10491062

10501063

10511064
class ItemGenerator:
@@ -1117,13 +1130,17 @@ def decrypt_by_list(self, rs: list):
11171130
msg3 = " key(s) found"
11181131
n = 1
11191132
gen = ItemGenerator(rs)
1120-
with Pool(cpu_count()) as pool:
1133+
with Pool(
1134+
processes=cpu_count(),
1135+
initializer=_init_mfkey32v2_run,
1136+
) as pool:
11211137
for result in pool.imap(_run_mfkey32v2, gen):
11221138
# TODO: if some keys already recovered, test them on item before running mfkey32 on item
11231139
if result is not None:
11241140
gen.key_found(*result)
11251141
print(f"{msg1}{n}{msg2}{len(gen.keys)}{msg3}\r", end="")
11261142
n += 1
1143+
pool.imap(_run_mfkey32v2, [[]] * cpu_count())
11271144
print()
11281145
return gen.keys
11291146

software/src/mfkey32v2.c

Lines changed: 92 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,61 +3,41 @@
33
#include <stdbool.h>
44
#include <stdio.h>
55
#include <stdlib.h>
6+
#include <string.h>
67
#include "crapto1.h"
78

8-
int main(int argc, char *argv[]) {
9+
void attack(
10+
uint32_t uid, // serial number
11+
uint32_t nt0, // tag challenge first
12+
uint32_t nr0_enc, // first encrypted reader challenge
13+
uint32_t ar0_enc, // first encrypted reader response
14+
uint32_t nt1, // tag challenge second
15+
uint32_t nr1_enc, // second encrypted reader challenge
16+
uint32_t ar1_enc, // second encrypted reader response
17+
bool verbose
18+
) {
919
struct Crypto1State *s, *t;
1020
uint64_t key; // recovered key
11-
uint32_t uid; // serial number
12-
uint32_t nt0; // tag challenge first
13-
uint32_t nt1; // tag challenge second
14-
uint32_t nr0_enc; // first encrypted reader challenge
15-
uint32_t ar0_enc; // first encrypted reader response
16-
uint32_t nr1_enc; // second encrypted reader challenge
17-
uint32_t ar1_enc; // second encrypted reader response
1821
uint32_t ks2; // keystream used to encrypt reader response
19-
20-
printf("MIFARE Classic key recovery - based 32 bits of keystream VERSION2\n");
21-
printf("Recover key from two 32-bit reader authentication answers only\n");
22-
printf("This version implements Moebius two different nonce solution (like the supercard)\n\n");
23-
24-
if (argc < 8) {
25-
printf("syntax: %s <uid> <nt> <nr_0> <ar_0> <nt1> <nr_1> <ar_1>\n\n", argv[0]);
26-
return 1;
27-
}
28-
29-
sscanf(argv[1], "%x", &uid);
30-
sscanf(argv[2], "%x", &nt0);
31-
sscanf(argv[3], "%x", &nr0_enc);
32-
sscanf(argv[4], "%x", &ar0_enc);
33-
sscanf(argv[5], "%x", &nt1);
34-
sscanf(argv[6], "%x", &nr1_enc);
35-
sscanf(argv[7], "%x", &ar1_enc);
36-
37-
printf("Recovering key for:\n");
38-
printf(" uid: %08x\n", uid);
39-
printf(" nt_0: %08x\n", nt0);
40-
printf(" {nr_0}: %08x\n", nr0_enc);
41-
printf(" {ar_0}: %08x\n", ar0_enc);
42-
printf(" nt_1: %08x\n", nt1);
43-
printf(" {nr_1}: %08x\n", nr1_enc);
44-
printf(" {ar_1}: %08x\n", ar1_enc);
45-
4622
// Generate lfsr successors of the tag challenge
47-
printf("\nLFSR successors of the tag challenge:\n");
23+
if (verbose) {
24+
printf("\nLFSR successors of the tag challenge:\n");
25+
}
4826
uint32_t p64 = prng_successor(nt0, 64);
4927
uint32_t p64b = prng_successor(nt1, 64);
50-
51-
printf(" nt': %08x\n", p64);
52-
printf(" nt'': %08x\n", prng_successor(p64, 32));
28+
if (verbose) {
29+
printf(" nt': %08x\n", p64);
30+
printf(" nt'': %08x\n", prng_successor(p64, 32));
31+
printf("\nKeystream used to generate {ar} and {at}:\n");
32+
}
33+
bool found = false;
5334

5435
// Extract the keystream from the messages
55-
printf("\nKeystream used to generate {ar} and {at}:\n");
5636
ks2 = ar0_enc ^ p64;
57-
printf(" ks2: %08x\n", ks2);
58-
37+
if (verbose) {
38+
printf(" ks2: %08x\n", ks2);
39+
}
5940
s = lfsr_recovery32(ar0_enc ^ p64, 0);
60-
6141
for (t = s; t->odd | t->even; ++t) {
6242
lfsr_rollback_word(t, 0, 0);
6343
lfsr_rollback_word(t, nr0_enc, 1);
@@ -67,10 +47,77 @@ int main(int argc, char *argv[]) {
6747
crypto1_word(t, uid ^ nt1, 0);
6848
crypto1_word(t, nr1_enc, 1);
6949
if (ar1_enc == (crypto1_word(t, 0, 0) ^ p64b)) {
70-
printf("\nFound Key: [%012" PRIx64 "]\n\n", key);
50+
if (verbose) {
51+
printf("\nFound Key: [%012" PRIx64 "]\n\n", key);
52+
}
53+
else {
54+
printf("%012" PRIx64 "\n", key);
55+
}
56+
found = true;
7157
break;
7258
}
7359
}
7460
free(s);
75-
return 0;
61+
if (! (found || verbose))
62+
printf("\n");
63+
fflush(stdout);
64+
}
65+
66+
int main(int argc, char *argv[]) {
67+
struct Crypto1State *s, *t;
68+
uint64_t key; // recovered key
69+
uint32_t uid; // serial number
70+
uint32_t nt0; // tag challenge first
71+
uint32_t nt1; // tag challenge second
72+
uint32_t nr0_enc; // first encrypted reader challenge
73+
uint32_t ar0_enc; // first encrypted reader response
74+
uint32_t nr1_enc; // second encrypted reader challenge
75+
uint32_t ar1_enc; // second encrypted reader response
76+
uint32_t ks2; // keystream used to encrypt reader response
77+
78+
if (argc == 8) {
79+
printf("MIFARE Classic key recovery - based 32 bits of keystream VERSION2\n");
80+
printf("Recover key from two 32-bit reader authentication answers only\n");
81+
printf("This version implements Moebius two different nonce solution (like the supercard)\n\n");
82+
83+
sscanf(argv[1], "%x", &uid);
84+
sscanf(argv[2], "%x", &nt0);
85+
sscanf(argv[3], "%x", &nr0_enc);
86+
sscanf(argv[4], "%x", &ar0_enc);
87+
sscanf(argv[5], "%x", &nt1);
88+
sscanf(argv[6], "%x", &nr1_enc);
89+
sscanf(argv[7], "%x", &ar1_enc);
90+
91+
printf("Recovering key for:\n");
92+
printf(" uid: %08x\n", uid);
93+
printf(" nt_0: %08x\n", nt0);
94+
printf(" {nr_0}: %08x\n", nr0_enc);
95+
printf(" {ar_0}: %08x\n", ar0_enc);
96+
printf(" nt_1: %08x\n", nt1);
97+
printf(" {nr_1}: %08x\n", nr1_enc);
98+
printf(" {ar_1}: %08x\n", ar1_enc);
99+
100+
attack(uid, nt0, nr0_enc, ar0_enc, nt1, nr1_enc, ar1_enc, true);
101+
return 0;
102+
}
103+
else if (argc == 2 && ! strncmp(argv[1], "--server", 9)) {
104+
char *line =NULL;
105+
size_t len = 0;
106+
ssize_t line_size;
107+
while(true) {
108+
line_size = getline(&line, &len, stdin);
109+
if (line_size > 1) {
110+
sscanf(line, "%x %x %x %x %x %x %x\n", &uid, &nt0, &nr0_enc, &ar0_enc, &nt1, &nr1_enc, &ar1_enc);
111+
attack(uid, nt0, nr0_enc, ar0_enc, nt1, nr1_enc, ar1_enc, false);
112+
}
113+
else {
114+
break;
115+
}
116+
}
117+
}
118+
else {
119+
printf("syntax: %s <uid> <nt> <nr_0> <ar_0> <nt1> <nr_1> <ar_1>\n", argv[0]);
120+
printf(" %s --server\n\n", argv[0]);
121+
return 1;
122+
}
76123
}

0 commit comments

Comments
 (0)