From 4e5d68851befee5c0806bc99cbdb2dd789df459c Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Thu, 20 Feb 2025 15:06:46 +0100 Subject: [PATCH] Add pm3_resources helpers for Python scripts to find tools & dicts --- client/Makefile | 5 ++ client/pyscripts/fm11rf08s_recovery.py | 61 ++++++----------- client/pyscripts/pm3_resources.py | 92 ++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 41 deletions(-) create mode 100644 client/pyscripts/pm3_resources.py diff --git a/client/Makefile b/client/Makefile index 46fa40439..cf4b65b5f 100644 --- a/client/Makefile +++ b/client/Makefile @@ -887,7 +887,12 @@ ifneq (,$(INSTALLBIN)) endif ifneq (,$(INSTALLSHARE)) $(Q)$(INSTALLSUDO) $(MKDIR) $(DESTDIR)$(PREFIX)$(PATHSEP)$(INSTALLSHARERELPATH) + # hack ahead: inject installation path into pm3_resources.py + $(Q)sed -i 's|^TOOLS_PATH \?= \?None|TOOLS_PATH="$(DESTDIR)$(PREFIX)$(PATHSEP)$(INSTALLTOOLSRELPATH)"|' pyscripts/pm3_resources.py + $(Q)sed -i 's|^DICTS_PATH \?= \?None|DICTS_PATH="$(DESTDIR)$(PREFIX)$(PATHSEP)$(INSTALLSHARERELPATH)/dictionaries"|' pyscripts/pm3_resources.py $(Q)$(INSTALLSUDO) $(CP) $(INSTALLSHARE) $(DESTDIR)$(PREFIX)$(PATHSEP)$(INSTALLSHARERELPATH) + $(Q)sed -i 's|^TOOLS_PATH \?=.*|TOOLS_PATH = None|' pyscripts/pm3_resources.py + $(Q)sed -i 's|^DICTS_PATH \?=.*|DICTS_PATH = None|' pyscripts/pm3_resources.py endif @true diff --git a/client/pyscripts/fm11rf08s_recovery.py b/client/pyscripts/fm11rf08s_recovery.py index b94381117..19407ec12 100755 --- a/client/pyscripts/fm11rf08s_recovery.py +++ b/client/pyscripts/fm11rf08s_recovery.py @@ -20,6 +20,8 @@ import subprocess import argparse import json import pm3 +from pm3_resources import find_tool, find_dict + # optional color support try: # pip install ansicolors @@ -42,38 +44,11 @@ BACKDOOR_KEYS = ["A396EFA4E24F", "A31667A8CEC1", "518B3354E760"] NUM_SECTORS = 16 NUM_EXTRA_SECTORS = 1 -DICT_DEF = "mfc_default_keys.dic" DEFAULT_KEYS = set() -if __name__ == '__main__': - DIR_PATH = os.path.dirname(os.path.abspath(sys.argv[0])) -else: - DIR_PATH = os.path.dirname(os.path.abspath(__file__)) -if os.path.basename(os.path.dirname(DIR_PATH)) == 'client': - # dev setup - TOOLS_PATH = os.path.normpath(os.path.join(DIR_PATH, - "..", "..", "tools", "mfc", "card_only")) - DICT_DEF_PATH = os.path.normpath(os.path.join(DIR_PATH, - "..", "dictionaries", DICT_DEF)) -else: - # assuming installed - TOOLS_PATH = os.path.normpath(os.path.join(DIR_PATH, - "..", "tools")) - DICT_DEF_PATH = os.path.normpath(os.path.join(DIR_PATH, - "dictionaries", DICT_DEF)) - -tools = { - "staticnested_1nt": os.path.join(f"{TOOLS_PATH}", "staticnested_1nt"), - "staticnested_2x1nt": os.path.join(f"{TOOLS_PATH}", "staticnested_2x1nt_rf08s"), - "staticnested_2x1nt1key": os.path.join(f"{TOOLS_PATH}", "staticnested_2x1nt_rf08s_1key"), -} -for tool, bin in tools.items(): - if not os.path.isfile(bin): - if os.path.isfile(bin + ".exe"): - tools[tool] = bin + ".exe" - else: - print(f"Cannot find {bin}, abort!") - exit() +staticnested_1nt_path = find_tool("staticnested_1nt") +staticnested_2x1nt_path = find_tool("staticnested_2x1nt_rf08s") +staticnested_2x1nt1key_path = find_tool("staticnested_2x1nt_rf08s_1key") def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debug=False, supply_chain=False, quiet=True, keyset=False): @@ -193,14 +168,18 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu show("----Step 1: " + color(f"{minutes:2}", fg="yellow") + " minutes " + color(f"{seconds:2}", fg="yellow") + " seconds -----------") - if os.path.isfile(DICT_DEF_PATH): - show(f"Loading {DICT_DEF}") - with open(DICT_DEF_PATH, 'r', encoding='utf-8') as file: + dict_def = "mfc_default_keys.dic" + try: + dict_path = find_dict(dict_def) + with open(dict_path, 'r', encoding='utf-8') as file: for line in file: if line[0] != '#' and len(line) >= 12: DEFAULT_KEYS.add(line[:12]) - else: - show(f"Warning, {DICT_DEF} not found.") + show(f"Loaded {dict_def}") + except FileNotFoundError: + show(f"Warning, {dict_def} not found.") + except Exception as e: + raise Exception(f"Error loading {dict_def}: {e}") dict_dnwd = None def_nt = ["" for _ in range(NUM_SECTORS)] @@ -233,12 +212,12 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu continue if found_keys[sec][0] == "" and found_keys[sec][1] == "" and nt[sec][0] != nt[sec][1]: for key_type in [0, 1]: - cmd = [tools["staticnested_1nt"], f"{uid:08X}", f"{real_sec}", + cmd = [staticnested_1nt_path, f"{uid:08X}", f"{real_sec}", nt[sec][key_type], nt_enc[sec][key_type], par_err[sec][key_type]] if debug: print(' '.join(cmd)) subprocess.run(cmd, capture_output=True) - cmd = [tools["staticnested_2x1nt"], + cmd = [staticnested_2x1nt_path, f"keys_{uid:08x}_{real_sec:02}_{nt[sec][0]}.dic", f"keys_{uid:08x}_{real_sec:02}_{nt[sec][1]}.dic"] if debug: print(' '.join(cmd)) @@ -254,7 +233,7 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu all_keys.update(keys_set) if dict_dnwd is not None and sec < NUM_SECTORS: # Prioritize keys from supply-chain attack - cmd = [tools["staticnested_2x1nt1key"], def_nt[sec], "FFFFFFFFFFFF", + cmd = [staticnested_2x1nt1key_path, def_nt[sec], "FFFFFFFFFFFF", f"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}_filtered.dic"] if debug: print(' '.join(cmd)) @@ -285,7 +264,7 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu key_type = 0 else: key_type = 1 - cmd = [tools["staticnested_1nt"], f"{uid:08X}", f"{real_sec}", + cmd = [staticnested_1nt_path, f"{uid:08X}", f"{real_sec}", nt[sec][key_type], nt_enc[sec][key_type], par_err[sec][key_type]] if debug: print(' '.join(cmd)) @@ -299,7 +278,7 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu all_keys.update(keys_set) if dict_dnwd is not None and sec < NUM_SECTORS: # Prioritize keys from supply-chain attack - cmd = [tools["staticnested_2x1nt1key"], def_nt[sec], "FFFFFFFFFFFF", + cmd = [staticnested_2x1nt1key_path, def_nt[sec], "FFFFFFFFFFFF", f"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}.dic"] if debug: print(' '.join(cmd)) @@ -509,7 +488,7 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu dic = f"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type_target]}_filtered.dic" else: dic = f"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type_target]}.dic" - cmd = [tools["staticnested_2x1nt1key"], nt[sec][key_type_source], found_keys[sec][key_type_source], dic] + cmd = [staticnested_2x1nt1key_path, nt[sec][key_type_source], found_keys[sec][key_type_source], dic] if debug: print(' '.join(cmd)) result = subprocess.run(cmd, capture_output=True, text=True).stdout diff --git a/client/pyscripts/pm3_resources.py b/client/pyscripts/pm3_resources.py new file mode 100644 index 000000000..c50ede801 --- /dev/null +++ b/client/pyscripts/pm3_resources.py @@ -0,0 +1,92 @@ +""" +Helper library to locate resources for pm3 scripts. + +This module provides functionality to locate tools and dictionaries required +for pm3 scripts. It determines the paths based on the directory structure +and whether the script is being run in a development setup or an installed setup. + +Functions: + find_tool(tool_name): + Finds the specified tool in the tools directory. + Args: + tool_name (str): The name of the tool to find. + Returns: + str: The full path to the tool if found, otherwise None. + + find_dict(dict_name): + Find the specified dictionary in the dicts directory. + Args: + dict_name (str): The name of the dict to find. + Returns: + str: The full path to the dict if found, otherwise None. +""" + +import os + +# Install script can hardcode paths in the following variables +TOOLS_PATH = None +DICTS_PATH = None + +if __name__ == "__main__": + print("This is a library, don't use it as a script") + exit() + +DIR_PATH = os.path.dirname(os.path.abspath(__file__)) + +if TOOLS_PATH is None: + if os.path.basename(os.path.dirname(DIR_PATH)) == 'client': + # dev setup + DEV_TOOLS_PATH = os.path.normpath(os.path.join(DIR_PATH, "..", "..", "tools", "mfc", "card_only")) + if os.path.isdir(DEV_TOOLS_PATH): + TOOLS_PATH = DEV_TOOLS_PATH + +if TOOLS_PATH is None: + # assuming installed without having defined TOOLS_PATH + TEST_TOOLS_PATH = os.path.normpath(os.path.join(DIR_PATH, "..", "tools")) + if os.path.isdir(TEST_TOOLS_PATH): + TOOLS_PATH = TEST_TOOLS_PATH + + +if DICTS_PATH is None: + DEV_DICTS_PATH = os.path.normpath(os.path.join(DIR_PATH, "..", "dictionaries")) + if os.path.isdir(DEV_DICTS_PATH): + DICTS_PATH = DEV_DICTS_PATH + + +def find_tool(tool_name): + """Find the specified tool in the tools directory. + + Args: + tool_name (str): The name of the tool to find. + Returns: + str: The full path to the tool if found, otherwise None. + """ + if TOOLS_PATH is not None: + tool = os.path.join(TOOLS_PATH, tool_name) + if os.path.isfile(tool): + return tool + elif os.path.isfile(tool + ".exe"): + return tool + ".exe" + # if not found, search in the user PATH + for path in os.environ["PATH"].split(os.pathsep): + env_tool = os.path.join(path, tool_name) + if os.path.isfile(env_tool): + return env_tool + elif os.path.isfile(env_tool + ".exe"): + return env_tool + ".exe" + raise FileNotFoundError(f"Cannot find {tool_name}, abort!") + + +def find_dict(dict_name): + """Find the specified dictionary in the dicts directory. + + Args: + dict_name (str): The name of the dict to find. + Returns: + str: The full path to the dict if found, otherwise None. + """ + if DICTS_PATH is not None: + dictionary = os.path.join(DICTS_PATH, dict_name) + if os.path.isfile(dictionary): + return dictionary + raise FileNotFoundError(f"Cannot find {dict_name}, abort!")