josevcm/nfc-laboratory
GitHub: josevcm/nfc-laboratory
基于SDR接收机的NFC信号嗅探与协议解码工具,支持非接触式NFC-A/B/F/V和接触式ISO7816的实时捕获、解调与协议分析。
Stars: 544 | Forks: 58
# SDR nfc-laboratory 3
[](https://github.com/josevcm/nfc-laboratory/releases/latest)
[](https://github.com/josevcm/nfc-laboratory/releases/download/3.4.2/nfc-lab-3.4.2-x86_64-installer.exe)
[](https://github.com/josevcm/nfc-laboratory/releases/download/3.4.2/nfc-lab_3.4.2_amd64.deb)
使用 SDR 接收机进行 NFC 信号嗅探和协议解码,支持实时解调与解码 NFC-A、NFC-B、NFC-F 和 NFC-V 信号(速率高达 424 Kbps)。支持带有 ISO7816 协议的接触式智能卡逻辑分析仪。
## 功能
- NFC 实时信号捕获与解调。
- ISO7816 实时信号捕获与解码。
- 支持非接触式 ISO14443-A、ISO14443-B、ISO15693 和 ISO18092 的射频解码器。
- 支持带有 ISO7816 协议的接触式智能卡的逻辑解码器。
- 信号分析与协议时序分析。
- 信号频谱与波形视图。
- 信号帧与协议详细视图。
- 将信号捕获导出为压缩的 TRZ 格式。
- 支持从 WAV 和压缩的 TRZ 格式导入信号。
- 支持 AirSpy 和 RTL-SDR 接收机。
- 支持 DreamSourceLab DSLogic Plus、Pro16 和 Pro32 逻辑分析仪。
## 安装说明
### Windows
您可以使用预编译的 Windows 安装程序从 [releases](https://github.com/josevcm/nfc-laboratory/releases/latest/) 安装 NFC Laboratory。
或者从 [releases](https://github.com/josevcm/nfc-laboratory/releases/latest/) 下载便携版安装包。
### Linux
#### Debian/Ubuntu
从 [releases](https://github.com/josevcm/nfc-laboratory/releases/latest/) 下载 `.deb` 软件包并安装:
```
sudo dpkg -i nfc-lab_*_amd64.deb
```
## 描述
通过使用 SDR 接收机,可以捕获、解调并解码卡片与读卡器之间的 NFC 信号。
目前,已实现的检测与解码功能包括:
- NFC-A (ISO14443A):106kbps、212kbps 和 424kbps,采用 ASK / BPSK 调制。
- NFC-B (ISO14443B):106kbps、212kbps 和 424kbps,采用 ASK / BPSK 调制。
- NFC-V (ISO15693):26kbps 和 53kbps,1 of 4 编码和 1 of 256 编码的 PPM / BPSK 调制(FSK 待支持)。
- NFC-F (ISO18092):初步支持 212kbps 和 424kbps,采用 manchester 调制。
对于接触式智能卡,借助 DreamSourceLab 的逻辑分析仪实现了 ISO7816 协议的支持。
## 应用程序截图
主信号视图。


NFC 波形详细视图。

ISO7816 波形详细视图。

无线电频谱视图。

协议详细视图和过滤功能。



如你所见,应用程序将功能划分到不同的选项卡中:
- 左上方:
- **Frames**(帧):在接触式接口和非接触式接口中捕获的已解码帧的图形视图。
- **Signal**(信号):显示用逻辑分析仪捕获的原始信号以及带有协议标记的射频接口信号。
- **Receiver**(接收机):在采集期间显示射频接口捕获的信号频谱。
- 底部:
- **Frames**(帧):在接触式接口和非接触式接口中捕获的已解码帧的表格视图。
## 应用程序设置
设置存储在用户主目录中,对于 Windows 位于 Roaming 文件夹下 %USERPROFILE%\AppData\Roaming\josevcm\nfc-lab.ini。
该文件在应用程序首次运行时创建,可以包含以下部分:
窗口状态,每次应用程序关闭时更新。
```
[window]
timeFormat=false
followEnabled=true
filterEnabled=true
windowWidth=1024
windowHeight=700
```
逻辑解码器和 ISO7816 状态,由工具栏选项 **Logic Acquire**、**Features** 和 **Protocol** 菜单下的 **Logic Decoder** 控制。
目前,通道信号映射 **channelIO**、**channelCLK**、**channelRST**、**channelVCC** 是固定的,更改这些值无效。
```
[decoder.logic]
enabled=true
[decoder.logic.protocol.iso7816]
enabled=true
channelIO=0
channelCLK=1
channelRST=2
channelVCC=3
```
射频解码器以及 NFC-A、NFC-B、NFC-F、NFC-V 状态,由工具栏选项 **Radio Acquire**、**Features** 和 **Protocol** 菜单下的 **Radio Decoder** 控制。
```
[decoder.radio]
enabled=true
[decoder.radio.protocol.nfca]
enabled=true
[decoder.radio.protocol.nfcb]
enabled=true
[decoder.radio.protocol.nfcf]
enabled=true
[decoder.radio.protocol.nfcv]
enabled=true
```
Airspy 接收机的配置参数,在 3rd 谐波 40.68Mhz 处调谐可获得最佳性能。
```
[device.radio.airspy]
centerFreq=40680000
sampleRate=10000000
gainMode=1
gainValue=4
mixerAgc=0
tunerAgc=0
biasTee=0
directSampling=0
enabled=true
```
HydraSDR RFOne 接收机的配置参数,在 2nd 谐波 27.12Mhz 处调谐可获得最佳性能。
```
[device.radio.hydrasdr]
centerFreq=27120000
sampleRate=10000000
gainMode=1
gainValue=3
mixerAgc=0
tunerAgc=0
biasTee=0
directSampling=0
enabled=true
```
对于某些读卡器(如 Renesas NFC Readers),在 2nd 谐波调谐将无法解码信号,因此我们建议将中心频率设置在 3rd 谐波,即 40.68MHz。
```
[device.radio.hydrasdr]
centerFreq=40680000
sampleRate=10000000
gainMode=1
gainValue=3
mixerAgc=0
tunerAgc=0
biasTee=0
directSampling=0
enabled=true
```
RTL-SDR 接收机的配置参数,在 2nd 谐波 27.12Mhz 处调谐可获得最佳性能。由于该设备采样率较低且为 8-bit 分辨率,使用该设备进行解码非常有限,无法提供必要的质量,仅作为实验参考支持。
```
[device.radio.rtlsdr]
centerFreq=27120000
sampleRate=3200000
gainMode=1
gainValue=125
biasTee=0
directSampling=0
mixerAgc=0
tunerAgc=0
```
用于查看运行状况的日志控制。
```
[logger]
root=WARN
app.main=INFO
app.qt=INFO
decoder.IsoDecoder=WARN
decoder.Iso7816=WARN
decoder.NfcDecoder=WARN
decoder.NfcA=WARN
decoder.NfcB=WARN
decoder.NfcF=WARN
decoder.NfcV=WARN
worker.FourierProcess=WARN
worker.LogicDecoder=INFO
worker.LogicDevice=INFO
worker.RadioDecoder=INFO
worker.RadioDevice=INFO
worker.SignalResampling=WARN
worker.SignalStorage=WARN
worker.TraceStorage=WARN
hw.AirspyDevice=WARN
hw.MiriDevice=WARN
hw.RealtekDevice=WARN
hw.RecordDevice=WARN
hw.DSLogicDevice=WARN
hw.DeviceFactory=WARN
hw.UsbContext=WARN
hw.UsbDevice=WARN
rt.Executor=INFO
rt.Worker=INFO
```
所有默认值都是固定的,对于大多数情况已经足够。
## 已测试的 SDR 接收机
我尝试了多种接收机,其中使用 AirSpy Mini 获得了最佳效果。我没有更多设备,但肯定它也适用于其他设备。
- HydraSDR RFOne:全新的 SDR 接收机,效果非常好,在 27.12Mhz 的二次谐波或 40.68Mhz 的三次谐波调谐,采样频率为 10 Mbps,通过这些参数可以捕获高达 424 Kbps 的通信。这是在此工具中推荐使用的设备。非常感谢 Benjamin Vernoux 对这款新接收机的支持,参见 [HydraSDR RFOne](https://github.com/hydrasdr)。
- AirSpy Mini 或 R2:效果非常好,在 40.68Mhz 的三次谐波调谐,采样频率为 10 Mbps,通过这些参数可以捕获高达 424 Kbps 的通信。这是此工具推荐使用的设备,参见 [AirSpy](https://github.com/airspy)。
- RTL SDR:在 27.12Mhz 的二次谐波调谐时可用,由于最大采样频率限制为 3Mbps 以及 8 位分辨率,仅允许捕获高达 106Kbps 的命令以及天线信号良好时的一些响应。此设备仅作为实验参考支持,如果想获得良好的结果,我不建议使用它。
- RTL SDR BLOG V4:此设备能够直接在 13.56Mhz 调谐,但仍保留了旧版 RTLSDR 在最大采样频率为 3Mbps 和 8 位分辨率上的限制,仅允许捕获高达 106Kbps 的命令以及天线信号良好时的一些响应。此设备仅作为实验参考支持,如果想获得良好的结果,我不建议使用它。
已测试的接收机,从左到右:

带有 HydraNFC 校准线圈的 Nooelec RTL-SDR:

带有自制天线和 ARC122U 读卡器的 AirSpy:

带有由 RC522 读卡器 PCB 制成的自制天线的 HydraSDR RFOne:

### 使用商用 RC522 PCB 制作天线
可以通过改造标准的 MFRC522 读卡器板(通常作为 RC522 模块出售)来构建一种廉价且非常有效的天线。这些板载天线环路已经为 13.56 MHz 设计和调谐,并带有匹配的电容网络,非常适合此用途。
#### 修改说明
1. **移除 MFRC522 芯片** 从 PCB 上取下。该芯片是不需要的;仅使用天线及其匹配网络。如果移除不可行,可以将其拆焊或直接留在原位而不连接。
2. **识别天线馈电点** 在 PCB 上。在大多数 RC522 板上,这些对应于先前连接到芯片 TX1 和 VMID 引脚的焊盘。

3. **连接 SDR 接收机** 到天线电路:
- SDR 天线输入(中心导体) → RC522 PCB 上的 **TX1** 或 **TX2** 焊盘
- SDR 地线 → RC522 PCB 上的 **GND / VMID** 焊盘

#### 结果
此修改提供了一个紧凑且匹配良好的天线,能够与直接放置在板表面的 NFC 卡和标签高效耦合。上面的照片展示了此设置与 HydraSDR RFOne 接收机配合使用的情况。
### RTL-SDR 驱动设置
你可以通过以下链接找到说明:https://www.rtl-sdr.com/rtl-sdr-quick-start-guide/
### 上变频器 & Bias-tee
为了避免调谐谐波,可以使用上变频器,从而直接调谐到 13.56Mhz 的载波频率。目前,结合 SpyVerter,AirSpy 和 HydraSDR RFOne 支持 biasTee 功能,这要感谢 [Benjamin DELPY](https://github.com/gentilkiwi)。
所需配置如下:
```
[device.radio.airspy]
centerFreq=133560000
sampleRate=10000000
gainMode=0
gainValue=3
tunerAgc=false
mixerAgc=false
biasTee=1
```
```
[device.radio.hydrasdr]
centerFreq=133560000
sampleRate=10000000
gainMode=0
gainValue=3
tunerAgc=false
mixerAgc=false
biasTee=1
```
### 直接采样模式
另一种避免使用谐波的方法是激活直接采样模式,并在允许的设备上调谐到 13.56Mhz 的载波频率。
目前,仅 RTLSDR 支持此功能,这要感谢 [Vincent Långström](https://github.com/vinicentus) 的贡献。你可以在 Q 分支或 I 分支上使用直接采样。由于 Q 分支效果更好,因此首选 Q 分支,使用 directSampling=2 设置 Q 分支,使用 directSampling=1 设置 I 分支,directSampling=0 关闭直接采样。
注意:并非所有 RTLSDR 设备都支持此功能。
所需配置如下:
```
[device.rtlsdr]
...
centerFreq=13560000
directSampling=1
...
```
## 已测试的逻辑分析仪
唯一测试过的逻辑分析仪 (LA) 是 DreamSourceLab DSLogic Plus,它与该应用程序完美配合,Pro16 和 Pro32 也受支持但未经测试(我没有这些设备)。
此逻辑分析仪的固件文件已包含在仓库中,你可以在 **dat/firmware** 文件夹中找到它们,这些文件必须与 nfc-lab.exe 应用程序一起放在 firmware 文件夹中。感谢 [DreamSourceLab](https://www.dreamsourcelab.com/product/dslogic-series/)。

解码 ISO7816 协议所需的通道连接为:
- Channel 0: IO
- Channel 1: CLK
- Channel 2: RST
- Channel 3: VCC

我使用了一个简单的适配器来将智能卡连接到从 aliexpress 购买的逻辑分析仪,例如 [这个](https://aliexpress.com/item/4000967188424.html) 和 [这个](https://aliexpress.com/item/1005007077428556.html):


## 硬件要求和性能
解调器设计为实时运行,因此需要一台具有大量处理能力的现代计算机。
我选择了一种折中方案,牺牲了一些优化,以保持代码的清晰度并方便其跟踪和调试。
出于这个原因,某些部分的性能可能还有提升空间,但我更多是将其作为一个教学练习,而不是作为一个生产级别的应用程序来完成的。
## 输入 / 输出格式
应用程序允许读取和写入两种不同格式的文件:
- WAV:读取每个采样 16 位的 WAV 格式信号,NFC 信号支持 1 或 2 个通道,逻辑分析仪信号支持 4 个通道。
- 具有 1 个通道的射频信号应包含绝对实数值的采样。如果使用 2 个通道,它们应包含 I/Q 分量的采样。
- 具有 4 个通道的逻辑信号应按顺序包含、CLK、RST 和 VCC 信号的采样。
- TRZ:分析后的信号可以存储和读取基于 TGZ 的压缩格式,其中包含:
- 自定义二进制格式的信号数据。
- JSON 格式的信号元数据。
JSON 内容位于 TRZ 文件中名为 **frame.json** 的条目中:
```
{
"frames": [
{
"dateTime": 1731144155.0108738,
"frameData": "05:00:08:39:73",
"frameFlags": 0,
"framePhase": 257,
"frameRate": 105938,
"frameType": 258,
"sampleEnd": 115545,
"sampleRate": 10000000,
"sampleStart": 108739,
"techType": 258,
"timeEnd": 0.0115545,
"timeStart": 0.0108739
},
...
}
```
- datetime: 帧的日期和时间,以自 epoch 以来的秒为单位。
- frameData: 十六进制格式的帧数据。
- frameFlags: 帧的标志,以下值的组合:
- ShortFrame = 0x01
- Encrypted = 0x02
- Truncated = 0x08
- ParityError = 0x10
- CrcError = 0x20
- SyncError = 0x40
- framePhase: 帧的阶段,以下值之一:
- NfcAnyPhase = 0x0100
- NfcCarrierPhase = 0x0101
- NfcSelectionPhase = 0x0102
- NfcApplicationPhase = 0x0103
- IsoAnyPhase = 0x0200
- frameRate: 帧的速率,以每秒比特数为单位,以下值之一:
- frameType: 帧的类型,以下值之一:
- NfcCarrierOff = 0x0100
- NfcCarrierOn = 0x0101
- NfcPollFrame = 0x0102
- NfcListenFrame = 0x0103
- IsoVccLow = 0x200
- IsoVccHigh = 0x201
- IsoRstLow = 0x202
- IsoRstHigh = 0x203
- IsoATRFrame = 0x0210
- IsoRequestFrame = 0x0211
- IsoResponseFrame = 0x0212
- IsoExchangeFrame = 0x0213
- sampleStart: 帧开始的采样点。
- sampleEnd: 帧结束的采样点。
- techType: 技术类型,以下值之一:
- NoneTech = 0x0000
- NfcAnyTech = 0x0100
- NfcATech = 0x0101
- NfcBTech = 0x0102
- NfcFTech = 0x0103
- NfcVTech = 0x0104
- IsoAnyTech = 0x0200
- Iso7816Tech = 0x0201
- timeEnd: 帧结束的时间(秒)。
- timeStart: 帧开始的时间(秒)。
## 实时控制台输出
使用 -j 选项,应用程序能够将解码的帧以 JSON 格式输出到控制台。感谢 [Steffen-W](https://github.com/Steffen-W) 提供此功能。
每个 JSON 帧包含以下字段:
| Field | Type | Description |
|----------------------|--------|-----------------------------------------------------------------------|
| `timestamp` | int | 帧采样时间 |
| `tech` | string | NfcA, NfcB, NfcF, NfcV, UNKNOWN |
| `type` | string | CarrierOff, CarrierOn, Poll, Listen |
| `tech_type` | int | 技术: 0x0101=NfcA, 0x0102=NfcB, 0x0103=NfcF, 0x0104=NfcV |
| `frame_type` | int | 类型: 0x0100=CarrierOff, 0x0101=CarrierOn, 0x0102=Poll, 0x0103=Listen |
| `sample_rate` | int | 采样率,单位 Hz (例如., 3200000) |
| `sample_start/end` | int | 帧采样的起始 / 结束 (相对) |
| `time_start/end` | float | 时间(秒,相对) |
| `date_time` | float | 绝对 Unix 时间戳 |
| `rate` | int | 比特率,单位 bps |
| `flags` | array | 错误: crc-error, parity-error, truncated, sync-error |
| `data` | string | 十六进制载荷: `"AA:BB:CC"` (对于载波事件是可选的) |
| `length` | int | frameData 中的字节数 |
示例:
```
$./nfc-lab -j
{"date_time":1737800643,"frame_type":256,"sample_end":0,"sample_rate":10000000,"sample_start":0,"tech":"UNKNOWN","tech_type":256,"time_end":0,"time_start":0,"timestamp":0,"type":"CarrierOff"}
{"date_time":1737800643,"frame_type":256,"sample_end":1,"sample_rate":10000000,"sample_start":1,"tech":"UNKNOWN","tech_type":256,"time_end":1e-07,"time_start":1e-07,"timestamp":1,"type":"CarrierOff"}
{"date_time":1737800643.0000038,"frame_type":257,"sample_end":38,"sample_rate":10000000,"sample_start":38,"tech":"UNKNOWN","tech_type":256,"time_end":3.8e-06,"time_start":3.8e-06,"timestamp":38,"type":"CarrierOn"}
{"data":"e0:80:31:73","date_time":1737800643.0031676,"flags":["request"],"frame_type":258,"length":4,"rate":105938,"sample_end":35216,"sample_rate":10000000,"sample_start":31677,"tech":"NfcA","tech_type":257,"time_end":0.0035216,"time_start":0.0031677,"timestamp":31677,"type":"Poll"}
{"data":"06:75:77:81:02:80:02:f0","date_time":1737800643.0036075,"flags":["response"],"frame_type":259,"length":8,"rate":105938,"sample_end":42918,"sample_rate":10000000,"sample_start":36075,"tech":"NfcA","tech_type":257,"time_end":0.0042918,"time_start":0.0036075,"timestamp":36075,"type":"Listen"}
{"data":"02:90:5a:00:00:03:ab:22:e5:00:eb:6b","date_time":1737800643.0061498,"flags":["request"],"frame_type":258,"length":12,"rate":105938,"sample_end":71838,"sample_rate":10000000,"sample_start":61497,"tech":"NfcA","tech_type":257,"time_end":0.0071838,"time_start":0.0061497,"timestamp":61497,"type":"Poll"}
{"data":"02:91:00:29:10","date_time":1737800643.0086663,"flags":["response"],"frame_type":259,"length":5,"rate":105938,"sample_end":91003,"sample_rate":10000000,"sample_start":86662,"tech":"NfcA","tech_type":257,"time_end":0.0091003,"time_start":0.0086662,"timestamp":86662,"type":"Listen"}
{"data":"03:90:6c:00:00:01:08:00:67:ce","date_time":1737800643.0235095,"flags":["request"],"frame_type":258,"length":10,"rate":105938,"sample_end":243727,"sample_rate":10000000,"sample_start":235096,"tech":"NfcA","tech_type":257,"time_end":0.0243727,"time_start":0.0235096,"timestamp":235096,"type":"Poll"}
{"data":"03:d4:17:00:00:91:00:3f:fe","date_time":1737800643.0254395,"flags":["response"],"frame_type":259,"length":9,"rate":105938,"sample_end":262136,"sample_rate":10000000,"sample_start":254396,"tech":"NfcA","tech_type":257,"time_end":0.0262136,"time_start":0.0254396,"timestamp":254396,"type":"Listen"}
{"data":"02:90:bd:00:00:07:01:00:00:00:80:00:00:00:1a:83","date_time":1737800643.0412457,"flags":["request"],"frame_type":258,"length":16,"rate":105938,"sample_end":426181,"sample_rate":10000000,"sample_start":412458,"tech":"NfcA","tech_type":257,"time_end":0.0426181,"time_start":0.0412458,"timestamp":412458,"type":"Poll"}
{"data":"02:04:3c:70:02:52:48:80:24:66:4d:fb:bb:a5:78:8d:00:00:45:67:10:10:20:11:00:70:29:d5:f7:1b:00:00:00:00:00:00:00:01:97:3e:07:d2:04:00:00:00:00:00:00:00:00:00:00:00:00:97:3e:00:00:00:91:af:a7:98","date_time":1737800643.0433269,"flags":["response"],"frame_type":259,"length":64,"rate":105938,"sample_end":487734,"sample_rate":10000000,"sample_start":433268,"tech":"NfcA","tech_type":257,"time_end":0.0487734,"time_start":0.0433268,"timestamp":433268,"type":"Listen"}
```
### 结合 Python 解析的实时控制台输出
此外,借助 **tools/py_nfclab** 模块,可以更友好的方式解析和显示帧。有关此模块的更多信息,请参见 [tools/py_nfclab/README.md](tools/py_nfclab/README.md)。
```
$ ./nfc-lab -j | python3 -m tools.py_nfclab
NFC Frame Monitor
Live mode - reading from stdin
(Press Ctrl+C to stop)
================================================================================
[ 0.000000] NfcAnyTech CarrierOff | | 0B | (no data)
[ 0.000000] NfcAnyTech CarrierOff | | 0B | (no data)
[ 0.000004] NfcAnyTech CarrierOn | | 0B | (no data)
[ 0.003168] NfcA 105938 Poll | RATS | 4B | Cmd:E0 Params:80 CRC:3173
[ 0.003608] NfcA 105938 Listen | I-Block | 8B | Payload:067577810280 CRC:02F0
[ 0.006150] NfcA 105938 Poll | I-Block | 12B | Cmd:02 Params:905A000003AB22E500 CRC:EB6B
[ 0.008666] NfcA 105938 Listen | I-Block | 5B | Payload:029100 CRC:2910
[ 0.023510] NfcA 105938 Poll | I-Block | 10B | Cmd:03 Params:906C0000010800 CRC:67CE
[ 0.025440] NfcA 105938 Listen | I-Block | 9B | Payload:03D41700009100 CRC:3FFE
[ 0.041246] NfcA 105938 Poll | I-Block | 16B | Cmd:02 Params:90BD0000070100000080000000 CRC:1A83
[ 0.043327] NfcA 105938 Listen | I-Block | 64B | Payload[62B]:02043C700252488024664DFBBBA5788D... CRC:A798
```
## 测试文件
在 "wav" 文件夹中,你可以找到一系列针对 NFC-A、NFC-B、NFC-F 和 NFC-V 调制的不同捕获样本,并在 "json" 文件中进行了相应的分析。
这些文件可以直接从 NFC-LAB 应用程序的工具栏中打开以查看其分析结果,但主要目的是通过单元测试并检查解码器是否正常工作。
要运行单元测试,必须编译 **test-sdr** 构件,并使用指向 "wav" 文件夹的路径作为参数启动它,例如:
```
test-sdr.exe ../wav/
TEST FILE "test_NFC-A_106kbps_001.wav": PASS
TEST FILE "test_NFC-A_106kbps_002.wav": PASS
TEST FILE "test_NFC-A_106kbps_003.wav": PASS
TEST FILE "test_NFC-A_106kbps_004.wav": PASS
TEST FILE "test_NFC-A_212kbps_001.wav": PASS
TEST FILE "test_NFC-A_424kbps_001.wav": PASS
TEST FILE "test_NFC-A_424kbps_002.wav": PASS
TEST FILE "test_NFC-B_106kbps_001.wav": PASS
TEST FILE "test_NFC-B_106kbps_002.wav": PASS
TEST FILE "test_NFC-F_212kbps_001.wav": PASS
TEST FILE "test_NFC-F_212kbps_002.wav": PASS
TEST FILE "test_NFC-V_26kbps_001.wav": PASS
TEST FILE "test_NFC-V_26kbps_002.wav": PASS
TEST FILE "test_POLL_ABF_001.wav": PASS
TEST FILE "test_POLL_AB_001.wav": PASS
```
## 构建说明
本项目基于 Qt6。
包含以下组件:
- /src/nfc-app/app-qt:基于 Qt Widgets 的应用程序接口
- /src/nfc-app/app-rx:命令行解码器应用程序。
- /src/nfc-lib/lib-ext:SDR 和逻辑分析仪的外部库及驱动程序。
- /src/nfc-lib/lib-hw:SDR 和逻辑分析仪的硬件抽象层。
- /src/nfc-lib/lib-lab:信号处理和协议解码。
- /src/nfc-lib/lib-rt:运行时实用程序和线程管理。
所有组件均可使用 mingw-g64 编译,要求支持 C++17 的最低版本,推荐 11.0 或更高版本。
### Windows 手动构建 (MSYS2)
本指南使用 **UCRT64** 环境和 **Ninja** 构建系统,与 CI 流水线保持一致。
#### 1. 安装 MSYS2
从 https://www.msys2.org/ 下载并安装 MSYS2,或者通过 winget 安装:
```
winget install -e --id MSYS2.MSYS2
```
#### 2. 安装依赖
打开 **MSYS2 UCRT64** 终端(`C:\msys64\ucrt64.exe`)并安装所需的软件包:
```
pacman -Syu
pacman -S --needed \
mingw-w64-ucrt-x86_64-toolchain \
mingw-w64-ucrt-x86_64-cmake \
mingw-w64-ucrt-x86_64-ninja \
mingw-w64-ucrt-x86_64-qt6 \
mingw-w64-ucrt-x86_64-libusb \
mingw-w64-ucrt-x86_64-pkgconf
```
#### 3. 克隆仓库
```
git clone https://github.com/josevcm/nfc-laboratory.git
cd nfc-laboratory
```
#### 4. 配置并构建
```
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=/ucrt64
cmake --build build --parallel
```
生成的二进制文件位于 `build/src/nfc-app/app-qt/nfc-lab.exe`。
#### 5. 部署 Qt 运行环境
运行 `windeployqt` 以将所需的 Qt DLL 复制到可执行文件旁边:
```
/ucrt64/bin/windeployqt \
--compiler-runtime \
--no-translations \
--no-system-d3d-compiler \
--no-opengl-sw \
build/src/nfc-app/app-qt/nfc-lab.exe
```
#### 6. 复制 SDR 驱动 DLL
```
cp dll/airspy/x86_64/bin/*.dll build/src/nfc-app/app-qt/
cp dll/hydrasdr/x86_64/bin/*.dll build/src/nfc-app/app-qt/
cp dll/rtlsdr/x86_64/bin/*.dll build/src/nfc-app/app-qt/
```
#### 7. 复制资源
```
cp -r dat/config build/src/nfc-app/app-qt/
cp -r dat/drivers build/src/nfc-app/app-qt/
cp -r dat/firmware build/src/nfc-app/app-qt/
```
#### 8. 运行
```
./build/src/nfc-app/app-qt/nfc-lab.exe
```
### Linux 手动构建
安装依赖
```
sudo apt install cmake g++ g++-11 qt6-base-dev libqt6svg6 libusb-1.0-0-dev zlib1g-dev libgl1-mesa-dev libairspy-dev librtlsdr-dev
```
克隆仓库
```
git clone https://github.com/josevcm/nfc-laboratory.git
```
创建一个 **build** 目录并配置项目(更改 `CMAKE_BUILD_TYPE=Debug` 和 `-B cmake-build-debug` 以输出调试信息)
```
cmake -DCMAKE_BUILD_TYPE=Release -S nfc-laboratory -B build
```
编译项目:
```
cmake --build build --target nfc-lab -- -j$(nproc)
```
将基本配置文件复制到构建目录:
```
cp -r nfc-laboratory/dat/firmware build/src/nfc-app/app-qt/
```
创建指向该应用程序的符号链接以便于访问:
```
ln -s build/src/nfc-app/app-qt/nfc-lab nfc-lab
```
启动应用程序:
```
./nfc-lab
```
## 源码许可
如果你认为这是一项有趣的工作,或者打算将其用于某些用途,请给我发送电子邮件告诉我,我将很高兴交流经验,非常感谢。
本项目在 GPLv3 许可条款下发布,但是其中部分内容受其他类型的许可约束,如果你对此工作感兴趣,请自行检查。
- 位于 `src/nfc-lib/lib-ext/airspy` 的 AirSpy SDR 驱动 参见 https://github.com/airspy/airspyone_host
- 位于 `src/nfc-lib/lib-ext/hydrasdr` 的 HydraSDR RFOne 驱动 参见 https://github.com/hydrasdr/rfone_host
- 位于 `src/nfc-lib/lib-ext/mirisdr` 的 MiriSDR 驱动 参见 https://github.com/f4exb/libmirisdr-4
- 位于 `src/nfc-lib/lib-ext/rtlsdr` 的 RTL SDR 驱动 参见 https://osmocom.org/projects/rtl-sdr
- 位于 `src/nfc-lib/lib-ext/mufft` 的 mufft 库 参见 https://github.com/Themaister/muFFT
- 位于 `src/nfc-lib/lib-ext/nlohmann` 的 nlohmann json 参见 https://github.com/nlohmann/json
- 位于 `src/nfc-lib/lib-ext/microtar` 的 microtar 参见 https://github.com/rxi/microtar
- 位于 `src/nfc-app/app-qt/src/main/cpp/3party/customplot` 的 QCustomPlot 参见 https://www.qcustomplot.com/
- 位于 `src/nfc-app/app-qt/src/main/assets/theme` 的 QDarkStyleSheet 参见 https://github.com/ColinDuquesnoy/QDarkStyleSheet
- 位于 `src/nfc-lib/lib-ext/crapto1` 的 Crapto1
## 发布版本
预编译的 x86_64 安装程序可以在仓库中找到,你可以从 [releases](https://github.com/josevcm/nfc-laboratory/releases/latest/) 下载最新版本。
# 它是如何工作的?
## 待分析信号的基础概念
普通的 NFC 卡工作在 13.56 Mhz 频率,因此第一步是接收该信号并将其解调以获取基带流。为此,任何能够调谐到该频率的 SDR 设备都可以使用,我有一台出色且廉价的 AirSpy Mini,能够调谐 24Mhz 到 1700Mhz。(https://airspy.com/airspy-mini/)
然而,这个接收机无法调谐 13.56Mhz,取而代之的是我使用在 27.12Mhz 的二次谐波或 40.68Mhz 的三次谐波,并取得了良好的效果。
接收到的信号将由如上图所示的 I 和 Q 分量组成。

从这些分量中,使用经典公式 sqrt(I^2 + Q^2) 计算出真实的幅度。让我们来看一下在基带中接收到的信号(经过 I/Q 到幅度转换后)对于 REQA 命令及其响应的捕获:

如你所见,这是一个 100% ASK 调制的信号,对应于 NFC 规范中的 NFC-A REQA 26h 命令,卡片的响应使用一种称为负载调制的技术,在命令之后的主信号上表现为一系列脉冲。这是最基础的调制方式,但每个 NFC-A / B / F / V 标准都有其自身的特性。
### NFC-A 调制
该标准对应于 ISO14443A 规范,其中描述了调制方式以及适用的时序。
读卡器帧使用 100% ASK 调制和改进的 miller 编码。

当速率为 106 Kbps 时,卡响应使用 manchester 方案编码,并在 848 KHz 的副载波上采用 OOK 负载调制。

对于更高的速度,212 kbps、424 kbps 和 848 kbps,它采用 NRZ-L 编码,并在相同的副载波上使用二进制相移键控 (BPSK)。

### NFC-B 调制
该标准对应于 ISO14443B 规范,其中描述了调制方式以及适用的时序。
读卡器帧使用 10% ASK 和 NRZ-L 编码。

来自卡片的响应使用二进制相移键控 (BPSK) 进行编码,采用 NRZ-L 编码。

### NFC-F 调制
该标准对应于 ISO18092 和 JIS.X.6319 规范,其中描述了调制方式以及适用的时序。
支持 212 kbps 到 848 kbps 的速度,读卡器和卡片帧均使用观察型或反向 manchester 编码,如下所示。

观察型 manchester 调制。

反向 manchester 调制。

### NFC-V 调制
该标准对应于 ISO15693 规范,其中描述了调制方式以及适用的时序。
编码基于脉冲位置调制 (PPM),其中信息通过修改脉冲在每个时隙中的位置来进行编码。
有两种模式,1 of 4 和 1 of 256,其中每个符号分别编码 2 位和 8 位,这是前者的示例。

根据 VCD 发出的请求中 flags 字段的 bit 0 的值,卡响应可以采用两种不同的编码方式。
如果 flags bit 0 = 0,卡片将仅使用一个 fc/32 (423.75 kHz) 的副载波进行响应,采用 OOK 调制(与 NFC-A 一样)。
如果 flags bit 0 = 1,卡片将使用两个副载波 fc/32 (423.75 kHz) 和 fc/28 (484.28 kHz) 进行响应,采用 2-FSK 调制。
注意:目前本软件仅支持第一种模式(抱歉)。
根据编码方式,可能的速度为 26Kbps 和 53Kbps,然而这些卡片可以在更远的距离被读取。
## 信号处理
现在我们来看看如何对其进行解码。
### 第一步,准备基础信号
在开始对每种调制进行解码之前,有必要从一系列基础信号开始,这些信号将在过程的其余部分为我们提供帮助。
我接下来要解释的概念在 Sam Koblenski 的页面 (https://sam-koblenski.blogspot.com/2015/08/everyday-dsp-for-programmers-basic.html) 上有很好的描述,我建议你阅读它以充分理解我们将要进行的分析相关的所有过程。
请记住,从 SDR 接收机收到的采样由 I/Q 值组成,因此第一步是获取真实的信号。
一旦我们获得了真实信号,就需要消除直流分量 (DC),这将极大地方便后续的分析过程。为此我们将使用一个简单的 IIR 滤波器。
为了计算调制深度,我们需要知道信号的包络,就好像它没有被脉冲或副载波调制一样,为此我们将使用一个简单的慢速指数平均值。
最后,我们将获得信号的标准差或方差,这将有助于我们根据背景噪声计算出合适的检测阈值。

每个分量的示例,x(t)、w(t)、v(t) 和 a(t)。

### 接下来,识别所需的调制类型
正如我们在描述中所见,NFC-A / B / F / V 标准将使用不同的调制,但所有这些都基于两种基本技术,幅度调制和相位调制。
对于每个符号的编码,它们使用 Miller、Manchester 或 NRZ-L。前两者可以通过相关技术检测到,而对于 NRZ-L,只需在每个同步点检测信号的电平即可,让我们详细看看。
### 信号相关性的基础概念
相关运算衡量的是一个信号与另一个作为参考的信号的相似程度。它在数字信号分析中被广泛使用。对于模拟信号,每个样本 x(t) 的相关运算需要 N 次乘法,因此一个符号需要 N^2 次乘法,这是一个成本高昂的过程。
但由于参考信号是数字的,它只有两个可能的值 0 或 1,这极大地简化了计算,消除了所有的乘法,并允许通过简单的移动平均过程来执行相关运算。

这些是我们进行相关运算所需的两个基本符号,如果你稍微研究一下需要执行的操作,你会发现它们可简化为计算符号持续时间内的平均值,然后获得关键点之间的差值,t = 0、t = N / 2 和 t = N,如下方图表所示。

我们将广泛使用此操作来提取 NFC 信号中的信息。
### ASK miller 和 manchester 信号的解调
对于 ASK 调制信号,只需对基带信号 x(t) 执行上述相关运算。下面显示了用于计算所有其他符号的两个基本符号 S0、S1 的相关函数。最后一个值是函数 SD,表示 S0 和 S1 之间的绝对差值,用于检测时序。

当速度为 106 kbps 时,可以通过应用相同的技术来提取响应,但不使用信号 x(t),而是使用 w(t) 将其自身相乘,从而获得功率的度量,然后我们在 1/4 的符号周期内对其进行积分,这样我们将获得足够清晰的 ASK 信号,以便应用上述相关运算,这就是该过程的示意图。

卡片的信号微弱得多,但足以允许使用相同的技术检测模式 E、D、F,此处以更好的比例显示了所描述的过程。从上到下的信号依次为:x(t)、w(t)、w(t)^2 和 y(t)。

### BPSK 信号的解调
对于 BPSK 解调,需要一个参考信号来检测相位变化(载波恢复),由于这很复杂,我选择通过将每个符号与前一个符号相乘来实现它,从而可以通过它们之间产生的变化来确定符号的值。
信号不能包含直流偏移,这一点非常重要,因此先前获得的信号 w(t) 被作为该过程的输入。

下面你可以看到以 424Kbps 响应帧的 BPSK 调制信号,随后是解调 y(t) 以及在四分之一符号 r(t) 上的积分过程。

最后,通过检查结果是正还是负,可以确定每个符号的值。由于必须考虑时序和同步,这稍微有些复杂,但有了这个信号,检测符号值就变得直接了。
### 符号检测
从相关过程中,我们获得了一个符号流,其中已经可以应用每个标准 NFC-A / B / F / V 中定义的特定解码。每个符号的相关性会根据同步在适当的时刻进行评估。
相关过程从计算 S0 和 S1 的值开始,这些值代表基本符号,随后用于区分 NFC 模式 X、Y、Z、E、D、F、M、N 等,这些模式随后由状态机根据 ISO ISO14443A、ISO14443B、ISO15693 和 Felica 的规范进行解释,以获得易于处理的字节流。

### 比特率识别
那么,我们已经了解了如何执行解调,但是当存在不同的速度时,这该如何应用呢?由于我们事先不知道传输速度,因此必须通过相关器组为所有可能的速度应用相同的过程。实际上,只需要对每个帧的第一个符号执行此操作,一旦已知比特率,其余的帧就会使用该速度进行解码。

标签:AirSpy, Bash脚本, DSLogic, ISO14443, ISO15693, ISO18092, ISO7816, NFC, RTL-SDR, SDR, 信号嗅探, 信号捕获, 协议分析, 实时解调, 射频解码, 开源, 智能卡, 权限提升, 波形分析, 硬件黑客, 软件定义无线电, 近场通信, 通信协议, 逻辑分析仪, 频谱分析