From 22dc438464eb8982dc031470d0f0654570a77603 Mon Sep 17 00:00:00 2001 From: michael Date: Sun, 15 Mar 2026 22:11:38 -0700 Subject: [PATCH] feat(pm3py): project scaffolding with CRC-16/A and protocol constants --- pm3py/__init__.py | 1 + pm3py/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 197 bytes pm3py/__pycache__/protocol.cpython-312.pyc | Bin 0 -> 7797 bytes pm3py/protocol.py | 261 ++++++++++++++++++ pyproject.toml | 6 + setup.py | 11 + tests/__init__.py | 0 tests/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 131 bytes .../test_crc.cpython-312-pytest-9.0.1.pyc | Bin 0 -> 4159 bytes tests/test_crc.py | 17 ++ 10 files changed, 296 insertions(+) create mode 100644 pm3py/__init__.py create mode 100644 pm3py/__pycache__/__init__.cpython-312.pyc create mode 100644 pm3py/__pycache__/protocol.cpython-312.pyc create mode 100644 pm3py/protocol.py create mode 100644 pyproject.toml create mode 100644 setup.py create mode 100644 tests/__init__.py create mode 100644 tests/__pycache__/__init__.cpython-312.pyc create mode 100644 tests/__pycache__/test_crc.cpython-312-pytest-9.0.1.pyc create mode 100644 tests/test_crc.py 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 0000000000000000000000000000000000000000..e8f3fd331bde25a74086bc50662561e34023f6fb GIT binary patch literal 197 zcmX@j%ge<81hXe>&$I&4k3k$5V1zP0a{w9B8B!Rc7%CYxnW~Hna*Ycr6?7E>DoZl* z^AyT6i&7N|it)2$EV~c$H%W^ z_zW`nmy&))er~FMd45s0KG;Yo86TgSmst`YuUAm{i^C>2KczG$)vkyQs0HMrVvuV- PFf%eT-eRyUVgYghbwf2} literal 0 HcmV?d00001 diff --git a/pm3py/__pycache__/protocol.cpython-312.pyc b/pm3py/__pycache__/protocol.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5ac37a1a49e96a28d67d748b779ab9cd2559e5a2 GIT binary patch literal 7797 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..1f3521f79e41bba8c10ada3259c2023f169bd6ec GIT binary patch literal 131 zcmX@j%ge<81al^A&jitrK?FMZ%mNgd&QQsq$>_I|p@<2{`wUX^OG!T?KQ~psJijPg zzaZDRpi;jiwYa2MKR!M)FS8^*Uaz3?7l%!5eoARhs$CH)Pzxgv7lRldnHd=wiA0#2i{QVP4+7^t9X)i&yhKY+_>@vakt?X_k%Bv4yL zRSu~VDZW(Uz&{Xi=&{G1t6l&@s8*4BXfM4*L8>Y)>3g%Ed7B1en%lFAhWVR4b?Nw;0(UP&GD=js$x;#xhk1U@Yo|%q-t_5 z3B34_n@br4XoryinqKB*=}&YtEAwu$NQ-Jg(~HcrPm)lJ@a}K(Y#_eLIly_P!EN|_ zlQ)E>(BK=w;xNZG#fE7A?4TVh3nbv8+5nvrZxFO82^+qKk#KNCRu)nL7u6d+yTsXj zG5>6#A}!>cPz>fs;})G^cgP&KsPQnL=qpaJiB{kYJwBMl8G1sXXGrU0MS`^?M$$+% zNSS9+!nqJkIOge{pe?E!iNI4(e+$Rs0qh^VJiKC4Smxx8*?qJr7h%_*^9RDrjLfen zH;3!XdD%{X?K6WjgUBheJ&g63Y|H89;EdNgA=?xeNhYocI!MNlb(D^9s)yHdph4con{ z*>|mb6@#g2cPiGrUeRiL)pR>FTQ8aD!R`Kxjas&9Us7sO_mWn!XjOMK^Fh6$*K`MZ zWXa9o2;KzMxR8qrg}5;837)q}Z;GCvDmLsFH^|CCcJ!`QHAnATbaAv^&DEDj@k4D! zHMnUb^<}q*eY=0{G_zWFmT5n@z}2+B0kX>ddg;n08G16&$~~S2W~~6^(ezqjD>Jl5 z@Y(fbPYCH2+IUhsVt2?;E9bRtqglwV6++99c|nF~0_Fe*BPwAy7={Q0ap(Nu;IG(m z{U3(|LppbW>WzKz2>~h!9W<_aYLJ0|B*na*r2Fp!O)?BLDWl^p2AU|yq-X68I78}K zEm3_bVrv7#Uz0-MMaWBQaFf*oJlX_ zOwukKPND;-AG`KrwC_kuK7PX|;=)wyg46fVtnbhbUaVR7Yub`obSz3`=*LgAzX!6) zy^^@jtE*qU%=EPKk7qVXcI`HhM>A`;w=&s1g3qoedqPOJNEWX$Dzqau?A|8XRzBK?ovwsDy=(AW3UBK2O#R!iYU;>O3tVGAU$wybE!8>6# z-|)c;;02+@+ZjVJL?;v2FPw0w4`ux@|1F%5^&DJrR)VYtSwFJq33{XYxCLA?(IDrZ zQE$xu2{UShs|45l)R{z@FarX}<87@;wDLY$J9ABrSuiAY`s|M*7t!er);ad&SAw>v zZivBd1vO%oreGwSqyY$V@fzTugpq1WfEFbt35^7W^JD)I^L#b(7;q5K5{NI2H2P^s zB{4vnF}o8^MS}!0`R`A{88el_nP%2Sh7prSr_t3&`iL=g?p;U)o`Tv(j0DhP0$!mh z0b=Z)b!W$wyphWl)G=jJQB-9L-Z3SoMQsQz@y6s;XmZPR*<1 zYC)Y)C)FucSLfBDYN)1KS{aW)#8Y4=WP)1^Mtb^ho7FK!?&Vt{;X%EZ7=w2HzK^vqTS@irdS6TFT z1IY*Iq{~grBZzgAC2CdgnhzMXrMFOF1PKNOdJ#!C554l@om7;o+C z5E)jvKQ3H;+WfA$*i|j79asnK4jI@yJPP!~(7$FnH%b2^wIyz4`hjaLArV_k zAnpkvg>;t7uic!-Aln0S1aY@|