用Matlab进行无线电信号逆向实战1——对模拟广播逆向:从单声道FM找回遗失的电波

欢迎来到 MATLAB SDR CTF 闯关指南系列!在这个系列中,我们将开启一段包含18道题目的宏大无线电逆向之旅,从最基础的模拟信号解调,一路打怪升级到复杂的数字通信盲解调。

作为系列的第一期,我们将从 模拟广播层 破冰。模拟调制是最朴素的信号处理方式,能提供最直接的感官反馈(听声音、看图像),是 SDR 入门的首选。

1. 题目背景与逆向方法论

题目描述: 复古的电波 - 单声道FM广播解调

背景: 截获特工在野外使用老旧收音机发送的一段模拟无线电信号。初步侦测显示,这是一段干净的单声道调频(FM)广播信号。信号已被录制为基带复数(IQ)数据文件。请解调该信号,还原出隐藏在语音中的口播 Flag。

附件: Q01_fm_mono.iq(复数双精度二进制文件,小端序)下载地址

当我们拿到一个未知的 .iq 文件,通用思路是:

  1. 明确格式 :IQ 数据通常以复数形式存储,最常见的是交错存放的 Float32(I1, Q1, I2, Q2...)。
  2. 时频分析:不要急于写解调算法,先用工具看看信号的"长相"。
  3. 调制识别:通过频谱和时域特征判断调制方式。
  4. 解调与提取 :根据识别出的方式编写算法。
    那么,具体到这道题,我们如何一步步分析出这是一个单声道 FM 信号,并成功解调呢?

2. MATLAB 实战:从"盲盒"到识别 FM

第一步:数据读取与初步观察

首先,使用 fread 将二进制文件读取为浮点数,由于 I 和 Q 是交错存放的,我们需要将其重组为复数数组。

matlab 复制代码
clear; clc;
fileID = fopen('Q01_fm_mono.iq', 'r');
raw_data = fread(fileID, 'float32');
fclose(fileID);
% 重组为复数
iq_data = complex(raw_data(1:2:end), raw_data(2:2:end));
fs = 1e6; % 假设我们已知或测试得出采样率为 1 MHz

拿到复数数据后,如果直接画时域波形 plot(real(iq_data)),你只能看到一团密密麻麻的高频振荡,无法获取有效信息。

这时,我们需要将其转换到频域。

第二步:频谱侦测与如何认出 FM

利用 pwelch 绘制功率谱密度(PSD):

matlab 复制代码
figure;
pwelch(iq_data, [], [], [], fs);
title('IQ Signal PSD');

看着生成的频谱图,我们如何逆向推导出它是 FM 信号?核心在于以下三个特征的观察与推理:

  1. 观察中心频率 :信号能量并未集中在 0 Hz(纯基带),而是有一个明显的中心频点。在这张图上,中心位于 192 kHz 处。这说明信号被搬移到了一个非零的中频上。
  2. 观察占用带宽 :这是最关键的线索!信号从大约 114 kHz 延伸到 270 kHz,占据的带宽约为 156 kHz
    • 推理:如果这是 AM(调幅)信号,且基带是人声(最高频率约 3~4 kHz),那么 AM 的带宽最多只有 8 kHz,频谱会非常窄。眼前这 156 kHz 的宽带信号直接排除了 AM 和窄带数字通信的可能。
    • 反推参数 :根据 FM 的卡森公式: B = 2 × ( Δ f + f m ) B = 2 \times (\Delta f + f_m) B=2×(Δf+fm)。假设基带音频 f m ≈ 3 f_m \approx 3 fm≈3 kHz,那么频偏 Δ f = B / 2 − f m = 78 − 3 = 75 \Delta f = B/2 - f_m = 78 - 3 = 75 Δf=B/2−fm=78−3=75 kHz。75 kHz 正是标准 FM 广播的最大频偏! 这与"广播信号"的背景完美契合。
  3. 观察频谱形态 :FM 信号的频谱由贝塞尔函数展开决定,其主瓣呈现出类似于"起伏的丘陵"状,能量在带内分布相对平缓,而不是像 AM 那样有一条极强的尖锐载波线加上平坦的边带。
    通过上述"看图说话"和公式反推,即使题目不告诉你参数,我们也能确信:这是一个宽带 FM 信号(WBFM)

3. 核心解调:剥茧抽丝

既然确认是 FM,破题关键就在于提取瞬时频率。FM 信号的信息不在幅度里,而在相位的变化率中。只要取出"瞬时频率",就能还原原始声音。

第三步:核心解调算法(鉴频)

数学上,瞬时频率正比于相邻采样点的相位差: m n ∝ ∠ ( x n ⋅ x ∗ n − 1 ) mn \propto \angle(xn \cdot x^*n-1) mn∝∠(xn⋅x∗n−1)。

在 MATLAB 中,我们可以通过提取相位 angle() 并求差分 diff() 来实现。同时,必须使用 unwrap 函数处理相位卷绕,防止由于 2 π 2\pi 2π 跳变导致的信号断裂。

matlab 复制代码
%% 3. FM 鉴频解调 (相位差分法)
% FM 信号: s(t) = A * exp(j * 2*pi*fc*t + j*2*pi*kf*int(m(t)))
% 相位差: diff(phase) = 2*pi*fc/fs + 2*pi*kf*m(t)/fs
% 因为 fc 是已知的,解调后可以通过低通滤除 fc 分量(或直接取相位差模 2pi)
phase = angle(iq_data);
phase_unwrapped = unwrap(phase);        % 先解包络
diff_phase = [0; diff(phase_unwrapped)]; % 再差分,并补一个 0 保持长度一致
demod_sig = diff_phase;

第四步:后处理(滤波与重采样)

解调后的 demod_sig 采样率仍为 1 MHz,其中包含了高频噪声和残留载波分量。我们需要设计一个截止频率为 4 kHz 的低通滤波器(给人声留足保护带)。

另外,1 MHz 采样率的音频如果直接用 sound 播放,会变成尖锐的变音。必须将其重采样到标准音频采样率(如 44.1 kHz)。

matlab 复制代码
%% 低通滤波与重采样
% 设计低通滤波器,截止频率 4 kHz
lp_filter = designfilt('lowpassfir', 'FilterOrder', 64, 'CutoffFrequency', 4000, 'SampleRate', fs);
audio_baseband = filtfilt(lp_filter, demod_sig);
% 重采样到 44.1 kHz
fs_audio = 44100;
audio_out = resample(audio_baseband, fs_audio, fs);
% 归一化
audio_out = audio_out / max(abs(audio_out));

4. 通关时刻:听取 Flag

经过上述步骤,信号已经被还原为标准的人声音频。戴上耳机,运行播放代码:

matlab 复制代码
%% 播放音频
disp('正在播放解调音频,请仔细听取 Flag...');
sound(audio_out, fs_audio);
audiowrite('decoded_flag.wav', audio_out, fs_audio); % 保存方便回放

伴随着极其轻微的底噪(SNR 40dB,环境非常干净),耳机里传来了清晰的声音。你也可以将导出的 decoded_flag.wav 拖入 Audacity 中,通过波形更加直观地分析这段语音。

解析IQ数据的完整代码如下:

matlab 复制代码
% Q01 FM 解调脚本
clear; clc;

%% 1. 读取 IQ 数据
fileID = fopen('Q01_fm_mono.iq', 'r');
raw_data = fread(fileID, 'float32');
fclose(fileID);

% 重组为复数
iq_data = complex(raw_data(1:2:end), raw_data(2:2:end));
fs = 1e6; % 采样率

%% 2. 时频分析
figure;
pwelch(iq_data, [], [], [], fs);
title('FM Signal PSD');

%% 3. FM 鉴频解调 (相位差分法)
% FM 信号: s(t) = A * exp(j * 2*pi*fc*t + j*2*pi*kf*int(m(t)))
% 相位差: diff(phase) = 2*pi*fc/fs + 2*pi*kf*m(t)/fs
% 因为 fc 是已知的,解调后可以通过低通滤除 fc 分量(或直接取相位差模 2pi)
phase = angle(iq_data);
phase_unwrapped = unwrap(phase);        % 先解包络
diff_phase = [0; diff(phase_unwrapped)]; % 再差分,并补一个 0 保持长度一致
demod_sig = diff_phase;

%% 4. 低通滤波与重采样
% 设计低通滤波器,截止频率 4 kHz (留一定保护带)
lp_filter = designfilt('lowpassfir', 'FilterOrder', 64, 'CutoffFrequency', 4000, 'SampleRate', fs);
audio_baseband = filtfilt(lp_filter, demod_sig);

% 重采样到 44.1 kHz
fs_audio = 44100;
audio_out = resample(audio_baseband, fs_audio, fs);

% 归一化
audio_out = audio_out / max(abs(audio_out));

%% 5. 播放音频
disp('正在播放解调音频,请仔细听取 Flag...');
sound(audio_out, fs_audio);

% 保存音频方便回放
audiowrite('decoded_flag.wav', audio_out, fs_audio);

5. 复盘与下期预告

本题总结:

Q01 难度 1 星,是一道非常友好的破冰题。除了掌握 MATLAB 中 freadunwrap 以及 resample 等基础函数的用法,本题最大的收获在于通过带宽和频谱形状反推调制方式 。这是无线电逆向分析中最核心的基本功。

进阶预告:

真实的广播电台只有单声道吗?下一题 Q02 立体声 FM 将引入 19kHz 导频和 38kHz 副载波,并叠加 AWGN 噪声。你的频域分离能力将迎来真正的挑战!我们下期见。