From 38a745fdf42fdefb24c1f0fcec700c152678e7be Mon Sep 17 00:00:00 2001 From: DidierA <1620015+didiera@users.noreply.github.com> Date: Wed, 17 Jun 2026 14:16:49 +0200 Subject: [PATCH] hf 14a sniff: add parity checks --- software/script/chameleon_cli_unit.py | 22 +++++++++++++++------- software/script/chameleon_utils.py | 5 +++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 264da66c..7b756ed1 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -31,6 +31,7 @@ from chameleon_utils import ( execute_tool, tqdm_if_exists, print_key_table, + odd_parity_byte, default_cwd ) @@ -7786,7 +7787,7 @@ class HF14ASniff(BaseCLIUnit): # Bit 15 of szBits: 0 = reader→card, 1 = card→reader (new firmware). # Old firmware always sends bit15=0; parser is backward compatible. buf = bytes(resp.data) - frames = [] # (szBits, data, is_tx) + frames = [] # (szBits, data, is_tx, parity) i = 0 while i + 2 <= len(buf): hdr = (buf[i] << 8) | buf[i+1] @@ -7801,6 +7802,9 @@ class HF14ASniff(BaseCLIUnit): raw = buf[i:i+szBytes] i += szBytes + # parity array: parity for each byte of data array + parity_bits = [] + # ISO14443-A frames include one parity bit per byte. # Short frames (< 8 bits, e.g. REQA=7 bits) have no parity. # All other frames: szBits = data_bytes * 9, strip every 9th bit. @@ -7816,19 +7820,20 @@ class HF14ASniff(BaseCLIUnit): for b in range(8): val |= all_bits[nb * 9 + b] << b stripped.append(val) + parity_bits.append(all_bits[nb * 9 + 8]) data = bytes(stripped) szBits = n_bytes * 8 else: data = raw - frames.append((szBits, data, is_tx)) + frames.append((szBits, data, is_tx, parity_bits)) if not frames: print(f"{CR}No frames decoded{C0}") return - rx_count = sum(1 for _, _, tx in frames if not tx) - tx_count = sum(1 for _, _, tx in frames if tx) + rx_count = sum(1 for _, _, tx, _ in frames if not tx) + tx_count = sum(1 for _, _, tx, _ in frames if tx) if tx_count > 0: print(f" Captured : {CG}{len(frames)}{C0} frame(s) " f"({CY}{rx_count}{C0} reader→card {CG}{tx_count}{C0} card→reader)") @@ -7844,8 +7849,11 @@ class HF14ASniff(BaseCLIUnit): last_auth_keytype = None last_auth_block = None - for n, (szBits, data, is_tx) in enumerate(frames): - hex_str = ' '.join(f'{b:02x}' for b in data) + for n, (szBits, data, is_tx, parity_bits) in enumerate(frames): + if (len(data) == len(parity_bits)): + hex_str = ' '.join(f"{b:02x}{'!' if odd_parity_byte(b)!= p else ''}" for (b,p) in zip(data,parity_bits)) + else: + hex_str = ' '.join(f"{b:02x}" for b in data) # is_tx==True means CU transmitted (card -> reader). # is_tx==False means reader -> card. @@ -7887,7 +7895,7 @@ class HF14ASniff(BaseCLIUnit): # Summary block (pass only reader→card frames for protocol decode) print() - _print_14a_sniff_summary(frames) # full frames needed for nonce extraction + _print_14a_sniff_summary([(szBits, data, is_tx) for (szBits, data, is_tx,parity) in frames]) # full frames needed for nonce extraction @hf_14a.command("auth-trace") diff --git a/software/script/chameleon_utils.py b/software/script/chameleon_utils.py index c257dc18..d24abde5 100644 --- a/software/script/chameleon_utils.py +++ b/software/script/chameleon_utils.py @@ -209,6 +209,11 @@ def parity_to_str(nt_par_err): ] ) +def odd_parity_byte(value: int) -> int: + assert(value >=0 and value <= 255) + for i in [4, 2, 1]: + value ^= value >> i + return (value & 1) ^ 1 def execute_tool(tool_name, args): if sys.platform == "win32":