python script: wip, less globals, clearer dataflow

This commit is contained in:
Philippe Teuwen 2024-11-07 17:01:35 +01:00
commit 2293874402

View file

@ -56,104 +56,111 @@ except ModuleNotFoundError:
# Print and Log # Print and Log
# >> "logfile" # >> "logfile"
# ============================================================================== # ==============================================================================
def startlog(uid, append=False): def initlog():
global logbuffer
global logfile global logfile
logbuffer = ''
logfile = None
logfile = f"{dpath}hf-mf-{uid:08X}-log.txt"
def startlog(uid, dpath, append=False):
global logfile
global logbuffer
logfile = f"{dpath}hf-mf-{uid.hex().upper()}-log.txt"
if append is False: if append is False:
with open(logfile, 'w'): with open(logfile, 'w'):
pass pass
if logbuffer != '':
with open(logfile, 'a') as f:
f.write(logbuffer)
logbuffer = ''
# +========================================================= def lprint(s='', end='\n', flush=False, prompt="[=]", log=True):
def lprint(s, end='\n', flush=False):
s = f"{prompt} " + f"\n{prompt} ".join(s.split('\n'))
print(s, end=end, flush=flush) print(s, end=end, flush=flush)
if logfile is not None: if log is True:
with open(logfile, 'a') as f: global logbuffer
f.write(s + end) if logfile is not None:
with open(logfile, 'a') as f:
f.write(s + end)
else:
# buffering
logbuffer += s + end
# ++============================================================================ # ++============================================================================
# == MAIN == # == MAIN ==
# >> "prompt"
# >> p. [console handle] # >> p. [console handle]
# >> "keyfile"
# ============================================================================== # ==============================================================================
def main(): def main():
global prompt
global p global p
prompt = "[=]"
p = pm3.pm3() # console interface p = pm3.pm3() # console interface
initlog()
getPrefs()
if not checkVer(): if not checkVer():
return return
parseCli() dpath = getPrefs()
args = parseCli()
print(f"{prompt} Fudan FM11RF08[S] full card recovery") # No logfile name yet
lprint("Fudan FM11RF08[S] full card recovery")
lprint(f"\nDump folder: {dpath}")
print(prompt) bdkey, blk0 = getBackdoorKey()
print(f"{prompt} Dump folder: {dpath}") if bdkey is None:
if not getDarkKey():
return return
decodeBlock0() uid = getUIDfromBlock0(blk0)
startlog(uid, dpath, append=False)
global keyfile decodeBlock0(blk0)
global mad
mad = False mad = False
keyfile = f"{dpath}hf-mf-{uid:08X}-key.bin" keyfile = f"{dpath}hf-mf-{uid.hex().upper()}-key.bin"
keyok = False
if args.force is False and loadKeys() is True: if args.force or (key := loadKeys(keyfile)) is None:
keyok = True
else:
if args.recover is False: if args.recover is False:
lprint(f"{prompt} * Keys not loaded, use --recover to run recovery script [slow]") lprint("* Keys not loaded, use --recover to run recovery script [slow]")
else: else:
recoverKeys() keyfile = recoverKeys()
if loadKeys() is True: key = loadKeys(keyfile)
keyok = True
if keyok is True: if key is not None:
if verifyKeys() is False: ret, mad, key = verifyKeys(key)
if ret is False:
if args.nokeys is False: if args.nokeys is False:
lprint(f"{prompt} ! Use --nokeys to keep going past this point") lprint("! Use --nokeys to keep going past this point")
return return
readBlocks() data, blkn = readBlocks(bdkey)
patchKeys(keyok) data = patchKeys(data, key)
diskDump() # save it before you do anything else dump18 = diskDump(data, uid, dpath) # save it before you do anything else
dumpData() dumpData(data, blkn)
dumpAcl() dumpAcl(data)
if mad is True: if mad is True:
dumpMad() dumpMad(dump18)
if (args.bambu is True) or (detectBambu() is True): if (args.bambu is True) or (detectBambu(data) is True):
dumpBambu() dumpBambu(data)
lprint(prompt) lprint("\nTadah!")
lprint(f"{prompt} Tadah!")
return return
# +============================================================================= # +=============================================================================
# Get PM3 preferences # Get PM3 preferences
# >> "dpath"
# ============================================================================== # ==============================================================================
def getPrefs(): def getPrefs():
global dpath
p.console("prefs show --json") p.console("prefs show --json")
prefs = json.loads(p.grabbed_output) prefs = json.loads(p.grabbed_output)
dpath = prefs['file.default.dumppath'] + os.path.sep dpath = prefs['file.default.dumppath'] + os.path.sep
return dpath
# +============================================================================= # +=============================================================================
@ -170,10 +177,8 @@ def checkVer():
# +============================================================================= # +=============================================================================
# Parse the CLi arguments # Parse the CLi arguments
# >> args.
# ============================================================================== # ==============================================================================
def parseCli(): def parseCli():
global args
parser = argparse.ArgumentParser(description='Full recovery of Fudan FM11RF08* cards.') parser = argparse.ArgumentParser(description='Full recovery of Fudan FM11RF08* cards.')
@ -187,76 +192,73 @@ def parseCli():
if args.force is True: if args.force is True:
args.recover = True args.recover = True
return args
# +============================================================================= # +=============================================================================
# Find backdoor key # Find backdoor key
# >> "dkey"
# >> "blk0"
# [=] # | sector 00 / 0x00 | ascii # [=] # | sector 00 / 0x00 | ascii
# [=] ----+-------------------------------------------------+----------------- # [=] ----+-------------------------------------------------+-----------------
# [=] 0 | 5C B4 9C A6 D2 08 04 00 04 59 92 25 BF 5F 70 90 | \........Y.%._p. # [=] 0 | 5C B4 9C A6 D2 08 04 00 04 59 92 25 BF 5F 70 90 | \........Y.%._p.
# ============================================================================== # ==============================================================================
def getDarkKey(): def getBackdoorKey():
global dkey
global blk0
# FM11RF08S FM11RF08 FM11RF32 # FM11RF08S FM11RF08 FM11RF32
dklist = ["A396EFA4E24F", "A31667A8CEC1", "518b3354E760"] dklist = ["A396EFA4E24F", "A31667A8CEC1", "518b3354E760"]
print(prompt) lprint("\nTrying known backdoor keys...")
print(f"{prompt} Trying known backdoor keys...")
dkey = "" bdkey = ""
for k in dklist: for k in dklist:
cmd = f"hf mf rdbl -c 4 --key {k} --blk 0" cmd = f"hf mf rdbl -c 4 --key {k} --blk 0"
print(f"{prompt} `{cmd}`", end='', flush=True) lprint(f"\n`{cmd}`", end='', flush=True)
res = p.console(f"{cmd}") res = p.console(f"{cmd}")
for line in p.grabbed_output.split('\n'): for line in p.grabbed_output.split('\n'):
if " | " in line and "# | s" not in line: if " | " in line and "# | s" not in line:
blk0 = line[10:56+1] blk0 = line[10:56+1]
if res == 0: if res == 0:
print(" - success") lprint(" - success", prompt='')
dkey = k bdkey = k
break break
print(f" - fail [{res}]") lprint(f" - fail [{res}]", prompt='')
if bdkey == "":
lprint("\n! Unknown key, or card not detected.")
return None, None
lprint(f" Backdoor Key : {bdkey}") # show key
return bdkey, blk0
# +=============================================================================
# Extract UID from block 0
# ==============================================================================
def getUIDfromBlock0(blk0):
uids = blk0[0:11] # UID string : "11 22 33 44"
uid = bytes.fromhex(uids.replace(' ', '')) # UID (bytes) : 11223344
return uid
if dkey == "":
print(f"{prompt}")
print(f"{prompt} ! Unknown key, or card not detected.")
return False
return True
# +============================================================================= # +=============================================================================
# Extract data from block 0 # Extract data from block 0
# >> "uid"
# >> "uids"
# ============================================================================== # ==============================================================================
def decodeBlock0(): def decodeBlock0(blk0):
global uid lprint("")
global uids lprint(" UID BCC ++----- RF08 ID -----++")
lprint(" ! ! SAK !! !!")
# We do this early so we can name the logfile! lprint(" ! ! ! ATQA !! Fudan Sig !!")
uids = blk0[0:11] # UID string : "11 22 33 44" lprint(" !---------. !. !. !---. VV .---------------. VV")
uid = int(uids.replace(' ', ''), 16) # UID (value) : 0x11223344
startlog(uid, append=False)
lprint(prompt)
lprint(f"{prompt} UID BCC ++----- RF08 ID -----++")
lprint(f"{prompt} ! ! SAK !! !!")
lprint(f"{prompt} ! ! ! ATQA !! Fudan Sig !!")
lprint(f"{prompt} !---------. !. !. !---. VV .---------------. VV")
# 0 12 15 18 24 27 45 # 0 12 15 18 24 27 45
# ! ! ! ! ! ! ! # ! ! ! ! ! ! !
# 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF # 00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF
lprint(f"{prompt} Block 0 : {blk0}") lprint(f" Block 0 : {blk0}")
# --- decode block 0 --- # --- decode block 0 ---
uid = getUIDfromBlock0(blk0)
bcc = int(blk0[12:14], 16) # BCC bcc = int(blk0[12:14], 16) # BCC
chk = 0 # calculate checksum chk = 0 # calculate checksum
for h in uids.split(): for h in uid:
chk ^= int(h, 16) chk ^= h
sak = int(blk0[15:17], 16) # SAK sak = int(blk0[15:17], 16) # SAK
atqa = int(blk0[18:23].replace(' ', ''), 16) # 0x7788 atqa = int(blk0[18:23].replace(' ', ''), 16) # 0x7788
@ -284,93 +286,77 @@ def decodeBlock0():
# --- show results --- # --- show results ---
lprint(prompt) lprint()
lprint(f"{prompt} UID/BCC : {uid:08X}/{bcc:02X} - ", end='')
if bcc == chk: if bcc == chk:
lprint("verified") desc = "verified"
else: else:
lprint(f"fail. Expected {chk:02X}") desc = f"fail. Expected {chk:02X}"
lprint(f" UID/BCC : {uid.hex().upper()}/{bcc:02X} - {desc}")
lprint(f"{prompt} SAK : {sak:02X} - ", end='')
if sak == 0x01: if sak == 0x01:
lprint("NXP MIFARE TNP3xxx 1K") desc = "NXP MIFARE TNP3xxx 1K"
elif sak == 0x08: elif sak == 0x08:
lprint("NXP MIFARE CLASSIC 1k | Plus 1k | Ev1 1K") desc = "NXP MIFARE CLASSIC 1k | Plus 1k | Ev1 1K"
elif sak == 0x09: elif sak == 0x09:
lprint("NXP MIFARE Mini 0.3k") desc = "NXP MIFARE Mini 0.3k"
elif sak == 0x10: elif sak == 0x10:
lprint("NXP MIFARE Plus 2k") desc = "NXP MIFARE Plus 2k"
elif sak == 0x18: elif sak == 0x18:
lprint("NXP MIFARE Classic 4k | Plus 4k | Ev1 4k") desc = "NXP MIFARE Classic 4k | Plus 4k | Ev1 4k"
else: else:
lprint("{unknown}") desc = "{unknown}"
lprint(f" SAK : {sak:02X} - {desc}")
lprint(f"{prompt} ATQA : {atqa:04X}") # show ATQA lprint(f" ATQA : {atqa:04X}") # show ATQA
lprint(f"{prompt} Fudan ID : {type}") # show type lprint(f" Fudan ID : {type}") # show type
lprint(f"{prompt} Fudan Sig: {hash}") # show ?Partial HMAC? lprint(f" Fudan Sig: {hash}") # show ?Partial HMAC?
lprint(f"{prompt} Dark Key : {dkey}") # show key
# +============================================================================= # +=============================================================================
# Fudan validation # Fudan validation
# >> "blk0"
# ============================================================================== # ==============================================================================
def fudanValidate(): def fudanValidate(blk0, live=False):
# Warning, this import causes a "double free or corruption" crash if the script is called twice...
# So for now we limit the import only when really needed
import requests
global blk0
url = "https://rfid.fm-uivs.com/nfcTools/api/M1KeyRest" url = "https://rfid.fm-uivs.com/nfcTools/api/M1KeyRest"
hdr = "Content-Type: application/text; charset=utf-8" hdr = "Content-Type: application/text; charset=utf-8"
post = f"{blk0.replace(' ', '')}" post = f"{blk0.replace(' ', '')}"
lprint(prompt) lprint(f"\n Validator: `wget -q -O -"
lprint(f"{prompt} Validator: `wget -q -O -"
f" --header=\"{hdr}\"" f" --header=\"{hdr}\""
f" --post-data \"{post}\"" f" --post-data \"{post}\""
f" {url}" f" {url}"
" | json_pp`") " | json_pp`")
if args.validate: if live:
lprint(prompt) # Warning, this import causes a "double free or corruption" crash if the script is called twice...
lprint(f"{prompt} Check Fudan signature (requires internet)...") # So for now we limit the import only when really needed
import requests
lprint("\nCheck Fudan signature (requires internet)...")
headers = {"Content-Type": "application/text; charset=utf-8"} headers = {"Content-Type": "application/text; charset=utf-8"}
resp = requests.post(url, headers=headers, data=post) resp = requests.post(url, headers=headers, data=post)
if resp.status_code != 200: if resp.status_code != 200:
lprint(f"{prompt} HTTP Error {resp.status_code} - check request not processed") lprint(f"HTTP Error {resp.status_code} - check request not processed")
else: else:
r = json.loads(resp.text) r = json.loads(resp.text)
lprint(f"{prompt} The man from Fudan, he say: {r['code']} - {r['message']}", end='')
if r['data'] is not None: if r['data'] is not None:
lprint(f" {{{r['data']}}}") desc = f" {{{r['data']}}}"
else: else:
lprint("") desc = ""
lprint(f"The man from Fudan, he say: {r['code']} - {r['message']}{desc}")
else: else:
lprint(prompt) lprint("\n ...Use --validate to perform Fudan signature check automatically")
lprint(f"{prompt} ...Use --validate to perform Fudan signature check automatically")
# +============================================================================= # +=============================================================================
# Load keys from file # Load keys from file
# If keys cannot be loaded AND --recover is specified, then run key recovery # If keys cannot be loaded AND --recover is specified, then run key recovery
# >> "keyfile"
# >> "key[17][2]"
# ============================================================================== # ==============================================================================
def loadKeys(keyfile):
def loadKeys():
global keyfile
global key
key = [[b'' for _ in range(2)] for _ in range(17)] # create a fresh array key = [[b'' for _ in range(2)] for _ in range(17)] # create a fresh array
lprint(prompt) lprint(f"\nLoad Keys from file: |{keyfile}|")
lprint(f"{prompt} Load Keys from file: |{keyfile}|")
try: try:
with (open(keyfile, "rb")) as fh: with (open(keyfile, "rb")) as fh:
@ -379,25 +365,20 @@ def loadKeys():
key[sec][ab] = fh.read(6) key[sec][ab] = fh.read(6)
except IOError: except IOError:
return False return None
return True return key
# +============================================================================= # +=============================================================================
# Run key recovery script # Run key recovery script
# >> "keyfile"
# ============================================================================== # ==============================================================================
def recoverKeys(): def recoverKeys():
global keyfile
badrk = 0 # 'bad recovered key' count (ie. not recovered) badrk = 0 # 'bad recovered key' count (ie. not recovered)
lprint(prompt) lprint("\nRunning recovery script, ETA: Less than 30 minutes")
lprint(f"{prompt} Running recovery script, ETA: Less than 30 minutes")
lprint(prompt) lprint('\n`-._,-\'"`-._,-"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,')
lprint(f'{prompt} `-._,-\'"`-._,-"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,')
r = recovery(quiet=False) r = recovery(quiet=False)
keyfile = r['keyfile'] keyfile = r['keyfile']
@ -405,38 +386,36 @@ def recoverKeys():
# fdump = r['dumpfile'] # fdump = r['dumpfile']
# rdata = r['data'] # rdata = r['data']
lprint(f'{prompt} `-._,-\'"`-._,-"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,') lprint('`-._,-\'"`-._,-"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,')
for k in range(0, 16+1): for k in range(0, 16+1):
for ab in [0, 1]: for ab in [0, 1]:
if rkey[k][ab] == "": if rkey[k][ab] == "":
if badrk == 0: if badrk == 0:
lprint(f"{prompt} Some keys were not recovered: ", end='') lprint("Some keys were not recovered: ", end='')
else: else:
lprint(", ", end='') lprint(", ", end='', prompt='')
badrk += 1 badrk += 1
kn = k kn = k
if kn > 15: if kn > 15:
kn += 16 kn += 16
lprint(f"[{kn}/", end='') lprint(f"[{kn}/", end='', prompt='')
lprint("A]" if ab == 0 else "B]", end='') lprint("A]" if ab == 0 else "B]", end='', prompt='')
if badrk > 0: if badrk > 0:
lprint("") lprint("")
return keyfile
# +============================================================================= # +=============================================================================
# Verify keys # Verify keys
# >> "key[][]"
# >> mad!
# ============================================================================== # ==============================================================================
def verifyKeys(): def verifyKeys(key):
global key
global mad
badk = 0 badk = 0
mad = False
lprint(f"{prompt} Check keys..") lprint("Check keys..")
for sec in range(0, 16+1): # 16 normal, 1 dark for sec in range(0, 16+1): # 16 normal, 1 dark
sn = sec sn = sec
@ -449,14 +428,14 @@ def verifyKeys():
bn += 64 bn += 64
cmd = f"hf mf rdbl -c {ab} --key {key[sec][ab].hex()} --blk {bn}" cmd = f"hf mf rdbl -c {ab} --key {key[sec][ab].hex()} --blk {bn}"
lprint(f"{prompt} `{cmd}`", end='', flush=True) lprint(f" `{cmd}`", end='', flush=True)
res = p.console(f"{cmd}", capture=False) res = p.console(f"{cmd}", capture=False)
lprint(" " * (3-len(str(bn))), end="") lprint(" " * (3-len(str(bn))), end="", prompt='')
if res == 0: if res == 0:
lprint(" ... PASS", end="") lprint(" ... PASS", end="", prompt='')
else: else:
lprint(" ... FAIL", end="") lprint(" ... FAIL", end="", prompt='')
badk += 1 badk += 1
key[sec][ab] = b'' key[sec][ab] = b''
@ -464,36 +443,33 @@ def verifyKeys():
if (sec == 0) and (ab == 0) \ if (sec == 0) and (ab == 0) \
and (key[0][0] == b'\xa0\xa1\xa2\xa3\xa4\xa5'): and (key[0][0] == b'\xa0\xa1\xa2\xa3\xa4\xa5'):
mad = True mad = True
lprint(" - MAD Key") lprint(" - MAD Key, prompt=''")
else: else:
lprint("") lprint("", prompt='')
if badk > 0: if badk > 0:
lprint(f"{prompt} ! {badk} bad key", end='') lprint(f"! {badk} bad key", end='')
lprint("s exist" if badk != 1 else " exists") lprint("s exist" if badk != 1 else " exists")
rv = False rv = False, mad, key
else: else:
lprint(f"{prompt} All keys verified OK") lprint("All keys verified OK")
rv = True rv = True, mad, key
if mad is True: if mad is True:
lprint(f"{prompt} MAD key detected") lprint("MAD key detected")
return rv return rv
# +============================================================================= # +=============================================================================
# Read all block data - INCLUDING Dark blocks # Read all block data - INCLUDING advanced verification blocks
# >> blkn #
# >> "data[]"
# [=] # | sector 00 / 0x00 | ascii # [=] # | sector 00 / 0x00 | ascii
# [=] ----+-------------------------------------------------+----------------- # [=] ----+-------------------------------------------------+-----------------
# [=] 0 | 5C B4 9C A6 D2 08 04 00 04 59 92 25 BF 5F 70 90 | \........Y.%._p. # [=] 0 | 5C B4 9C A6 D2 08 04 00 04 59 92 25 BF 5F 70 90 | \........Y.%._p.
# ============================================================================== # ==============================================================================
def readBlocks(): def readBlocks(bdkey):
global data
global blkn
data = [] data = []
blkn = list(range(0, 63+1)) + list(range(128, 135+1)) blkn = list(range(0, 63+1)) + list(range(128, 135+1))
@ -502,15 +478,13 @@ def readBlocks():
# The vendor uses keyhole #2 (-b) # The vendor uses keyhole #2 (-b)
# The thief uses keyhole #4 (backdoor) # The thief uses keyhole #4 (backdoor)
# |___ # |___
rdbl = f"hf mf rdbl -c 4 --key {dkey} --blk"
lprint(prompt) lprint("\n Load blocks {0..63, 128..135}[64+8=72] from the card")
lprint(prompt + " Load blocks {0..63, 128..135}[64+8=72] from the card")
bad = 0 bad = 0
for n in blkn: for n in blkn:
cmd = f"{rdbl} {n}" cmd = f"hf mf rdbl -c 4 --key {bdkey} --blk {n}"
print(f"\r{prompt} `{cmd}`", end='', flush=True) lprint(f"`{cmd}`", flush=True, log=False)
for retry in range(5): for retry in range(5):
p.console(f"{cmd}") p.console(f"{cmd}")
@ -528,26 +502,21 @@ def readBlocks():
data.append(f"{n:3d} | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- | ----------------") data.append(f"{n:3d} | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- | ----------------")
bad += 1 bad += 1
print(" .. OK") lprint(" .. OK", log=False)
return data, blkn
# +============================================================================= # +=============================================================================
# Patch keys in to data # Patch keys in to data
# >> "key[][]" #
# >> "data[]"
# >> keyok!
# 3 | 00 00 00 00 00 00 87 87 87 69 00 00 00 00 00 00 | .........i...... # 3 | 00 00 00 00 00 00 87 87 87 69 00 00 00 00 00 00 | .........i......
# ============================================================================== # ==============================================================================
def patchKeys(keyok): def patchKeys(data, key):
global key lprint("\nPatch keys in to data")
global data
lprint(prompt)
lprint(f"{prompt} Patch keys in to data")
for sec in range(0, 16+1): for sec in range(0, 16+1):
blk = (sec * 4) + 3 # find "trailer" for this sector blk = (sec * 4) + 3 # find "trailer" for this sector
if keyok: if key is not None:
if key[sec][0] == b'': if key[sec][0] == b'':
keyA = "-- -- -- -- -- -- " keyA = "-- -- -- -- -- -- "
else: else:
@ -564,22 +533,19 @@ def patchKeys(keyok):
else: else:
data[blk] = data[blk][:6] + "-- -- -- -- -- -- " + data[blk][24:36] + "-- -- -- -- -- --" data[blk] = data[blk][:6] + "-- -- -- -- -- -- " + data[blk][24:36] + "-- -- -- -- -- --"
return data
# +============================================================================= # +=============================================================================
# Dump data # Dump data
# >> blkn
# >> "data[]"
# ============================================================================== # ==============================================================================
def dumpData(): def dumpData(data, blkn):
global blkn
global data
lprint(prompt) lprint()
lprint(f"{prompt} ===========") lprint("===========")
lprint(f"{prompt} Card Data") lprint(" Card Data")
lprint(f"{prompt} ===========") lprint("===========")
lprint(f"{prompt}") lprint()
cnt = 0 cnt = 0
for n in blkn: for n in blkn:
@ -588,19 +554,19 @@ def dumpData():
sec = sec + 16 sec = sec + 16
if (n % 4 == 0): if (n % 4 == 0):
lprint(f"{prompt} {sec:2d}:{data[cnt]}") lprint(f"{sec:2d}:{data[cnt]}")
else: else:
lprint(f"{prompt} :{data[cnt]}") lprint(f" :{data[cnt]}")
cnt += 1 cnt += 1
if (cnt % 4 == 0) and (n != blkn[-1]): # Space between sectors if (cnt % 4 == 0) and (n != blkn[-1]): # Space between sectors
lprint(prompt) lprint()
# +============================================================================= # +=============================================================================
# Let's try to detect a Bambu card by the date strings... # Let's try to detect a Bambu card by the date strings...
# ============================================================================== # ==============================================================================
def detectBambu(): def detectBambu(data):
try: try:
dl = bytes.fromhex(data[12][6:53]).decode('ascii').rstrip('\x00') dl = bytes.fromhex(data[12][6:53]).decode('ascii').rstrip('\x00')
dls = dl[2:13] dls = dl[2:13]
@ -613,33 +579,31 @@ def detectBambu():
# yy y y m m d d h h m m # yy y y m m d d h h m m
exp = r"20[2-3][0-9]_[0-1][0-9]_[0-3][0-9]_[0-2][0-9]_[0-5][0-9]" exp = r"20[2-3][0-9]_[0-1][0-9]_[0-3][0-9]_[0-2][0-9]_[0-5][0-9]"
lprint(f"{prompt}")
if re.search(exp, dl) and (ds == dls): if re.search(exp, dl) and (ds == dls):
lprint(f"{prompt} Bambu date strings detected.") lprint("\nBambu date strings detected.")
return True return True
else: else:
lprint(f"{prompt} Bambu date strings not detected.") lprint("\nBambu date strings not detected.")
return False return False
# +============================================================================= # +=============================================================================
# Dump bambu details # Dump bambu details
# https://github.com/Bambu-Research-Group/RFID-Tag-Guide/blob/main/README.md # https://github.com/Bambu-Research-Group/RFID-Tag-Guide/blob/main/README.md
# >> "data[]" #
# 6 18 30 42 53 # 6 18 30 42 53
# | | | | | # | | | | |
# 3 | 00 00 00 00 00 00 87 87 87 69 00 00 00 00 00 00 | .........i...... # 3 | 00 00 00 00 00 00 87 87 87 69 00 00 00 00 00 00 | .........i......
# +============================================================================= # +=============================================================================
def dumpBambu(): def dumpBambu(data):
global data
try: try:
lprint(f"{prompt}") lprint()
lprint(f"{prompt} ===========") lprint("===========")
lprint(f"{prompt} Bambu Tag") lprint(" Bambu Tag")
lprint(f"{prompt} ===========") lprint("===========")
lprint(f"{prompt}") lprint()
lprint(f"{prompt} Decompose as Bambu tag .. ", end='') lprint("Decompose as Bambu tag .. ", end='')
MaterialVariantIdentifier_s = bytes.fromhex(data[1][6:29]).decode('ascii').rstrip('\x00') MaterialVariantIdentifier_s = bytes.fromhex(data[1][6:29]).decode('ascii').rstrip('\x00')
UniqueMaterialIdentifier_s = bytes.fromhex(data[1][30:53]).decode('ascii').rstrip('\x00') # [**] 8not16 UniqueMaterialIdentifier_s = bytes.fromhex(data[1][30:53]).decode('ascii').rstrip('\x00') # [**] 8not16
@ -693,49 +657,48 @@ def dumpBambu():
Hash.append(data[b][6:53]) Hash.append(data[b][6:53])
lprint("[offset:length]") lprint("[offset:length]")
lprint(f"{prompt} Block 1:") lprint(" Block 1:")
lprint(f"{prompt} [ 0: 8] MaterialVariantIdentifier_s = \"{MaterialVariantIdentifier_s}\"") lprint(f" [ 0: 8] MaterialVariantIdentifier_s = \"{MaterialVariantIdentifier_s}\"")
lprint(f"{prompt} [ 8: 8] UniqueMaterialIdentifier_s = \"{UniqueMaterialIdentifier_s}\"") lprint(f" [ 8: 8] UniqueMaterialIdentifier_s = \"{UniqueMaterialIdentifier_s}\"")
lprint(f"{prompt} Block 2:") lprint(" Block 2:")
lprint(f"{prompt} [ 0:16] FilamentType_s = \"{FilamentType_s}\"") lprint(f" [ 0:16] FilamentType_s = \"{FilamentType_s}\"")
lprint(f"{prompt} Block 4:") lprint(" Block 4:")
lprint(f"{prompt} [ 0:16] DetailedFilamentType_s = \"{DetailedFilamentType_s}\"") lprint(f" [ 0:16] DetailedFilamentType_s = \"{DetailedFilamentType_s}\"")
lprint(f"{prompt} Block 5:") lprint(" Block 5:")
lprint(f"{prompt} [ 0: 4] Colour_rgba = 0x{Colour_rgba:08X}") lprint(f" [ 0: 4] Colour_rgba = 0x{Colour_rgba:08X}")
lprint(f"{prompt} [ 4: 2] SpoolWeight_g = {SpoolWeight_g}g") lprint(f" [ 4: 2] SpoolWeight_g = {SpoolWeight_g}g")
lprint(f"{prompt} [6: 2] Block5_7to8 = {{{Block5_7to8}}}") lprint(f" [6: 2] Block5_7to8 = {{{Block5_7to8}}}")
lprint(f"{prompt} [ 8: 4] FilamentDiameter_mm = {FilamentDiameter_mm}mm") lprint(f" [ 8: 4] FilamentDiameter_mm = {FilamentDiameter_mm}mm")
lprint(f"{prompt} [12: 4] Block5_12to15 = {{{Block5_12to15}}}") lprint(f" [12: 4] Block5_12to15 = {{{Block5_12to15}}}")
lprint(f"{prompt} Block 6:") lprint(" Block 6:")
lprint(f"{prompt} [ 0: 2] DryingTemperature_c = {DryingTemperature_c}^C") lprint(f" [ 0: 2] DryingTemperature_c = {DryingTemperature_c}^C")
lprint(f"{prompt} [ 2: 2] DryingTime_h = {DryingTime_h}hrs") lprint(f" [ 2: 2] DryingTime_h = {DryingTime_h}hrs")
lprint(f"{prompt} [ 4: 4] BedTemperatureType_q = {BedTemperatureType_q}") lprint(f" [ 4: 4] BedTemperatureType_q = {BedTemperatureType_q}")
lprint(f"{prompt} [6: 2] BedTemperature_c = {BedTemperature_c}^C") lprint(f" [6: 2] BedTemperature_c = {BedTemperature_c}^C")
lprint(f"{prompt} [ 8: 2] MaxTemperatureForHotend_c = {MaxTemperatureForHotend_c}^C") lprint(f" [ 8: 2] MaxTemperatureForHotend_c = {MaxTemperatureForHotend_c}^C")
lprint(f"{prompt} [10: 2] MinTemperatureForHotend_c = {MinTemperatureForHotend_c}^C") lprint(f" [10: 2] MinTemperatureForHotend_c = {MinTemperatureForHotend_c}^C")
lprint(f"{prompt} [12: 4] Block6_12to15 = {{{Block6_12to15}}}") lprint(f" [12: 4] Block6_12to15 = {{{Block6_12to15}}}")
lprint(f"{prompt} Block 8:") lprint(" Block 8:")
lprint(f"{prompt} [ 0:12] XCamInfo_x = {{{XCamInfo_x}}}") lprint(f" [ 0:12] XCamInfo_x = {{{XCamInfo_x}}}")
lprint(f"{prompt} [12: 4] NozzleDiameter_q = {NozzleDiameter_q:.6f}__") lprint(f" [12: 4] NozzleDiameter_q = {NozzleDiameter_q:.6f}__")
lprint(f"{prompt} Block 9:") lprint(" Block 9:")
# lprint(f"{prompt} [ 0:16] TrayUID_s = \"{TrayUID_s}\"") # lprint(f" [ 0:16] TrayUID_s = \"{TrayUID_s}\"")
lprint(f"{prompt} [ 0:16] TrayUID_s = {{{TrayUID_s}}} ; not ASCII") lprint(f" [ 0:16] TrayUID_s = {{{TrayUID_s}}} ; not ASCII")
lprint(f"{prompt} Block 10:") lprint(" Block 10:")
lprint(f"{prompt} [ 0: 4] Block10_0to3 = {{{Block10_0to3}}}") lprint(f" [ 0: 4] Block10_0to3 = {{{Block10_0to3}}}")
lprint(f"{prompt} [ 4: 2] SppolWidth_um = {SppolWidth_um}um") lprint(f" [ 4: 2] SppolWidth_um = {SppolWidth_um}um")
lprint(f"{prompt} [6:10] Block10_6to15 = {{{Block10_6to15}}}") lprint(f" [6:10] Block10_6to15 = {{{Block10_6to15}}}")
lprint(f"{prompt} Block 12:") lprint(" Block 12:")
lprint(f"{prompt} [ 0:16] ProductionDateTime_s = \"{ProductionDateTime_s}\"") lprint(f" [ 0:16] ProductionDateTime_s = \"{ProductionDateTime_s}\"")
lprint(f"{prompt} Block 13:") lprint(" Block 13:")
lprint(f"{prompt} [ 0:16] ShortProductionDateTime_s = \"{ShortProductionDateTime_s}\"") lprint(f" [ 0:16] ShortProductionDateTime_s = \"{ShortProductionDateTime_s}\"")
lprint(f"{prompt} Block 14:") lprint(" Block 14:")
lprint(f"{prompt} [ 0: 4] Block10_0to3 = {{{Block10_0to3}}}") lprint(f" [ 0: 4] Block10_0to3 = {{{Block10_0to3}}}")
lprint(f"{prompt} [ 4: 2] FilamentLength_m = {FilamentLength_m}m") lprint(f" [ 4: 2] FilamentLength_m = {FilamentLength_m}m")
lprint(f"{prompt} [6:10] Block10_6to15 = {{{Block10_6to15}}}") lprint(f" [6:10] Block10_6to15 = {{{Block10_6to15}}}")
lprint(f"{prompt}") lprint(f"\n Blocks {hblk}:")
lprint(f"{prompt} Blocks {hblk}:")
for i in range(0, len(hblk)): for i in range(0, len(hblk)):
lprint(f"{prompt} [ 0:16] HashBlock[{i:2d}] = {{{Hash[i]}}} // #{hblk[i]:2d}") lprint(f" [ 0:16] HashBlock[{i:2d}] = {{{Hash[i]}}} // #{hblk[i]:2d}")
except Exception as e: except Exception as e:
lprint(f"Failed: {e}") lprint(f"Failed: {e}")
@ -743,7 +706,7 @@ def dumpBambu():
# +============================================================================= # +=============================================================================
# Dump ACL # Dump ACL
# >> "data[][]" #
# 6 18 24 27 30 33 42 53 # 6 18 24 27 30 33 42 53
# | | | | | | | | # | | | | | | | |
# 3 | 00 00 00 00 00 00 87 87 87 69 00 00 00 00 00 00 | .........i...... # 3 | 00 00 00 00 00 00 87 87 87 69 00 00 00 00 00 00 | .........i......
@ -824,17 +787,16 @@ def dumpBambu():
# WARNING: # WARNING:
# IF YOU PLAN TO CHANGE ACCESS BITS, RTFM, THERE IS MUCH TO CONSIDER ! # IF YOU PLAN TO CHANGE ACCESS BITS, RTFM, THERE IS MUCH TO CONSIDER !
# ============================================================================== # ==============================================================================
def dumpAcl(): def dumpAcl(data):
global blkn global blkn
aclkh = [] # key header aclkh = [] # key header
aclk = [""] * 8 # key lookup aclk = [""] * 8 # key lookup
aclkx = [] # key output aclkx = [] # key output
lprint(f"{prompt}") lprint("\n=====================")
lprint(f"{prompt} =====================") lprint(" Access Control List")
lprint(f"{prompt} Access Control List") lprint("=====================")
lprint(f"{prompt} =====================")
aclkh.append(" _______________________________________________________ ") aclkh.append(" _______________________________________________________ ")
aclkh.append("| | Sector Trailers |") aclkh.append("| | Sector Trailers |")
@ -872,7 +834,7 @@ def dumpAcl():
acld[6] = "| A+B ¦ KeyB || KeyB ¦ A+B | [110]" # noqa: E222 acld[6] = "| A+B ¦ KeyB || KeyB ¦ A+B | [110]" # noqa: E222
acld[7] = "| -- ¦ -- || -- ¦ -- | [111]" # noqa: E222 acld[7] = "| -- ¦ -- || -- ¦ -- | [111]" # noqa: E222
idx = [] * (16+2) idx = [[]] * (16+2)
# --- calculate the ACL indices for each sector:block --- # --- calculate the ACL indices for each sector:block ---
for d in data: for d in data:
@ -905,39 +867,33 @@ def dumpAcl():
# --- print it all out --- # --- print it all out ---
for line in aclkh: for line in aclkh:
lprint(f"{prompt} {line}") lprint(f" {line}")
i = 0 i = 0
for line in aclkx: for line in aclkx:
lprint(f"{prompt} {line}") lprint(f" {line}")
if (i % 4) == 3: if (i % 4) == 3:
lprint(f"{prompt} | | ¦ || ¦ || ¦ |") lprint(" | | ¦ || ¦ || ¦ |")
i += 1 i += 1
lprint(f"{prompt}") lprint()
for line in acldh: for line in acldh:
lprint(f"{prompt} {line}") lprint(f" {line}")
i = 0 i = 0
for line in acldx: for line in acldx:
lprint(f"{prompt} {line}") lprint(f" {line}")
if (i % 3) == 2: if (i % 3) == 2:
lprint(f"{prompt} | | ¦ || ¦ |") lprint(" | | ¦ || ¦ |")
i += 1 i += 1
# +============================================================================= # +=============================================================================
# Full Dump # Full Dump
# >> "uid"
# >> "dump18"
# ============================================================================== # ==============================================================================
def diskDump(): def diskDump(data, uid, dpath):
global uid dump18 = f"{dpath}hf-mf-{uid.hex().upper()}-dump18.bin"
global dump18
dump18 = f"{dpath}hf-mf-{uid:08X}-dump18.bin" lprint(f"\nDump Card Data to file: {dump18}")
lprint(prompt)
lprint(f"{prompt} Dump Card Data to file: {dump18}")
bad = False bad = False
with open(dump18, 'wb') as f: with open(dump18, 'wb') as f:
@ -947,35 +903,32 @@ def diskDump():
b = bytes.fromhex(d[6:53].replace(" ", "").replace("--", "FF")) b = bytes.fromhex(d[6:53].replace(" ", "").replace("--", "FF"))
f.write(b) f.write(b)
if bad: if bad:
lprint(f"{prompt} Bad data exists, and has been saved as 0xFF") lprint("Bad data exists, and has been saved as 0xFF")
return dump18
# +============================================================================= # +=============================================================================
# Dump MAD # Dump MAD
# >> "dump18"
# ============================================================================== # ==============================================================================
def dumpMad(): def dumpMad(dump18):
global dump18
lprint(f"{prompt}") lprint()
lprint(f"{prompt} ====================================") lprint("====================================")
lprint(f"{prompt} MiFare Application Directory (MAD)") lprint(" MiFare Application Directory (MAD)")
lprint(f"{prompt} ====================================") lprint("====================================")
lprint(f"{prompt}") lprint()
cmd = f"hf mf mad --verbose --file {dump18}" cmd = f"hf mf mad --verbose --file {dump18}"
print(f"{prompt} `{cmd}`") lprint(f"`{cmd}`", log=False)
lprint(f"{prompt}") lprint('\n`-._,-\'"`-._,-"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,\n')
lprint(f'{prompt} `-._,-\'"`-._,-"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,')
lprint("")
p.console(f"{cmd}") p.console(f"{cmd}")
for line in p.grabbed_output.split('\n'): for line in p.grabbed_output.split('\n'):
lprint(line) lprint(line)
lprint(f'{prompt} `-._,-\'"`-._,-"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,') lprint('`-._,-\'"`-._,-"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,')
# ++============================================================================ # ++============================================================================