Created
September 10, 2024 20:22
-
-
Save doegox/1ddc5725d0f6e3e58a023f6ffbff0d8c to your computer and use it in GitHub Desktop.
Crappy script for fingerprinting MFC cards
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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