Add pm3_resources helpers for Python scripts to find tools & dicts

This commit is contained in:
Philippe Teuwen 2025-02-20 15:06:46 +01:00
parent ffbf033937
commit 4e5d68851b
3 changed files with 117 additions and 41 deletions

@ -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

@ -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

@ -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!")