Proxmark3 EM4x70 位级命令协议剖析:任意 LF EM 命令抽象与 Trace 日志体系

发布时间:2026/9/17 18:49:00
Proxmark3 EM4x70 位级命令协议剖析:任意 LF EM 命令抽象与 Trace 日志体系
Proxmark3 EM4x70 位级命令协议剖析任意 LF EM 命令抽象与 Trace 日志体系【免费下载链接】proxmark3Iceman Fork - Proxmark3项目地址: https://gitcode.com/GitHub_Trending/pr/proxmark3本文基于 Proxmark3 仓库中的 arbitrary_lf_em_commands.md 展开完整梳理 EM4x70ID48/Megamos低频芯片的六条位级命令序列、LIW/HEADER/ACK 等特殊时序处理的设计考量并结合 armsrc/em4x70.c 源码说明任意命令序列抽象的实际落地位流bitstream描述结构、三种应答等待模式、以及逐比特 trace 日志系统。读完本文你将掌握 EM4x70 读写/认证/解锁的位级时序、命令参数含义以及如何在 Proxmark3 上开启调试日志来验证每一次lf em 4x70命令实际收发比特。1. 背景与设计目标EM4x70 是低频125 kHz可编程 ID 芯片的总称常见商品名为 ID48IDCLASS 之外的 Megamos 系列。Proxmark3 通过lf em 4x70命令族对它进行识别、写入、认证、PIN 操作和解锁。原实现中每个命令各自硬编码了发送哪些比特、等多久、收多少比特导致命令与响应缺少完整日志难以复现和排查问题修改命令序列如增加命令奇偶位时缺乏回归验证手段新增任意 LF EM 命令没有统一的测试入口。arbitrary_lf_em_commands.md 明确了三个目标与五阶段方法论目标改进lf em命令及其响应的日志记录提高命令序列的确定性使新命令的测试更简单。方法论记录现有代码实际使用的命令序列记录现有日志 API将少量对时序敏感的函数定义为抽象实现这些抽象加入日志。本文第 2~3 节对应记录命令序列第 4~5 节对应抽象定义与实现第 6 节对应日志 API 与 trace 验证第 7 节给出客户端侧的完整用法。2. EM4x70 的六条命令序列文档首先给出结论现有代码实际只使用六条命令序列。命令 ID 定义见 armsrc/em4x70.c#L95-L100#define EM4X70_COMMAND_ID 0x01 #define EM4X70_COMMAND_UM1 0x02 #define EM4X70_COMMAND_AUTH 0x03 #define EM4X70_COMMAND_PIN 0x04 #define EM4X70_COMMAND_WRITE 0x05 #define EM4X70_COMMAND_UM2 0x07这些 ID 来自 EM4170 数据手册。需要特别注意命令奇偶位parity问题某些芯片版本要求命令后跟一个偶校验位某些则不要求因此命令实际只保留最低三位掩码0x07。源码中为每个命令同时列出了两种编码// // w/o parity with parity #define EM4X70_COMMAND_ID 0x01 // 0b0001 -- 0b0011 #define EM4X70_COMMAND_UM1 0x02 // 0b0010 -- 0b0101 #define EM4X70_COMMAND_AUTH 0x03 // 0b0011 -- 0b0110 #define EM4X70_COMMAND_PIN 0x04 // 0b0100 -- 0b1001 #define EM4X70_COMMAND_WRITE 0x05 // 0b0101 -- 0b1010 #define EM4X70_COMMAND_UM2 0x07 // 0b0111 -- 0b1111芯片型号差异从 armsrc/em4x70.c#L102-L110 注释可确认命令 ID 与行为在 EM4170 与 V4070/EM4070 上相同V4070/EM4070 不支持 PIN 命令、不能读 UM2且 WRITE 只限 block 0..9其 10 个块可能是 OTPEM4170 增加了 PIN 与 UM2共 16 个块include/em4x70.h#L26-L30 定义EM4X70_NUM_BLOCKS 16、PIN 低字地址 10、高字地址 11。以下逐条给出文档记录的命令序列表RM 是 Reader/Modulation 前导位LIW 是 Listen Window 收听窗口HEADER 是 16 位响应头0b1111111111110000。2.1 ID 命令读取 32 位卡号等待LIW在下一个LIW开始时发送sourcebitscommenttagLIWlisten window syncreader0b00RMreader0b001CMDreader0b1command parity bittagHEADERHEADER (0b1111111111110000)tag32-bitsID (D31..D0)tagLIWtag 回到待命可接收下一条命令2.2 UM1 命令读取 User Memory 1sourcebitscommenttagLIWlisten windowreader0b00RMreader0b010CMDreader0b1command parity bittag16-bitsHEADERtag32-bitsUM1 datatagLIWtag 回到待命UM1 的 32 位中高位包含锁定位Lockbit 0/1Proxmark3 用它判断卡是否 LOCKED。2.3 UM2 命令读取 User Memory 2sourcebitscommenttagLIWlisten windowreader0b00RMreader0b111CMDreader0b1command parity bittag16-bitsHEADERtag64-bitsUM2 datatagLIWtag 回到待命2.4 Auth 命令加密认证sourcebitscommenttagLIWlisten windowreader0b00RMreader0b011CMDreader0b0command parity bitreader56-bitsRN读者随机数reader7-bitsTdiv 0b0000000恒为零reader28-bitsf(RN)密钥派生值tag16-bitsHEADERtag20-bitsg(RN)标签应答tagLIWtag 回到待命这是唯一带长随机载荷的读取命令发送共 95 位不含 RM接收 20 位 g(RN)认证算法见 doc/md/em4x70/lf_em4x70_trace_notes.md 中的完整 trace 对照。2.5 Write Word 命令写入一个 16 位块sourcebitscommenttagLIWlisten windowreader0b00RMreader0b101CMDreader0b0command parity bitreader4-bits目标地址/块reader1-bit地址奇偶位reader25-bits5x5 数据含行、列奇偶tagACK等待 (TWA) 后出现第一个 ACKtagACK等待 (WEE) 后出现第二个 ACKtagLIWtag 回到待命数据部分是 16 位有效数据按 5 行 × 5 列排布每个 4 位半字节后跟 1 位行奇偶共 4×520 位再加 4 位列奇偶和 1 位填充 0合计 25 位。文档中给出的等待逻辑为WaitTicks(EM4X70_T_TAG_TWA); if (check_ack()) { WaitTicks(EM4X70_T_TAG_WEE); if (check_ack()) { return PM3_SUCCESS; } }这与当前 send_bitstream_wait_ack_wait_ack() 的实现完全一致先等 TWA写访问时间查第一次 ACK再等 WEEEEPROM 写时间查第二次 ACK。2.6 PIN 命令设置/验证 PIN 解锁sourcebitscommenttagLIWlisten windowreader0b00RMreader0b100CMDreader0b1command parity bitreader32-bits标签 IDreader32-bitsPINtagACK等待 (TWALB) 后出现 ACKtagHEADERDELAYED (TWEE) 后出现 HEADERtag32-bits标签 ID回读tagLIWtag 回到待命注意 PIN 命令要求先通过 ID 命令读回标签 ID再填充发送序列——这正是em4x70_unlock、em4x70_write_pin入口函数里先调用em4x70_read_id()的原因见 armsrc/em4x70.c#L1523-L1555。3. 时序常量与 tick 体系文档将 LIW、ACK、HEADER 列为需要特殊处理的三类对象其核心都是时序。源码把数据手册中的时间全部换算为 tick见 armsrc/em4x70.c#L54-L83// 1 us 1.5 ticks // 1 RF Period (FC) 8 us 12 Ticks #define TICKS_PER_FC 12 #define EM4X70_T_TAG_QUARTER_PERIOD (8 * TICKS_PER_FC) #define EM4X70_T_TAG_HALF_PERIOD (16 * TICKS_PER_FC) #define EM4X70_T_TAG_THREE_QUARTER_PERIOD (24 * TICKS_PER_FC) #define EM4X70_T_TAG_FULL_PERIOD (32 * TICKS_PER_FC) // 1 Bit Period #define EM4X70_T_TAG_TWA (128 * TICKS_PER_FC) // Write Access Time #define EM4X70_T_TAG_DIV (224 * TICKS_PER_FC) // Divergency Time #define EM4X70_T_TAG_AUTH (4224 * TICKS_PER_FC) // Authentication Time #define EM4X70_T_TAG_WEE (3072 * TICKS_PER_FC) // EEPROM write Time #define EM4X70_T_TAG_TWALB (672 * TICKS_PER_FC) // Write Access Time of Lock Bits #define EM4X70_T_TAG_BITMOD (4 * TICKS_PER_FC) // 发送 0 时先停调制的时长 #define EM4X70_T_TAG_TOLERANCE (8 * TICKS_PER_FC) // 接收/LIW 容差 #define EM4X70_T_DELAY_FROM_LIW_TO_RM (72 * TICKS_PER_FC) // 从 LIW 到发送 RM 的默认延迟 #define EM4X70_T_PULSES_TO_SEARCH_FOR_LIW 50 // 搜索收听窗口的脉冲数 #define EM4X70_COMMAND_LIW_SEARCH_RETRIES 5 // 发送/读取命令的重试次数 #define EM4X70_MAX_SEND_BITCOUNT 96u // Auth 最长发送序列 #define EM4X70_MAX_RECEIVE_BITCOUNT 64u // 最长接收不含 16 位 HEADER要点1 bit 周期 32 个 RF 周期 256 µs即约 3906 bit/s所有WaitTicks()的等待都基于这套换算EM4X70_T_TAG_TOLERANCE提供 ±8 RF 周期的脉宽容差保证接收判定在不同天线耦合条件下仍然稳定。4. 抽象设计从特殊处理清单到位流引擎文档Abstraction required一节列出需要抽象的六类要素bits to send待发送比特数 存储这些比特的缓冲区bits to receive期望接收的比特数 接收缓冲区LIW同步下一条命令的特殊处理ACK等待 ACK 的特殊处理HEADER等待 HEADER 的特殊处理DELAY处理下一项之前延迟的 tick 数。当前实现正是围绕这六类要素组织核心数据结构是命令位流armsrc/em4x70.c#L528-L568typedef struct _em4x70_bitstream_t { uint8_t bitcount; // 发送要发的位数接收期望的位数 uint8_t one_bit_per_byte[EM4X70_MAX_BITSTREAM_BITS]; // 一字节存一位避免时序敏感代码里做位移 } em4x70_bitstream_t; typedef struct _em4x70_command_bitstream { uint8_t command; // 三位命令值用于选择收发处理函数 em4x70_bitstream_t to_send; em4x70_bitstream_t to_receive; uint8_t received_data_converted_to_bytes[...]; // 接收比特转字节逆序存储 } em4x70_command_bitstream_t;设计上有两点值得注意一字节一位one_bit_per_byte的注释说明这是在时序敏感代码中刻意避免位移运算、让发送/接收路径尽可能简单的取舍位流可预生成em4x70_command_generators_t是一组函数指针表id/um1/um2/auth/pin/write 各一个生成器在发送前就把整条待发送位流构造完毕。文档中的判断得到印证只需要定义三种与标签交互的序列而且读者可以在发出任何比特之前预先生成整条位流。三种交互模式对应文档中只有三种交互序列的结论模式函数适用命令行为发后直接读send_bitstream_and_read()ID / UM1 / UM2 / AUTH发送位流后立即同步 HEADER 并读取 N 位发—等 ACK—等 HEADER—读send_bitstream_wait_ack_wait_read()PIN等 TWALB 查 ACK再等 WEE 同步 HEADER 读 32 位发—等 ACK—等 ACKsend_bitstream_wait_ack_wait_ack()WRITE等 TWA 查 ACK再等 WEE 查第二个 ACK不接收数据各生成器都带参数校验例如 create_legacy_em4x70_bitstream_for_cmd_write() 校验地址必须只有低 4 位有效并强制发送位数为 34 位不含 RMAUTH 生成器 强制 95 位命令 4 RN 56 Tdiv 7 f(RN) 28任何构造错误都会打INTERNAL ERROR日志并返回失败而不是发出半条命令。send_bitstream_and_read()还特别处理了 AUTH 收到 20 位非字节整数倍的编码问题向上取整到 24 位解码保持与客户端既有行为兼容armsrc/em4x70.c#L677-L692。5. LIW、HEADER、ACK 的特殊处理文档指出这三类对象不能按普通比特流处理原因和实现如下。5.1 LIW同步而非数据LIW 是标签发射的收听窗口长脉冲读者必须在标签调制器开启的窗口内发送。find_listen_window() 通过脉宽特征识别它连续两个 80 FC6416的上升沿脉冲、一个 96 FC 的下降沿脉冲、一个 64 FC 的下降沿脉冲。识别成功后WaitTicks(EM4X70_T_DELAY_FROM_LIW_TO_RM)—— 等 72 个 RF 周期源码注释记录实测 24~40 FC 也能成功取 72 作为默认由find_listen_window直接发出两位 RM00调用方 send_bitstream_internal() 在TIMING SENSITIVE SECTION内以最小延迟逐位发送失败则最多重试EM4X70_COMMAND_LIW_SEARCH_RETRIES5次——注意重试的只是找 LIW不重发命令。5.2 HEADER带过渡失配的同步头HEADER 是 12 个1加 4 个0。文档特别提醒等待 HEADER 时可能因标签离场而长时间收不到脉冲如 PIN 命令且读取 HEADER 时可能漏掉过渡期间的最初几位因此必须特殊处理。em4x70_receive() 的处理是先跳过约半个头WaitTicks(6 * EM4X70_T_TAG_FULL_PERIOD)容忍起始噪声然后寻找1→0过渡1.5 个全周期的脉冲来同步再消费剩余 3 个0之后按脉宽解码数据脉宽 1 个全周期 → 1 位脉宽 1.5 个全周期 → 两位相同值 翻转边沿检测方向脉宽 2 个全周期 → 两位互补值。这正是 Manchester 编码的位恢复过程EM4X70_T_TAG_TOLERANCE保证 ±8 FC 内仍正确归类遇到 LIW 长脉冲或非法脉宽即结束接收并返回已收位数。5.3 ACK当前仍是延迟时间而非超时等待文档对此的评述值得保留ACK目前是一个 time-to-delay。它应该改为等待 ACK 的最大时间吗当前如果无卡在场check_ack()没有超时可能长时间坐等。 当前 check_ack() 按脉宽特征判别连续两个 2 全周期6464 FC的下降沿脉冲判为 ACK其余视为 NAK/LIW源码中也留有 TODOAdd similar function that will wait for an ACK/NAK up to a given timeout。也就是说文档中提出的改进方向带超时的 ACK 等待在仓库中仍属于已知待办读者在分析写命令偶发卡住类问题时可以把这一点纳入考虑。6. 位级日志与 Trace 对照验证文档的目标之一是能轻松测试新 LF 命令配套手段就是全量比特日志。实现分两层记录层em4x70_log_t结构记录每次传输/接收的start_tick / end_tick / bits_used / bit[]armsrc/em4x70.c#L387-L398。发送路径中em4x70_send_bit()是唯一实际切换调制的函数每个比特的起止 tick 与值在此被记录接收路径在em4x70_receive()首末比特处打点。输出层log_dump()按调试级别输出格式与 lf_em4x70_trace_notes.md 中的General format of the output一致[#] sent : [ 17169 .. 19545 ] ( 2376 ) 6 bits: 000001 ^^^^^^^^ ^^^^^^ .. ^^^^^^ ^^^^^^ ^^ ^^^^^ direction 首个比特的 tick 末个比特的 tick (END-START) 比特数 实际比特日志级别通过客户端命令hw dbg -N调节armsrc/em4x70.c#L29-L47 的DPRINTF_*宏族开发时也可在编译期把FORCE_ENABLE_LOGGING置为true。另外bitstream_dump()会打印计划发送/接收的位流与实际日志格式刻意保持一致便于肉眼比对应发与实发。6.1 Trace 笔记揭示的历史问题lf_em4x70_trace_notes.md 逐条记录了各lf em 4x70子命令在无--par/ 有--par时的实际发送位并拆解出 RM/CMD/Addr/Data 各字段。其中两条潜在 bug记录非常典型FRN 末四位恒为 0xF 而非 0xC已修复现象是auth命令的 f(RN) 最低半字节始终发0b1111。根因是日志缓冲区太小——由于包含 2 位 RMauth 需要 98 位容量。这条说明 trace 系统的价值它发现的是观测链路缺陷而非协议缺陷命令奇偶位应用不一致记录在案trace 显示 ID/UM1/UM2 无校验位而 WRITE/AUTH/PIN 恒带第 5 校验位。这与当前代码状态吻合legacy_*生成器把命令奇偶位固化进常量如 ID 发0x3 0b0011、AUTH 发0x6 0b0110而--par选项已被标记为弃用并忽略armsrc/em4x70.c#L48-L49 的g_deprecated_command_parity恒被置false各入口函数对--par都会打印 non-functional 警告。该笔记还记录了一个实操细节setpin/unlock/setkey的第一条 ID 命令在旧代码中曾按 3 位命令校验位发出会被标签误解为 AUTH 命令而拒绝。修改lf em 4x70相关代码后用同一份 trace 做回归比对确保没有改变实际发送/接收内容就是文档方法论的收尾验证步骤。7. 客户端命令与实战客户端解析与帮助文本位于 client/src/cmdlfem4x70.c常用子命令示例摘自源码帮助文本lf em 4x70 info lf em 4x70 write -b 15 -d c0de # 写 c0de 到块 15 lf em 4x70 auth --rnd 7D5167003571F8 --frn 982DBCC0 # autorecovery 测试密钥 lf em 4x70 setpin -p 11223344 lf em 4x70 unlock -p 11223344 lf em 4x70 setkey -k 022A028C02BE000102030405 # autorecovery 测试密钥 lf em 4x70 brute -b 7 --rnd 7D5167003571F8 --frn 982DBCC0 # 爆破 16 位部分密钥 lf em 4x70 recover ... # 由部分密钥位恢复完整密钥其中三组公开测试密钥可用于复现文档全部 tracepm3 测试密钥F32AA98CF5BE4ADFA6D3480B、research paper 密钥A090A0A02080000000000000、autorecovery 测试密钥022A028C02BE000102030405对应--rnd/--frn组合见 client/src/cmdlfem4x70.c 帮助文本。写键与写 PIN 的流程约束从 armsrc/em4x70.c 的em4x70_write_key/em4x70_write_pin入口可确认setkey先read_id探活再依次写 block 9→4 共 6 个字任一步失败即中止setpin先read_id依次写 PIN 高字block 11、低字block 10然后立即用新 PIN 发送 PIN 命令验证最后回读 UM1/UM2unlock先read_idPIN 命令依赖 ID发 PIN 命令成功后回读 UM1/UM2write写入成功后自动回读 ID/UM1/UM2客户端据此打印分块信息表与锁定位状态。做回归测试前可参考 lf_em4x70_trace_notes.md 的Initialization of the tag脚本把卡恢复到已知状态UM2 全AAAA、PIN/KEY 全AAAAAAAA、固定 ID78B8E012、UM121DF5678解锁态关键顺序约束是最后写 block 1UM1 锁定位所在块先写数据块再写 UM1lf em 4x70 write -b 15 -d AAAA lf em 4x70 write -b 14 -d AAAA lf em 4x70 write -b 13 -d AAAA lf em 4x70 write -b 12 -d AAAA lf em 4x70 write -b 11 -d AAAA lf em 4x70 write -b 10 -d AAAA lf em 4x70 write -b 9 -d AAAA lf em 4x70 write -b 8 -d AAAA lf em 4x70 write -b 7 -d AAAA lf em 4x70 write -b 6 -d AAAA lf em 4x70 write -b 5 -d AAAA lf em 4x70 write -b 4 -d AAAA lf em 4x70 write -b 3 -d 78B8 lf em 4x70 write -b 2 -d E012 lf em 4x70 write -b 0 -d 5678 lf em 4x70 write -b 1 -d 21DF8. 小结arbitrary_lf_em_commands.md 的价值在于把 EM4x70 驱动中隐性的时序知识显性化六条命令序列的位级结构、LIW/HEADER/ACK 三类特殊对象、以及预生成位流 三种交互模式的抽象边界。对照 armsrc/em4x70.c 可以看到这套抽象已经完整落地——位流生成器负责构造与校验、find_listen_window/em4x70_receive/check_ack负责时序敏感段、log_dump/bitstream_dump负责全比特 trace。对要扩展新 LF 命令或排查偶发通信失败的读者建议的工作路径是先按第 2 节表格核对协议结构再打开hw dbg高调试级别用第 6 节的日志格式比对应发/实发最后用第 7 节的初始化脚本保证每次测试从同一已知状态出发。【免费下载链接】proxmark3Iceman Fork - Proxmark3项目地址: https://gitcode.com/GitHub_Trending/pr/proxmark3创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考