From 4226c9a11c898fdf4a4c100c0dc3d566a4e6b150 Mon Sep 17 00:00:00 2001 From: michael Date: Thu, 16 Jul 2026 17:59:14 -0700 Subject: [PATCH] docs: C client oracle + LF demod parity notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Capture the investigation behind the T5577 live validation: why the stock C client won't run here (VS Code snap sandbox injects core20 libpthread — not a client bug), the LF clock-detection parity gap vs the C client's DetectASKClock (pm3py's detect_clock was weaker; now partially closed), and the data-block read investigation. The latter reached ground truth via datasheet + C source + an on-hardware write-read-back: no read/addressing bug — the live tag is just a reverse-order EM4100 clone; the only artifact is unresolved Manchester polarity (a real, resolvable improvement, noted). Co-Authored-By: Claude Opus 4.8 --- ...-16-c-client-oracle-and-lf-demod-parity.md | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 docs/plans/2026-07-16-c-client-oracle-and-lf-demod-parity.md diff --git a/docs/plans/2026-07-16-c-client-oracle-and-lf-demod-parity.md b/docs/plans/2026-07-16-c-client-oracle-and-lf-demod-parity.md new file mode 100644 index 0000000..eb03beb --- /dev/null +++ b/docs/plans/2026-07-16-c-client-oracle-and-lf-demod-parity.md @@ -0,0 +1,87 @@ +# C client oracle + LF demod parity — future-work notes + +*2026-07-16. Written while finishing the rawcli T5577 catalog and validating on real +hardware (a T5577 emulating **EM4100 `00FFFFFFFF`**, config word **`00148040`**).* + +I wanted the stock proxmark3 C client as a ground-truth **oracle** for LF demod — to +cross-check pm3py's decode of the live tag. It wouldn't run in this environment, and digging +into why surfaced the notes below. **Net: there is no C-client _code_ bug to PR from what we +found** — the two real items are (1) an environment blocker and (2) a pm3py-side demod-parity +gap. Captured here so a future effort doesn't re-derive it. + +## 1. The C client won't run inside the VS Code snap sandbox (oracle blocker) + +- **Symptom:** `./pm3 -p /dev/ttyACM0 -c 'lf search'` → + `symbol lookup error: /snap/core20/current/lib/x86_64-linux-gnu/libpthread.so.0: + undefined symbol: __libc_pthread_init, version GLIBC_PRIVATE` +- **Root cause:** this shell runs *inside* the VS Code snap (`SNAP_REVISION`, `SNAP_REAL_HOME`, + `/snap/code/250/...`). The snap runtime injects core20's older glibc / `libpthread.so.0` + ahead of the system libs the client was built against, so a `GLIBC_PRIVATE` symbol the + system libc no longer exports gets looked up in the old snap libpthread and fails. +- **Confirmed it is NOT a client defect:** + - The binary has **no RPATH/RUNPATH**; its `NEEDED` libs (`libpython3.12`, `Qt5*`, + `readline`, …) all resolve to system `/lib/x86_64-linux-gnu` under `ldd`. + - `LD_LIBRARY_PATH` is **unset**; clearing `LD_LIBRARY_PATH`/`GTK_PATH` did not help — the + injection is at the snap-runtime level, not via a variable we can scrub. +- **To use the client as an oracle:** run it from a **non-snap login shell / real terminal** + (outside VS Code's snap), or build+run it inside a matched (non-snap) environment. No + proxmark3 change fixes a sandbox library injection. +- **Marginal upstream idea (only if it ever matters):** the `pm3` launcher could sanitize + obviously-contaminating library paths before `exec`, but that is a weak, environment-specific + band-aid — not worth a PR on its own. +- Client checkout here: `dangerous-tac0s/proxmark3` `v4.20728-1264-g273777b21`. + +## 2. LF clock detection — pm3py's port was weaker than the C client (pm3py-side parity) + +We found and fixed a real bug in pm3py `pm3py/lf/dsp.py::detect_clock`: it estimated the bit +clock from the **minimum** edge interval, so a handful of stray sub-bit edges on a +strongly-coupled, rail-clipped tag collapsed the estimate (true half-bit 32 → ~3), snapping the +data clock to 8 and producing an all-phase-error ASK demod (nothing decoded). Fixed to key off +the **shortest _well-supported_ interval** (≥20% of the modal count) instead of the raw minimum. + +The C client's `DetectASKClock` (`common/lfdemod.c`) is already robust to this — worth porting +its approach for full parity: + +- It first runs `DetectCleanAskWave` and, for clean/strong/**clipped** peaks, routes to a + dedicated `DetectStrongAskClock` — exactly the case that broke our port. +- Otherwise it **error-scores every candidate clock** `{8,16,32,40,50,64,100,128,272}` against + the wave and picks the best fit — not a single-interval heuristic. + +**Action (pm3py, not a C-client PR):** port the candidate-clock error-scoring + a +strong/clean-ASK fast path into `pm3py/lf`. Our support-based `detect_clock` fix is a partial, +targeted step; the C client's scoring is the fuller solution. + +## 3. Data-block reads — investigated to ground truth (no bug), one real improvement + +Post-fix, pm3py decodes the live tag as: + +- Emitted credential: **EM4100 `00FFFFFFFF`** +- T5577 config block 0: **`00148040`** (Manchester, RF/64, MAXBLK 2, `ST=0`, non-inverted → + EM4100/EM4102) — decoded rotation-resolved against presets (reliable). +- Data blocks: `READ(1)=A108421F`, `READ(2)=007FE108`, which at first looked *wrong* (not a + rotation of the encoder's `FF801EF7`/`BDEF7BC0`). + +Chased to ground truth against the **ATA5577C datasheet + proxmark3 C source + an on-hardware +write-read-back** — and there is **no read/addressing bug**: + +- Datasheet §5.9: the direct-access command (`opcode 10` + `0` + 3-bit addr — exactly what the + firmware sends) reads *only the addressed block*, repetitively. A clean 32-bit repeat in the + capture proves block-read mode engaged (the 64-bit emulation stream can't produce one). +- `T55xx_SetBits` clocks the address MSB-first; pm3py's `readbl` payload matches the firmware + struct byte-for-byte. Addressing is correct end-to-end. +- **Write-read-back (decisive):** wrote `DEADBEEF` to unused block 3 → `READ(3)` returned + `~DEADBEEF` at **rot 0**, and *only* block 3 changed. So `READ(N)` addresses block N, reads + the right bits, and the only artifact is a **consistent Manchester polarity inversion**. + +Conclusion: this tag was simply **cloned with the two EM4100 halves in reverse order** +(block 1 = `BDEF7BC0`, block 2 = `FF801EF7`); both orders stream the same cyclic waveform and +read as `00FFFFFFFF`. pm3py reads them correctly. The "wrong" appearance was polarity (inversion) ++ rotation, i.e. the documented "best-effort — bit alignment not verified". + +**Real improvement (open):** `read_t55xx_block` should *resolve* data-block polarity/rotation +instead of leaving it best-effort. Polarity is determinable — read block 0 (config), resolve its +polarity against the known preset, and carry that polarity to the data blocks (they share the +tag's modulation). Rotation can be anchored off the block-read response start. The C client's +`cmdlft55xx` demod already does this alignment — worth porting alongside §2. Still not +cross-checked against `lf t55 dump` (client blocked by §1), but the write-read-back makes that +non-blocking.