mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2025-08-20 21:33:47 -07:00
fm11rf08s_recovery: prioritize known keys and estimate total time. Beware options x/y are now inverted:
as running fchk before and after the core script does not make much sense, now the default is to *not* run fchk. Use -x or -y to reenable the old behavior.
This commit is contained in:
parent
be7b0ecf69
commit
a6d0b40fa3
1 changed files with 125 additions and 28 deletions
|
@ -30,14 +30,20 @@ except ModuleNotFoundError:
|
||||||
|
|
||||||
BACKDOOR_RF08S = "A396EFA4E24F"
|
BACKDOOR_RF08S = "A396EFA4E24F"
|
||||||
NUM_SECTORS = 16
|
NUM_SECTORS = 16
|
||||||
|
DICT_DEF = "mfc_default_keys.dic"
|
||||||
|
DEFAULT_KEYS = set()
|
||||||
if os.path.basename(os.path.dirname(os.path.dirname(sys.argv[0]))) == 'client':
|
if os.path.basename(os.path.dirname(os.path.dirname(sys.argv[0]))) == 'client':
|
||||||
# dev setup
|
# dev setup
|
||||||
TOOLS_PATH = os.path.normpath(os.path.join(f"{os.path.dirname(sys.argv[0])}",
|
TOOLS_PATH = os.path.normpath(os.path.join(f"{os.path.dirname(sys.argv[0])}",
|
||||||
"..", "..", "tools", "mfc", "card_only"))
|
"..", "..", "tools", "mfc", "card_only"))
|
||||||
|
DICT_DEF_PATH = os.path.normpath(os.path.join(f"{os.path.dirname(sys.argv[0])}",
|
||||||
|
"..", "dictionaries", DICT_DEF))
|
||||||
else:
|
else:
|
||||||
# assuming installed
|
# assuming installed
|
||||||
TOOLS_PATH = os.path.normpath(os.path.join(f"{os.path.dirname(sys.argv[0])}",
|
TOOLS_PATH = os.path.normpath(os.path.join(f"{os.path.dirname(sys.argv[0])}",
|
||||||
"..", "tools"))
|
"..", "tools"))
|
||||||
|
DICT_DEF_PATH = os.path.normpath(os.path.join(f"{os.path.dirname(sys.argv[0])}",
|
||||||
|
"dictionaries", DICT_DEF))
|
||||||
|
|
||||||
tools = {
|
tools = {
|
||||||
"staticnested_1nt": os.path.join(f"{TOOLS_PATH}", "staticnested_1nt"),
|
"staticnested_1nt": os.path.join(f"{TOOLS_PATH}", "staticnested_1nt"),
|
||||||
|
@ -54,8 +60,8 @@ for tool, bin in tools.items():
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='A script combining staticnested* tools '
|
parser = argparse.ArgumentParser(description='A script combining staticnested* tools '
|
||||||
'to recover all keys from a FM11RF08S card.')
|
'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('-x', '--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('-y', '--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')
|
parser.add_argument('-d', '--debug', action='store_true', help='Enable debug mode')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
@ -66,7 +72,6 @@ p.console("hf 14a read")
|
||||||
uid = None
|
uid = None
|
||||||
|
|
||||||
for line in p.grabbed_output.split('\n'):
|
for line in p.grabbed_output.split('\n'):
|
||||||
print(line)
|
|
||||||
if "UID:" in line:
|
if "UID:" in line:
|
||||||
uid = int(line[10:].replace(' ', '')[-8:], 16)
|
uid = int(line[10:].replace(' ', '')[-8:], 16)
|
||||||
|
|
||||||
|
@ -82,7 +87,7 @@ def print_key(sec, key_type, key):
|
||||||
|
|
||||||
|
|
||||||
found_keys = [["", ""] for _ in range(NUM_SECTORS)]
|
found_keys = [["", ""] for _ in range(NUM_SECTORS)]
|
||||||
if not args.no_init_check:
|
if args.init_check:
|
||||||
print("Checking default keys...")
|
print("Checking default keys...")
|
||||||
p.console("hf mf fchk")
|
p.console("hf mf fchk")
|
||||||
for line in p.grabbed_output.split('\n'):
|
for line in p.grabbed_output.split('\n'):
|
||||||
|
@ -123,21 +128,34 @@ for line in p.grabbed_output.split('\n'):
|
||||||
nt_enc[sec][key_type] = data
|
nt_enc[sec][key_type] = data
|
||||||
data = line[128:136]
|
data = line[128:136]
|
||||||
par_err[sec][key_type] = data
|
par_err[sec][key_type] = data
|
||||||
|
|
||||||
|
# Check if we got all nonces, else abort.
|
||||||
|
# TODO: retry instead...
|
||||||
for sec in range(NUM_SECTORS):
|
for sec in range(NUM_SECTORS):
|
||||||
if found_keys[sec][0] == "" or found_keys[sec][1] == "":
|
if found_keys[sec][0] == "" or found_keys[sec][1] == "":
|
||||||
for key_type in [0, 1]:
|
for key_type in [0, 1]:
|
||||||
if (nt[sec][key_type] == "" or
|
if (nt[sec][key_type] == "" or
|
||||||
nt_enc[sec][key_type] == "" or
|
nt_enc[sec][key_type] == "" or
|
||||||
par_err[sec][key_type] == ""):
|
par_err[sec][key_type] == ""):
|
||||||
print("Error, could not collect nonces, abort")
|
print("Error, could not collect all nonces, try again.")
|
||||||
exit()
|
exit()
|
||||||
|
|
||||||
|
if os.path.isfile(DICT_DEF_PATH):
|
||||||
|
print(f"Loading {DICT_DEF}")
|
||||||
|
with open(DICT_DEF_PATH, 'r') as file:
|
||||||
|
for line in file:
|
||||||
|
if line[0] != '#' and len(line) >= 12:
|
||||||
|
DEFAULT_KEYS.add(line[:12])
|
||||||
|
else:
|
||||||
|
print(f"Warning, {DICT_DEF} not found.")
|
||||||
|
|
||||||
print("Running staticnested_1nt & 2x1nt when doable...")
|
print("Running staticnested_1nt & 2x1nt when doable...")
|
||||||
keys = [[set(), set()] for _ in range(NUM_SECTORS)]
|
keys = [[set(), set()] for _ in range(NUM_SECTORS)]
|
||||||
all_keys = set()
|
all_keys = set()
|
||||||
duplicates = set()
|
duplicates = set()
|
||||||
# Availability of filtered dicts
|
# Availability of filtered dicts
|
||||||
filtered_dicts = [[False, False] for _ in range(NUM_SECTORS)]
|
filtered_dicts = [[False, False] for _ in range(NUM_SECTORS)]
|
||||||
|
found_default = [[False, False] for _ in range(NUM_SECTORS)]
|
||||||
for sec in range(NUM_SECTORS):
|
for sec in range(NUM_SECTORS):
|
||||||
if found_keys[sec][0] != "" and found_keys[sec][1] != "":
|
if found_keys[sec][0] != "" and found_keys[sec][1] != "":
|
||||||
continue
|
continue
|
||||||
|
@ -155,14 +173,23 @@ for sec in range(NUM_SECTORS):
|
||||||
subprocess.run(cmd, capture_output=True)
|
subprocess.run(cmd, capture_output=True)
|
||||||
filtered_dicts[sec][key_type] = True
|
filtered_dicts[sec][key_type] = True
|
||||||
for key_type in [0, 1]:
|
for key_type in [0, 1]:
|
||||||
|
keys_set = set()
|
||||||
with (open(f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type]}_filtered.dic")) as f:
|
with (open(f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type]}_filtered.dic")) as f:
|
||||||
keys_set = set()
|
|
||||||
while line := f.readline().rstrip():
|
while line := f.readline().rstrip():
|
||||||
if line not in keys_set:
|
keys_set.add(line)
|
||||||
keys_set.add(line)
|
|
||||||
keys[sec][key_type] = keys_set
|
keys[sec][key_type] = keys_set
|
||||||
duplicates.update(all_keys.intersection(keys_set))
|
duplicates.update(all_keys.intersection(keys_set))
|
||||||
all_keys.update(keys_set)
|
all_keys.update(keys_set)
|
||||||
|
# Prioritize default keys
|
||||||
|
keys_def_set = DEFAULT_KEYS.intersection(keys_set)
|
||||||
|
keys_set.difference_update(DEFAULT_KEYS)
|
||||||
|
if len(keys_def_set) > 0:
|
||||||
|
found_default[sec][key_type] = True
|
||||||
|
with (open(f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type]}_filtered.dic", "w")) as f:
|
||||||
|
for k in keys_def_set:
|
||||||
|
f.write(f"{k}\n")
|
||||||
|
for k in keys_set:
|
||||||
|
f.write(f"{k}\n")
|
||||||
else: # one key not found or both identical
|
else: # one key not found or both identical
|
||||||
if found_keys[sec][0] == "":
|
if found_keys[sec][0] == "":
|
||||||
key_type = 0
|
key_type = 0
|
||||||
|
@ -173,14 +200,23 @@ for sec in range(NUM_SECTORS):
|
||||||
if args.debug:
|
if args.debug:
|
||||||
print(' '.join(cmd))
|
print(' '.join(cmd))
|
||||||
subprocess.run(cmd, capture_output=True)
|
subprocess.run(cmd, capture_output=True)
|
||||||
|
keys_set = set()
|
||||||
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:
|
||||||
keys_set = set()
|
|
||||||
while line := f.readline().rstrip():
|
while line := f.readline().rstrip():
|
||||||
if line not in keys_set:
|
keys_set.add(line)
|
||||||
keys_set.add(line)
|
|
||||||
keys[sec][key_type] = keys_set
|
keys[sec][key_type] = keys_set
|
||||||
duplicates.update(all_keys.intersection(keys_set))
|
duplicates.update(all_keys.intersection(keys_set))
|
||||||
all_keys.update(keys_set)
|
all_keys.update(keys_set)
|
||||||
|
# Prioritize default keys
|
||||||
|
keys_def_set = DEFAULT_KEYS.intersection(keys_set)
|
||||||
|
keys_set.difference_update(DEFAULT_KEYS)
|
||||||
|
if len(keys_def_set) > 0:
|
||||||
|
found_default[sec][key_type] = True
|
||||||
|
with (open(f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type]}.dic", "w")) as f:
|
||||||
|
for k in keys_def_set:
|
||||||
|
f.write(f"{k}\n")
|
||||||
|
for k in keys_set:
|
||||||
|
f.write(f"{k}\n")
|
||||||
|
|
||||||
print("Looking for common keys across sectors...")
|
print("Looking for common keys across sectors...")
|
||||||
keys_filtered = [[set(), set()] for _ in range(NUM_SECTORS)]
|
keys_filtered = [[set(), set()] for _ in range(NUM_SECTORS)]
|
||||||
|
@ -207,6 +243,66 @@ for sec in range(NUM_SECTORS):
|
||||||
f.write(f"{k}\n")
|
f.write(f"{k}\n")
|
||||||
duplicates_dicts[sec][key_type] = True
|
duplicates_dicts[sec][key_type] = True
|
||||||
|
|
||||||
|
print("Computing needed time for attack...")
|
||||||
|
candidates = [[0, 0] for _ in range(NUM_SECTORS)]
|
||||||
|
for sec in range(NUM_SECTORS):
|
||||||
|
for key_type in [0, 1]:
|
||||||
|
if found_keys[sec][0] == "" and found_keys[sec][1] == "" and duplicates_dicts[sec][key_type]:
|
||||||
|
kt = ['a', 'b'][key_type]
|
||||||
|
dic = f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type]}_duplicates.dic"
|
||||||
|
with open(dic, 'r') as file:
|
||||||
|
count = sum(1 for _ in file)
|
||||||
|
# print(f"dic {dic} size {count}")
|
||||||
|
candidates[sec][key_type] = count
|
||||||
|
if nt[sec][0] == nt[sec][1]:
|
||||||
|
candidates[sec][key_type ^ 1] = 1
|
||||||
|
for key_type in [0, 1]:
|
||||||
|
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:
|
||||||
|
if found_default[sec][key_type]:
|
||||||
|
# We assume the default key is correct
|
||||||
|
candidates[sec][key_type] = 1
|
||||||
|
else:
|
||||||
|
kt = ['a', 'b'][key_type]
|
||||||
|
dic = f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type]}_filtered.dic"
|
||||||
|
with open(dic, 'r') as file:
|
||||||
|
count = sum(1 for _ in file)
|
||||||
|
# print(f"dic {dic} size {count}")
|
||||||
|
candidates[sec][key_type] = count
|
||||||
|
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:
|
||||||
|
if found_default[sec][0]:
|
||||||
|
# We assume the default key is correct
|
||||||
|
candidates[sec][0] = 1
|
||||||
|
candidates[sec][1] = 1
|
||||||
|
else:
|
||||||
|
key_type = 0
|
||||||
|
kt = ['a', 'b'][key_type]
|
||||||
|
dic = f"keys_{uid:08x}_{sec:02}_{nt[sec][key_type]}.dic"
|
||||||
|
with open(dic, 'r') as file:
|
||||||
|
count = sum(1 for _ in file)
|
||||||
|
# print(f"dic {dic} size {count}")
|
||||||
|
candidates[sec][0] = count
|
||||||
|
candidates[sec][1] = 1
|
||||||
|
|
||||||
|
if args.debug:
|
||||||
|
for sec in range(NUM_SECTORS):
|
||||||
|
print(f" {sec:03} | {sec*4+3:03} | {candidates[sec][0]:6} | {candidates[sec][1]:6} ")
|
||||||
|
total_candidates = sum(candidates[sec][0] + candidates[sec][1] for sec in range(NUM_SECTORS))
|
||||||
|
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
minutes1 = int(elapsed_time // 60)
|
||||||
|
seconds1 = int(elapsed_time % 60)
|
||||||
|
print("----Step 1: " + color(f"{minutes1:2}", fg="yellow") + " minutes " +
|
||||||
|
color(f"{seconds1:2}", fg="yellow") + " seconds -----------")
|
||||||
|
|
||||||
|
# fchk: 147 keys/s. Correct key found after 50% of candidates on average
|
||||||
|
FCHK_KEYS_S = 147
|
||||||
|
foreseen_time = (total_candidates / 2 / FCHK_KEYS_S) + 5
|
||||||
|
minutes = int(foreseen_time // 60)
|
||||||
|
seconds = int(foreseen_time % 60)
|
||||||
|
print("Still about " + color(f"{minutes:2}", fg="yellow") + " minutes " +
|
||||||
|
color(f"{seconds:2}", fg="yellow") + " seconds to run...")
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
abort = False
|
abort = False
|
||||||
print("Brute-forcing keys... Press any key to interrupt")
|
print("Brute-forcing keys... Press any key to interrupt")
|
||||||
for sec in range(NUM_SECTORS):
|
for sec in range(NUM_SECTORS):
|
||||||
|
@ -299,7 +395,6 @@ for sec in range(NUM_SECTORS):
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True).stdout
|
result = subprocess.run(cmd, capture_output=True, text=True).stdout
|
||||||
keys = set()
|
keys = set()
|
||||||
for line in result.split('\n'):
|
for line in result.split('\n'):
|
||||||
# print(line)
|
|
||||||
if "MATCH:" in line:
|
if "MATCH:" in line:
|
||||||
keys.add(line[12:])
|
keys.add(line[12:])
|
||||||
if len(keys) > 1:
|
if len(keys) > 1:
|
||||||
|
@ -324,9 +419,19 @@ for sec in range(NUM_SECTORS):
|
||||||
|
|
||||||
if abort:
|
if abort:
|
||||||
print("Brute-forcing phase aborted via keyboard!")
|
print("Brute-forcing phase aborted via keyboard!")
|
||||||
args.no_final_check = True
|
args.final_check = False
|
||||||
|
|
||||||
if args.no_final_check:
|
if args.final_check:
|
||||||
|
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)
|
||||||
|
p.console(cmd, passthru = True)
|
||||||
|
else:
|
||||||
plus = "[" + color("+", fg="green") + "] "
|
plus = "[" + color("+", fg="green") + "] "
|
||||||
print()
|
print()
|
||||||
print(plus + color("found keys:", fg="green"))
|
print(plus + color("found keys:", fg="green"))
|
||||||
|
@ -361,19 +466,11 @@ if args.no_final_check:
|
||||||
if unknown:
|
if unknown:
|
||||||
print("[" + color("=", fg="yellow") + "] --[ " + color("FFFFFFFFFFFF", fg="yellow") +
|
print("[" + color("=", fg="yellow") + "] --[ " + color("FFFFFFFFFFFF", fg="yellow") +
|
||||||
" ]-- has been inserted for unknown keys")
|
" ]-- 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)
|
|
||||||
p.console(cmd, passthru = True)
|
|
||||||
|
|
||||||
elapsed_time = time.time() - start_time
|
elapsed_time = time.time() - start_time
|
||||||
minutes = int(elapsed_time // 60)
|
minutes2 = int(elapsed_time // 60)
|
||||||
seconds = int(elapsed_time % 60)
|
seconds2 = int(elapsed_time % 60)
|
||||||
print("--- " + color(minutes, fg="yellow") + " minutes " +
|
print("----Step 2: " + color(f"{minutes2:2}", fg="yellow") + " minutes " +
|
||||||
color(seconds, fg="yellow") + " seconds ---")
|
color(f"{seconds2:2}", fg="yellow") + " seconds -----------")
|
||||||
|
print("---- TOTAL: " + color(f"{minutes1+minutes2:2}", fg="yellow") + " minutes " +
|
||||||
|
color(f"{seconds1+seconds2:2}", fg="yellow") + " seconds -----------")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue