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 拼接),随后是固定字段0x71、0x40与两个未知保留位0x00,最后同样附加偶校验位。解调端会校验固定字段0x71/0x40并打印 "Second Card" 的 ID,与生成端一一对应。
比特流生成与 Biphase 调制
编码出的 8/16 字节如何变成射频上的波形?NedapPrepareBigBuffer()(lf_nedap_sim.c#L79-L98)分两步:
bytes_to_bytebits()把字节按LSB 在前展开为比特序列(64 或 128 bit);- 逐比特调用
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-fullimageSTANDALONE=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.c的Tag常量 →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),仅供参考