Proxmark3(Iceman Fork)NG 变长帧格式:主机-设备通信协议的设计、API 与实现剖析
2026/9/17 17:47:02 网站建设 项目流程

Proxmark3(Iceman Fork)NG 变长帧格式:主机-设备通信协议的设计、API 与实现剖析

【免费下载链接】proxmark3Iceman Fork - Proxmark3项目地址: https://gitcode.com/GitHub_Trending/pr/proxmark3

本文围绕 doc/new_frame_format.md 展开,系统讲解 Proxmark3 在客户端与设备之间引入的 NG(新一代)变长帧格式:帧头/帧尾的逐字段定义、新旧格式的双向兼容策略、客户端与固件两侧的收发 API、Bootrom 的兼容边界,以及 USART 重写与超时调优背后的实测依据,并对照当前仓库源码给出每一处结论的文件级证据。读完本文,你可以完整理解 Proxmark3 通信协议中"一帧数据在线路上长什么样",并具备新增命令或排查通信问题的协议层基础。

需要说明的是:原文档标注其"主要面向开发者",因此本文也定位为协议实现级内容,面向需要编写、转换或调试命令处理的开发者。

旧格式:固定 544 字节的问题

在 NG 格式出现之前,主机与 Proxmark3 之间双向的帧结构是:

uint64_t cmd; uint64_t arg[3]; union { uint8_t asBytes[PM3_CMD_DATA_SIZE]; uint32_t asDwords[PM3_CMD_DATA_SIZE / 4]; } d;

当时PM3_CMD_DATA_SIZE = 512,且没有任何 API 抽象——每个模块各自手工拼装/解析这类帧。由此带来两个问题:

  • 帧大小固定为 544 字节,哪怕只是回一个简单 ACK,也要在线路上发送完整的 512 字节 payload(8 + 24 + 512 = 544);
  • 链路分片放大:抓 USB 传输可以观察到,主机一次发出 544B 的 Bulk 帧,而设备侧受内部缓冲区限制,只能按 128B、128B、128B、128B、32B 共 5 个包发回,一条"空 ACK"被拆成 5 次发送。

对于 USB 这个开销尚可忍受,但对 FPC/USART/蓝牙这类低速链路(9600~460800 波特率)而言,固定 512B 的尾随浪费是致命的——这正是引入变长帧的主要动机。

对应的新结构体如今定义在 include/pm3_cmd.h:

typedef struct { uint64_t cmd; uint64_t arg[3]; union { uint8_t asBytes[PM3_CMD_DATA_SIZE_OLD]; uint32_t asDwords[PM3_CMD_DATA_SIZE_OLD / 4]; } d; } PACKED PacketCommandOLD;

注意关键点:旧格式结构体改用PM3_CMD_DATA_SIZE_OLD定死在512,与PM3_CMD_DATA_SIZE解耦。头文件注释(include/pm3_cmd.h#L34-L36)说明了原因:bootloader 只说 OLD 格式,若把旧结构体跟着新值变大,会对所有已部署的 bootrom 破坏刷写兼容性。

PM3_CMD_DATA_SIZE本身在仓库中已按平台分化(include/pm3_cmd.h#L28-L41):

#if defined(ON_DEVICE) && !defined(PM5) #define PM3_CMD_DATA_SIZE 624 #else #define PM3_CMD_DATA_SIZE 4064 // PM5 firmware and the client #endif // 旧帧钉死在 512 #define PM3_CMD_DATA_SIZE_OLD 512 // FPC/BWM 前向缓冲较小,设备经 FPC 回复时限制 payload #define PM3_FPC_MAX_DATA 2048

即文档写作时"暂时 512"的上限,在当前仓库中 PM3 设备固件为 624、PM5 固件与客户端为 4064,客户端侧SendCommandNG会以g_conn.max_cmd_data_size做上限检查(见下文)。

NG 帧的逐字段定义

NG 格式从零设计:即使只把旧格式的 payload 变成变长,单帧最少也要 32 字节且字段任意宽,收益有限,因此直接重新设计。

命令帧(客户端 → Proxmark3)

uint32_t magic; uint16_t length : 15; bool ng : 1; uint16_t cmd; uint8_t data[length]; uint16_t crc;
字段含义
magic任意魔数PM3a0x61334d50),用于出流后重新同步
length变长 payload 长度,0 表示无;上限为PM3_CMD_DATA_SIZE
ng标志位:数据按 NG 格式还是 OLD 格式编码(过渡期用)
cmd16 位命令码,"够用了"
data变长 payload
crc真实 CRC(CRC-14443A)或占位魔数a3

应答帧(Proxmark3 → 客户端)

uint32_t magic; uint16_t length : 15; bool ng : 1; int8_t status; int8_t reason; uint16_t cmd; uint8_t data[length]; uint16_t crc;

字段差异:magicPM3b0x62334d50),占位魔数为b3;新增status(命令执行状态)与reason(对指定命令的状态细化说明)两个 8 位字段。

CRC 的规则(文档原文):CRC 可选,接收侧接受a3/b3作为占位值,若不是占位值则按真实 CRC 校验;USART 链路默认启用 CRC,USB 双向默认禁用 CRC。这一策略在当前代码中仍然成立——client/src/comms.c#L208-L214 中SendCommandNG依据send_via_fpc_usart/send_with_crc_on_fpc/send_with_crc_on_usb决定是否调用compute_crc(CRC_14443_A, ...),否则写入COMMANDNG_POSTAMBLE_MAGICa3)。

由此得到帧大小结论(见文末"参考帧"):NG 命令帧 10~522 字节、NG 应答帧 12~524 字节(按上限 512 计),OLD 帧恒 544 字节。以最小的hw ping为例,NG 命令帧仅10 字节,OLD 格式则固定544 字节——这就是 FPC/USART/BT 链路的核心收益。

内部结构体与 API 抽象

文档区分了两类结构体:

  • 传输用打包结构体PACKED,只描述线上字节):PacketCommandNGPreamblePacketCommandNGPostamblePacketCommandNGRawPacketResponseNGPreamblePacketResponseNGPostamblePacketResponseNGRaw
  • API 用结构体PacketCommandNG/PacketResponseNG):向开发者隐藏帧细节,同时保留oldarg[3]ng标志以兼容尚未转换的调用。

两者都在 include/pm3_cmd.h。传输侧:

typedef struct { uint32_t magic; uint16_t length : 15; // 变长部分长度,0 表示无 bool ng : 1; uint16_t cmd; } PACKED PacketCommandNGPreamble; #define COMMANDNG_PREAMBLE_MAGIC 0x61334d50 // PM3a #define COMMANDNG_POSTAMBLE_MAGIC 0x3361 // a3

API 侧(include/pm3_cmd.h#L67-L78):

typedef struct { uint16_t cmd; uint16_t length; uint32_t magic; // NG uint16_t crc; // NG uint64_t oldarg[3]; // OLD union { uint8_t asBytes[PM3_CMD_DATA_SIZE]; uint32_t asDwords[PM3_CMD_DATA_SIZE / 4]; } data; bool ng; // 存的是 NG 数据还是 OLD 数据? } PacketCommandNG;

PacketResponseNG同形,多出status/reason。文档明确:布尔字段ng表示该结构体保存的是 NEW 还是 OLD 格式数据;OLD 来源既可能是旧的 544B 帧,也可能是"混合帧"(变长但仍携带 oldargs);过渡完成后可能会移除oldargng字段——事实上当前仓库中 MIX 形态已删除(见下文),oldarg的读取方仅剩 client/src/flash.c 与 client/src/proxmark3.c 两处,且都是与 bootrom 经SendCommandBL通信。

客户端发送侧 API(client/src/comms.c)

void SendCommandNG(uint16_t cmd, uint8_t *data, size_t len); void SendCommandBL(uint64_t cmd, uint64_t arg0, uint64_t arg1, uint64_t arg2, void *data, size_t len); void SendCommandOLD(uint64_t cmd, uint64_t arg0, uint64_t arg1, uint64_t arg2, void *data, size_t len);

原型见 client/src/comms.h#L95-L97,实现见 client/src/comms.c#L111-L235:

  • SendCommandNG:所有命令的统一入口。参数打包进临时PACKED结构体放进data字段即可。实现中它会:① 用g_conn.max_cmd_data_size检查 payload 上限(PM5 场景下 4064 比设备缓冲大,必须钳制);② 填 preamble(magic/ng=1/length/cmd);③ 按链路策略计算或写入 CRC 占位;④ 计算总长sizeof(PacketCommandNGPreamble) + len + sizeof(PacketCommandNGPostamble);⑤ 交给通信线程,uart_wakeup()打断接收循环让发送尽快上线。
  • SendCommandBLSendCommandOLD的"改名别名",专门标记"必须保持 OLD 格式"的 bootrom 帧,源码注释(client/src/comms.c#L108-L113)写明了两类用途:进入 bootloader 模式的命令(可能与旧固件对话)、直接发给只支持 OLD 帧的 bootloader。
  • SendCommandOLD:不再有SendCommandBL之外的其他调用者。
  • SendCommandMIXPM3_CMD_DATA_SIZE_MIX已删除:MIX 是历史折中——NG 帧封装但前 24 字节仍放三个 64 位 oldarg。现在客户端已无法构造 MIX 帧。

内部流程:这些函数准备帧后经uart_communicationuart_send上线。

设备接收与发送侧(armsrc/)

接收

设备侧主循环AppMainreceive_ng(armsrc/cmd.c),receive_ng再调usb_read_ng/usart_read_ng取出PacketCommandNG,交给PacketReceived做命令分发(broker)。无论来的是旧帧还是新帧,检查PacketCommandNG.ng字段即可判断oldarg中是否有效数据,旧 handler 仍可在oldarg里找到原参数。

receive_ng_internal的实现(armsrc/cmd.c#L181-L199)体现了变长接收的要点:先读 8 字节 preamble,按其中length决定 payload 大小,rx->data只在确有包进入时整体清零一次(文档注释提到:对主循环每次空闲轮询都清零的开销"比主循环其余部分还大")。

发送

回复 API 定义在 armsrc/cmd.h#L32 附近,实现在 armsrc/cmd.c#L43-L179:

int reply_old(uint64_t cmd, uint64_t arg0, uint64_t arg1, uint64_t arg2, const void *data, size_t len); int reply_ng(uint16_t cmd, int8_t status, const uint8_t *data, size_t len); int reply_reason(uint16_t cmd, int8_t status, int8_t reason, const uint8_t *data, size_t len);
  • reply_mix已删除;
  • reply_old只保留给 bootrom 同样服务的 handler——注意它无论len是多少都发送sizeof(PacketResponseOLD)即 544 字节(armsrc/cmd.c#L70-L72 中usb_write(..., sizeof(PacketResponseOLD))),而reply_ng只发送你给它的精确长度;
  • 双通道(USB + FPC/BWM)同时可用时,两个通道的发送结果取故障优先、USB 优先的裁决逻辑(armsrc/cmd.c#L159-L168)。

转换完成后的典型 handler 写法——校验长度后在自己的操作码上应答(文档示例,可直接作为模板):

case CMD_FOOBAR: { if (packet->length != sizeof(foobar_t)) { reply_ng(CMD_FOOBAR, PM3_EINVARG, NULL, 0); break; } foobar_t *payload = (foobar_t *)packet->data.asBytes; ... reply_ng(CMD_FOOBAR, PM3_SUCCESS, resp, resplen); break; }

过渡期部分 handler 曾有if (packet->ng) { ... } else { ... }双模分支,以便新旧调用方共存;目前这类分支已全部移除——不带ng位的帧现在会收到PM3_EINVARG应答

另一个值得注意的实现细节:NG 应答 preamble 为 10 字节,payload 会落在非字对齐偏移上,而 ARM7TDMI 上非对齐 32 位读会静默位旋转而不是触发异常。reply_ng_internal用 2 字节前垫把整个帧偏移 2 字节,使data[]落回字边界(线上字节不变),见 armsrc/cmd.c#L94-L102 的注释。

客户端接收侧(client/src/comms.c)

流程:uart_communicationuart_receive构造出PacketResponseNG,交给PacketResponseReceived(client/src/comms.c#L297)。它要么立即处理(如调试打印),要么存入环形缓冲rxBufferCMD_BUFFER_SIZEPacketResponseNG,头/尾指针 + 互斥锁,见 client/src/comms.c#L62-L73)。命令层通过WaitForResponseTimeout/WaitForResponseTimeoutW(或下载场景的dl_it)调getReply取包。

与"超时"直接相关的一处关键机制在PacketResponseReceived入口(client/src/comms.c#L299-L303):每收到一个包就把WaitForResponseTimeout/dl_it的超时计时重置。因此实际超时是"距最近一个包的时间",与已收到的包数量无关——这对hw statuslf read这类先连发大量帧再收尾的命令在 9600 波特率下的存活至关重要。

迁移清单与工程实践笔记

把一个命令从 OLD 迁到 NG,四个方向各改一处:

方向改动
客户端发送SendCommandOLDSendCommandNG(一切参数装进临时 PACKED 结构体放data
设备接收解析PacketCommandNG,从oldarg改为只读data
设备发送reply_oldreply_ng(同样参数进 PACKED 结构体)
客户端接收解析PacketResponseNG,从oldarg改为只读data

文档"从整树转换中得到的实践笔记"值得逐条保留,它们都是踩坑的产物:

  1. 用自己命令的操作码应答,永远别用CMD_ACK匿名 ACK 迫使过去做 hack——例如 iCLASS 把自己的CMD_HF_ICLASS_SIMULATE塞进回复的arg0里让客户端区分来源。
  2. 成功标志放 NG 的status字段,而不是塞进数据字节;需要第二层区分时用reply_reason()
  3. 两边都要在解引用前校验packet->length。有多个已转换命令此前会读越短帧的尾部。
  4. PACKED不是免费的:它把结构体对齐设为 1,对任何宽于字节的成员取地址会触发-Werror=address-of-packed-member;且 ARM7TDMI 上非对齐 32 位加载会静默位旋转。只在"去掉填充"时才用 PACKED:若sizeof打不打包都一样,就别打。文档给了对照:epa_replay_t(打包,uint16_t落在奇偏移)对比epa_result_tt55xx_setconfig_t(未打包,成员天然无填充且会被取地址)。
  5. PM3_CMD_DATA_SIZE_MIX是 MIX 专用常量。任何改为 NG 的分块传输必须用PM3_CMD_DATA_SIZE - sizeof(payload_t)来定块大小。
  6. reply_old恒发 544 字节reply_ng只发给定长度。
  7. 一条命令可以应答多次:例如CMD_HF_ISO14443A_READERISO14A_CONNECT | ISO14A_RAW时会应答两次(select 一次、raw 交换一次),客户端必须把两次都消费掉。

大块下载

CMD_DOWNLOAD_BIGBUFCMD_DOWNLOAD_EML_BIGBUFCMD_SPIFFS_DOWNLOADCMD_FLASHMEM_DOWNLOAD使用download_req_t/download_chunk_t/download_done_t(见 include/pm3_cmd.h)。块头只带 offset——帧长本身就给出了块大小——传输以download_done_t结束,且应答在原CMD_DOWNLOAD_*操作码上而非匿名 ACKclient/src/comms.c中的dl_it()仍理解 OLD 块格式,因为CMD_READ_MEM_DOWNLOAD由 bootrom 服务。

设备侧遗留 OLD 应答(截至文档时的状态)

C 客户端与 ARM 固件均已完成转换。客户端无SendCommandOLD/SendCommandMIX调用点;设备上仅剩三处刻意保留的 legacy 应答:

位置保留原因
armsrc/appmain.cCMD_READ_MEM_DOWNLOADED及其CMD_ACK终止符bootrom.c同样服务它,而 bootrom 只说 OLD
armsrc/appmain.cCMD_DEVICE_INFO同上

SendCommandBL标记了必须保持 OLD 的帧,不要"转换"它们。这些位置按PM3_CMD_DATA_SIZE_OLD而非PM3_CMD_DATA_SIZE分块:reply_old会把 payload 钳制到 OLD 尺寸,若发送方按 NG 尺寸分块会构造出超大块,被线路截断后oldarg[1]里仍声明全长。

另有一处从遗留清单中"移出"的案例:armsrc/hfsnoop.cCMD_FPGAMEM_DOWNLOADED曾因 FPGA 追踪 DMA 双缓冲"看起来塞不下 NG 头"而保留 OLD,现已转换——DMA 直接写入download_chunk_tchunk->data,填满即完整 NG payload。文档特别提醒:循环中每次传输只武装"还差多少字节",因为FPGA_TRACE_SIZE不是DOWNLOAD_CHUNK_MAX的整数倍,若对最后一个短块仍武装整块,会永远等待 FPGA 永远不会发送的字节。

Bootrom:刻意保留旧格式

Bootrom 继续用 OLD 帧,理由有二(文档原文):

  • 绝大多数 bootrom 帧恰好携带 512B payload,新旧开销差异可忽略;
  • 经 USART 走 flash 既危险又慢(115200 波特 vs 7M 波特),不值得。

同时它需要保持与仍支持旧格式的其他仓库的兼容。设备侧路径(bootrom/bootrom.c):

  • 接收:usb_read(common/usb_cdc.c)⇒UsbPacketReceived(bootrom/bootrom.c#L106)⇒CMD_DEVICE_INFO/CMD_START_FLASH/CMD_FINISH_WRITE/CMD_HARDWARE_RESET;另有usb_enable/usb_disable
  • 发送:reply_old(bootrom.c)⇒usb_write(common/usb_cdc.c)。

客户端侧的 flasher(client/src/flash.c 等)因此必须继续用 OLD 帧,它与现行客户端代码共用少数原语:OpenProxmarkCloseProxmarkSendCommandOLD(⇒ 上述四条 bootrom 命令),接收侧仍走WaitForResponseTimeout ⇒ PacketResponseNG(旧帧被同一套传输层支持)。

USART RX FIFO 重写:应对未知大小的包

变长帧要求 USART 侧彻底重写以应对未知尺寸的数据包。方案要点:

  • USART 全双工,RX 与 TX 均为双 DMA 缓冲
  • RX 增加内部 FIFO

各函数职责(文档"New usart RX FIFO"一节):

  • usart_init:USART 在usart_init()后全程保持激活,RX/TX 例程无需再碰:pUS1->US_PTCR = AT91C_PDC_RXTEN | AT91C_PDC_TXTEN
  • usart_writebuffer_sync:仍用 DMA,但接受任意包大小;删掉了不必要的 memcpy;返回前等待 DMA 缓冲处理完毕,故称 "sync"(可以做成异步版,但调用方必须保证 DMA 缓冲在此期间不被回收;既然同步就不需要第二个 DMA 缓冲)。
  • usart_read_ng:调用方告知期望包长;依赖usart_rxdata_available判断 FIFO 中是否有数据;从 FIFO 取数;重试次数是动态的,随 FPC 链路速度变化
  • usart_rxdata_available:轮询usart_fill_rxfifo,返回 FIFO 中可用字节数。
  • usart_fill_rxfifo:若下一 DMA 缓冲已移入当前缓冲(US_RNCR == 0),说明一个 DMA 缓冲已满——把当前 DMA 缓冲数据转入 FIFO、换到另一个 DMA 缓冲、把腾空的 DMA 缓冲挂为下一缓冲;若当前 DMA 缓冲部分填充,则转入可用数据并记录已复制量。

接收侧的重试上限也在调优之列(common/usart.c,文档给出片段,当前仓库中USART_SLOW_LINK宏在 include/pm3_cmd.h#L26 定义并注释"用于 BT 等慢速链路"):

uint32_t usart_read_ng(uint8_t *data, size_t len) { // 实测经验值:3000000 / USART_BAUD_RATE // 取 10 倍 uint32_t tryconstant = 0; #ifdef USART_SLOW_LINK // BT 链路在 460800 下实测过 13200 次 tryconstant = 50000; #endif uint32_t maxtry = 10 * (3000000 / USART_BAUD_RATE) + tryconstant; ...

超时与实测时延

链路吞吐参考(新格式引入前)

Linux USB:#db#实测 PM3 ⇒ Client 约 545109 Bytes/s;Windows 虚拟机上 ProxSpace USB 约 233998 Bytes/s。

USART(波特率定义于common/usart.hUSART_BAUD_RATE):

链路实测 PM3 ⇒ Client
9600934 Bytes/s
11520011137 Bytes/s
46080043119 Bytes/s
Linux USB666624 Bytes/s(等效约 7M 波特)

接收超时

经 USART 接收比 USB 慢得多:USB 下 30ms 内完成的接收,USART 上会报"部分包"错误。经验值:

FTDI 9600 hw status ⇒ 需 20ms FTDI 115200 hw status ⇒ 需 50ms FTDI 460800 hw status ⇒ 需 30ms BT 115200 hf mf fchk --1k -f file.dic ⇒ 需 140ms

对应常量定义在 include/pm3_cmd.h#L1449 一带:

#define UART_FPC_CLIENT_RX_TIMEOUT_MS 200 #define UART_USB_CLIENT_RX_TIMEOUT_MS 20 #define UART_NET_CLIENT_RX_TIMEOUT_MS 500 #define UART_TCP_LOCAL_CLIENT_RX_TIMEOUT_MS 40 #define UART_UDP_LOCAL_CLIENT_RX_TIMEOUT_MS 20

这些值最终进入uart_posix.ctimeval结构与uart_win32.cserial_port_windows结构。初始用UART_FPC_CLIENT_RX_TIMEOUT_MS,一旦检测到走 USB 就降为UART_USB_CLIENT_RX_TIMEOUT_MS(切换逻辑见 client/src/comms.c#L982)。超时可用hw timeout命令在线调整,且下限即 USB 超时——client/src/cmdhw.c#L1565-L1567 会对小于UART_USB_CLIENT_RX_TIMEOUT_MS的取值给出告警。

通信延时补偿

WaitForResponseTimeoutdl_it的超时里会自动加入一段"通信延时",仅在走 FPC 时启用:timeout = 2 × 实测 FTDI 线缆经验延时:

// client/src/comms.c static size_t communication_delay(void) { if (conn.send_via_fpc_usart) return 2 * (12000000 / uart_speed); return 100; }

hw ping -l 512实测:USB 6–32ms,460800 下 40–70ms,9600 下 1100–1150ms。)

新旧应答路径的实测对比

hw statusDbpStringEx)耗时对比(文档实测值):

路径USB (/dev/ttyACM0)4608001152009600
reply_old2.52s3.03s4.88s26.5s
reply_mix(9600)7.08s
reply_ng2.10s2.22s2.43s5.75s

lf read(9600 vs 115200):50.38s vs 6.28s;mem dump(USB vs 115200 FPC):1.48s vs 25.34s。可见reply_ng在慢链路上把单命令耗时压低了一个量级,USB 上则与旧路径持平略优。

快速连发(fast push mode)

连续发送多条命令仍可能变慢:客户端会周期性地等待入站 RX 帧,而超时设置因 BT 而偏保守(uart_posix.cstruct timeval现为 200ms)。若确定下一条命令无需等待应答,可以借用 flasher 的技巧——

// fast push mode conn.block_after_ACK = true; some loop { if (sending_last_command) // 关闭快速模式 conn.block_after_ACK = false; SendCommandOLD / SendCommandMix if (WaitForResponseTimeout(CMD_ACK, &resp, some_timeout) == false) { ... conn.block_after_ACK = false; return PM3_ETIMEOUT; } } return PM3_SUCCESS;

若难以判定何时是最后一条命令:

// fast push mode conn.block_after_ACK = true; some loop { SendCommandNG if (WaitForResponseTimeout(CMD_FOOBAR, &resp, some_timeout) == false) { ... conn.block_after_ACK = false; return PM3_ETIMEOUT; } } // 关闭快速模式,并发送一条哑命令使其生效 conn.block_after_ACK = false; SendCommandNG(CMD_PING, NULL, 0); WaitForResponseTimeout(CMD_ACK, NULL, 1000); return PM3_SUCCESS;

参考帧(调试用)

帧大小的硬约束(按上限 512 计):

  • OLD 命令与应答帧恒为544 字节
  • NG & MIX 命令帧10~522 字节
  • NG & MIX 应答帧12~524 字节

链路分片行为:

  • Linux USB:发送包可为 544B;接收包最大 128B,故 544 = 128+128+128+128+32;
  • Linux UART(FTDI):发送包最大 256B,544 = 256+256+32;接收包最大 512B,544 = 512+32。

hw ping三个时代的线上字节对照(十六进制小端):

// 旧版(OLD 请求 + MIX 应答) TestProxmark: SendCommandOLD(CMD_PING, 0, 0, 0, NULL, 0); ->544=0901000000000000000000000000000000000000000000000000000000000000 -> OLD CMD_PING: reply_mix(CMD_ACK, reply_via_fpc, 0, 0, 0, 0); <-36=504d336218000000ff0000000000000000000000000000000000000000000000 <- MIX // 中间版(双向 MIX) CmdPing SendCommandMIX(CMD_PING, 0, 0, 0, NULL, 0); ->34=504d33611800090100000000000000000000000000000000000000000000000000 -> MIX CMD_PING reply_mix(CMD_ACK, reply_via_fpc, 0, 0, 0, 0); <-36=504d336218000000ff00000000000000000000000000000000000000000000000 <- MIX // 现行 NG 版 CmdPing SendCommandNG(CMD_PING, data, len); ->10=504d3361008009016133 -> NG CMD_PING reply_ng(CMD_PING, PM3_SUCCESS, packet->data.asBytes, packet->length); <-12=504d33620080000009016233 <- NG

解析一下现行 NG 版:50 4d 33 61即小端的PM3a00 80是 length/ng 联合体(length=0、ng=1);09 01CMD_PING(0x0109);61 33是占位魔数a3。应答50 4d 33 62PM3b)、00 8000 00(status=0 即PM3_SUCCESS、reason=0)、09 0162 33(占位b3)——12 字节收尾,而旧版同样语义要 544 字节请求 + 36 字节应答。

hw ping -l 512(NG)的完整收发则演示了大 payload 在 128B 接收窗口下的自然分片:

->522=504d336100820901000102030405060708090a0b0c0d0e0f1011121314151617 -> NG <-128=504d3362008200000901000102030405060708090a0b0c0d0e0f101112131415 <- NG <-128=767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495 <-128=f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415 <-128=767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495 <-12=f6f7f8f9fafbfcfdfeff6233

注意请求length字段为00 82(0x0200=512,高位 ng=1),且整帧 522 字节 = 10 头 + 512 payload + 2 CRC 尾,正好落在"NG 命令帧 10~522"的上界;应答 524 字节 = 12 头 + 512 payload + 2 尾,落在应答帧上界,随后按 128B 窗口分 5 段到达。

小结:NG 格式的设计取舍

把 doc/new_frame_format.md 与当前仓库源码放在一起看,这套协议演进遵循几条清晰的原则:

  1. 变长解决慢链路:10 字节的空命令帧 vs 544 字节的旧帧,在 9600 波特链路上就是 26.5s 与 5.75s 的差距;
  2. 可辨识与可重同步PM3a/PM3b魔数 + 可选 CRC,慢链路(USART/BT)默认开 CRC,USB 用占位魔数省一次计算;
  3. 语义回到应答本身status/reason字段 + "用自己的操作码应答",消灭了匿名CMD_ACK带来的 arg 借位 hack;
  4. 兼容边界精确锁定:只有 bootrom 相关帧(SendCommandBL/reply_old)保持 OLD,OLD 结构体尺寸钉死在 512,其余路径完成转换后旧字段(oldargng、MIX 常量)按计划删除。

开发者定位协议问题时的入口文件:字段与魔数在 include/pm3_cmd.h,客户端收发在 client/src/comms.c,设备侧回复与接收在 armsrc/cmd.c 与 armsrc/appmain.c,bootrom 在 bootrom/bootrom.c,刷写旧帧路径在 client/src/flash.c。

【免费下载链接】proxmark3Iceman Fork - Proxmark3项目地址: https://gitcode.com/GitHub_Trending/pr/proxmark3

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询