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 <noreply@anthropic.com>
5.7 KiB
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.0ahead of the system libs the client was built against, so aGLIBC_PRIVATEsymbol 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
NEEDEDlibs (libpython3.12,Qt5*,readline, …) all resolve to system/lib/x86_64-linux-gnuunderldd. LD_LIBRARY_PATHis unset; clearingLD_LIBRARY_PATH/GTK_PATHdid not help — the injection is at the snap-runtime level, not via a variable we can scrub.
- The binary has no RPATH/RUNPATH; its
- 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
pm3launcher could sanitize obviously-contaminating library paths beforeexec, but that is a weak, environment-specific band-aid — not worth a PR on its own. - Client checkout here:
dangerous-tac0s/proxmark3v4.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
DetectCleanAskWaveand, for clean/strong/clipped peaks, routes to a dedicatedDetectStrongAskClock— 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'sFF801EF7/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_SetBitsclocks the address MSB-first; pm3py'sreadblpayload matches the firmware struct byte-for-byte. Addressing is correct end-to-end.- Write-read-back (decisive): wrote
DEADBEEFto unused block 3 →READ(3)returned~DEADBEEFat rot 0, and only block 3 changed. SoREAD(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.