7.1 KiB
pm3py — Development Guide
What is this?
A pure-Python async library that speaks the Proxmark3 NG wire protocol directly over USB serial. Returns structured dicts, not text. No dependency on the C client binary.
Quick reference
# Run tests (no hardware needed, all mocked)
cd /home/work/pm3py
python -m pytest tests/ -v
# Install for development
pip install -e .
Package structure
pm3py/
__init__.py # re-exports Proxmark3, PM3Error, PM3Response, Cmd, PM3Status
core/ # wire protocol and device commands
protocol.py # wire constants, CRC-16/A, Cmd enum, PM3Status
transport.py # frame encode/decode, PM3Transport async serial
client.py # Proxmark3 class, _SyncProxy, FirmwareInfo
hw.py # hardware commands, LED API (platform-aware)
hf.py # HF core — tune, search, sniff, dropfield
hf_iso14a.py # ISO 14443-A — scan, raw. Uses MIX frames
hf_iso15.py # ISO 15693 — scan, rdbl, wrbl, thin sniff cmd
hf_mfc.py # MIFARE Classic — rdbl, wrbl, rdsc, chk, nested, cident
lf.py # LF commands + T55xxCommands + LFSearchResult
trace/ # firmware trace infrastructure (shared by sniff, sim, reader)
trace.py # parse_tracelog, TRACELOG_HDR_SIZE
decode_iso15.py # ISO 15693 command/response decoders
ndef.py # NDEF TLV/record decode for trace annotation
format.py # ANSI color formatting, format_sniff_line
sniff/ # sniff orchestration — protocol detection, session lifecycle
session.py # SniffSession — start/download/decode per protocol
sim/ # card simulation framework (~7.8k LOC, 686 tests)
transponder.py # Transponder ABC, MemoryRegion
medium.py # Medium, SoftwareMedium (RF simulation)
reader.py # Reader ABC, ScriptedReader, InteractiveReader
frame.py # RFFrame (bit/byte level)
iso14443a.py # Tag14443A, Tag14443A_3/4, Reader14443A
iso15693.py # Tag15693, Reader15693
mifare.py # MifareClassicTag + Crypto1
desfire.py # DesfireTag, DesfireReader
type5.py # NfcType5Tag (NDEF on 15693)
nxp_icode.py # NxpIcodeTag base
icode_slix.py # → icode_slix2 → icode3, icode_dna
ntag5_platform.py # → ntag5_switch, ntag5_link → ntag5_boost
sim_session.py # SimSession (table compile + WTX relay)
table_compiler.py # ResponseTable, TableCompiler
trace_fmt.py # TraceFormatter (14443-A + 15693 decoders)
# + lf_base, em4100, hid, t5577, ndef, implants, fuzzer, relay, etc.
reader/ # higher-level reader modes by protocol/vendor (scaffold)
transponders/ # tag/transponder models independent of hardware (scaffold)
Client API
pm3.hw.ping() # hardware
pm3.hf.iso14a.scan() # ISO 14443-A
pm3.hf.iso15.rdbl(4) # ISO 15693
pm3.hf.mfc.rdbl(0) # MIFARE Classic
pm3.lf.t55.readbl(0) # T55xx
pm3.hf.tune() # HF antenna tune
pm3.hf.dropfield() # drop field
Wire protocol essentials
- NG frame (client→device): magic
0x61334d50+length|0x8000+ cmd + payload + crc/nocrc - MIX frame: Same but
ngbit unset, payload starts with 3x uint64 args (24 bytes) - Response frame: magic
0x62334d50+length|ng+ status + reason + cmd + payload + crc/nocrc - USB: no CRC (postamble =
0x3361cmd /0x3362resp). C client setssend_with_crc_on_usb = false. - FPC UART: CRC-16/A with byte-swapped wire encoding
Key patterns
- All command methods are
async. The sync wrapper in_SyncProxy(incore/client.py) intercepts via__getattr__and callsloop.run_until_complete(). - Command classes hold a
self._treference toPM3Transport(or mock in tests). - Tests use
AsyncMockfor transport. Sethw._is_rdv4 = Falseto skip capabilities fetch in LED tests. capabilities()response parsed at known byte/bit offsets from the C struct (version=7 format).
Platform differences (PM3 Easy vs RDV4)
| Color | Easy | RDV4 |
|---|---|---|
| green | A (0x01) | B (0x02) |
| red | B (0x02) | C (0x04) |
| orange | C (0x04) | A (0x01) |
| blue | D (0x08) | (none) |
| red2 | (none) | D (0x08) |
PWM-capable: Easy = A,B. RDV4 = A,D.
Sim framework (worktree feature/sim-framework)
Software-defined transponder/reader simulation framework — 750+ tests. Pure-Python models for ISO 14443-A, 15693, MIFARE Classic, DESFire, JCOP, LF (EM4100, HID, T5577), NDEF, NXP ICODE/SLIX2/DNA/NTAG5, access control (Wiegand, OSDP), implant profiles. See .worktrees/sim-framework/docs/SIM_FRAMEWORK_STATUS.md for full status.
Table compiler: proprietary command match patterns
Critical: For NXP custom commands (0xA0+), table entry match patterns must NOT include the manufacturer code byte (0x04). The firmware's UID addressing logic consumes the mfg byte as part of UID parsing, so after normalization the mfg byte is absent from the command passed to table lookup.
Addressing flow for 22 AB 04 <uid_8_bytes>:
- Firmware sees
cmd[0] & ADDRESS→ addressed mode cmd[2](mfg code 0x04) doesn't match UID → triescmd[3:11]→ UID matchescmdCptadvances past mfg + UID →cmdCpt = 11- Normalization:
norm = [flags & ~ADDRESS, cmd] + cmd[cmdCpt:]→02 AB(no mfg code!) - Table lookup on
02 AB→ match pattern must be[0x02, 0xAB](PREFIX), NOT[0x02, 0xAB, 0x04]
For unaddressed commands (02 AB 04), mfg code stays → 02 AB 04. PREFIX match on [0x02, 0xAB] matches both forms.
Rule: All NXP custom command table entries use match=bytes([flags, cmd_byte]) with MATCH_PREFIX. Never include 0x04 in the match.
Python-driven card simulation (in progress)
Design doc: docs/PYTHON_SIM_DESIGN.md. Firmware patch (~320 lines of C) + Python extensions to enable Python-controlled card simulation on unmodified PM3 Easy/RDV4 hardware. Two mechanisms:
- Response table in BigBuf — pre-compiled by Python, served by firmware at wire speed (86µs FDT for 14443-A Layer 3)
- WTX relay — firmware sends S(WTX) on Layer 4 table miss, relays APDU to Python over USB for real-time crypto (DESFire, JCOP, EMV)
- 15693 retry relay — reader retry-based relay for unknown commands
Firmware maintenance: atomic single-file commits for easy rebase against upstream PM3. See design doc for CI workflow.
Adding new commands
- Find the
CMD_*constant ininclude/pm3_cmd.hand add toCmdenum incore/protocol.py - Check if the C client uses
SendCommandNG(→send_ng) orSendCommandMIX(→send_mix) - Check the payload struct in
pm3_cmd.hand usestruct.packto build it - Parse the response using
struct.unpack_fromonresp.data - Return a dict with human-readable keys
- Write test with
AsyncMocktransport — no hardware needed