news 2026/9/17 21:58:02

Proxmark3 LF_NEDAP_SIM:Nedap 125 kHz 门禁标签独立模拟模式实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Proxmark3 LF_NEDAP_SIM:Nedap 125 kHz 门禁标签独立模拟模式实战解析

Proxmark3 LF_NEDAP_SIM:Nedap 125 kHz 门禁标签独立模拟模式实战解析

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

本文基于 doc/standalone/lf_nedap_sim.md 与 armsrc/Standalone/lf_nedap_sim.c 的完整源码,讲解 Proxmark3(Iceman Fork)中LF_NEDAP_SIM独立模式(Standalone Mode)的工作原理:它如何在无 PC 的环境下编码一个带 CRC 与奇偶校验的 Nedap 标签(支持 64/128 位两种格式),并通过 LF 射频调制持续模拟给读卡器。读完你可以修改内置标签参数、独立编译该固件并刷入设备,同时理解 Nedap 编码中数字翻译表、CRC16 XMODEM 校验与 Biphase 反相信号生成的完整调用链。

模式定位:What 与 Why

LF_NEDAP_SIM是一个低频(125 kHz)独立模拟模式,运行在 Proxmark3 本体固件上,不依赖上位机。它的核心能力:

  • 模拟一个硬编码 ID 的 Nedap RFID 标签,同时支持 64 位与 128 位(long)两种 Nedap 帧格式,帧结构包含正确的 CRC 与成帧;
  • Nedap 是一种在欧洲仍有部署但相对小众的门禁系统。相比 HID 等协议,Nedap 卡更难获取,因此内置一个"已知标签"模拟器,可以低成本地测试 Nedap 读卡器的识别行为。

该模式在固件中注册为LF_NEDAP_SIM,由 armsrc/Standalone/Makefile.inc 通过编译开关WITH_STANDALONE_LF_NEDAP_SIM决定是否把lf_nedap_sim.c编入镜像:

# WITH_STANDALONE_LF_NEDAP_SIM ifneq (,$(findstring WITH_STANDALONE_LF_NEDAP_SIM,$(APP_CFLAGS))) SRC_STANDALONE = lf_nedap_sim.c endif

需要遵循 Proxmark3 独立模式的基本约束:一次只能编译一个 standalone 模式(除非使用 DANKARMULTI 加载器),详见 armsrc/Standalone/readme.md。

标签数据结构与默认值

模式模拟的标签用一个简单结构体描述(源码中的NEDAP_TAG):

typedef struct _NEDAP_TAG { uint8_t subType; // 子类型,4 bit uint16_t customerCode; // 客户代码,12 bit uint32_t id; // 标签 ID,5 位十进制数 uint8_t bIsLong; // 1 = 128 位 long 格式 } NEDAP_TAG, *PNEDAP_TAG;

固件中硬编码的默认标签为:

const NEDAP_TAG Tag = {.subType = 0x5, .customerCode = 0x123, .id = 42424, .bIsLong = 1};

subType=5、customerCode=0x123、id=42424、isLong=1(128 位格式)。这三个字段对应上位机lf nedap命令组的--st--cc--id参数(参见 client/src/cmdlfnedap.c),取值范围与独立模式保持一致:subType 0–15、customerCode 0–4095、id 0–99999(5 位十进制数)。

Nedap 编码原理:数字拆分、翻译表与 CRC

模式的核心函数是NedapGen()(lf_nedap_sim.c#L118-L175),它与上位机 client/src/cmdlfnedap.c#L304-L361 中的同名函数逐行一致,保证离线生成的比特流与lf nedap sim命令完全相同。编码过程分三步:

1. ID 拆位与索引链

5 位 ID 被拆成 5 个十进制位r1..r5,再递推生成 5 个"索引":

uint8_t idxC1 = r1; uint8_t idxC2 = (idxC1 + 1 + r2) % 10; uint8_t idxC3 = (idxC2 + 1 + r3) % 10; uint8_t idxC4 = (idxC3 + 1 + r4) % 10; uint8_t idxC5 = (idxC4 + 1 + r5) % 10;

每个索引再通过一张固定的 10 项非线性映射表转换成 4 bit 编码:

static const uint8_t translateTable[10] = {8, 2, 1, 12, 4, 5, 10, 13, 0, 9};

2. 首帧拼装 + CRC16 XMODEM 校验

buffer[0..4]依次装入0xC0 | subType(帧头标志 + 4 位子类型)、12 位 customerCode(分两个 nibble)、以及翻译后的 5 个 ID 位;随后对前 5 字节计算 CRC:

init_table(CRC_XMODEM); uint16_t checksum = crc16_xmodem(buffer, 5);

CRC 的 16 个数据位被逐 nibble交叉插入buffer[3..6]的低位,与原有数据按位交织——这是 Nedap 帧的"加密式"排列,目的是让静态特征不明显。解码端(demodNedap())执行的是同一过程的正向拆分,并用invTranslateTable[16](非法值标记为0xFF)反向还原 5 个索引,再按r2 = (10 + idxC2 - (idxC1 + 1)) % 10的递推逆式恢复 ID。

3. 成帧:前导字节、进位与奇偶位

// carry calc uint8_t carry = 0; for (uint8_t i = 0; i < sizeof(buffer); i++) { uint8_t tmp = buffer[sizeof(buffer) - 1 - i]; data[7 - i] = ((tmp & 0x7F) << 1) | carry; carry = (tmp & 0x80) >> 7; } data[0] = 0xFE | carry; data[7] |= isEven_64_63(data);

7 字节 buffer 按"低位在前"反向展开成 8 字节data[],最高进位并入0xFE帧头,使帧首呈现解调端识别的1111111110前导码(preamble,见 cmdlfnedap.c#L47 中的preamble[] = {1,1,1,1,1,1,1,1,1,0,0})。isEven_64_63()对 64 位数据做偶校验(位计数奇偶),结果写入末字节最低位。

128 位 long 格式的第二段bIsLong=1时)再追加 8 字节:前 5 字节以位域方式平铺原始 ID(id0/id1/id2的 nibble 拼接),随后是固定字段0x710x40与两个未知保留位0x00,最后同样附加偶校验位。解调端会校验固定字段0x71/0x40并打印 "Second Card" 的 ID,与生成端一一对应。

比特流生成与 Biphase 调制

编码出的 8/16 字节如何变成射频上的波形?NedapPrepareBigBuffer()(lf_nedap_sim.c#L79-L98)分两步:

  1. bytes_to_bytebits()把字节按LSB 在前展开为比特序列(64 或 128 bit);
  2. 逐比特调用biphaseSimBitInverted(),把每个逻辑比特写成64 个采样点的 Biphase(ASK/Diphase)波形
static void biphaseSimBitInverted(uint8_t c, int *n, uint8_t *phase) { uint8_t *dest = BigBuf_get_addr(); if (c) { // 数据位 1:半周期翻转 memset(dest + (*n), c ^ 1 ^ *phase, 32); memset(dest + (*n) + 32, c ^ *phase, 32); *n += 64; } else { // 数据位 0:保持,相位翻转 memset(dest + (*n), c ^ *phase, 64); *phase ^= 1; *n += 64; } }

这正是 "Biphase = 数据 0 时相位翻转、数据 1 时半周期翻转" 的标准实现,且整体取反(!bitStream[i],invert 输出)。若最后一位结束于相位 1,函数会再跑一遍取反序列以锁定相位——这与客户端lf nedap sim使用encoding=2 (Biphase)、invert=1、clock=64下发 CMD_LF_ASK_SIMULATE 的参数完全一致,两者是同一物理层的两种实现路径。

波形写入 BigBuf 后由SimulateTagLowFrequency(n, 0, true)播放。该函数(armsrc/lfops.c#L855-L938)工作在 LF 边沿检测模式下:先用 FPGA 检测读卡器场(等待SSC_CLK拉高),检测到场后逐采样点切换OPEN_COIL()/SHORT_COIL()实现线圈调制——1 打开通路、0 短路负载调制,即 125 kHz 上的 ASK 负载响应。每 64 个采样点构成一个数据位,因此数据速率与编码时约定一致。

运行行为:状态机、LED 与退出方式

RunMod()(lf_nedap_sim.c#L54-L77)的主循环结构:

void RunMod(void) { StandAloneMode(); FpgaDownloadAndGo(FPGA_BITSTREAM_LF); // 加载 LF 比特流 n = NedapPrepareBigBuffer(&Tag); do { WDT_HIT(); if (data_available()) break; // USB 有命令 → 退出 SimulateTagLowFrequency(n, 0, true); // 持续发射(检测到场才响应) } while (BUTTON_HELD(1000) == BUTTON_NO_CLICK); // 长按 1 秒 → 退出 LEDsoff(); }
  • 状态机只有一个Simulate状态:启动后连续发射,循环等待读卡器场出现并应答,直到"按钮长按 1000 ms"或"收到 USB 数据"任一条件满足才退出;
  • LED 行为极简ledcontrol=true使SimulateTagLowFrequencyEx()在读卡器场检测期间点亮 LED_D(检测到场 → 亮灯并模拟,离场 → 灭灯),除此之外无额外指示;
  • 启动时通过Dbprintf打印一行[=] LF Nedap simple simulator -- started及标签参数(ID / subtype / customer code),若设备仍通过 USB/FPC 连接,可在终端看到这些调试输出,方便确认模拟的是哪个标签。

对应的 mermaid 状态图(摘自原文档):

[*] --> Simulate : Startup Simulate --> Simulate : Continuous transmission Simulate --> [*] : Button hold 1s / USB data

自定义:修改被模拟的标签

独立模式的标签参数是编译期硬编码的(这正是它与上位机lf nedap sim --st 5 --cc 307 --id 42424 -l命令的本质区别——后者无需重新编译)。修改 lf_nedap_sim.c 中的常量即可:

static NEDAP_TAG tag = { .subType = 0x5, .customerCode = 0x123, .id = 42424, .bIsLong = 1, // 0 = 64 位短格式,1 = 128 位 long 格式 };

修改时请遵守NedapGen()的隐含约束:id必须 ≤ 99999(5 位十进制)、customerCode≤ 0xFFF、subType≤ 0xF,否则编码与解调端的校验逻辑(索引表逆映射、固定字段比对)将无法通过。

编译与烧录

按原文档给出的流程,在仓库根目录执行:

make clean make STANDALONE=LF_NEDAP_SIM -j ./pm3-flash-fullimage

STANDALONE=LF_NEDAP_SIM会展开成编译开关WITH_STANDALONE_LF_NEDAP_SIM(见 Makefile.inc 中的 findstring 逻辑)。编译头信息应确认包含该模式名。刷写后,在设备上长按按钮触发 LED 显示并进入 standalone 模式。注意:独立模式编译时会替换常规主程序的入口行为,烧录该固件后设备默认就运行此模式(配合ModInfo()打印的 "LF Nedap simple simulator" 标识,可通过客户端hw status确认当前固件装的是什么模式)。

与主机端命令族的交叉验证

独立模式的价值需要配套验证手段,仓库中完整的 Nedap 支持集中在 client/src/cmdlfnedap.c 的lf nedap命令族:

命令功能与独立模式的关系
lf nedap reader [-@]读一次(或连续读)真实 Nedap 卡,输出 ID/subtype/customer code用真卡校准你对格式的理解
lf nedap demod从已抓取的 GraphBuffer 中做 ASK/Biphase 解调(clock 64、invert)并解码验证模拟信号能否被正确解调
lf nedap sim --st 5 --cc 307 --id 42424 -l主机端模拟,参数与独立模式默认值一一对应独立模式的"联机版"
lf nedap clone --cc <n> --id <n> [-l] [-q5|-em]把 Nedap 标签克隆到 T55x7 / Q5(T5555) / EM4305用可编程卡片固化模拟结果

其中 clone 使用的 T55x7 配置块定义在 client/src/cmdlft55xx.h#L72-L73:

#define T55X7_NEDAP_64_CONFIG_BLOCK 0x907f0042 // BiPhase, data rate 64, 2 data blocks #define T55X7_NEDAP_128_CONFIG_BLOCK 0x907f0082 // BiPhase, data rate 64, 4 data blocks

Biphase、数据率 64、64 位 2 块 / 128 位 4 块——与独立模式biphaseSimBitInverted()按 64 采样点/位生成的波形约定吻合。推荐工作流:先用lf nedap reader读取目标卡的参数 → 把参数写入lf_nedap_sim.cTag常量 →make STANDALONE=LF_NEDAP_SIM编译烧录 → 用读卡器验证独立模式下的模拟行为,或直接用lf nedap sim联机模拟做快速回归。

小结与延伸阅读

LF_NEDAP_SIM用不到 200 行代码展示了 Proxmark3 独立模式的典型骨架:ModInfo()/RunMod()双入口、FPGA 比特流加载、BigBuf 波形生成、WDT_HIT()喂狗、USB/按钮双退出通道,以及"编码逻辑与主机端共享"(NedapGen在两端逐行一致)以保证帧格式一致性。理解它之后,你可以低成本地搭建 Nedap 读卡器测试环境,也能以此为模板扩展自己的 LF 模拟模式(参考 LF_SKELETON 模板 与 EM4100 Emulator)。

关键文件索引:

  • 模式实现:armsrc/Standalone/lf_nedap_sim.c
  • 模式文档(本文主体):doc/standalone/lf_nedap_sim.md
  • 编译注册:armsrc/Standalone/Makefile.inc
  • 独立模式开发指南:armsrc/Standalone/readme.md
  • 主机端命令族(demod/reader/clone/sim):client/src/cmdlfnedap.c
  • LF 模拟底层:armsrc/lfops.c(SimulateTagLowFrequencyEx

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 21:57:58

Flink 1.13集成Hadoop 3.x:版本冲突解决与源码编译实战

这些年在公司做实时数仓&#xff0c;经常要面对的一个问题就是 Flink 和 Hadoop 的版本配套关系。Flink 1.13 这个版本用得人不少&#xff0c;但当你拿着官方下载的 Flink 1.13 包去对接一个 Hadoop 3.x 集群时&#xff0c;十有八九会碰一鼻子灰&#xff0c;报错信息千奇百怪&a…

作者头像 李华
网站建设 2026/9/17 21:55:46

SIB严重障碍量表电子化:从.doc解析到计分入库与随访

简介&#xff1a;《严重障碍量表SIB.doc》是一份面向临床医生、护理人员、老年精神科研究者及临床试验从业者的专业评估文档&#xff0c;用于系统测量晚期阿尔茨海默病患者的认知功能水平&#xff0c;帮助判断损伤程度并支持个体化照护方案与疗效跟踪。量表共51个条目&#xff…

作者头像 李华