简介这是一份面向网络安全研究者与Python开发者的GSM短信嗅探实践资源基于gsmsms_sdr开源项目利用SDR设备如RTL-SDR、USRP捕获并解析GSM频段信号进而还原短信内容。由于国内GSM网络使用的A5加密算法已被破解该技术主要应用于无线通信安全教学、协议研究和渗透测试入门。资源包共11个文件以Python源码为核心覆盖信号接收、帧同步、解码与短信转发等关键模块同时包含SQL数据库脚本用于短信存储多张运行截图和图片辅助说明实验效果以及Markdown说明文档梳理使用流程整体仅544KB。该资源已有1165人学习使用适合具备一定Python基础、对SDR和GSM协议感兴趣的读者。通过研读代码与文档可掌握GNU Radio/gr-gsm的调用方式、A5算法解密思路、短信解析与转发逻辑并能快速搭建自己的嗅探实验环境。1. GSM 短信嗅探SDR 能收到不等于你能碰明文“GSM 在中国没有加密”这句话在软件无线电圈子里流传了很多年但它并不准确。国内运营商部署 2G 网络时普遍启用了 A5/1 加密真正的问题是这套来自 1990 年代的加密算法早已经被攻破了攻击者抓到几个已知明文对应的加密帧借助预计算好的彩虹表用普通电脑就能反推出会话密钥让加密形同虚设。对做无线安全研究和协议学习的人来说用一套百元级的 SDR 接收机加上 Python就可以把 GSM 的加密从“听说过”变成“看得见”的实验。但有一点必须放在最前面空中接口的数据你能收到不代表你有权利解密和阅读别人的短信。这篇文章先讲加密为什么失效再讲怎么搭建接收链路、怎么解码最后讲合规边界。2. GSM 加密为什么挡不住 SDR从 A5/1 到空中接口2.1 64 位密钥与彩虹表A5/1 被攻破的本质A5/1 是 GSM 最早商用的流密码由三个线性反馈移位寄存器组成长度分别是 19、22、23 位三者合计 64 位会话密钥 Kc 就是 64 位。不过算法在部分实现里有效密钥长度被削减到 54 位这让穷举空间从 2^64 降到了 2^54。这个规模依然很大但对攻击者来说已经看到了希望至少不再是不可能完成的穷举。真正的突破口不在密钥长度而在算法结构。A5/1 的三个 LFSR 采用不规则钟控每个时钟周期只有两个寄存器被驱动没有被驱动的那一个保持原状态。这种钟控方式让内部状态之间存在数学关联输出的密钥流偏离了理想随机序列。密码学界在 1990 年代末就开始发表针对 A5/1 的攻击方案核心思路是如果攻击者知道某一段明文就能用密文异或明文得到对应的密钥流片段再通过密钥流反推三个寄存器的内部状态最终恢复 Kc。预计算的彩虹表把这种反推的成本提前预付了。表的基本构造思路是把大量可能的内部状态加密成对应的密钥流片段再用时间-内存权衡算法压缩存储。破解时从捕获帧中取 64 比特已知明文对应的密钥流查表还原寄存器状态再正向推导出 Kc。这个过程的耗时取决于表格规模和采集帧的质量实际从秒级到分钟级波动。注意理论攻击和实际工程之间距离不小。GSM 的每个突发脉冲只有 156.25 个符号其中还包含训练序列和尾比特可用的业务比特不到 114 个。A5/1 攻击需要足够多的连续已知明文比特所以一般要从多个突发脉冲中凑。实际中大家更多使用“已知明文 校验关系”的联合方法GSM 信道编码里大量使用卷积码校验关系本身构成对候选密钥的约束让攻击者不需要非常多的已知明文。对做 SDR 实验的人来说A5/1 的破解并不每次都成功。如果目标基站关掉了加密A5/0那就直接跨过破解步骤解析第三层消息就能看到明文。国内某些网络为了兼容老旧终端确实配置过 A5/0这让“GSM 在中国没有加密”有了现实基础——它只描述了部分地区和部分基站并非网络全貌。2.2 国内 GSM 网络的实际加密配置A5/0 尚未完全消失国内现网 2G 以 900MHz 和 1800MHz 两个频段运行。900MHz 频段的下行基站到手机频率范围在 935 到 960MHz信道间隔 200kHz共 124 个频点。由于 900MHz 的传播特性好语音和短信业务主要承载在这个频段这也是 SDR 实验的首选目标。加密模式的启用由基站配置决定。城市区域的 2G 基站普遍配置 A5/1部分场景还会协商到更安全的 A5/3但 GSM 的加密协商过程是“手机只支持什么基站就兼容什么”。一部老旧的 2G 手机上报不支持 A5/3 时基站会自动降级到 A5/1 甚至 A5/0。这种向下兼容的设计让加密强度最终取决于网络里最弱的那台终端。所以在某些区域你用 SDR 做频谱扫描很可能会同时看到“加密”和“不加密”的基站混在一起。近几年 2G 退网在持续推进很多地方的核心城区已经扫不到连续可用的 GSM 信号。做实验时要么选择距离城市较远的基站要么自己搭一个微型 GSM 基站作为测试对象。微型基站方案对学习协议更友好因为你可以完全控制加密模式把 A5/0、A5/1 都配置一遍每次对比接收链路的表现比在真实网络上被动捕捉要可控得多也安全得多。另外要注意频段配置差异。同是 900MHz不同运营商的频段范围不同但 SDR 只关心频点不关心归属。扫描时把整个下行频段扫一遍标记出持续存在载波的频点。GSM 基站的信号和 4G/5G 不同它在频谱上看起来是周期性突发的窄带信号而不是持续占用的宽带信号用 FFT 瀑布图能很直观地分辨。2.3 SDR 接收 GSM 信号的采样与增益2.4Msps 的取舍SDR 接收机输出的是复数基带 IQ 数据。采样率越低覆盖频谱越窄但每赫兹的处理资源越多。GSM 一个频点只占 200kHz理论上 1Msps 就够但接收机前端的镜像抑制和滤波器过渡带都依赖过采样留出余量。我最常用的设置是 2.4Msps。这个采样率能覆盖 12 个 GSM 频点带宽边缘的过渡带大约各留 200kHz中心区域的平坦度最好。如果把采样率降到 1Msps理论上能覆盖 5 个频点但晶振偏差和滤波器波动会让最外侧的频点解调失败率明显上升。提到 2.4Msps 是工程上的平衡点覆盖够宽边缘质量够好USB 传输带宽也稳定。增益设置是一个经验问题。接收机的总增益可调范围大约在 0 到 50dB。开 AGC 在 GSM 场景下往往不稳因为基站突发是时分多址的信号功率在每个时隙之间跳变AGC 跟不上这种快速变化。做法是固定增益先扫一遍频谱把目标频点的噪声底抬高到比背景高 6 到 10dB 的位置再微调增益直到信号峰值不削顶。提示不要试图同时解调所有频点。GSM 是时分系统一个接收机同时跟踪多个频点的计算量很容易跑不满实时。更稳的方式是先抓一个频点的 IQ 数据存盘再离线做处理。离线意味着你可以反复调试解调参数而接收机不用一直跑。3. Python SDR 环境搭建硬件选型与最小工具链3.1 硬件组合SDR 接收机、天线与时钟源做 GSM 嗅探实验的入门硬件组合很固定一套能覆盖 900MHz 频段的接收机、一根 900MHz 频段的 1/4 波长垂直天线以及一个稳定的时钟源。接收机不必追求最高端的型号GSM 信号带宽窄动态范围要求并不苛刻入门级设备已经能完成整个协议栈的解码。部件作用选择要点接收机射频到 IQ 数据转换频率覆盖到 960MHz 以上采样率稳定到 2.4Msps天线接收 900MHz 下行信号1/4 波长单极或垂直地网天线垂直极化时钟源提供稳定采样时钟TCXO 优于普通晶振GPS 驯钟更适合长时间捕获时钟源是很多人忽略的环节。普通晶振的频率误差通常在 10 到 20ppm在 900MHz 频段相当于 9 到 18kHz 频偏。GSM 的 GMSK 解调对频偏有一定容忍度但 A5/1 破解需要精确的帧号而帧号是靠时间累积推算的频偏会导致符号定时一步步漂移。方案有两个买带 TCXO 的版本把频偏控制在 1 到 2ppm或者在实验中使用 GPS 驯钟信号做参考。对离线分析来说TCXO 够用要做长时间连续捕获GPS 驯钟更稳。3.2 Python 虚拟环境与驱动安装第一步是建立干净的环境。我的习惯是用 venv 隔离出专门实验环境避免系统 Python 里的依赖互相踩踏python3 -m venv ~/gsm_exp source ~/gsm_exp/bin/activate pip install --upgrade pip pip install numpy scipy matplotlib pyrtlsdr pyyamlpyrtlsdr 是接收机驱动的 Python 封装。安装后可以用一个最小脚本测试设备是否工作from rtlsdr import RtlSdr sdr RtlSdr() sdr.sample_rate 2.4e6 # 采样率单位 Hz sdr.center_freq 947.5e6 # 中心频率落在 GSM 900 下行 sdr.gain 40 # 固定增益避免 AGC 抖动 samples sdr.read_samples(8192) # 读一段数据验证 DMA 通路这段代码里sample_rate、center_freq、gain 是 SDR 实验最核心的三个旋钮。sample_rate 按 2.4e6 设置的理由上一章已经讲过center_freq 的选择要确保目标频点落在采样带宽中央gain 固定后read_samples 返回的就是 8192 个复数样本。如果返回的数据全是 NaN 或数值极低通常是驱动或 USB 控制器的问题要先检查设备枚举和 usb 权限。注意Linux 下接收机驱动默认需要 udev 规则才能免 sudo 访问。如果你在设备列表里看不到设备多半是权限问题。先用相应系统命令确认设备枚举再确认规则已加载。Windows 下则需要安装对应驱动并在 Python 中指定设备索引。3.3 天线架设与信号勘察把频点挑对在投入解码之前花半小时做频谱勘察比什么都值。把接收频率范围设置为 GSM 900 下行全段用瀑布图模式持续观察几分钟。正常 GSM 基站在频谱上会呈现周期性的窄带脉冲每个脉冲持续约 0.577ms表现在瀑布图上是一段段短促的亮线。持续存在的宽带信号一般不是 GSM可能是 4G/5G 的边带泄漏或工业干扰。天线位置的影响远超想象。室内窗边和室外阳台只差三米信号强度可能差 12dB。GSM 900MHz 波长约 33 厘米1/4 波长垂直天线的地网和垂直度都要认真调。天线尽量靠近窗户、远离金属框架和 LED 灯具。电源适配器对 SDR 的干扰也很大劣质开关电源会在 900MHz 附近产生宽带杂散把本来干净的频点彻底淹没。选频点的方法在瀑布图上找到最强的 GSM 载波后先不要选它。最弱但稳定的频点往往更能说明解码链路的真实性能。强信号的基站可能离你只有几十米多径衰落反而严重弱信号基站通常距离更远路径相对干净突发脉冲的形态更接近理想信号。先拿一个中等强度的频点练手确认整条链路通了再回头挑战强信号频点的均衡。4. 从 IQ 数据到明文短信Python 解码链路逐段拆解4.1 用 SCH 突发脉冲完成时间同步与频偏校正GSM 的每个下行帧的特定时隙承载 SCH同步信道SCH 突发脉冲里包含一段 64 比特的训练序列接收端依靠它与本地参考序列做相关就能找到时隙边界和频偏。这是整个解调链路的第一环相关峰的位置直接决定后续所有符号的定时。在 Python 里做 SCH 捕获的简化流程如下import numpy as np # iq: 复数基带信号sch_ref: 64 比特同步序列的 GMSK 参考波形 def find_sch(iq, sch_ref, threshold2.0): # 用翻转参考序列做匹配滤波得到滑动相关 corr np.convolve(iq, np.conj(sch_ref[::-1]), modesame) corr_abs np.abs(corr) peak_idx np.argmax(corr_abs) # 相关峰必须显著高于平均噪声水平防止误检 if corr_abs[peak_idx] / np.mean(corr_abs) threshold: return None return peak_idx这个函数用滑动相关在整段 IQ 数据里搜索 SCH相关峰幅度与背景噪声均值的比值是判决依据。threshold 设 2.0 已经比较严格误检一次会让后续所有帧号错位。sch_ref 是把 64 比特训练序列按 GSM 规范做 GMSK 调制后得到的复基带波形所以相关本质上是匹配滤波。找到 SCH 后相关峰处复数的相位还能用来估计频偏把实际相关输出与理论值做相位差就能得到残余频偏。用这个频偏值对 IQ 数据做数字下变频补偿。这一步省了GMSK 差分解调的误码率会明显抬高尤其是采样率较高、突发持续时间较长时。频偏估计的精度直接影响后续均衡的效果。4.2 GMSK 解调与信道均衡的工程取舍GSM 的 GMSK 调制指数为 0.5是带限的连续相位调制。接收端可以用差分解调实现但性能较差工程上常用的是基于维特比算法的最大似然序列估计。对 SDR 捕获的数据来说先做匹配滤波再按符号周期抽样然后计算状态转移度量。简化的差分解调代码只适合检查和排错def gmsk_diff_demod(samples, sps): sps int(round(sps)) phase np.unwrap(np.angle(samples)) # 相隔一个符号周期的相位差 diff phase[sps:] - phase[:-sps] bits (diff 0).astype(np.uint8) return bits # 使用示例采样率 2.4e6符号率 270.833e3 sps_calc 2.4e6 / 270.833e3 # 约 8.86取整后为 9 bits gmsk_diff_demod(iq_burst, sps_calc)sps 是每个符号的采样点数2.4e6 除以符号率约等于 8.86代码中取整到 9。这段代码的问题在于它把符号间干扰和噪声一起当成相位变化低信噪比下误码率很高。真正可靠的做法是先用训练序列估计信道冲激响应再跑 MLSE。GMSK 的频率脉冲长度是 3 个符号周期信道记忆很短MLSE 的状态数很小完全可以在 Python 里实时跑。信道均衡的效果要看训练序列位置的误码率。GSM 每个突发的训练序列是已知的把解调后的比特与理论比特比对就能得到该突发的误码估计。如果训练序列误码率高于 5%先怀疑频偏校正再怀疑定时偏差最后才是均衡器参数。4.3 去交织、卷积码与 A5/1 密钥恢复解调出原始比特后下一个环节是去交织和信道解码。GSM 的普通突发承载 114 个加密比特业务数据经过卷积编码后被交织到多个突发所以必须先把多个突发的比特收集齐按交织规则复原再做维特比解码。这个流程在现成工具里已经封装好自己写的价值在于理解每一层的边界。关于 A5/1前面讲过它的破解依赖已知明文或密文校验。短信场景下已知明文来自 SDCCH 上的系统消息加密前后内容不变所以你可以通过比对加密突发和已知明文恢复一段密钥流。下面只是逻辑示意不是完整破解代码# encrypted_bits: 截获的加密突发known_bits: 对应的已知明文 # 密钥流 密文 XOR 明文 keystream_bits encrypted_bits ^ known_bits # 进入 A5/1 逆推得到 64 位会话密钥 Kc # kc invert_a5_1(keystream_bits, frame_number)这里的核心参数是 frame_number。A5/1 算法把 22 位帧号作为参数混入初始化过程每个帧的密钥流不同。所以逆推时必须知道这段密钥流对应哪个帧号。帧号来自 SCH 同步后对 TDMA 帧的计数GPS 同步在这里起作用——时间每过一帧约 4.615ms帧号就加 1从 SCH 初始帧号开始推算时间基准不对帧号就会偏得离谱。实际工程中A5/1 破解很少每次成功。目标基站的信号波动、信道解码残留错误、帧号偏差都会让查表失败。成熟的实验流程是采集一段连续 IQ 数据重复解调解码把同一位置的密钥流候选值做投票表决再交给查表。这样做会显著提高成功率也会显著增加计算时间。4.4 第三层信令解析从 PDU 到短信正文A5 破解成功或遇到 A5/0 之后SDCCH 上的第三层消息就是明文。短信内容封装在 RPDU 里外面还包了一层 LAPDm 协议。解析顺序是先剥离 LAPDm 的地址和控制字段再取上层数据最后定位到 SMS 的 TPDU。TPDU 前面的地址字段包含短信中心号码和发送方号码都是 BCD 编码同一字节的高低半字节各表示一个数字。解析时最容易出错的是号码长度字段GSM 里地址长度以半字节为单位很多人误以为是字节导致号码整体错位。自己实现一遍这个解析非常有价值因为所有短信解析工具都从这里开始。短信正文的编码取决于数据编码方案字段。常见的有 7-bit 默认字符集、8-bit 数据和 UCS-2。中文短信绝大多数是 UCS-2即每个汉字占两个字节按 UTF-16BE 读取。解析时先读编码方案字段再选择解码器顺序颠倒就会出现乱码。这一步是后面避坑章节要展开的高频故障点。5. GSM 嗅探避坑指南5 个高频翻车点5.1 采样率保护带不足导致边缘信道解调失败现象目标频点位于采样带宽最外侧时相关峰能测到但解调出的误码率始终在 20% 以上无论怎么调增益都无效。原因接收机前端滤波器的边缘幅度响应跌落且镜像频率离得近带宽边缘信噪比明显劣于中心。解决把采样率从 2.0Msps 提到 2.4Msps并让目标频点处于频谱中央附近。如果受限于 USB 带宽就微调中心频率让目标频点移到频率窗的中心位置。同理如果要跟踪多个频点也要保证它们全部落在平坦区。5.2 自动增益让频谱在强弱信号之间反复跳变现象瀑布图上基站的突发脉冲时有时无解调结果一段时间全对、一段时间全错。原因自动增益按整体能量调整增益GSM 是时分信号脉冲出现时能量瞬间抬高增益不断追赶导致波动。解决关闭自动增益手动固定。调增益时不要盯着峰值看要看脉冲之间空隙处的噪声底。把噪声底抬到比本底高 6 到 10dB同时确保最强脉冲不削顶这时候的动态范围基本合适。5.3 短信正文乱码问题不在破解现象号码解析正常但正文输出一堆乱码有的甚至是空字符。原因编码方案字段判读错误。把 UCS-2 的短信按 7-bit 解码中文字符全变乱码反过来把 7-bit 短信按 UCS-2 解会得到一长串空白字符。解决在解析正文前先打印编码方案字段的原始字节对照协议规范逐位解析。注意字段高两位不同时对应不同字符集。做实验时准备一个能显示 hex 的调试工具先看两个字节的 UTF-16BE 是否可读再去确定解码路径。5.4 A5/1 批量破解内存爆炸现象批量查密钥流时内存占用从几百 MB 暴涨到几十 GB进程被杀。原因批量查表时把整个彩虹表载入内存或者索引分段没有与密文帧的时间顺序对齐。解决不要一次性加载全表。把彩虹表按帧号分段每段对应一个时间窗口按时间顺序逐段处理密文。这样内存占用稳定在单段大小还能利用帧号连续性做候选密钥预筛。另外确认查表脚本的索引类型是 uint64 而不是 Python int后者的内存开销是前者的好几倍。5.5 帧号漂移GPS 同步失效的连锁反应现象解码和密钥流恢复逻辑上没问题但结果始终对不上密文而且错误看起来没有规律。原因帧号推算依赖绝对时间基准。GPS 驯钟失效或秒脉冲中断后帧号计数器偏差导致每帧的初始化参数都错。解决在软件里记录 SCH 同步时的绝对时间和帧号之后每收到一个突发就做帧号增量校验。如果连续两帧的帧号差不固定立刻报警。GPS 信号在室内经常丢失实验时把 GPS 天线放在窗口或者改用时间戳更稳的 TCXO 方案。6. 验证你的解码链路与合规边界6.1 用已知短信做端到端回环验证要确认链路是通的最可靠的方法不是在真实网络上碰运气而是在自己的实验环境里发一条已知短信。常见做法是用一台旧手机连接自己搭的微型 GSM 基站让基站发送一条内容固定的短信再用 SDR 在射频端抓取并解码比对解码结果与原始短信。这套回环验证能同时检验同步、频偏校正、GMSK 解调、信道解码和第三层解析五段链路哪一段出错都一目了然。如果手上没有微型基站退而求其次的做法是抓取广播信道上的系统消息因为系统消息内容本身已知。只要解析出来的系统消息与协议字段一一对应就说明链路前半段是健康的。短信的解码风险主要在后半段所以回环验证还是值得做。6.2 看信噪比和误码率判断链路健康链路排错时我最看重两个指标。一是同步相关峰与噪声均值的比值这个值低于阈值说明接收信号不够强或频偏太大。二是训练序列位置的误码率它能直接反映解调质量。把这两项指标输出成日志后再去看是前端的射频问题还是后端的解码参数问题。训练序列误码率很高时先调天线和增益训练序列误码率正常而正文乱码时问题就在第三层解析。用指标分隔问题域是排错最快的方式。6.3 技术研究的红线不能碰最后必须说清楚被动接收空中信号、学习协议、做设备自检这些在授权范围内是有价值的技术研究。但对别人的通信进行截取和解密无论出于什么目的都是明确的违法行为哪怕只是把采集到的加密数据包存在硬盘里没有用于传播一旦涉及非授权场景风险同样存在。我在某实验室做微型基站回环测试时会先确认三件事测试环境是否完全封闭、使用的手机和基站是否属于实验设备、采集到的数据是否最终会被删除。这三条下来实验边界就清晰了。这也是我在 GSM 嗅探这个方向上最想分享的一条经验技术本身无善恶但边界要自己提前画好。希望帮到你。本文还有配套的精品资源点击获取