Skip to content

Commit a853d37

Browse files
committed
refactor: move logic from standalone funcs to state class
1 parent dbd0f36 commit a853d37

File tree

17 files changed

+571
-689
lines changed

17 files changed

+571
-689
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,5 @@ jobs:
1919
- name: Install dependencies
2020
run: bun i
2121
- name: Run tests
22-
run: bun test:nested
23-
- name: Setup LCOV
24-
uses: hrishikesh-kadam/setup-lcov@v1
25-
- name: Report code coverage
26-
uses: zgosalvez/github-actions-report-lcov@v4
27-
with:
28-
coverage-files: coverage/lcov.info
29-
github-token: ${{ secrets.GITHUB_TOKEN }}
22+
run: bun test
3023

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,4 @@ Some parts were based on or greatly inspired by these projects:
7878

7979
- [crapto1](https://github.com/li0ard/crapto1) - Original version in C
8080
- [Crapto1Sharp](https://github.com/kgamecarter/Crapto1Sharp) - Version in C#
81-
- [mfkey32nested](https://github.com/RfidResearchGroup/proxmark3/blob/master/tools/mfc/card_reader/mfkey32nested.c) - Recovery by nested auth (by @doegox)
82-
- [chameleon-ultra.js](https://github.com/taichunmin/chameleon-ultra.js/) - Implementation of nested attacks on JS (by @taichunmin)
81+
- [mfkey32nested](https://github.com/RfidResearchGroup/proxmark3/blob/master/tools/mfc/card_reader/mfkey32nested.c) - Recovery by nested auth (by @doegox)

bunfig.toml

Lines changed: 0 additions & 3 deletions
This file was deleted.

examples/mfkey32.ts

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,22 @@
44
* Ported from https://github.com/equipter/mfkey by li0ard
55
*/
66

7-
import { crypto1_word, lfsr_recovery32, lfsr_rollback_word, prng_successor } from "../src";
7+
import { lfsr_recovery32, prng_successor } from "../src";
88

99
if (process.argv.length < 9) {
10-
console.log('Usage: [bun/node] ' + process.argv[1] + ' <uid> <tag challenge> <reader challenge> <reader response> <tag challenge #2> <reader challenge #2> <reader response #2>')
11-
console.log('Example: [bun/node] mfkey32.ts 23a12659 182c6685 3893952a 9613a859 b3aac455 f05e18ac 2c479869')
12-
process.exit(1)
10+
console.log('Usage: [bun/node] ' + process.argv[1] + ' <uid> <tag challenge> <reader challenge> <reader response> <tag challenge #2> <reader challenge #2> <reader response #2>');
11+
console.log('Example: [bun/node] mfkey32.ts 23a12659 182c6685 3893952a 9613a859 b3aac455 f05e18ac 2c479869');
12+
process.exit(1);
1313
}
1414

15-
const dec2hex = (dec: number, bits: number) => {
16-
if (dec < 0) {
17-
return (Math.pow(2, bits) + dec).toString(16).padStart(bits / 4, '0')
18-
} else {
19-
return dec.toString(16).padStart(bits / 4, '0');
20-
}
21-
}
22-
23-
const uid = parseInt(process.argv[2], 16)
24-
const chal = parseInt(process.argv[3], 16)
25-
const rchal = parseInt(process.argv[4], 16)
26-
const rresp = parseInt(process.argv[5], 16)
27-
const chal2 = parseInt(process.argv[6], 16)
28-
const rchal2 = parseInt(process.argv[7], 16)
29-
const rresp2 = parseInt(process.argv[8], 16)
15+
const dec2hex = (dec: number, bits: number): string => (dec < 0) ? (Math.pow(2, bits) + dec).toString(16).padStart(bits / 4, '0') : dec.toString(16).padStart(bits / 4, '0');
16+
const uid = parseInt(process.argv[2], 16);
17+
const chal = parseInt(process.argv[3], 16);
18+
const rchal = parseInt(process.argv[4], 16);
19+
const rresp = parseInt(process.argv[5], 16);
20+
const chal2 = parseInt(process.argv[6], 16);
21+
const rchal2 = parseInt(process.argv[7], 16);
22+
const rresp2 = parseInt(process.argv[8], 16);
3023

3124
console.log(`MIFARE Classic key recovery - based 32 bits of keystream
3225
Recover key from two 32-bit reader authentication answers only
@@ -38,30 +31,30 @@ Recovering key for:
3831
{ar_0}: ${dec2hex(rresp, 32)}
3932
nt_1: ${dec2hex(chal2, 32)}
4033
{nr_1}: ${dec2hex(rchal2, 32)}
41-
{ar_1}: ${dec2hex(rresp2, 32)}\n`)
34+
{ar_1}: ${dec2hex(rresp2, 32)}\n`);
4235

4336
const p64 = prng_successor(chal, 64)
4437
const p64b = prng_successor(chal2, 64);
4538

4639
console.log(`LFSR successors of the tag challenge:
4740
nt': ${dec2hex(p64, 32)}
48-
nt'': ${dec2hex(p64b, 32)}\n`)
41+
nt'': ${dec2hex(p64b, 32)}\n`);
4942

5043
let ks2 = rresp ^ p64;
5144

5245
console.log(`Keystream used to generate {ar} and {at}:
53-
ks2: ${dec2hex(ks2, 32)}\n`)
46+
ks2: ${dec2hex(ks2, 32)}\n`);
5447

5548
let s = lfsr_recovery32(rresp ^ p64, 0);
5649
for (let t = 0; (s[t].odd !== 0) || (s[t].even !== 0); ++t) {
57-
lfsr_rollback_word(s[t], 0, false);
58-
lfsr_rollback_word(s[t], rchal, true);
59-
lfsr_rollback_word(s[t], uid ^ chal, false);
60-
let key = s[t].lfsr
61-
crypto1_word(s[t], uid ^ chal2, false);
62-
crypto1_word(s[t], rchal2, true);
63-
if (rresp2 === (crypto1_word(s[t], 0, false) ^ prng_successor(chal2, 64))) {
64-
console.log(`Found Key: [${key.toString(16).padStart(12, "0")}]`)
50+
s[t].rollback_word();
51+
s[t].rollback_word(rchal, true);
52+
s[t].rollback_word(uid ^ chal);
53+
const key = s[t].lfsr;
54+
s[t].word(uid ^ chal2);
55+
s[t].word(rchal2, true);
56+
if (rresp2 === (s[t].word() ^ prng_successor(chal2, 64))) {
57+
console.log(`Found Key: [${key.toString(16).padStart(12, "0")}]`);
6558
break;
6659
}
6760
}

examples/mfkey32_flipper.ts

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,21 @@
1-
import fs from "fs"
1+
import { readFileSync } from "fs";
22
import { recovery32 } from "../src";
33

44
if (process.argv.length < 3) {
5-
console.log('Usage: [bun/node] ' + process.argv[1] + ' /path/to/.mfkey32.log')
6-
console.log('Example: [bun/node] mfkey32_flipper.ts .mfkey32.log')
7-
process.exit(1)
5+
console.log('Usage: [bun/node] ' + process.argv[1] + ' /path/to/.mfkey32.log');
6+
console.log('Example: [bun/node] mfkey32_flipper.ts .mfkey32.log');
7+
process.exit(1);
88
}
99

10-
const nonces = new TextDecoder().decode(fs.readFileSync(process.argv[2])).split('\n')
11-
if (nonces[nonces.length - 1]!.length === 0) {
12-
nonces.pop()
13-
}
10+
const nonces = new TextDecoder().decode(readFileSync(process.argv[2])).split('\n');
11+
if (nonces[nonces.length - 1]!.length === 0) nonces.pop();
12+
const keys = new Set<string>();
1413

15-
const keys = new Set<string>()
1614
for (let i = 0; i < nonces.length; i++) {
17-
const args = nonces[i]!.slice(nonces[i]!.indexOf('cuid')).split(' ').filter((e, i) => i % 2 === 1)
18-
console.log(`Cracking nonce ${i + 1} of ${nonces.length}`)
19-
const key = recovery32(
20-
parseInt(args[0], 16),
21-
parseInt(args[1], 16),
22-
parseInt(args[2], 16),
23-
parseInt(args[3], 16),
24-
parseInt(args[4], 16),
25-
parseInt(args[5], 16),
26-
parseInt(args[6], 16),
27-
)
28-
if(key !== -1n) {
29-
keys.add(key.toString(16).padStart(12, "0"))
30-
}
15+
const args = nonces[i]!.slice(nonces[i]!.indexOf('cuid')).split(' ').filter((e, i) => i % 2 === 1);
16+
console.log(`Cracking nonce ${i + 1} of ${nonces.length}`);
17+
const key = recovery32(parseInt(args[0], 16), parseInt(args[1], 16), parseInt(args[2], 16), parseInt(args[3], 16), parseInt(args[4], 16), parseInt(args[5], 16), parseInt(args[6], 16));
18+
if(key !== -1n) keys.add(key.toString(16).padStart(12, "0"));
3119
}
3220

33-
console.log(`\nKeys: ${keys.size != 0 ? Array.from(keys).join(", ") : "-"}`)
21+
console.log(`\nKeys: ${keys.size != 0 ? Array.from(keys).join(", ") : "-"}`);

examples/mfkey32nested.ts

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,20 @@
44
* Ported from https://github.com/RfidResearchGroup/proxmark3/blob/master/tools/mfc/card_reader/mfkey32nested.c by li0ard
55
*/
66

7-
import { crypto1_word, lfsr_recovery32, lfsr_rollback_word, prng_successor } from "../src";
7+
import { lfsr_recovery32, prng_successor } from "../src";
88

99
if (process.argv.length < 7) {
10-
console.log('Usage: [bun/node] ' + process.argv[1] + ' <uid> <nt> <{nt}> <{nr}> <{ar}>')
11-
console.log('Example: [bun/node] mfkey32nested.ts 5c467f63 4bbf8a12 abb30bd1 46033966 adc18162')
12-
process.exit(1)
10+
console.log('Usage: [bun/node] ' + process.argv[1] + ' <uid> <nt> <{nt}> <{nr}> <{ar}>');
11+
console.log('Example: [bun/node] mfkey32nested.ts 5c467f63 4bbf8a12 abb30bd1 46033966 adc18162');
12+
process.exit(1);
1313
}
1414

15-
const dec2hex = (dec: number, bits: number) => {
16-
if (dec < 0) {
17-
return (Math.pow(2, bits) + dec).toString(16).padStart(bits / 4, '0')
18-
} else {
19-
return dec.toString(16).padStart(bits / 4, '0');
20-
}
21-
}
22-
23-
const uid = parseInt(process.argv[2], 16)
24-
const chal = parseInt(process.argv[3], 16)
25-
const enc_chal = parseInt(process.argv[4], 16)
26-
const rchal = parseInt(process.argv[5], 16)
27-
const rresp = parseInt(process.argv[6], 16)
15+
const dec2hex = (dec: number, bits: number): string => (dec < 0) ? (Math.pow(2, bits) + dec).toString(16).padStart(bits / 4, '0') : dec.toString(16).padStart(bits / 4, '0');
16+
const uid = parseInt(process.argv[2], 16);
17+
const chal = parseInt(process.argv[3], 16);
18+
const enc_chal = parseInt(process.argv[4], 16);
19+
const rchal = parseInt(process.argv[5], 16);
20+
const rresp = parseInt(process.argv[6], 16);
2821

2922
console.log(`MIFARE Classic key recovery - based 32 bits of keystream
3023
Recover key from one reader authentication answer only
@@ -34,28 +27,28 @@ Recovering key for:
3427
nt: ${dec2hex(chal, 32)}
3528
{nt}: ${dec2hex(enc_chal, 32)}
3629
{nr}: ${dec2hex(rchal, 32)}
37-
{ar}: ${dec2hex(rresp, 32)}\n`)
30+
{ar}: ${dec2hex(rresp, 32)}\n`);
3831

39-
let ar = prng_successor(chal, 64);
40-
let ks0 = enc_chal ^ chal;
41-
let ks2 = rresp ^ ar;
32+
const ar = prng_successor(chal, 64);
33+
const ks0 = enc_chal ^ chal;
34+
const ks2 = rresp ^ ar;
4235

43-
console.log(`\nLFSR successor of the tag challenge:`)
44-
console.log(` ar: ${dec2hex(ar, 32)}`)
36+
console.log(`\nLFSR successor of the tag challenge:`);
37+
console.log(` ar: ${dec2hex(ar, 32)}`);
4538
console.log(`\nKeystream used to generate {nt}:
46-
ks0: ${dec2hex(ks0, 32)}`)
39+
ks0: ${dec2hex(ks0, 32)}`);
4740
console.log(`\nKeystream used to generate {ar}:
48-
ks2: ${dec2hex(ks2, 32)}`)
41+
ks2: ${dec2hex(ks2, 32)}`);
4942

50-
let s = lfsr_recovery32(ks0, uid ^ chal);
43+
const s = lfsr_recovery32(ks0, uid ^ chal);
5144

5245
for (let t = 0; (s[t].odd !== 0) || (s[t].even !== 0); t++) {
53-
crypto1_word(s[t], rchal, true);
54-
if(ks2 == crypto1_word(s[t], 0)) {
55-
lfsr_rollback_word(s[t], 0);
56-
lfsr_rollback_word(s[t], rchal, true);
57-
lfsr_rollback_word(s[t], uid ^ chal);
58-
let key = s[t].lfsr
46+
s[t].word(rchal, true);
47+
if(ks2 == s[t].word()) {
48+
s[t].rollback_word();
49+
s[t].rollback_word(rchal, true);
50+
s[t].rollback_word(uid ^ chal);
51+
const key = s[t].lfsr;
5952
console.log(`\nFound Key: [${key.toString(16).padStart(12, "0")}]`)
6053
break;
6154
}

examples/mfkey64.ts

Lines changed: 36 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,29 @@
33
*
44
* Ported from https://github.com/equipter/mfkey by li0ard
55
*/
6-
import { crypto1_byte, lfsr_recovery64, lfsr_rollback_byte, lfsr_rollback_word, prng_successor } from "../src"
6+
import { lfsr_recovery64, prng_successor } from "../src";
77

88
if (process.argv.length < 7) {
9-
console.log('Usage: [bun/node] ' + process.argv[1] + ' <uid> <tag challenge> <reader challenge> <reader response> <tag response>')
10-
console.log('Example: [bun/node] mfkey64.ts 14579f69 ce844261 f8049ccb 0525c84f 9431cc40')
11-
process.exit(1)
9+
console.log('Usage: [bun/node] ' + process.argv[1] + ' <uid> <tag challenge> <reader challenge> <reader response> <tag response>');
10+
console.log('Example: [bun/node] mfkey64.ts 14579f69 ce844261 f8049ccb 0525c84f 9431cc40');
11+
process.exit(1);
1212
}
1313

14-
const dec2hex = (dec: number, bits: number) => {
15-
if (dec < 0) {
16-
return (Math.pow(2, bits) + dec).toString(16).padStart(bits / 4, '0')
17-
} else {
18-
return dec.toString(16).padStart(bits / 4, '0');
19-
}
20-
}
14+
const dec2hex = (dec: number, bits: number): string => (dec < 0) ? (Math.pow(2, bits) + dec).toString(16).padStart(bits / 4, '0') : dec.toString(16).padStart(bits / 4, '0');
2115

22-
const uid = parseInt(process.argv[2], 16)
23-
const chal = parseInt(process.argv[3], 16)
24-
const rchal = parseInt(process.argv[4], 16)
25-
const rresp = parseInt(process.argv[5], 16)
26-
const tresp = parseInt(process.argv[6], 16)
16+
const uid = parseInt(process.argv[2], 16);
17+
const chal = parseInt(process.argv[3], 16);
18+
const rchal = parseInt(process.argv[4], 16);
19+
const rresp = parseInt(process.argv[5], 16);
20+
const tresp = parseInt(process.argv[6], 16);
2721

28-
let encc = process.argv.length - 7
29-
let enclen: number[] = Array(encc)
30-
let enc: number[][] = Array.from({ length: encc }, () => new Array(120));
22+
const encc = process.argv.length - 7;
23+
const enclen: number[] = Array(encc);
24+
const enc: number[][] = Array.from({ length: encc }, () => new Array(120));
3125

3226
for (let i = 0; i < encc; i++) {
3327
enclen[i] = (process.argv[i + 7].length) / 2;
34-
for (let i2 = 0; i2 < enclen[i]; i2++) {
35-
enc[i][i2] = parseInt(process.argv[i + 7].substring(i2 * 2, i2 * 2 + 2), 16)
36-
}
28+
for (let i2 = 0; i2 < enclen[i]; i2++) enc[i][i2] = parseInt(process.argv[i + 7].substring(i2 * 2, i2 * 2 + 2), 16);
3729
}
3830

3931
console.log(`MIFARE Classic key recovery - based 64 bits of keystream
@@ -42,51 +34,47 @@ Recovering key for:
4234
nt: ${dec2hex(chal, 32)}
4335
{nr}: ${dec2hex(rchal, 32)}
4436
{ar}: ${dec2hex(rresp, 32)}
45-
{at}: ${dec2hex(tresp, 32)}\n`)
37+
{at}: ${dec2hex(tresp, 32)}\n`);
4638

4739
for (let i = 0; i < encc; i++) {
48-
process.stdout.write(`{enc${i}}: `)
49-
for (let i2 = 0; i2 < enclen[i]; i2++) {
50-
process.stdout.write(dec2hex(enc[i][i2], 8))
51-
}
52-
console.log("")
40+
process.stdout.write(`{enc${i}}: `);
41+
for (let i2 = 0; i2 < enclen[i]; i2++) process.stdout.write(dec2hex(enc[i][i2], 8));
42+
console.log("");
5343
}
5444

55-
const p64 = prng_successor(chal, 64)
45+
const p64 = prng_successor(chal, 64);
5646
console.log(`\nLFSR successors of the tag challenge:
5747
nt': ${dec2hex(p64, 32)}
58-
nt'': ${dec2hex(prng_successor(p64, 32), 32)}\n`)
48+
nt'': ${dec2hex(prng_successor(p64, 32), 32)}\n`);
5949

60-
const ks2 = rresp ^ p64
61-
const ks3 = tresp ^ prng_successor(p64, 32)
50+
const ks2 = rresp ^ p64;
51+
const ks3 = tresp ^ prng_successor(p64, 32);
6252

6353
console.log(`Keystream used to generate {ar} and {at}:
6454
ks2: ${dec2hex(ks2, 32)}
65-
ks3: ${dec2hex(ks3, 32)}\n`)
55+
ks3: ${dec2hex(ks3, 32)}\n`);
6656

67-
let s = lfsr_recovery64(ks2, ks3)[0]
57+
const s = lfsr_recovery64(ks2, ks3)[0];
6858

6959
if(process.argv.length > 7) {
70-
console.log("Decrypted communication:")
60+
console.log("Decrypted communication:");
7161
let ks4 = 0;
7262
let rollb = 0;
7363
for (let i = 0; i < encc; i++) {
74-
process.stdout.write(`{dec${i}}: `)
64+
process.stdout.write(`{dec${i}}: `);
7565
for (let i2 = 0; i2 < enclen[i]; i2++) {
76-
ks4 = crypto1_byte(s, 0);
77-
process.stdout.write(dec2hex(ks4 ^ enc[i][i2], 8))
66+
ks4 = s.byte(0);
67+
process.stdout.write(dec2hex(ks4 ^ enc[i][i2], 8));
7868
rollb += 1;
7969
}
80-
console.log("")
70+
console.log("");
8171
}
82-
for (let i = 0; i < rollb; i++) lfsr_rollback_byte(s, 0)
72+
for (let i = 0; i < rollb; i++) s.rollback_byte(0);
8373
}
84-
let postAuthState = s.lfsr
85-
86-
lfsr_rollback_word(s, 0)
87-
lfsr_rollback_word(s, 0)
88-
lfsr_rollback_word(s, rchal, true)
89-
lfsr_rollback_word(s, uid^chal)
74+
const postAuthState = s.lfsr;
75+
s.rollback_word();
76+
s.rollback_word();
77+
s.rollback_word(rchal, true);
78+
s.rollback_word(uid^chal);
9079

91-
console.log(`\nFound Key: [${s.lfsr.toString(16).padStart(12, "0")}]
92-
Post-auth state: [${postAuthState.toString(16).padStart(12, "0")}]`)
80+
console.log(`\nFound Key: [${s.lfsr.toString(16).padStart(12, "0")}]\nPost-auth state: [${postAuthState.toString(16).padStart(12, "0")}]`);

0 commit comments

Comments
 (0)