commit 22dc438464eb8982dc031470d0f0654570a77603 Author: michael Date: Sun Mar 15 22:11:38 2026 -0700 feat(pm3py): project scaffolding with CRC-16/A and protocol constants diff --git a/pm3py/__init__.py b/pm3py/__init__.py new file mode 100644 index 0000000..0359d17 --- /dev/null +++ b/pm3py/__init__.py @@ -0,0 +1 @@ +"""pm3py - Python wire protocol library for Proxmark3.""" diff --git a/pm3py/__pycache__/__init__.cpython-312.pyc b/pm3py/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..e8f3fd3 Binary files /dev/null and b/pm3py/__pycache__/__init__.cpython-312.pyc differ diff --git a/pm3py/__pycache__/protocol.cpython-312.pyc b/pm3py/__pycache__/protocol.cpython-312.pyc new file mode 100644 index 0000000..5ac37a1 Binary files /dev/null and b/pm3py/__pycache__/protocol.cpython-312.pyc differ diff --git a/pm3py/protocol.py b/pm3py/protocol.py new file mode 100644 index 0000000..c94e5dd --- /dev/null +++ b/pm3py/protocol.py @@ -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 = " 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 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..671961c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,6 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.backends._legacy:_Backend" + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..74ca9ae --- /dev/null +++ b/setup.py @@ -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", +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/__pycache__/__init__.cpython-312.pyc b/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..1f3521f Binary files /dev/null and b/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/tests/__pycache__/test_crc.cpython-312-pytest-9.0.1.pyc b/tests/__pycache__/test_crc.cpython-312-pytest-9.0.1.pyc new file mode 100644 index 0000000..fa77035 Binary files /dev/null and b/tests/__pycache__/test_crc.cpython-312-pytest-9.0.1.pyc differ diff --git a/tests/test_crc.py b/tests/test_crc.py new file mode 100644 index 0000000..6d31728 --- /dev/null +++ b/tests/test_crc.py @@ -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