Skip to content

Instantly share code, notes, and snippets.

@doegox
Created September 10, 2024 20:22
Show Gist options
  • Save doegox/1ddc5725d0f6e3e58a023f6ffbff0d8c to your computer and use it in GitHub Desktop.
Save doegox/1ddc5725d0f6e3e58a023f6ffbff0d8c to your computer and use it in GitHub Desktop.
Crappy script for fingerprinting MFC cards
#!/usr/bin/env python3
import time
MFINFO = True
ATS = True
FDT = True
NACK = True
SF = True
CMD00 = True
ACL = True
WRITE = True
# MFINFO = False
# ATS = False
# FDT = False
# NACK = False
# SF = False
# CMD00 = False
# ACL = False
# WRITE = False
USERKEY = "FFFFFFFFFFFF"
# USERKEY = "000000000000"
def append_crc16_a(buf):
binbuf = bytes.fromhex(buf)
crc = 0x6363
for i in range(len(binbuf)):
crc ^= binbuf[i]
for j in range(8):
if crc & 0x0001:
crc = (crc >> 1) ^ 0x8408
else:
crc >>= 1
return binbuf.hex()+f"{crc & 0xFF:02x}{(crc >> 8) & 0xFF:02x}"
def validate_nonce(nonce):
x = nonce >> 16
x = (x & 0xff) << 8 | x >> 8
for i in range(16):
x = x >> 1 | (x ^ x >> 2 ^ x >> 3 ^ x >> 5) << 15
x &= 0xffff
x = (x & 0xff) << 8 | x >> 8
return x == (nonce & 0xFFFF)
i_fibonacci = [0] * (1 << 16)
s_fibonacci = [0] * (1 << 16)
x = 1
for i in range(0, 1 << 16):
i_fibonacci[(x & 0xff) << 8 | x >> 8] = i
s_fibonacci[i] = (x & 0xff) << 8 | x >> 8
x = x >> 1 | (x ^ x >> 2 ^ x >> 3 ^ x >> 5) << 15
x &= 0xffff
def nonce_distance_fibonacci(nt16from, nt16to):
return (65535 + i_fibonacci[nt16to] - i_fibonacci[nt16from]) % 65535
def nonce16_index(nt16):
return nonce_distance_fibonacci(0x100, nt16) + 1
class crypto1():
@staticmethod
def BIT(x, n):
return x >> n & 1
@staticmethod
def BEBIT(x, n):
return crypto1.BIT(x, (n) ^ 24)
@staticmethod
def crypto1_filter(x):
f = 0xf22c0 >> (x & 0xf) & 16
f |= 0x6c9c0 >> (x >> 4 & 0xf) & 8
f |= 0x3c8b0 >> (x >> 8 & 0xf) & 4
f |= 0x1e458 >> (x >> 12 & 0xf) & 2
f |= 0x0d938 >> (x >> 16 & 0xf) & 1
return crypto1.BIT(0xEC57E80A, f)
OddByteParity = [
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0,
1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1]
@staticmethod
def evenparity8(x):
return not crypto1.OddByteParity[x]
@staticmethod
def evenparity32(x):
x ^= x >> 16
x ^= x >> 8
return crypto1.evenparity8(x & 0xFF)
def crypto1_bit(self, input, is_encrypted):
ret = crypto1.crypto1_filter(self.crypto1_state[0])
LF_POLY_ODD = 0x29CE5C
LF_POLY_EVEN = 0x870804
feedin = ret & is_encrypted
feedin ^= input & 1
feedin ^= LF_POLY_ODD & self.crypto1_state[0]
feedin ^= LF_POLY_EVEN & self.crypto1_state[1]
self.crypto1_state[1] = (self.crypto1_state[1] << 1 | (crypto1.evenparity32(feedin))) & 0xFFFFFFFF
self.crypto1_state[0], self.crypto1_state[1] = self.crypto1_state[1], self.crypto1_state[0]
return ret
def crypto1_word(self, input, is_encrypted):
ret = 0
ret |= self.crypto1_bit(crypto1.BEBIT(input, 0), is_encrypted) << (24 ^ 0)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 1), is_encrypted) << (24 ^ 1)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 2), is_encrypted) << (24 ^ 2)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 3), is_encrypted) << (24 ^ 3)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 4), is_encrypted) << (24 ^ 4)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 5), is_encrypted) << (24 ^ 5)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 6), is_encrypted) << (24 ^ 6)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 7), is_encrypted) << (24 ^ 7)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 8), is_encrypted) << (24 ^ 8)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 9), is_encrypted) << (24 ^ 9)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 10), is_encrypted) << (24 ^ 10)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 11), is_encrypted) << (24 ^ 11)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 12), is_encrypted) << (24 ^ 12)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 13), is_encrypted) << (24 ^ 13)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 14), is_encrypted) << (24 ^ 14)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 15), is_encrypted) << (24 ^ 15)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 16), is_encrypted) << (24 ^ 16)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 17), is_encrypted) << (24 ^ 17)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 18), is_encrypted) << (24 ^ 18)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 19), is_encrypted) << (24 ^ 19)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 20), is_encrypted) << (24 ^ 20)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 21), is_encrypted) << (24 ^ 21)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 22), is_encrypted) << (24 ^ 22)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 23), is_encrypted) << (24 ^ 23)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 24), is_encrypted) << (24 ^ 24)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 25), is_encrypted) << (24 ^ 25)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 26), is_encrypted) << (24 ^ 26)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 27), is_encrypted) << (24 ^ 27)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 28), is_encrypted) << (24 ^ 28)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 29), is_encrypted) << (24 ^ 29)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 30), is_encrypted) << (24 ^ 30)
ret |= self.crypto1_bit(crypto1.BEBIT(input, 31), is_encrypted) << (24 ^ 31)
return ret
@staticmethod
def SWAPENDIAN(x):
x = (x >> 8 & 0xff00ff) | (x & 0xff00ff) << 8
x = x >> 16 | (x & 0xffff) << 16
return x
@staticmethod
def prng_successor(x, n):
x = crypto1.SWAPENDIAN(x)
for _ in range(n):
x = (x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31) & 0xFFFFFFFF
return crypto1.SWAPENDIAN(x)
@staticmethod
def suc64(nt):
return crypto1.prng_successor(nt, 64)
@staticmethod
def suc96(nt):
return crypto1.prng_successor(nt, 96)
def crypto1_init(self, key):
self.crypto1_state = [0, 0]
for i in range(47, 0, -2):
self.crypto1_state[0] = (self.crypto1_state[0] << 1 | crypto1.BIT(key, (i - 1) ^ 7)) & 0xFFFFFFFF
self.crypto1_state[1] = (self.crypto1_state[1] << 1 | crypto1.BIT(key, i ^ 7)) & 0xFFFFFFFF
def __init__(self, key, uid, nt, nr="12345678"):
key = int(key, 16)
uid = int(uid, 16)
nt = int(nt, 16)
nt = int(nr, 16)
self.crypto1_init(key)
_ = self.crypto1_word(uid ^ nt, 0) # ks0
ks1 = self.crypto1_word(nr, 0)
enc_nr = nr ^ ks1
ar = crypto1.suc64(nt)
ks2 = self.crypto1_word(0, 0)
enc_ar = ks2 ^ ar
return f"{enc_nr:08X}{enc_ar:08X}"
def crypto1_next(self, nt, enc_at, cmd):
nt = int(nt, 16)
enc_at = int(enc_at, 16)
ks3 = self.crypto1_word(0, 0)
at = ks3 ^ enc_at
_ = at
ks4 = self.crypto1_word(0, 0)
cmd_crc = int(append_crc16_a(cmd), 16)
enc_cmd = ks4 ^ cmd_crc
return f"{enc_cmd:08x}"
class mypm3():
interrupt_requested = False
def signal_handler(self, sig, frame):
print('You pressed CTRL+C!')
self.interrupt_requested = True
def __init__(self):
import pm3
import signal
# Setting up signal handler
signal.signal(signal.SIGINT, self.signal_handler)
self.start_time = time.time()
self.p = pm3.pm3()
def stop_session(self):
elapsed_time = time.time() - self.start_time
minutes = int(elapsed_time // 60)
seconds = int(elapsed_time % 60)
print(f"--- {minutes} minutes {seconds} seconds ---")
def run(self, cmds):
DEBUG = False
if type(cmds) is not list:
cmds = [cmds]
if DEBUG:
for cmd in cmds:
print(cmd)
for cmd in cmds:
self.p.console(cmd)
grabbed_output = self.p.grabbed_output
if DEBUG:
print(grabbed_output)
return grabbed_output.split('\n')
uid = None
buid = None
atqa = None
sak = None
def read_14a(self):
for line in self.run(["hf 14a read"]):
if "UID:" in line:
self.uid = int(line[10:].replace(' ', ''), 16)
if len(line) > 31:
self.buid = self.uid.to_bytes(10, byteorder='big')
elif len(line) > 22:
self.buid = self.uid.to_bytes(7, byteorder='big')
else:
self.buid = self.uid.to_bytes(4, byteorder='big')
if "ATQA:" in line:
self.atqa = line[10:15]
if "SAK:" in line:
self.sak = line[10:12]
return self.uid
block0 = None
block0_direct = False
auth = None
auth_desc = None
def check_auth(self):
auths = {
"-c 4 --key A31667A8CEC1": "FM11RF08 backdoor key",
"-c 5 --key A31667A8CEC1": "FM11RF08 backdoor key",
"-c 6 --key A31667A8CEC1": "FM11RF08 backdoor key",
"-c 7 --key A31667A8CEC1": "FM11RF08 backdoor key",
"-c 4 --key A396EFA4E24F": "FM11RF08S backdoor key",
"-c 5 --key A396EFA4E24F": "FM11RF08S backdoor key",
"-c 6 --key A396EFA4E24F": "FM11RF08S backdoor key",
"-c 7 --key A396EFA4E24F": "FM11RF08S backdoor key",
"-c 4 --key 518B3354E760": "FM11?? backdoor key",
"-c 5 --key 518B3354E760": "FM11?? backdoor key",
"-c 6 --key 518B3354E760": "FM11?? backdoor key",
"-c 7 --key 518B3354E760": "FM11?? backdoor key",
"-c 4 --key FFFFFFFFFFFF": "backdoor with regular keyA/keyB",
"-c 5 --key FFFFFFFFFFFF": "backdoor with regular keyA/keyB",
"-c 6 --key FFFFFFFFFFFF": "backdoor with regular keyA/keyB",
"-c 7 --key FFFFFFFFFFFF": "backdoor with regular keyA/keyB",
"-c 4 --key A0A1A2A3A4A5": "backdoor with regular keyA/keyB",
"-c 5 --key A0A1A2A3A4A5": "backdoor with regular keyA/keyB",
"-c 6 --key A0A1A2A3A4A5": "backdoor with regular keyA/keyB",
"-c 7 --key A0A1A2A3A4A5": "backdoor with regular keyA/keyB",
"-c 4 --key 000000000000": "backdoor with regular keyA/keyB",
"-c 5 --key 000000000000": "backdoor with regular keyA/keyB",
"-c 6 --key 000000000000": "backdoor with regular keyA/keyB",
"-c 7 --key 000000000000": "backdoor with regular keyA/keyB",
"-c 0 --key FFFFFFFFFFFF": "no backdoor command",
"-c 1 --key FFFFFFFFFFFF": "no backdoor command",
"-c 0 --key A0A1A2A3A4A5": "no backdoor command",
"-c 1 --key A0A1A2A3A4A5": "no backdoor command",
"-c 0 --key 000000000000": "no backdoor command",
"-c 1 --key 000000000000": "no backdoor command"
}
for auth, authrem in auths.items():
for line in self.run([f"hf mf rdbl --blk 0 {auth}"]):
if "0 |" in line:
self.block0 = line[10:57].replace(' ', '')
self.auth_desc = authrem
self.auth = auth
break
if self.auth_desc is not None:
break
for line in self.run("hf 14a raw -sc 3000"):
if len(line) > 10:
self.block0_direct = True
if self.block0 is None:
self.block0 = line[4:52].replace(' ', '')
ats = None
def check_ats(self):
for line in self.run(["hf 14a raw -s3c e000"]):
if len(line) > 0:
self.ats = line[4:].replace(' ', '').replace('[', '').replace(']', '')
return self.ats
fdt = None
def check_fdt(self, auth="6000"):
flag = False
for line in self.run([f"hf 14a raw -s3c {auth}",
"trace list -t mf --frame"]):
if "Rdr |60 00" in line:
flag = True
if flag and "Frame Delay Time" in line:
self.fdt = int(line[56:].rstrip())
return self.fdt
nakp256 = None
nakalways = None
checkparity = None
def check_nack(self):
if self.auth is None:
self.check_auth()
assert self.auth is not None
self.nakp256 = False
self.nakalways = False
self.checkparity = False
for line in self.run([f"hf mf isen {self.auth} --corruptnrar"]):
if "Error card response" in line:
self.nakp256 = True
for line in self.run([f"hf mf isen {self.auth} --corruptnrarparity"]):
if "Card timeout" in line:
self.checkparity = True
if "Error card response" in line:
self.checkparity = True
for line in self.run([f"hf mf isen {self.auth} --corruptnrar --corruptnrarparity"]):
if "Error card response" in line:
self.nakalways = True
shortframes = None
def check_shortframes(self):
cmds = []
for cmd in range(128):
cmds.append(f"rem {cmd:02x}")
cmds.append(f"hf 14a raw -a -b7 {cmd:02x}")
self.shortframes = []
for line in self.run(cmds):
if 'remark:' in line:
cmd = int(line[33:], 16)
elif len(line) > 0:
resp = line[4:].replace(' ', '')
if cmd == 0x26:
extra = "(REQA)"
elif cmd == 0x52:
extra = "(WUPA)"
else:
extra = "(???)"
self.shortframes.append((cmd, resp, extra))
return self.shortframes
def check_shortcut_commands(self, sf=0x26, param="00"):
# Add REQA to possibly CHECK_SF list
cmds = []
for cmd in range(256):
cmds.append("rem SF")
cmds.append(f"hf 14a raw -ak -b7 {sf:02x}")
cmds.append(f"rem {cmd:02x}")
cmds.append(f"hf 14a raw -c {cmd:02x}{param}")
results = []
for line in self.run(cmds):
if 'remark: SF' in line:
cmd = None
elif 'remark:' in line:
cmd = int(line[33:], 16)
elif len(line) > 0 and cmd is not None:
resp = line[4:].replace(' ', '')
results.append((cmd, resp))
return results
def check_commands(self, sf=0x52, param="00"):
results = []
for cmd_byte in range(256):
if sf == 0x52:
cmd = f"hf 14a raw -s3c {cmd_byte:02x}{param}"
else:
self.select(sf, keep=True)
cmd = f"hf 14a raw -c {cmd_byte:02x}{param}"
# print("CMD", cmd)
for line in self.run(cmd):
# print("RSP", line)
if len(line) > 0:
resp = line[4:].replace(' ', '')
results.append((cmd_byte, resp))
return results
@staticmethod
def repr_range(vals):
s = ""
if len(vals) > 0:
prev = None
rang = False
for i in vals:
if prev is None:
s += f"{i:02x}"
prev = i
else:
if i == prev + 1:
if rang is False:
s += "-"
rang = True
prev = i
else:
if rang is True:
s += f"{prev:02x},{i:02x}"
else:
s += f",{i:02x}"
prev = i
rang = False
if rang is True:
s += f"{prev:02x}"
return s
uidx = None
uidx2 = None
def select(self, sf=0x26, keep=False):
if self.uidx is None:
cmds = [f"hf 14a raw -ak -b7 {sf:02x}",
"hf 14a raw 9320"]
for line in self.run(cmds):
if len(line) >= 22:
self.uidx = line[4:].replace(' ', '').replace('[', '').replace(']', '')
if self.uidx is None:
print(f"Failed selecting the card with {sf:02x}(7);9320")
return
if self.uidx[:2] == "88" and self.uidx2 is None:
cmds = [f"hf 14a raw -ak -b7 {sf:02x}",
# "hf 14a raw -k 9320",
f"hf 14a raw -kc 9370{self.uidx}",
"hf 14a raw 9520"]
for line in self.run(cmds):
if len(line) >= 22:
self.uidx2 = line[4:].replace(' ', '').replace('[', '').replace(']', '')
if self.uidx2 is None:
print(f"Failed L2 selecting the card with {sf:02x}(7);9520")
return
if keep:
cmds = [f"hf 14a raw -ak -b7 {sf:02x}",
f"hf 14a raw -kc 9370{self.uidx}"]
if self.uidx[:2] == "88" and self.uidx2 is not None:
cmds.append(f"hf 14a raw -kc 9570{self.uidx2}")
self.run(cmds)
def get_nt_first(self, n=1, blk=0, softreset=False):
if self.auth is None:
self.check_auth()
assert self.auth is not None
results = []
if softreset:
reset = "--reset"
else:
reset = "--hardreset"
for line in self.run(f"hf mf isen --blk {blk} {self.auth} -n {n} {reset}"):
if "auth cmd" in line:
nt32 = int(line.split('|')[3][5:13], 16)
ntindex = int(line.split('|')[3][19:], 16)
results.append((nt32, ntindex))
# print('\n'.join(self.run("trace list -t mf")))
# TODO: collect timestamps
return results
def add_backquotes_to_hex_ranges(hex_string):
# Split the input string by commas
parts = hex_string.split(',')
# Process each part
processed_parts = []
for part in parts:
# If the part contains a range (indicated by '-')
if '-' in part:
# Split the range into start and end
start, end = part.split('-')
# Add backquotes and reassemble the range
processed_part = f"`{start}`-`{end}`"
else:
# Otherwise, simply add backquotes to the part
processed_part = f"`{part}`"
# Add the processed part to the list
processed_parts.append(processed_part)
# Join the processed parts back with commas
result = ','.join(processed_parts)
return result
p = mypm3()
width = 16
p.read_14a()
if p.uid is None:
print("Error getting UID!!")
p.stop_session()
exit()
if MFINFO:
print('\n'.join(p.run("hf mf info")))
print(f"{'UID:':{width}} {p.uid:0{len(p.buid)*2}X}")
print(f"{'ATQA anticol:':{width}} {p.atqa}")
print(f"{'SAK anticol:':{width}} {p.sak}")
p.check_auth()
if p.block0 is not None:
print(f"{'Block 0:':{width}} {p.block0}")
if p.block0_direct:
print("Block 0: direct read support (3000)")
if len(p.buid) == 7:
sak_b0 = p.block0[14:16]
else:
sak_b0 = p.block0[10:12]
print(f"{'SAK Block 0:':{width}} {sak_b0}")
if p.auth is not None:
print(f"{'Auth Backdoor?:':{width}} {p.auth_desc} ({p.auth})")
if ATS:
ats = p.check_ats()
print(f"{'Answer to RATS:':{width}} {ats}")
if FDT:
fdt = p.check_fdt()
print(f"{'FDT to 6000:':{width}} {fdt}")
if NACK:
p.check_nack()
print(f"{'Check nRaR par:':{width}} {p.checkparity}")
if p.nakalways:
assert p.nakp256 is True
print(f"{'nRaR NAK:':{width}} p=1")
elif p.nakp256:
print(f"{'nRaR NAK:':{width}} p=1/256")
else:
print(f"{'nRaR NAK:':{width}} p=0")
if SF:
print("Checking ShortFrames...")
sf = ""
for cmd, resp, extra in p.check_shortframes():
print(f"ShortFrame {cmd:02x}:{'':{width-14}} {resp} {extra}")
if cmd not in [0x26, 0x52]:
if len(sf) == 0:
sf += f"`{cmd:02x}`:`{resp}`"
else:
sf += f", `{cmd:02x}`:`{resp}`"
if len(sf) > 0:
sf = f", a_sf:[{sf}]"
else:
sf = ", a_sf:[SKIPPED]"
# sf = 0x26
# print(f"Checking Shortcut Commands {sf:02x};**00 ...")
# results = p.check_shortcut_commands()
# for cmd, resp in results:
# if resp != '04':
# print(f"Cmd {sf:02x};{cmd:02x}00:{'':{width-4}} {resp}")
if CMD00:
print("Checking Commands **00 ...")
results = p.check_commands()
for cmd, resp in results:
if resp not in ['00', '04']:
print(f"Cmd {cmd:02x}00:{'':{width-12}} {resp}")
a_00 = ""
a_04 = ""
if '00' in [r for _, r in results]:
a_00 = p.repr_range([c for c, r in results if r == '00'])
print(f"Cmd **00=00:{'':{width-15}} " + a_00)
if '04' in [r for _, r in results]:
a_04 = p.repr_range([c for c, r in results if r == '04'])
print(f"Cmd **00=04:{'':{width-15}} " + a_04)
# sf = 0x26
# print(f"Checking Commands {sf:02x}...**00 ...")
# results = p.check_commands(sf=sf)
# for cmd, resp in results:
# if resp != '04':
# print(f"Cmd {cmd:02x}00:{'':{width-12}} {resp}")
# print(f"Cmd **00=04:{'':{width-15}} " + p.repr_range([c for c, r in results if r == '04']))
# Example of manual anticol with SF=0x0e
# p.select(0x0e, keep=True)
# print('\n'.join(p.run("hf 14a raw -c 6000")))
# print("Sampling 10 First nT after hardreset:")
# indexes = [i for _, i in p.get_nt_first(n=10)]
# counter = Counter(indexes)
# sorted_indexes = sorted(counter.items())
# for index, count in sorted_indexes:
# print(f"{index}: {count}")
# print("Sampling 10 First nT, chaining valid nested auths and soft reset:")
# indexes = [i for _, i in p.get_nt_first(n=10, softreset=True)]
# for index in indexes:
# print(f"{index}")
# TODO: more nonce loop analysis...
if ACL:
blk_7F0788 = 19
blk_FF0780 = 15
# http://calc.gmss.ru/Mifare1k/
# Configure block 19 ACL to allow reading with keyB:
# (it will require keyB to update the trailer sector)
if WRITE:
p.run(f"hf mf wrbl --blk {blk_FF0780} -a -k FFFFFFFFFFFF -d FFFFFFFFFFFFFF078069FFFFFFFFFFFF")
p.run(f"hf mf wrbl --blk {blk_FF0780} -b -k FFFFFFFFFFFF -d FFFFFFFFFFFFFF078069FFFFFFFFFFFF")
cmd = f"hf mf rdbl --blk {blk_FF0780} -a -k {USERKEY}"
found = False
acl = ""
for line in p.run(cmd):
if f"{blk_FF0780} |" in line:
acl = line[28:36].replace(' ', '')
if acl == "FF0780":
found = True
if not found:
print(f"WARNING ACL of block {blk_FF0780}={acl}")
if WRITE:
p.run(f"hf mf wrbl --blk {blk_7F0788} -a -k FFFFFFFFFFFF -d FFFFFFFFFFFF7F078869FFFFFFFFFFFF")
cmd = f"hf mf rdbl --blk {blk_7F0788} -a -k {USERKEY}"
found = False
acl = ""
for line in p.run(cmd):
if f"{blk_7F0788} |" in line:
acl = line[28:36].replace(' ', '')
if acl == "7F0788":
found = True
if not found:
print(f"WARNING ACL of block {blk_FF0780}={acl}")
backdoor = False
acls = ["", ""] # 7F0788, FF0780
for blk in [blk_7F0788, blk_FF0780]:
cmd = f"hf mf rdbl --blk {blk} -a -k {USERKEY}"
found = False
acl_off = blk == blk_FF0780
for line in p.run(cmd):
if f"{blk} |" in line:
acl = line[28:36].replace(' ', '')
if acl == "7F0788":
print("ACL 7F0788: keyB unreadable can be used for reading data")
elif acl == "FF0780":
print("ACL FF0780: keyB readable, should not be possible to read data")
else:
print(f"WARNING ACL {acl}")
for c in range(16):
keys = [
"A396EFA4E24F",
"A31667A8CEC1",
"518B3354E760",
f"{USERKEY}",
]
found = False
if c % 8 > 3:
mykeys = keys
else:
mykeys = [f"{USERKEY}"]
for k in mykeys:
cmd = f"hf mf rdbl --blk {blk-3} -c {c} --key {k}"
for line in p.run(cmd):
if "Read block error" in line:
print(f"blk={blk-3} c={c:2} read=FAIL k={k}")
found = True
p.run("hf mf rdbl --blk 0 -c 0")
for line in p.run(cmd):
if "Read block error" in line:
print(f"blk={blk-3} c={c:2} read=FAIL k={k}")
acls[acl_off] += "✗"
if f"{blk-3} |" in line:
print(f"blk={blk-3} c={c:2} read=OK k={k}")
found = True
if k == f"{USERKEY}":
acls[acl_off] += "v"
else:
acls[acl_off] += "b"
# acls[acl_off] += "✗"
if c > 1:
backdoor = True
if f"{blk-3} |" in line:
print(f"blk={blk-3} c={c:2} read=OK k={k}")
found = True
if k == f"{USERKEY}":
acls[acl_off] += "✓"
else:
acls[acl_off] += "B"
if c > 1:
backdoor = True
if found:
break
if not found:
if len(p.run(f"hf 14a raw -sc 6{c:x}{blk-3}")[0]) > 10:
print(f"blk={blk-3} c={c:2} Unknown key??")
backdoor = True
acls[acl_off] += "?"
else:
print(f"blk={blk-3} c={c:2} No nT")
acls[acl_off] += "⋅"
if c in [3, 7, 11]:
acls[acl_off] += " "
if WRITE:
p.run(f"hf mf wrbl --blk {blk_7F0788} -b -k FFFFFFFFFFFF -d FFFFFFFFFFFFFF078069FFFFFFFFFFFF")
print(" (sample:\"TODO:\", sample_desc:\"TODO:\"", end='')
print(f",\n block0:\"{p.block0}\"", end='')
if len(p.buid) == 7:
print(", uid7b:true", end='')
sak_b0 = f"TODO:{sak_b0}"
print(f",\n attrib:\"TODO:\"", end='')
print(f", sak:\"{p.sak}\"", end='')
print(f", sak_b0:\"{sak_b0}\"", end='')
print(f"{sf}", end='')
if CMD00:
if len(a_04) > 0:
print(f",\n a_04:[{add_backquotes_to_hex_ranges(a_04)}]", end='')
if len(a_00) > 0:
print(f",\n a_04:[{add_backquotes_to_hex_ranges(a_00)}"
"#footnote[Card returns 4-bit 0x00 instead of the usual 0x04 NAK.]]", end='')
if NACK:
if p.nakalways:
nak = ", p_nack:$p(\"NAK\")=1$"
elif p.nakp256:
nak = ", p_nack:$p(\"NAK\")=1/256$"
else:
nak = ", p_nack:$p(\"NAK\")=0$"
print(f"{nak}", end='')
print(",\n ", end='')
if NACK:
print(f"a_perr:{str(not p.checkparity).lower()}", end='')
if FDT:
print(f", fdt:\"TODO:{fdt+80}\"", end='')
if ACL:
if "⋅⋅ ⋅⋅⋅⋅ ⋅⋅⋅⋅ ⋅⋅⋅⋅" in acls[0] and "⋅⋅ ⋅⋅⋅⋅ ⋅⋅⋅⋅ ⋅⋅⋅⋅" in acls[1]:
backdoor = ""
elif "B" not in acls[1] and "B" not in acls[1] and "?" not in acls[1] and "?" not in acls[1]:
backdoor = ", backdoor: \"userkeys\""
elif "?" in acls[1] or "?" in acls[1]:
backdoor = ", backdoor: \"TODO:\""
elif "B" in acls[1] or "B" in acls[1]:
backdoor = f", backdoor: \"{p.auth[-12:]}\""
else: # should not happen
backdoor = ", backdoor: \"TODO:\""
else:
backdoor = ", backdoor: \"SKIPPED\""
print(f"{backdoor}", end='')
if ACL:
print(f",\n acl_7f0788:\"{acls[0]}\", acl_ff0780:\"{acls[1]}\"", end='')
if p.block0_direct:
print(",\n misc: \"Can read block 0 without auth\"", end='')
print("),")
p.stop_session()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment