Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
*.dic
*.dic
*.pyc
113 changes: 113 additions & 0 deletions lib/__init__.py
Original file line number Diff line number Diff line change
@@ -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('<f', data)[0]

def bytes_to_date(data):
string = bytes_to_string(data)
parts = string.split("_")
if len(parts) < 5:
return string # Not a date we can process, if it's a date at all
return datetime(
year=int(parts[0]),
month=int(parts[1]),
day=int(parts[2]),
hour=int(parts[3]),
minute=int(parts[4])
)

#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 run_command(command, pipe=True):
print(' '.join([str(c) for c in command]))
try:
# On Windows, use the shell=True argument to run the command
result = subprocess.run(command, shell=os.name == 'nt', capture_output=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() if pipe else ""
return None
except Exception as e:
return None

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.")
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
50 changes: 15 additions & 35 deletions parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@

# Python script to parse Bambu Lab RFID tag data
# Created for https://github.com/Bambu-Research-Group/RFID-Tag-Guide
# Written by Vinyl Da.i'gyu-Kazotetsu (www.queengoob.org), 2024
# Written by Vinyl Da.i'gyu-Kazotetsu (www.queengoob.org), 2024-2025

import sys
import struct
import re
from datetime import datetime
import json
from pathlib import Path

if not sys.version_info >= (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
Expand All @@ -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('<f', data)[0]

def bytes_to_date(data):
string = bytes_to_string(data)
parts = string.split("_")
if len(parts) < 5:
return string # Not a date we can process, if it's a date at all
return datetime(
year=int(parts[0]),
month=int(parts[1]),
day=int(parts[2]),
hour=int(parts[3]),
minute=int(parts[4])
)

# Flipper Helper
def strip_flipper_data(string):
# Remove comments
Expand Down Expand Up @@ -130,10 +100,20 @@ def extend(self, other):

class Tag():
def __init__(self, filename, data):
# Check to make sure the data is 1KB or a known alternative
# Proxmark3 JSON dump
try:
json_data = json.loads(data)
if json_data.get("Created") == "proxmark3":
data = b"".join([bytes.fromhex(json_data["blocks"][key].replace("??", "00")) for key in json_data["blocks"]])
except ValueError:
# We know that the data isn't JSON now
pass

# Flipper NFC dump
if data.startswith(b"Filetype: Flipper NFC"):
# Flipper NFC dump
data = strip_flipper_data(data)

# Check to make sure the data is 1KB or a known alternative
if len(data) not in TOTAL_BYTES:
raise TagLengthMismatchError(len(data))

Expand Down
105 changes: 11 additions & 94 deletions traceKeyExtractor.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# -*- 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

import subprocess
import os
import re
import sys
from pathlib import Path

if not sys.version_info >= (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
Expand All @@ -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}'")
Expand Down Expand Up @@ -85,24 +90,15 @@ 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:
# - "key" a known key
# - "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
Expand Down Expand Up @@ -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):
Expand All @@ -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
Loading