diff --git a/.gitignore b/.gitignore index f8ad61c..0fee154 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -*.dic \ No newline at end of file +*.dic +*.pyc \ No newline at end of file diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..bbdeb2a --- /dev/null +++ b/lib/__init__.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- + +# Common functions used throughout the project +# Created for https://github.com/Bambu-Research-Group/RFID-Tag-Guide + +import subprocess +import os +import sys +import struct +from pathlib import Path +from datetime import datetime + +if not sys.version_info >= (3, 6): + raise Exception("Python 3.6 or higher is required!") + +# Byte conversions +def bytes_to_string(data): + return data.decode('ascii').replace('\x00', ' ').strip() + +def bytes_to_hex(data, chunkify = False): + output = data.hex().upper() + return " ".join((output[0+i:2+i] for i in range(0, len(output), 2))) if chunkify else output + +def bytes_to_int(data): + return int.from_bytes(data, 'little') + +def bytes_to_float(data): + return struct.unpack('= (3, 6): - print("Python 3.6 or higher is required!") - exit(-1) +from lib import bytes_to_string, bytes_to_hex, bytes_to_int, bytes_to_float, bytes_to_date COMPARISON_BLOCKS = [1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14] IMPORTANT_BLOCKS = [0] + COMPARISON_BLOCKS @@ -21,33 +18,6 @@ BLOCKS_PER_TAG = [64, 72] # 64 = 1KB, 72 = Output from Proxmark fm11rf08 script TOTAL_BYTES = [blocks * BYTES_PER_BLOCK for blocks in BLOCKS_PER_TAG] -# Byte conversions -def bytes_to_string(data): - return data.decode('ascii').replace('\x00', ' ').strip() - -def bytes_to_hex(data, chunkify = False): - output = data.hex().upper() - return " ".join((output[0+i:2+i] for i in range(0, len(output), 2))) if chunkify else output - -def bytes_to_int(data): - return int.from_bytes(data, 'little') - -def bytes_to_float(data): - return struct.unpack('= (3, 6): - print("Python 3.6 or higher is required!") - exit(-1) +from lib import strip_color_codes, get_proxmark3_location, run_command, testCommands #Global variables #Default name of the dictionary file we create @@ -22,6 +25,8 @@ def setup(): global pm3Location,dictionaryFilepath pm3Location = get_proxmark3_location() + if not pm3Location: + exit(-1) #Create a dictionary file to store keys that we discover print(f"Creating dictionary file '{dictionaryFilename}'") @@ -85,15 +90,8 @@ def discoverKeys(traceFilepath): #Run PM3 with the trace # -o means run without connecting to PM3 hardware # -c specifies commands within proxmark 3 software - cmd_list = [pm3Location / pm3Command,"-o","-c", f"trace load -f {traceFilepath}; trace list -1 -t mf -f {dictionaryFilepath}; exit"] print(f"Viewing tracelog with {len(keyList)} discovered keys") - print(f"pm3 {' '.join(cmd_list[1:])}") - result = subprocess.run(cmd_list, shell=os.name == 'nt',stdout=subprocess.PIPE, stderr=subprocess.PIPE) - output = result.stdout - - - - #print(output) + output = run_command([pm3Location / pm3Command,"-o","-c", f"trace load -f {traceFilepath}; trace list -1 -t mf -f {dictionaryFilepath}"]) #Loop over output, line by line to try to find a key #3 things we're looking for: @@ -101,8 +99,6 @@ def discoverKeys(traceFilepath): # - "probable key" a key that should work # - "mf_nonce_brute" a key we need to calculate for line in output.splitlines(): - line = line.decode("utf-8") #Convert from byte array to string - if " key " in line or " key: " in line: #There's a lot of whitespace in this line @@ -191,17 +187,11 @@ def discoverKeys(traceFilepath): #Run the mf_nonce_brute program with the provided arguments to decode a key #Returns a key on success, "" otherwise def bruteForce(args): - cmd_list = [pm3Location / mfNonceBruteCommand] + args - print(" Running bruteforce command:") - print(" ",end = "") - print(f"tools/mf_nonce_brute {' '.join(cmd_list[1:])}") - result = subprocess.run(cmd_list, shell=os.name == 'nt', stdout=subprocess.PIPE, stderr=subprocess.PIPE) + print("Running bruteforce command:") + output = run_command([pm3Location / mfNonceBruteCommand] + args) #Parse out the key - output = result.stdout for line in output.splitlines(): - line = line.decode("utf-8") #Convert from byte array to string - #Search for the line that says valid key. Example: "Valid Key found [ 63654db94d97 ] - matches candidate" #In rare cases, multiple possible keys will be found. The "matches candidate" tag should indicate the right one if not ("Valid Key" in line and "matches candidate" in line): @@ -217,78 +207,5 @@ def bruteForce(args): return "" #Not found -#Some keys come surrounded in terminal color codes such as "63654db94d97" -#We need to remove these -def strip_color_codes(input_string): - # Define the regular expression pattern to match ANSI escape sequences - ansi_escape = re.compile(r'\x1B[@-_][0-?]*[ -/]*[@-~]') - # Use the sub method to replace the escape sequences with an empty string - return ansi_escape.sub('', input_string) - -def get_proxmark3_location(): - # Find a "pm3" command that works from a list of OS-specific possibilities - print("Checking program: pm3") - - # Check PROXMARK3_DIR environment variable - if os.environ.get('PROXMARK3_DIR'): - if run_command(os.environ['PROXMARK3_DIR'] + "/bin/pm3", "--help"): - return os.environ['PROXMARK3_DIR'] - else: - print("Warning: PROXMARK3_DIR environment variable points to the wrong folder, ignoring") - - # Get Homebrew installation - brew_install = run_command(["brew", "--prefix", "proxmark3"]) - if brew_install: - print("Found installation via Homebrew!") - return Path(brew_install) - - # Get global installation - which_pm3 = run_command(["which", "pm3"]) - if which_pm3: - which_pm3 = Path(which_pm3) - pm3_location = which_pm3.parent.parent - print(f"Found global installation ({pm3_location})!") - return pm3_location - - # At this point, we've tried all the paths to find it - print("Failed to find working 'pm3' command. You can set the Proxmark3 directory via the 'PROXMARK3_DIR' environment variable.") - exit(-1) # Halt program - -def run_command(command): - try: - # On Windows, use the shell=True argument to run the command - result = subprocess.run(command, shell=os.name == 'nt', stdout=subprocess.PIPE, stderr=subprocess.PIPE) - # Check the return code to determine if the command was successful - if result.returncode == 0 or result.returncode == 1: - return result.stdout.decode("utf-8").strip() - return None - except Exception as e: - return None - -# Test a list of commands to see which one works -# This lets us provide a list of OS-specific commands, test them -# and figure out which one works on this specific computer -# - Args: -# - commandList: An array of OS-specific commands (sometimes including absolute installation path) -# - arguments: Optional arguments to be appended to the command. Useful for programs that don't exit on their own -# This returns the command (string) of the first working command we encounter -# -def testCommands(directories, command, arguments = ""): - - for directory in directories: - - if directory is None: - continue - - #OPTIONAL: add arguments such as "--help" to help identify programs that don't exit on their own - cmd_list = [directory+"/"+command, arguments] - - #Test if this program works - print(" Trying:", directory, end=" ... ") - if run_command(cmd_list): - return Path(directory) - - return None #We didn't find any program that worked - if __name__ == "__main__": main() #Run main program diff --git a/writeTag.py b/writeTag.py new file mode 100644 index 0000000..838411d --- /dev/null +++ b/writeTag.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- + +# Python script to extract the keys from a Bambu Lab filament RFID tag, using a Proxmark3 +# Created for https://github.com/Bambu-Research-Group/RFID-Tag-Guide +# Written by Vinyl Da.i'gyu-Kazotetsu (www.queengoob.org), 2025 + +import subprocess +import os +import re +import sys +from pathlib import Path + +from lib import strip_color_codes, get_proxmark3_location, run_command, testCommands + +#Global variables +pm3Location = None #Calculated. The location of Proxmark3 +pm3Command = "bin/pm3" # The command that works to start proxmark3 + +def setup(): + global pm3Location + + pm3Location = get_proxmark3_location() + if not pm3Location: + exit(-1) + +def main(): + print("--------------------------------------------------------") + print("RFID Tag Writer v0.1.0 - Bambu Research Group 2025") + print("--------------------------------------------------------") + print("This will write a tag dump to a physical tag using your") + print("Proxmark3 device, allowing RFID tags for non-Bambu spools.") + print("--------------------------------------------------------") + + # Run setup + setup() + + if len(sys.argv) > 1: + # If the user included an argument, assume it's the path to the tracefile + tagdump = os.path.abspath(sys.argv[1]) + else: + #Get the tracename/filepath from user + tagdump = input("Enter the path to the tag dump you wish to write: ").replace("\\ ", " ") + + print() + print("Start by placing your Proxmark3 device onto the tag you") + print("wish to write to, then press Enter. I'll wait for you.") + + input() + + tagtype = getTagType() + + print() + print("=========== WARNING! == WARNING! == WARNING! ===========") + print("This script will write the contents of a dump to your") + print("RFID tag, and then PERMANENTLY WRITE LOCK the tag.") + print("") + print("This process is IRREVERSIBLE, proceed at your own risk.") + print("========================================================") + print() + + confirm = input("Are you SURE you wish to continue (y/N)? ") + if confirm.lower() not in ["y", "yes"]: + print("Confirmation not obtained, exiting") + exit(0) + + print("Writing tag data now...") + writeTag(tagdump, tagtype) + + print() + print("Writing complete! Your tag should now register on the AMS.") + print() + + +def getTagType(): + print(f"Checking tag type...") + output = run_command([pm3Location / pm3Command, "-d", "1", "-c", f"hf mf info"]) + + output = """[=] Session log /Users/queengooborg/.proxmark3/logs/log_20250707075834.txt +[#] Searching implicit relative paths +[+] loaded `/Users/queengooborg/.proxmark3/preferences.json` +[+] execute command from commandline: hf mf info; exit + +[+] Using UART port /dev/tty.usbmodem2101 +[+] Communicating with PM3 over USB-CDC +[usb|script] pm3 --> hf mf info + +[=] --- ISO14443-a Information --------------------- +[+] UID: 1E 3F 30 72 +[+] ATQA: 00 04 +[+] SAK: 08 [1] +[#] failed reading block +[#] failed reading block + +[=] --- Keys Information +[+] loaded 2 user keys +[+] loaded 61 hardcoded keys +[+] Backdoor key..... A396EFA4E24F +[+] Block 0.... 1E3F307263080400047BA384056B8E90 | .{...k.. + +[=] --- Fingerprint +[+] Fudan FM11RF08S + +[=] --- Magic Tag Information +[+] Magic capabilities... Gen 2 / CUID +[+] Magic capabilities... Gen 4 GDM / USCUID ( Gen4 Magic Wakeup ) +[+] Magic capabilities... Write Once / FUID + +[=] --- PRNG Information +[+] Prng....... weak +[+] Static enc nonce... yes +[?] Hint: Try `script run fm11rf08s_recovery.py` + +[usb|script] pm3 --> exit""" + + if 'iso14443a card select failed' in output: + raise RuntimeError("Tag not found or is wrong type") + + cap_re = r"(?:\[\+\] Magic capabilities\.\.\. ([()/\w\d ]+)\n)" + + match = re.search(rf"\[=\] --- Magic Tag Information\n(\[=\] \n|{cap_re}+)", output) + if not match: + raise RuntimeError("Could not obtain magic tag information") + + if "[=] " in match.group(1): + raise RuntimeError("Tag is not a compatible type (must be Gen 4 FUID or UFUID), or has already been locked") + + capabilities = re.findall(cap_re, match.group(1)) + + if "Gen 4 GDM / USCUID ( Gen4 Magic Wakeup )" in capabilities: + return "Gen 4 FUID" + if "Gen 4 GDM / USCUID ( ZUID Gen1 Magic Wakeup )" in capabilities: + return "Gen 4 UFUID" + + raise RuntimeError("Tag is not a compatible type (must be Gen 4 FUID or UFUID)") + +def writeTag(tagdump, tagtype): + if tagtype == "Gen 4 FUID": + # Load tag dump onto RFID tag + output = run_command([pm3Location / pm3Command, "-c", f"hf mf restore --force -f {tagdump.replace(" ", "\\ ")}"], pipe=False) + return + + if tagtype == "Gen 4 UFUID": + # Load tag dump onto RFID tag, then immediately seal + output = run_command([pm3Location / pm3Command, "-c", f"hf mf cload -f {tagdump.replace(" ", "\\ ")}; hf 14a raw -a -k -b 7 40; hf 14a raw -k 43; hf 14a raw -k -c e100; hf 14a raw -c 85000000000000000000000000000008"], pipe=False) + + +if __name__ == "__main__": + main() #Run main program