feat(pm3py): project scaffolding with CRC-16/A and protocol constants

This commit is contained in:
michael
2026-03-15 22:11:38 -07:00
commit 22dc438464
10 changed files with 296 additions and 0 deletions

1
pm3py/__init__.py Normal file
View File

@@ -0,0 +1 @@
"""pm3py - Python wire protocol library for Proxmark3."""

Binary file not shown.

Binary file not shown.

261
pm3py/protocol.py Normal file
View File

@@ -0,0 +1,261 @@
"""Proxmark3 NG wire protocol constants and CRC."""
import struct
from enum import IntEnum
# --- Magic values ---
CMD_PREAMBLE_MAGIC = 0x61334d50 # "PM3a"
RESP_PREAMBLE_MAGIC = 0x62334d50 # "PM3b"
CMD_POSTAMBLE_NOCRC = 0x3361 # "a3"
RESP_POSTAMBLE_NOCRC = 0x3362 # "b3"
PM3_CMD_DATA_SIZE = 512
# --- Frame formats (little-endian) ---
CMD_PREAMBLE_FMT = "<IHH" # magic(4) + length_ng(2) + cmd(2) = 8 bytes
CMD_PREAMBLE_SIZE = 8
CMD_POSTAMBLE_FMT = "<H" # crc(2) = 2 bytes
CMD_POSTAMBLE_SIZE = 2
RESP_PREAMBLE_FMT = "<IHbbH" # magic(4) + length_ng(2) + status(1) + reason(1) + cmd(2) = 10 bytes
RESP_PREAMBLE_SIZE = 10
RESP_POSTAMBLE_FMT = "<H"
RESP_POSTAMBLE_SIZE = 2
# --- MIX frame: 3x uint64 args at start of payload ---
MIX_ARGS_FMT = "<QQQ"
MIX_ARGS_SIZE = 24
# --- CRC-16/A (ISO 14443-3) ---
_CRC_TABLE: list[int] = []
def _reflect8(b: int) -> int:
r = 0
for i in range(8):
if b & (1 << i):
r |= 1 << (7 - i)
return r
def _reflect16(v: int) -> int:
r = 0
for i in range(16):
if v & (1 << i):
r |= 1 << (15 - i)
return r
def _init_crc_table() -> None:
global _CRC_TABLE
if _CRC_TABLE:
return
poly = 0x1021
_CRC_TABLE = [0] * 256
for i in range(256):
c = _reflect8(i) << 8
crc = 0
for _ in range(8):
if (crc ^ c) & 0x8000:
crc = ((crc << 1) ^ poly) & 0xFFFF
else:
crc = (crc << 1) & 0xFFFF
c = (c << 1) & 0xFFFF
_CRC_TABLE[i] = _reflect16(crc)
def crc16_a(data: bytes) -> int:
"""CRC-16/A: poly=0x1021, init=0xC6C6, refin=true, refout=true, xorout=0x0000."""
_init_crc_table()
crc = _reflect16(0xC6C6)
for b in data:
crc = ((crc >> 8) ^ _CRC_TABLE[(crc & 0xFF) ^ b]) & 0xFFFF
return crc
# --- Error codes ---
class PM3Status(IntEnum):
SUCCESS = 0
EUNDEF = -1
EINVARG = -2
EDEVNOTSUPP = -3
ETIMEOUT = -4
EOPABORTED = -5
ENOTIMPL = -6
ERFTRANS = -7
EIO = -8
EOVFLOW = -9
ESOFT = -10
EFLASH = -11
EMALLOC = -12
EFILE = -13
ENOTTY = -14
EINIT = -15
EWRONGANSWER = -16
EOUTOFBOUND = -17
ECARDEXCHANGE = -18
EAPDU_ENCODEFAIL = -19
EAPDU_FAIL = -20
EFAILED = -21
EPARTIAL = -22
ETEAROFF = -23
ECRC = -24
ESTATIC_NONCE = -25
ENOPACS = -26
ELENGTH = -27
ENOKEY = -28
ECRYPTO = -29
ENOFILE = -30
ENODATA = -98
EFATAL = -99
# --- Command IDs ---
class Cmd(IntEnum):
# Bootloader
DEVICE_INFO = 0x0000
FINISH_WRITE = 0x0003
HARDWARE_RESET = 0x0004
START_FLASH = 0x0005
CHIP_INFO = 0x0006
BL_VERSION = 0x0007
# Debug
DEBUG_PRINT_STRING = 0x0100
DEBUG_PRINT_INTEGERS = 0x0101
DEBUG_PRINT_BYTES = 0x0102
# System
NACK = 0x00FE
ACK = 0x00FF
BUFF_CLEAR = 0x0105
READ_MEM = 0x0106
VERSION = 0x0107
STATUS = 0x0108
PING = 0x0109
READ_MEM_DOWNLOAD = 0x010A
READ_MEM_DOWNLOADED = 0x010B
DOWNLOAD_EML_BIGBUF = 0x0110
DOWNLOADED_EML_BIGBUF = 0x0111
CAPABILITIES = 0x0112
QUIT_SESSION = 0x0113
SET_DBGMODE = 0x0114
STANDALONE = 0x0115
WTX = 0x0116
TIA = 0x0117
BREAK_LOOP = 0x0118
SET_TEAROFF = 0x0119
LED_CONTROL = 0x011A
GET_DBGMODE = 0x0120
# FPGA
FPGA_MAJOR_MODE_OFF = 0x0500
# LF
LF_TI_READ = 0x0202
LF_TI_WRITE = 0x0203
LF_ACQ_RAW_ADC = 0x0205
LF_MOD_THEN_ACQ_RAW_ADC = 0x0206
DOWNLOAD_BIGBUF = 0x0207
DOWNLOADED_BIGBUF = 0x0208
LF_UPLOAD_SIM_SAMPLES = 0x0209
LF_SIMULATE = 0x020A
LF_HID_WATCH = 0x020B
LF_HID_SIMULATE = 0x020C
LF_SET_DIVISOR = 0x020D
LF_SIMULATE_BIDIR = 0x020E
SET_ADC_MUX = 0x020F
LF_HID_CLONE = 0x0210
LF_EM410X_CLONE = 0x0211
LF_T55XX_READBL = 0x0214
LF_T55XX_WRITEBL = 0x0215
LF_T55XX_RESET_READ = 0x0216
LF_PCF7931_READ = 0x0217
LF_EM4X_READWORD = 0x0218
LF_EM4X_WRITEWORD = 0x0219
LF_IO_WATCH = 0x021A
LF_EM4X_PROTECTWORD = 0x021B
LF_EM410X_WATCH = 0x021C
LF_SAMPLING_SET_CONFIG = 0x021D
LF_FSK_SIMULATE = 0x021E
LF_ASK_SIMULATE = 0x021F
LF_PSK_SIMULATE = 0x0220
LF_AWID_WATCH = 0x0221
LF_VIKING_CLONE = 0x0222
LF_PCF7931_WRITE = 0x0223
LF_T55XX_WAKEUP = 0x0224
LF_COTAG_READ = 0x0225
LF_T55XX_SET_CONFIG = 0x0226
LF_SAMPLING_PRINT_CONFIG = 0x0227
LF_SAMPLING_GET_CONFIG = 0x0228
LF_EM4X_LOGIN = 0x0229
LF_EM4X_BF = 0x022A
LF_T55XX_CHK_PWDS = 0x0230
LF_T55XX_DANGERRAW = 0x0231
LF_NRZ_SIMULATE = 0x0232
LF_SNIFF_RAW_ADC = 0x0360
LF_HITAG_SNIFF = 0x0370
# HF General
HF_ACQ_RAW_ADC = 0x0301
HF_ISO15693_ACQ_RAW_ADC = 0x0300
HF_ISO15693_READER = 0x0310
HF_ISO15693_SIMULATE = 0x0311
HF_ISO15693_SNIFF = 0x0312
HF_ISO15693_COMMAND = 0x0313
HF_ISO15693_FINDAFI = 0x0315
HF_ISO15693_CSETUID = 0x0316
HF_ISO14443A_SNIFF = 0x0383
HF_ISO14443A_SIMULATE = 0x0384
HF_ISO14443A_READER = 0x0385
HF_ISO14443A_GET_CONFIG = 0x03B1
HF_ISO14443A_SET_CONFIG = 0x03B2
HF_ISO14443B_COMMAND = 0x0305
HF_ISO14443B_SIMULATE = 0x0381
HF_ISO14443B_SNIFF = 0x0382
# Tuning
MEASURE_ANTENNA_TUNING = 0x0400
MEASURE_ANTENNA_TUNING_HF = 0x0401
MEASURE_ANTENNA_TUNING_LF = 0x0402
LISTEN_READER_FIELD = 0x0420
HF_DROPFIELD = 0x0430
HF_DECAY = 0x0440
# MIFARE Classic
HF_MIFARE_EML_MEMCLR = 0x0601
HF_MIFARE_EML_MEMSET = 0x0602
HF_MIFARE_EML_MEMGET = 0x0603
HF_MIFARE_SIMULATE = 0x0610
HF_MIFARE_READER = 0x0611
HF_MIFARE_NESTED = 0x0612
HF_MIFARE_ACQ_ENCRYPTED_NONCES = 0x0613
HF_MIFARE_ACQ_NONCES = 0x0614
HF_MIFARE_STATIC_NESTED = 0x0615
HF_MIFARE_READBL = 0x0620
HF_MIFARE_READSC = 0x0621
HF_MIFARE_WRITEBL = 0x0622
HF_MIFARE_CHKKEYS = 0x0623
HF_MIFARE_SETMOD = 0x0624
HF_MIFARE_CHKKEYS_FAST = 0x0625
HF_MIFARE_CHKKEYS_FILE = 0x0626
HF_MIFARE_VALUE = 0x0627
HF_MIFARE_READBL_EX = 0x0628
HF_MIFARE_WRITEBL_EX = 0x0629
HF_MIFARE_SNIFF = 0x0630
HF_MIFARE_MFKEY = 0x0631
HF_MIFARE_PERSONALIZE_UID = 0x0632
HF_MIFARE_CSETBL = 0x0605
HF_MIFARE_CGETBL = 0x0606
HF_MIFARE_CIDENT = 0x0607
# MIFARE Ultralight
HF_MIFAREU_READBL = 0x0720
HF_MIFAREU_READCARD = 0x0721
HF_MIFAREU_WRITEBL = 0x0722
HF_MIFARE_NACK_DETECT = 0x0730
# HF Sniff/Plot
HF_SNIFF = 0x0800
HF_PLOT = 0x0801
FPGAMEM_DOWNLOAD = 0x0802
FPGAMEM_DOWNLOADED = 0x0803
UNKNOWN = 0xFFFF

6
pyproject.toml Normal file
View File

@@ -0,0 +1,6 @@
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.backends._legacy:_Backend"
[tool.pytest.ini_options]
testpaths = ["tests"]

11
setup.py Normal file
View File

@@ -0,0 +1,11 @@
from setuptools import setup, find_packages
setup(
name="pm3py",
version="0.1.0",
packages=find_packages(),
install_requires=[
"pyserial-asyncio>=0.6",
],
python_requires=">=3.10",
)

0
tests/__init__.py Normal file
View File

Binary file not shown.

17
tests/test_crc.py Normal file
View File

@@ -0,0 +1,17 @@
from pm3py.protocol import crc16_a
def test_crc16_a_empty():
assert crc16_a(b"") == 0x6363 # init 0xC6C6 reflected = 0x6363
def test_crc16_a_known_vector():
# Verified against C implementation: compute_crc(CRC_14443_A, ...)
# "123456789" is the standard CRC check string
data = b"123456789"
assert crc16_a(data) == 0xBF05
def test_crc16_a_ping_preamble():
# A CMD_PING preamble: magic=0x61334d50, length|ng=0x8020, cmd=0x0109
preamble = bytes.fromhex("504d3361208001090102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
crc = crc16_a(preamble)
assert isinstance(crc, int)
assert 0 <= crc <= 0xFFFF