fm11rf08s_recovery: now with help, args & colors

This commit is contained in:
Philippe Teuwen 2024-08-11 00:38:18 +02:00
commit 494a201e9d

View file

@ -15,22 +15,25 @@
import time import time
import subprocess import subprocess
import argparse
# pip install ansicolors
from colors import color
import pm3 import pm3
from output_grabber import OutputGrabber from output_grabber import OutputGrabber
BACKDOOR_RF08S = "A396EFA4E24F" BACKDOOR_RF08S = "A396EFA4E24F"
NUM_SECTORS = 16 NUM_SECTORS = 16
# Run an initial check with the default keys
INITIAL_CHECK = True
# Run a final check with the found keys, mostly for validation
FINAL_CHECK = True
TOOLS_PATH = "tools/mfc/card_only" TOOLS_PATH = "tools/mfc/card_only"
STATICNESTED_1NT = f"{TOOLS_PATH}/staticnested_1nt" STATICNESTED_1NT = f"{TOOLS_PATH}/staticnested_1nt"
STATICNESTED_2X1NT = f"{TOOLS_PATH}/staticnested_2x1nt_rf08s" STATICNESTED_2X1NT = f"{TOOLS_PATH}/staticnested_2x1nt_rf08s"
STATICNESTED_2X1NT1KEY = f"{TOOLS_PATH}/staticnested_2x1nt_rf08s_1key" STATICNESTED_2X1NT1KEY = f"{TOOLS_PATH}/staticnested_2x1nt_rf08s_1key"
DEBUG = False
parser = argparse.ArgumentParser(description='A script combining staticnested* tools '
'to recover all keys from a FM11RF08S card.')
parser.add_argument('-x', '--no-init-check', action='store_true', help='Do not run an initial fchk for default keys')
parser.add_argument('-y', '--no-final-check', action='store_true', help='Do not run a final fchk with the found keys')
parser.add_argument('-d', '--debug', action='store_true', help='Enable debug mode')
args = parser.parse_args()
start_time = time.time() start_time = time.time()
out = OutputGrabber() out = OutputGrabber()
@ -55,10 +58,16 @@ if uid is None:
with out: with out:
p.console("prefs set color --ansi") p.console("prefs set color --ansi")
exit() exit()
print(f"UID: {uid:08X}") print("UID: " + color(f"{uid:08X}", fg="green"))
def print_key(sec, key_type, key):
kt = ['A', 'B'][key_type]
print(f"Sector {sec:2} key{kt} = " + color(key, fg="green"))
found_keys = [["", ""] for _ in range(NUM_SECTORS)] found_keys = [["", ""] for _ in range(NUM_SECTORS)]
if INITIAL_CHECK: if not args.no_init_check:
print("Checking default keys...") print("Checking default keys...")
with out: with out:
p.console("hf mf fchk") p.console("hf mf fchk")
@ -68,10 +77,10 @@ if INITIAL_CHECK:
sec = int(res[0][4:]) sec = int(res[0][4:])
if res[3] == '1': if res[3] == '1':
found_keys[sec][0] = res[2] found_keys[sec][0] = res[2]
print(f"Sector {sec:2} keyA = {found_keys[sec][0]}") print_key(sec, 0, found_keys[sec][0])
if res[5] == '1': if res[5] == '1':
found_keys[sec][1] = res[4] found_keys[sec][1] = res[4]
print(f"Sector {sec:2} keyB = {found_keys[sec][1]}") print_key(sec, 1, found_keys[sec][1])
nt = [["", ""] for _ in range(NUM_SECTORS)] nt = [["", ""] for _ in range(NUM_SECTORS)]
nt_enc = [["", ""] for _ in range(NUM_SECTORS)] nt_enc = [["", ""] for _ in range(NUM_SECTORS)]
@ -115,12 +124,12 @@ for sec in range(NUM_SECTORS):
for key_type in [0, 1]: for key_type in [0, 1]:
cmd = [STATICNESTED_1NT, f"{uid:08X}", f"{sec}", cmd = [STATICNESTED_1NT, f"{uid:08X}", f"{sec}",
nt[sec][key_type], nt_enc[sec][key_type], par_err[sec][key_type]] nt[sec][key_type], nt_enc[sec][key_type], par_err[sec][key_type]]
if DEBUG: if args.debug:
print(' '.join(cmd)) print(' '.join(cmd))
subprocess.run(cmd, capture_output=True) subprocess.run(cmd, capture_output=True)
cmd = [STATICNESTED_2X1NT, cmd = [STATICNESTED_2X1NT,
f"keys_{uid:08x}_{sec:02}_{nt[sec][0]}.dic", f"keys_{uid:08x}_{sec:02}_{nt[sec][1]}.dic"] f"keys_{uid:08x}_{sec:02}_{nt[sec][0]}.dic", f"keys_{uid:08x}_{sec:02}_{nt[sec][1]}.dic"]
if DEBUG: if args.debug:
print(' '.join(cmd)) print(' '.join(cmd))
subprocess.run(cmd, capture_output=True) subprocess.run(cmd, capture_output=True)
filtered_dicts[sec][key_type] = True filtered_dicts[sec][key_type] = True
@ -140,7 +149,7 @@ for sec in range(NUM_SECTORS):
key_type = 1 key_type = 1
cmd = [STATICNESTED_1NT, f"{uid:08X}", f"{sec}", cmd = [STATICNESTED_1NT, f"{uid:08X}", f"{sec}",
nt[sec][key_type], nt_enc[sec][key_type], par_err[sec][key_type]] nt[sec][key_type], nt_enc[sec][key_type], par_err[sec][key_type]]
if DEBUG: if args.debug:
print(' '.join(cmd)) print(' '.join(cmd))
subprocess.run(cmd, capture_output=True) subprocess.run(cmd, capture_output=True)
with (open(f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type]}.dic")) as f: with (open(f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type]}.dic")) as f:
@ -188,7 +197,7 @@ for sec in range(NUM_SECTORS):
kt = ['a', 'b'][key_type] kt = ['a', 'b'][key_type]
dic = f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type]}_duplicates.dic" dic = f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type]}_duplicates.dic"
cmd = f"hf mf fchk --blk {sec * 4} -{kt} -f {dic} --no-default" cmd = f"hf mf fchk --blk {sec * 4} -{kt} -f {dic} --no-default"
if DEBUG: if args.debug:
print(cmd) print(cmd)
with out: with out:
p.console(cmd) p.console(cmd)
@ -197,12 +206,10 @@ for sec in range(NUM_SECTORS):
abort = True abort = True
if "found:" in line: if "found:" in line:
found_keys[sec][key_type] = line[30:] found_keys[sec][key_type] = line[30:]
kt = ['A', 'B'][key_type] print_key(sec, key_type, found_keys[sec][key_type])
print(f"Sector {sec:2} key{kt} = {found_keys[sec][key_type]}")
if nt[sec][0] == nt[sec][1] and found_keys[sec][key_type ^ 1] == "": if nt[sec][0] == nt[sec][1] and found_keys[sec][key_type ^ 1] == "":
found_keys[sec][key_type ^ 1] = found_keys[sec][key_type] found_keys[sec][key_type ^ 1] = found_keys[sec][key_type]
kt = ['A', 'B'][key_type ^ 1] print_key(sec, key_type ^ 1, found_keys[sec][key_type ^ 1])
print(f"Sector {sec:2} key{kt} = {found_keys[sec][key_type ^ 1]}")
if abort: if abort:
break break
if abort: if abort:
@ -217,7 +224,7 @@ for sec in range(NUM_SECTORS):
kt = ['a', 'b'][key_type] kt = ['a', 'b'][key_type]
dic = f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type]}_filtered.dic" dic = f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type]}_filtered.dic"
cmd = f"hf mf fchk --blk {sec * 4} -{kt} -f {dic} --no-default" cmd = f"hf mf fchk --blk {sec * 4} -{kt} -f {dic} --no-default"
if DEBUG: if args.debug:
print(cmd) print(cmd)
with out: with out:
p.console(cmd) p.console(cmd)
@ -226,8 +233,7 @@ for sec in range(NUM_SECTORS):
abort = True abort = True
if "found:" in line: if "found:" in line:
found_keys[sec][key_type] = line[30:] found_keys[sec][key_type] = line[30:]
kt = ['A', 'B'][key_type] print_key(sec, key_type, found_keys[sec][key_type])
print(f"Sector {sec:2} key{kt} = {found_keys[sec][key_type]}")
if abort: if abort:
break break
if abort: if abort:
@ -240,7 +246,7 @@ for sec in range(NUM_SECTORS):
kt = ['a', 'b'][key_type] kt = ['a', 'b'][key_type]
dic = f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type]}.dic" dic = f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type]}.dic"
cmd = f"hf mf fchk --blk {sec * 4} -{kt} -f {dic} --no-default" cmd = f"hf mf fchk --blk {sec * 4} -{kt} -f {dic} --no-default"
if DEBUG: if args.debug:
print(cmd) print(cmd)
with out: with out:
p.console(cmd) p.console(cmd)
@ -250,8 +256,8 @@ for sec in range(NUM_SECTORS):
if "found:" in line: if "found:" in line:
found_keys[sec][0] = line[30:] found_keys[sec][0] = line[30:]
found_keys[sec][1] = line[30:] found_keys[sec][1] = line[30:]
print(f"Sector {sec:2} keyA = {found_keys[sec][key_type]}") print_key(sec, 0, found_keys[sec][key_type])
print(f"Sector {sec:2} keyB = {found_keys[sec][key_type]}") print_key(sec, 1, found_keys[sec][key_type])
if abort: if abort:
break break
@ -270,7 +276,7 @@ for sec in range(NUM_SECTORS):
else: else:
dic = f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type_target]}.dic" dic = f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type_target]}.dic"
cmd = [STATICNESTED_2X1NT1KEY, nt[sec][key_type_source], found_keys[sec][key_type_source], dic] cmd = [STATICNESTED_2X1NT1KEY, nt[sec][key_type_source], found_keys[sec][key_type_source], dic]
if DEBUG: if args.debug:
print(' '.join(cmd)) print(' '.join(cmd))
result = subprocess.run(cmd, capture_output=True, text=True).stdout result = subprocess.run(cmd, capture_output=True, text=True).stdout
keys = set() keys = set()
@ -283,7 +289,7 @@ for sec in range(NUM_SECTORS):
cmd = f"hf mf fchk --blk {sec * 4} -{kt} --no-default" cmd = f"hf mf fchk --blk {sec * 4} -{kt} --no-default"
for k in keys: for k in keys:
cmd += f" -k {k}" cmd += f" -k {k}"
if DEBUG: if args.debug:
print(cmd) print(cmd)
with out: with out:
p.console(cmd) p.console(cmd)
@ -295,8 +301,7 @@ for sec in range(NUM_SECTORS):
elif len(keys) == 1: elif len(keys) == 1:
found_keys[sec][key_type_target] = keys.pop() found_keys[sec][key_type_target] = keys.pop()
if found_keys[sec][key_type_target] != "": if found_keys[sec][key_type_target] != "":
kt = ['A', 'B'][key_type_target] print_key(sec, key_type_target, found_keys[sec][key_type_target])
print(f"Sector {sec:2} key{kt} = {found_keys[sec][key_type_target]}")
if abort: if abort:
break break
if restore_color: if restore_color:
@ -305,36 +310,59 @@ if restore_color:
if abort: if abort:
print("Brute-forcing phase aborted via keyboard!") print("Brute-forcing phase aborted via keyboard!")
FINAL_CHECK = False args.no_final_check = True
if FINAL_CHECK: if args.no_final_check:
print("Letting fchk do a final dump, just for confirmation and display...") plus = "[" + color("+", fg="green") + "] "
keys_set = set([i for sl in found_keys for i in sl if i != ""]) print()
with (open(f"keys_{uid:08x}.dic", "w")) as f: print(plus + color("found keys:", fg="green"))
for k in keys_set: print()
f.write(f"{k}\n") print(plus + "-----+-----+--------------+---+--------------+----")
cmd = f"hf mf fchk -f keys_{uid:08x}.dic --no-default --dump" print(plus + " Sec | Blk | key A |res| key B |res")
if DEBUG: print(plus + "-----+-----+--------------+---+--------------+----")
print(cmd) for sec in range(NUM_SECTORS):
with out: keys = [["", 0], ["", 0]]
p.console(cmd) for key_type in [0, 1]:
for line in out.captured_output.split('\n'): if found_keys[sec][key_type] == "":
print(line) keys[key_type] = [color("------------", fg="red"), color("0", fg="red")]
else: else:
print(found_keys) keys[key_type] = [color(found_keys[sec][0], fg="green"), color("1", fg="green")]
print("Generating binary key file") print(plus + f" {sec:03} | {sec*4+3:03} | {keys[0][0]} | {keys[0][1]} | {keys[1][0]} | {keys[1][1]} ")
print(plus + "-----+-----+--------------+---+--------------+----")
print(plus + "( " + color("0", fg="red") + ":Failed / " +
color("1", fg="green") + ":Success )")
print()
print(plus + "Generating binary key file")
keyfile = f"hf-mf-{uid:08X}-key.bin" keyfile = f"hf-mf-{uid:08X}-key.bin"
unknown = False
with (open(keyfile, "wb")) as f: with (open(keyfile, "wb")) as f:
for key_type in [0, 1]: for key_type in [0, 1]:
for sec in range(NUM_SECTORS): for sec in range(NUM_SECTORS):
k = found_keys[sec][key_type] k = found_keys[sec][key_type]
if k == "": if k == "":
k = "FFFFFFFFFFFF" k = "FFFFFFFFFFFF"
unknown = True
f.write(bytes.fromhex(k)) f.write(bytes.fromhex(k))
print(f"Found keys have been dumped to `{keyfile}`") print(plus + "Found keys have been dumped to `" + color(keyfile, fg="yellow")+"`")
print(" --[ FFFFFFFFFFFF ]-- has been inserted for unknown keys") if unknown:
print("[" + color("=", fg="yellow") + "] --[ " + color("FFFFFFFFFFFF", fg="yellow") +
" ]-- has been inserted for unknown keys")
else:
print("Letting fchk do a final dump, just for confirmation and display...")
keys_set = set([i for sl in found_keys for i in sl if i != ""])
with (open(f"keys_{uid:08x}.dic", "w")) as f:
for k in keys_set:
f.write(f"{k}\n")
cmd = f"hf mf fchk -f keys_{uid:08x}.dic --no-default --dump"
if args.debug:
print(cmd)
with out:
p.console(cmd)
for line in out.captured_output.split('\n'):
print(line)
elapsed_time = time.time() - start_time elapsed_time = time.time() - start_time
minutes = int(elapsed_time // 60) minutes = int(elapsed_time // 60)
seconds = int(elapsed_time % 60) seconds = int(elapsed_time % 60)
print(f"--- {minutes} minutes {seconds} seconds ---") print("--- " + color(minutes, fg="yellow") + " minutes " +
color(seconds, fg="yellow") + " seconds ---")