Files
pm3py/README.md
2026-03-16 21:23:34 -07:00

186 lines
5.8 KiB
Markdown

# pm3py
Pure-Python library for talking to a [Proxmark3](https://github.com/RfidResearchGroup/proxmark3) over its NG wire protocol. Returns structured Python dicts instead of parsing terminal output. Async-first with a sync wrapper for REPL use.
**No dependency on the C client.** Speaks the wire protocol directly over USB serial.
## Install
```bash
cd pm3py
pip install -e .
```
Requires Python 3.10+ and `pyserial-asyncio`.
## Quick Start
```python
from pm3py import Proxmark3
# Sync mode — auto-detects the device
pm3 = Proxmark3.sync()
print(pm3.firmware.version_string)
print(pm3.hw.ping())
print(pm3.hw.version())
pm3.close()
```
Or with async:
```python
import asyncio
from pm3py import Proxmark3
async def main():
async with Proxmark3() as pm3:
print(await pm3.hw.ping())
print(await pm3.hf.a14.scan())
asyncio.run(main())
```
## API
All commands return Python dicts. The API mirrors the C client's command tree:
### Hardware — `pm3.hw.*`
```python
pm3.hw.ping() # verify connectivity
pm3.hw.version() # firmware version, chip ID
pm3.hw.status() # device status
pm3.hw.capabilities() # compiled features, platform (Easy vs RDV4)
pm3.hw.tune() # full antenna tuning (LF + HF voltages)
pm3.hw.led("green", on=True) # LED control with color names
pm3.hw.led("red", pulse=True, speed=300)# PWM effects (validates hardware support)
pm3.hw.led("all", on=False) # turn off
pm3.hw.dbg(level=2) # set debug level
pm3.hw.break_loop() # stop long-running operations
pm3.hw.reset() # reset device
```
LED colors are platform-aware — `"green"` maps to the correct physical LED on both PM3 Easy and RDV4. PWM operations (brightness, pulse, fade) validate hardware capability and raise `PM3Error` if the LED doesn't support it.
### Low Frequency — `pm3.lf.*`
```python
pm3.lf.tune(divisor=95) # LF antenna voltage at frequency
pm3.lf.read(samples=30000) # capture ADC samples
pm3.lf.config() # get sampling config
pm3.lf.config(divisor=88) # set config (134 kHz)
pm3.lf.search() # capture + return LFSearchResult
pm3.lf.sniff() # alias for read
pm3.lf.sim(gap=0, data=b"...") # simulate from buffer
# T55xx tags
pm3.lf.t55.readbl(block=0)
pm3.lf.t55.writebl(block=1, data=0xDEADBEEF)
pm3.lf.t55.wakeup(password=0x12345678)
```
`lf.search()` returns an `LFSearchResult` with callable next steps:
```python
result = pm3.lf.search()
print(result) # shows available actions
raw = result.download() # download raw ADC samples
result.tune() # check antenna voltage
result.read_t55xx(block=0) # try reading as T55xx
```
Full tag demodulation (EM410x, HID, AWID, etc.) requires the C client's DSP stack.
### High Frequency — `pm3.hf.*`
```python
pm3.hf.tune() # HF antenna voltage
pm3.hf.search() # try 14a then 15693
pm3.hf.sniff() # sniff HF traffic
pm3.hf.dropfield() # turn off field
```
### ISO 14443-A — `pm3.hf.a14.*`
```python
pm3.hf.a14.scan() # → {uid, atqa, sak, ats}
pm3.hf.a14.raw(data=b"\x50\x00") # raw APDU
pm3.hf.a14.sniff()
pm3.hf.a14.sim(uid=b"\x01\x02\x03\x04")
```
### ISO 15693 — `pm3.hf.iso15.*`
```python
pm3.hf.iso15.scan() # → {uid, dsfid}
pm3.hf.iso15.rdbl(block=0) # read block
pm3.hf.iso15.wrbl(block=0, data="00112233")
pm3.hf.iso15.sniff()
```
### MIFARE Classic — `pm3.hf.mf.*`
```python
pm3.hf.mf.rdbl(block=0) # read block (default key FF..FF)
pm3.hf.mf.rdbl(block=4, key="A0A1A2A3A4A5", key_type=1) # key B
pm3.hf.mf.wrbl(block=4, key="FFFFFFFFFFFF", data="00" * 16)
pm3.hf.mf.rdsc(sector=0) # read entire sector
pm3.hf.mf.chk(block=0, keys=["FFFFFFFFFFFF", "A0A1A2A3A4A5"])
pm3.hf.mf.sniff()
pm3.hf.mf.sim(uid="01020304", size="1k")
pm3.hf.mf.nested(block=0, key="FFFFFFFFFFFF")
pm3.hf.mf.cident() # identify magic card type
```
### Raw Commands
For anything not wrapped:
```python
from pm3py import Cmd
resp = pm3.send_ng(Cmd.STATUS) # NG frame
resp = pm3.send_mix(Cmd.HF_ISO14443A_READER, arg0=0x0103) # MIX frame
```
## Firmware Compatibility
On connect, pm3py pings the device and fetches the firmware version. Results are in `pm3.firmware`:
```python
pm3 = Proxmark3.sync()
print(pm3.firmware.compatible) # True if NG protocol works
print(pm3.firmware.version_string) # firmware version
print(pm3.firmware.chip_id) # e.g. "0x270B0A40"
print(pm3.firmware.warnings) # any issues detected
```
## Architecture
```
pm3py/
├── protocol.py # Wire protocol constants, CRC-16/A, command IDs, error codes
├── transport.py # Frame encode/decode, async serial I/O, PM3Transport
├── client.py # Proxmark3 class, sync proxy, firmware probe
├── hw.py # hw.* commands
├── lf.py # lf.* commands + T55xx
├── hf.py # hf.* commands (tune, search, sniff)
├── hf_14a.py # hf.14a.* (ISO 14443-A)
├── hf_15.py # hf.15.* (ISO 15693)
└── hf_mf.py # hf.mf.* (MIFARE Classic)
```
- **Transport layer** handles serial I/O, CRC, NG/MIX frame encoding/decoding
- **Command modules** send structured commands and parse responses into dicts
- **USB connections** skip CRC (matching the C client behavior)
- **FPC UART connections** use CRC-16/A with byte-swapped wire format
## Tests
```bash
cd pm3py
python -m pytest tests/ -v
```
All tests use mock transports — no hardware required.