python script: making Fluke8 pleased

This commit is contained in:
Philippe Teuwen 2024-11-07 14:00:17 +01:00
commit d0bd3266f2
2 changed files with 729 additions and 691 deletions

View file

@ -1,5 +1,19 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# ------------------------------------------------------------------------------
# Imports
#
import re
import os
import sys
import argparse
import pm3
import struct
import json
import requests
from fm11rf08s_recovery import recovery
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# Revision log & Licence # Revision log & Licence
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
@ -8,43 +22,27 @@
''' '''
script_ver = "1.2.0" script_ver = "1.2.0"
''' # Copyright @csBlueChip
Copyright @csBlueChip
This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or # the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. # (at your option) any later version.
This program is distributed in the hope that it will be useful, # This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. # GNU General Public License for more details.
See LICENSE.txt for the text of the license. # See LICENSE.txt for the text of the license.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The original version of this script can be found at: # The original version of this script can be found at:
https://github.com/csBlueChip/Proxmark_Stuff/tree/main/MiFare_Docs/Fudan_RF08(S)/PM3_Script # https://github.com/csBlueChip/Proxmark_Stuff/tree/main/MiFare_Docs/Fudan_RF08(S)/PM3_Script
The original version is released with an MIT Licence. # The original version is released with an MIT Licence.
Or please reach out to me [BlueChip] personally for alternative licenses. # Or please reach out to me [BlueChip] personally for alternative licenses.
'''
#------------------------------------------------------------------------------
# Imports
#
import re
import os
import sys
import time
import argparse
import pm3
import struct
import json
import requests
from fm11rf08s_recovery import recovery
# optional color support .. `pip install ansicolors` # optional color support .. `pip install ansicolors`
try: try:
@ -54,6 +52,7 @@ except ModuleNotFoundError:
_ = fg _ = fg
return str(s) return str(s)
# +============================================================================= # +=============================================================================
# Print and Log # Print and Log
# >> "logfile" # >> "logfile"
@ -62,8 +61,10 @@ def startlog(uid, append = False):
global logfile global logfile
logfile = f"{dpath}hf-mf-{uid:08X}-log.txt" logfile = f"{dpath}hf-mf-{uid:08X}-log.txt"
if append == False: if append is False:
with open(logfile, 'w'): pass with open(logfile, 'w'):
pass
# +========================================================= # +=========================================================
def lprint(s, end='\n', flush=False): def lprint(s, end='\n', flush=False):
@ -73,6 +74,7 @@ def lprint(s, end='\n', flush=False):
with open(logfile, 'a') as f: with open(logfile, 'a') as f:
f.write(s + end) f.write(s + end)
# ++============================================================================ # ++============================================================================
# == MAIN == # == MAIN ==
# >> "prompt" # >> "prompt"
@ -106,18 +108,19 @@ def main():
keyfile = f"{dpath}hf-mf-{uid:08X}-key.bin" keyfile = f"{dpath}hf-mf-{uid:08X}-key.bin"
keyok = False keyok = False
if args.force == False and loadKeys() == True: if args.force is False and loadKeys() is True:
keyok = True keyok = True
else: else:
if args.recover == False: if args.recover is False:
lprint(f"{prompt} * Keys not loaded, use --recover to run recovery script [slow]") lprint(f"{prompt} * Keys not loaded, use --recover to run recovery script [slow]")
else: else:
recoverKeys() recoverKeys()
if loadKeys() == True: keyok = True if loadKeys() is True:
keyok = True
if keyok == True: if keyok is True:
if verifyKeys() == False: if verifyKeys() is False:
if args.nokeys == False: if args.nokeys is False:
lprint(f"{prompt} ! Use --nokeys to keep going past this point") lprint(f"{prompt} ! Use --nokeys to keep going past this point")
exit(101) exit(101)
@ -129,9 +132,10 @@ def main():
dumpData() dumpData()
dumpAcl() dumpAcl()
if mad == True: dumpMad() if mad is True:
dumpMad()
if (args.bambu == True) or (detectBambu() == True): if (args.bambu is True) or (detectBambu() is True):
dumpBambu() dumpBambu()
lprint(prompt) lprint(prompt)
@ -139,6 +143,7 @@ def main():
return return
# +============================================================================= # +=============================================================================
# Get PM3 preferences # Get PM3 preferences
# >> "dpath" # >> "dpath"
@ -150,6 +155,7 @@ def getPrefs():
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
# +============================================================================= # +=============================================================================
# Assert python version # Assert python version
# ============================================================================== # ==============================================================================
@ -160,6 +166,7 @@ def checkVer():
print(f"The script needs at least Python v{required_version[0]}.{required_version[1]}. Abort.") print(f"The script needs at least Python v{required_version[0]}.{required_version[1]}. Abort.")
exit() exit()
# +============================================================================= # +=============================================================================
# Parse the CLi arguments # Parse the CLi arguments
# >> args. # >> args.
@ -177,17 +184,17 @@ def parseCli():
args = parser.parse_args() args = parser.parse_args()
if args.force == True: args.recover = True if args.force is True:
args.recover = True
# +============================================================================= # +=============================================================================
# Find backdoor key # Find backdoor key
# >> "dkey" # >> "dkey"
# >> "blk0" # >> "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 getDarkKey():
global dkey global dkey
@ -206,8 +213,8 @@ def getDarkKey():
res = p.console(f"{cmd}", capture=False) res = p.console(f"{cmd}", capture=False)
if res == 0: if res == 0:
print(" - success") print(" - success")
dkey = k; dkey = k
break; break
print(f" - fail [{res}]") print(f" - fail [{res}]")
if dkey == "": if dkey == "":
@ -219,6 +226,7 @@ def getDarkKey():
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]
# +============================================================================= # +=============================================================================
# Extract data from block 0 # Extract data from block 0
# >> "uid" # >> "uid"
@ -255,7 +263,7 @@ def decodeBlock0():
fida = int(blk0[24:26], 16) # Fudan ID 0x88 fida = int(blk0[24:26], 16) # Fudan ID 0x88
fidb = int(blk0[45:47], 16) # Fudan ID 0xFF fidb = int(blk0[45:47], 16) # Fudan ID 0xFF
fid = (fida<<8)|fidb # Fudan ID 0x88FF # fid = (fida<<8)|fidb # Fudan ID 0x88FF
hash = blk0[27:44] # Fudan hash "99 AA BB CC DD EE" hash = blk0[27:44] # Fudan hash "99 AA BB CC DD EE"
@ -279,22 +287,31 @@ def decodeBlock0():
lprint(prompt) lprint(prompt)
lprint(f"{prompt} UID/BCC : {uid:08X}/{bcc:02X} - ", end='') lprint(f"{prompt} UID/BCC : {uid:08X}/{bcc:02X} - ", end='')
if bcc == chk: lprint("verified") if bcc == chk:
else: lprint(f"fail. Expected {chk:02X}") lprint("verified")
else:
lprint(f"fail. Expected {chk:02X}")
lprint(f"{prompt} SAK : {sak:02X} - ", end='') lprint(f"{prompt} SAK : {sak:02X} - ", end='')
if sak == 0x01: lprint("NXP MIFARE TNP3xxx 1K") if sak == 0x01:
elif sak == 0x08: lprint("NXP MIFARE CLASSIC 1k | Plus 1k | Ev1 1K") lprint("NXP MIFARE TNP3xxx 1K")
elif sak == 0x09: lprint("NXP MIFARE Mini 0.3k") elif sak == 0x08:
elif sak == 0x10: lprint("NXP MIFARE Plus 2k") lprint("NXP MIFARE CLASSIC 1k | Plus 1k | Ev1 1K")
elif sak == 0x18: lprint("NXP MIFARE Classic 4k | Plus 4k | Ev1 4k") elif sak == 0x09:
else: lprint("{unknown}") lprint("NXP MIFARE Mini 0.3k")
elif sak == 0x10:
lprint("NXP MIFARE Plus 2k")
elif sak == 0x18:
lprint("NXP MIFARE Classic 4k | Plus 4k | Ev1 4k")
else:
lprint("{unknown}")
lprint(f"{prompt} ATQA : {atqa:04X}") # show ATQA lprint(f"{prompt} ATQA : {atqa:04X}") # show ATQA
lprint(f"{prompt} Fudan ID : {type}") # show type lprint(f"{prompt} Fudan ID : {type}") # show type
lprint(f"{prompt} Fudan Sig: {hash}") # show ?Partial HMAC? lprint(f"{prompt} Fudan Sig: {hash}") # show ?Partial HMAC?
lprint(f"{prompt} Dark Key : {dkey}") # show key lprint(f"{prompt} Dark Key : {dkey}") # show key
# +============================================================================= # +=============================================================================
# Fudan validation # Fudan validation
# >> "blk0" # >> "blk0"
@ -334,17 +351,20 @@ def fudanValidate():
lprint(prompt) lprint(prompt)
lprint(f"{prompt} ...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" # >> "keyfile"
# >> "key[17][2]" # >> "key[17][2]"
# ============================================================================== # ==============================================================================
def loadKeys(): def loadKeys():
global keyfile global keyfile
global key global key
key = [[0 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(prompt)
lprint(f"{prompt} Load Keys from file: |{keyfile}|") lprint(f"{prompt} Load Keys from file: |{keyfile}|")
@ -355,11 +375,12 @@ def loadKeys():
for sec in range((16+2)-1): for sec in range((16+2)-1):
key[sec][ab] = fh.read(6) key[sec][ab] = fh.read(6)
except IOError as e: except IOError:
return False return False
return True return True
# +============================================================================= # +=============================================================================
# Run key recovery script # Run key recovery script
# >> "keyfile" # >> "keyfile"
@ -378,23 +399,28 @@ def recoverKeys():
r = recovery(quiet=False) r = recovery(quiet=False)
keyfile = r['keyfile'] keyfile = r['keyfile']
rkey = r['found_keys'] rkey = r['found_keys']
fdump = r['dumpfile'] # fdump = r['dumpfile']
rdata = r['data'] # rdata = r['data']
lprint(f'{prompt} `-._,-\'"`-._,-"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,') lprint(f'{prompt} `-._,-\'"`-._,-"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,')
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: lprint(f"{prompt} Some keys were not recovered: ", end='') if badrk == 0:
else: lprint(f", ", end='') lprint(f"{prompt} Some keys were not recovered: ", end='')
else:
lprint(", ", end='')
badrk += 1 badrk += 1
kn = k kn = k
if kn > 15: kn += 16 if kn > 15:
kn += 16
lprint(f"[{kn}/", end='') lprint(f"[{kn}/", end='')
lprint("A]" if ab == 0 else "B]", end='') lprint("A]" if ab == 0 else "B]", end='')
if badrk > 0: lprint("") if badrk > 0:
lprint("")
# +============================================================================= # +=============================================================================
# Verify keys # Verify keys
@ -411,11 +437,13 @@ def verifyKeys():
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
if (sn > 15): sn = sn + 16 if (sn > 15):
sn = sn + 16
for ab in [0, 1]: for ab in [0, 1]:
bn = (sec * 4) + 3 bn = (sec * 4) + 3
if bn >= 64: bn += 64 if 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"{prompt} `{cmd}`", end='', flush=True)
@ -427,7 +455,7 @@ def verifyKeys():
else: else:
lprint(" ... FAIL", end="") lprint(" ... FAIL", end="")
badk += 1 badk += 1
key[sec][ab] = "" key[sec][ab] = b''
# check for Mifare Application Directory # check for Mifare Application Directory
if (sec == 0) and (ab == 0) \ if (sec == 0) and (ab == 0) \
@ -446,20 +474,19 @@ def verifyKeys():
lprint(f"{prompt} All keys verified OK") lprint(f"{prompt} All keys verified OK")
rv = True rv = True
if mad == True: if mad is True:
lprint(f"{prompt} MAD key detected") lprint(f"{prompt} MAD key detected")
return rv return rv
# +============================================================================= # +=============================================================================
# Read all block data - INCLUDING Dark blocks # Read all block data - INCLUDING Dark blocks
# >> blkn # >> blkn
# >> "data[]" # >> "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():
global data global data
@ -488,10 +515,11 @@ def readBlocks():
found = False found = False
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:
l = line[4:76] lsub = line[4:76]
data.append(l) data.append(lsub)
found = True found = True
if found: break if found:
break
if not found: if not found:
data.append(f"{n:3d} | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- | ----------------") data.append(f"{n:3d} | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- | ----------------")
@ -499,14 +527,13 @@ def readBlocks():
print(" .. OK") print(" .. OK")
# +============================================================================= # +=============================================================================
# Patch keys in to data # Patch keys in to data
# >> "key[][]" # >> "key[][]"
# >> "data[]" # >> "data[]"
# >> keyok! # >> 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(keyok):
global key global key
@ -518,13 +545,13 @@ def patchKeys(keyok):
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 keyok:
if key[sec][0] == "": if key[sec][0] == b'':
keyA = "-- -- -- -- -- -- " keyA = "-- -- -- -- -- -- "
else: else:
kstr = key[sec][0].hex() kstr = key[sec][0].hex()
keyA = "".join([kstr[i:i+2] + " " for i in range(0, len(kstr), 2)]) keyA = "".join([kstr[i:i+2] + " " for i in range(0, len(kstr), 2)])
if key[sec][1] == "": if key[sec][1] == b'':
keyB = "-- -- -- -- -- -- " keyB = "-- -- -- -- -- -- "
else: else:
kstr = key[sec][1].hex() kstr = key[sec][1].hex()
@ -535,6 +562,7 @@ def patchKeys(keyok):
else: else:
data[blk] = data[blk][:6] + "-- -- -- -- -- -- " + data[blk][24:36] + "-- -- -- -- -- --" data[blk] = data[blk][:6] + "-- -- -- -- -- -- " + data[blk][24:36] + "-- -- -- -- -- --"
# +============================================================================= # +=============================================================================
# Dump data # Dump data
# >> blkn # >> blkn
@ -553,7 +581,8 @@ def dumpData():
cnt = 0 cnt = 0
for n in blkn: for n in blkn:
sec = (cnt // 4) sec = (cnt // 4)
if sec > 15: sec = sec + 16 if sec > 15:
sec = sec + 16
if (n % 4 == 0): if (n % 4 == 0):
lprint(f"{prompt} {sec:2d}:{data[cnt]}") lprint(f"{prompt} {sec:2d}:{data[cnt]}")
@ -564,6 +593,7 @@ def dumpData():
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(prompt)
# +============================================================================= # +=============================================================================
# Let's try to detect a Bambu card by the date strings... # Let's try to detect a Bambu card by the date strings...
# ============================================================================== # ==============================================================================
@ -572,7 +602,7 @@ def detectBambu():
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]
ds = bytes.fromhex(data[13][6:41]).decode('ascii').rstrip('\x00') ds = bytes.fromhex(data[13][6:41]).decode('ascii').rstrip('\x00')
except Exception as e: except Exception:
return False return False
# ds 24_03_22_16 # ds 24_03_22_16
@ -593,11 +623,9 @@ def detectBambu():
# 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[]" # >> "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():
global data global data
@ -646,12 +674,17 @@ def dumpBambu():
ShortProductionDateTime_s = bytes.fromhex(data[13][6:53]).decode('ascii').rstrip('\x00') ShortProductionDateTime_s = bytes.fromhex(data[13][6:53]).decode('ascii').rstrip('\x00')
Block14_0to3 = data[14][ 6:17] # Block14_0to3 = data[14][6:17]
FilamentLength_m = int(data[14][21:23] + data[14][18:20], 16) FilamentLength_m = int(data[14][21:23] + data[14][18:20], 16)
Block14_6to15 = data[14][24:51] # Block14_6to15 = data[14][24:51]
# (16blocks * 16bytes = 256) * 8bits = 2048 bits # (16blocks * 16bytes = 256) * 8bits = 2048 bits
hblk = [42, 44,45,46, 48,49,50, 52,53,54, 56,57,58, 60,61,62] hblk = [42,
44, 45, 46,
48, 49, 50,
52, 53, 54,
56, 57, 58,
60, 61, 62]
Hash = [] Hash = []
for b in hblk: for b in hblk:
Hash.append(data[b][6:53]) Hash.append(data[b][6:53])
@ -704,97 +737,95 @@ def dumpBambu():
except Exception as e: except Exception as e:
lprint(f"Failed: {e}") lprint(f"Failed: {e}")
# +============================================================================= # +=============================================================================
# Dump ACL # Dump ACL
# >> "data[][]" # >> "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...... # ab cd ef
ab cd ef #
''' # ,-------------------.
''' # ( 2.2 : ACCESS BITS )
,-------------------. # `-------------------'
( 2.2 : ACCESS BITS )
`-------------------'
The Access bits on both (used) Sectors is the same: 78 77 88 # The Access bits on both (used) Sectors is the same: 78 77 88
Let's reorganise that according to the official spec Fig 9. # Let's reorganise that according to the official spec Fig 9.
Access C1 C2 C3 # Access C1 C2 C3
========== =========== # ========== ===========
78 77 88 --> 78 87 87 # 78 77 88 --> 78 87 87
ab cd ef --> cb fa ed # ab cd ef --> cb fa ed
The second nybble of each byte is the inverse of the first nybble. # The second nybble of each byte is the inverse of the first nybble.
It is there to trap tranmission errors, so we can just ignore it/them. # It is there to trap tranmission errors, so we can just ignore it/them.
So our Access Control value is : {c, f, e} == {7, 8, 8} # So our Access Control value is : {c, f, e} == {7, 8, 8}
Let's convert those nybbles to binary # Let's convert those nybbles to binary
(c) 7 --> 0111 # (c) 7 --> 0111
(f) 8 --> 1000 # (f) 8 --> 1000
(e) 8 --> 1000 # (e) 8 --> 1000
|||| ...and transpose them: # |||| ...and transpose them:
|||| # ||||
|||`--- 100 - Block 0 Access bits # |||`--- 100 - Block 0 Access bits
||`---- 100 - Block 1 Access bits # ||`---- 100 - Block 1 Access bits
|`----- 100 - Block 2 Access bits # |`----- 100 - Block 2 Access bits
`------ 011 - Block 3 Access bits [Sector Trailer] # `------ 011 - Block 3 Access bits [Sector Trailer]
Now we can use the lookup table [Table 3] to work out what we can do # Now we can use the lookup table [Table 3] to work out what we can do
with the Sector Trailer (Block(S,3)): # with the Sector Trailer (Block(S,3)):
| Key A | | Access Bits | | Key B | # | Key A | | Access Bits | | Key B |
| read ¦ write | | read ¦ write | | read ¦ write | # | read ¦ write | | read ¦ write | | read ¦ write |
+------¦-------+ +------¦-------+ +------¦-------+ # +------¦-------+ +------¦-------+ +------¦-------+
000 : | -- ¦ KeyA | | KeyA ¦ -- | | KeyA ¦ KeyA | # 000 : | -- ¦ KeyA | | KeyA ¦ -- | | KeyA ¦ KeyA |
001 : | -- ¦ KeyA | | KeyA ¦ KeyA | | KeyA ¦ KeyA | Transport Mode # 001 : | -- ¦ KeyA | | KeyA ¦ KeyA | | KeyA ¦ KeyA | Transport Mode
010 : | -- ¦ -- | | KeyA ¦ -- | | KeyA ¦ -- | # 010 : | -- ¦ -- | | KeyA ¦ -- | | KeyA ¦ -- |
011 : | -- ¦ KeyB | | A+B ¦ KeyB | | -- ¦ KeyB | <-- Our Card! # 011 : | -- ¦ KeyB | | A+B ¦ KeyB | | -- ¦ KeyB | <-- Our Card!
100 : | -- ¦ KeyB | | A+B ¦ -- | | -- ¦ KeyB | # 100 : | -- ¦ KeyB | | A+B ¦ -- | | -- ¦ KeyB |
101 : | -- ¦ -- | | A+B ¦ KeyB | | -- ¦ -- | # 101 : | -- ¦ -- | | A+B ¦ KeyB | | -- ¦ -- |
110 : | -- ¦ -- | | A+B ¦ -- | | -- ¦ -- | }__ # 110 : | -- ¦ -- | | A+B ¦ -- | | -- ¦ -- | }__
111 : | -- ¦ -- | | A+B ¦ -- | | -- ¦ -- | } The Same!? # 111 : | -- ¦ -- | | A+B ¦ -- | | -- ¦ -- | } The Same!?
Our card uses 011, for (both of) the (used) Sector Trailer(s). So: # Our card uses 011, for (both of) the (used) Sector Trailer(s). So:
Both Key A and Key B can READ the Access Bits # Both Key A and Key B can READ the Access Bits
Key B can (additionally) WRITE to Key A, Key B (itself), and the Access Bits # Key B can (additionally) WRITE to Key A, Key B (itself), and the Access Bits
Then we can do a similar lookup for the 3 data Blocks (in this Sector) # Then we can do a similar lookup for the 3 data Blocks (in this Sector)
This time using [Table 4] # This time using [Table 4]
| Data | Counter | # | Data | Counter |
| read ¦ write | Inc ¦ Dec | # | read ¦ write | Inc ¦ Dec |
+------¦-------+------¦------+ # +------¦-------+------¦------+
000 : | A+B ¦ A+B | A+B ¦ A+B | Transport Mode # 000 : | A+B ¦ A+B | A+B ¦ A+B | Transport Mode
001 : | A+B ¦ -- | -- ¦ A+B | # 001 : | A+B ¦ -- | -- ¦ A+B |
010 : | A+B ¦ -- | -- ¦ -- | # 010 : | A+B ¦ -- | -- ¦ -- |
011 : | KeyB ¦ KeyB | -- ¦ -- | # 011 : | KeyB ¦ KeyB | -- ¦ -- |
100 : | A+B ¦ KeyB | -- ¦ -- | <-- Our Card! # 100 : | A+B ¦ KeyB | -- ¦ -- | <-- Our Card!
101 : | KeyB ¦ -- | -- ¦ -- | # 101 : | KeyB ¦ -- | -- ¦ -- |
110 : | A+B ¦ KeyB | KeyB ¦ A+B | # 110 : | A+B ¦ KeyB | KeyB ¦ A+B |
111 : | -- ¦ -- | -- ¦ -- | # 111 : | -- ¦ -- | -- ¦ -- |
Our card uses 100, for all of the (used) Sectors. So: # Our card uses 100, for all of the (used) Sectors. So:
Both Key A and Key B can READ the Block # Both Key A and Key B can READ the Block
Only Key B can WRITE to the Block # Only Key B can WRITE to the Block
The block cannot be used as a "counter" because: # The block cannot be used as a "counter" because:
Neither key can perform increment nor decrement commands # Neither key can perform increment nor decrement commands
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():
global blkn global blkn
aclkh = [] # key header aclkh = [] # key header
aclk = [0] * 8 # key lookup aclk = [""] * 8 # key lookup
aclkx = [] # key output aclkx = [] # key output
lprint(f"{prompt}") lprint(f"{prompt}")
@ -809,17 +840,17 @@ def dumpAcl():
aclkh.append("| | read ¦ write || read ¦ write || read ¦ write |") aclkh.append("| | read ¦ write || read ¦ write || read ¦ write |")
aclkh.append("|--------+------¦-------++------¦-------++------¦-------|") aclkh.append("|--------+------¦-------++------¦-------++------¦-------|")
# "| xx | -- ¦ KeyA || KeyA ¦ -- || KeyA ¦ KeyA |" # "| xx | -- ¦ KeyA || KeyA ¦ -- || KeyA ¦ KeyA |"
aclk[0] = "| -- ¦ KeyA || KeyA ¦ -- || KeyA ¦ KeyA | [000]" aclk[0] = "| -- ¦ KeyA || KeyA ¦ -- || KeyA ¦ KeyA | [000]" # noqa: E222
aclk[1] = "| -- ¦ KeyA || KeyA ¦ KeyA || KeyA ¦ KeyA | [001]" aclk[1] = "| -- ¦ KeyA || KeyA ¦ KeyA || KeyA ¦ KeyA | [001]" # noqa: E222
aclk[2] = "| -- ¦ -- || KeyA ¦ -- || KeyA ¦ -- | [010]" aclk[2] = "| -- ¦ -- || KeyA ¦ -- || KeyA ¦ -- | [010]" # noqa: E222
aclk[3] = "| -- ¦ KeyB || A+B ¦ KeyB || -- ¦ KeyB | [011]" aclk[3] = "| -- ¦ KeyB || A+B ¦ KeyB || -- ¦ KeyB | [011]" # noqa: E222
aclk[4] = "| -- ¦ KeyB || A+B ¦ -- || -- ¦ KeyB | [100]" aclk[4] = "| -- ¦ KeyB || A+B ¦ -- || -- ¦ KeyB | [100]" # noqa: E222
aclk[5] = "| -- ¦ -- || A+B ¦ KeyB || -- ¦ -- | [101]" aclk[5] = "| -- ¦ -- || A+B ¦ KeyB || -- ¦ -- | [101]" # noqa: E222
aclk[6] = "| -- ¦ -- || A+B ¦ -- || -- ¦ -- | [110]" # yes, the same!? aclk[6] = "| -- ¦ -- || A+B ¦ -- || -- ¦ -- | [110]" # noqa: E222 # yes, the same!?
aclk[7] = "| -- ¦ -- || A+B ¦ -- || -- ¦ -- | [111]" # ... aclk[7] = "| -- ¦ -- || A+B ¦ -- || -- ¦ -- | [111]" # noqa: E222 # ...
acldh = [] # data header acldh = [] # data header
acld = [0] * 8 # data lookup acld = [""] * 8 # data lookup
acldx = [] # data output acldx = [] # data output
acldh.append(" _____________________________________ ") acldh.append(" _____________________________________ ")
@ -829,16 +860,16 @@ def dumpAcl():
acldh.append("| | read ¦ write || Inc ¦ Dec |") acldh.append("| | read ¦ write || Inc ¦ Dec |")
acldh.append("|-------+------¦-------++------¦------+") acldh.append("|-------+------¦-------++------¦------+")
# "| xxx | A+B ¦ A+B || A+B ¦ A+B | " # "| xxx | A+B ¦ A+B || A+B ¦ A+B | "
acld[0] = "| A+B ¦ A+B || A+B ¦ A+B | [000]" acld[0] = "| A+B ¦ A+B || A+B ¦ A+B | [000]" # noqa: E222
acld[1] = "| A+B ¦ -- || -- ¦ A+B | [001]" acld[1] = "| A+B ¦ -- || -- ¦ A+B | [001]" # noqa: E222
acld[2] = "| A+B ¦ -- || -- ¦ -- | [010]" acld[2] = "| A+B ¦ -- || -- ¦ -- | [010]" # noqa: E222
acld[3] = "| KeyB ¦ KeyB || -- ¦ -- | [011]" acld[3] = "| KeyB ¦ KeyB || -- ¦ -- | [011]" # noqa: E222
acld[4] = "| A+B ¦ KeyB || -- ¦ -- | [100]" acld[4] = "| A+B ¦ KeyB || -- ¦ -- | [100]" # noqa: E222
acld[5] = "| KeyB ¦ -- || -- ¦ -- | [101]" acld[5] = "| KeyB ¦ -- || -- ¦ -- | [101]" # noqa: E222
acld[6] = "| A+B ¦ KeyB || KeyB ¦ A+B | [110]" acld[6] = "| A+B ¦ KeyB || KeyB ¦ A+B | [110]" # noqa: E222
acld[7] = "| -- ¦ -- || -- ¦ -- | [111]" acld[7] = "| -- ¦ -- || -- ¦ -- | [111]" # noqa: E222
idx = [0] * (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:
@ -851,10 +882,10 @@ def dumpAcl():
c = int(d[27], 16) c = int(d[27], 16)
f = int(d[31], 16) f = int(d[31], 16)
e = int(d[30], 16) e = int(d[30], 16)
r0 = ((c & (2**0)) << 2) | ((f & (2**0)) << 1) | ((e & (2**0)) ) r0 = ((c & (2**0)) << 2) | ((f & (2**0)) << 1) | ((e & (2**0)) ) # noqa: E202
r1 = ((c & (2**1)) << 1) | ((f & (2**1)) ) | ((e & (2**1)) >> 1) r1 = ((c & (2**1)) << 1) | ((f & (2**1)) ) | ((e & (2**1)) >> 1) # noqa: E202
r2 = ((c & (2**2)) ) | ((f & (2**2)) >> 1) | ((e & (2**2)) >> 2) r2 = ((c & (2**2)) ) | ((f & (2**2)) >> 1) | ((e & (2**2)) >> 2) # noqa: E202
r3 = ((c & (2**3)) >> 1) | ((f & (2**3)) >> 2) | ((e & (2**3)) >> 3) r3 = ((c & (2**3)) >> 1) | ((f & (2**3)) >> 2) | ((e & (2**3)) >> 3) # noqa: E221
idx[sec] = [r0, r1, r2, r3] idx[sec] = [r0, r1, r2, r3]
# --- build the ACL conversion table --- # --- build the ACL conversion table ---
@ -870,24 +901,27 @@ def dumpAcl():
acldx.append(f"| {bn:3d} " + acld[idx[sec][bn % 4]]) acldx.append(f"| {bn:3d} " + acld[idx[sec][bn % 4]])
# --- print it all out --- # --- print it all out ---
for l in aclkh: for line in aclkh:
lprint(f"{prompt} {l}") lprint(f"{prompt} {line}")
i = 0 i = 0
for l in aclkx: for line in aclkx:
lprint(f"{prompt} {l}") lprint(f"{prompt} {line}")
if (i % 4) == 3: lprint(f"{prompt} | | ¦ || ¦ || ¦ |") if (i % 4) == 3:
lprint(f"{prompt} | | ¦ || ¦ || ¦ |")
i += 1 i += 1
lprint(f"{prompt}") lprint(f"{prompt}")
for l in acldh: for line in acldh:
lprint(f"{prompt} {l}") lprint(f"{prompt} {line}")
i = 0 i = 0
for l in acldx: for line in acldx:
lprint(f"{prompt} {l}") lprint(f"{prompt} {line}")
if (i % 3) == 2: lprint(f"{prompt} | | ¦ || ¦ |") if (i % 3) == 2:
lprint(f"{prompt} | | ¦ || ¦ |")
i += 1 i += 1
# +============================================================================= # +=============================================================================
# Full Dump # Full Dump
# >> "uid" # >> "uid"
@ -905,10 +939,13 @@ def diskDump():
bad = False bad = False
with open(dump18, 'wb') as f: with open(dump18, 'wb') as f:
for d in data: for d in data:
if "--" in d[6:53]: bad = True if "--" in d[6:53]:
bad = True
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: lprint(f"{prompt} Bad data exists, and has been saved as 0xFF") if bad:
lprint(f"{prompt} Bad data exists, and has been saved as 0xFF")
# +============================================================================= # +=============================================================================
# Dump MAD # Dump MAD
@ -930,13 +967,14 @@ def dumpMad():
lprint(f'{prompt} `-._,-\'"`-._,-"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,') lprint(f'{prompt} `-._,-\'"`-._,-"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,')
lprint("") lprint("")
res = 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(f'{prompt} `-._,-\'"`-._,-"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,')
# ++============================================================================ # ++============================================================================
if __name__ == "__main__": if __name__ == "__main__":
main() main()