【格物实验室】初探逆向将电缆调制解调器改装为SDR

一、前言

电缆调制解调器和数字电视调谐器从根本上说做了同样的事情—接收和解调QAM信号,因此萌生了一种想法,是否有可能将其变成一个SDR(软件定义无线电)?

电缆调制解调器支持5到1794 MHz的频率范围(具有特定的发送和接收频率范围,并取决于DOCSIS的产生),使用二次振幅调制(QAM)和二次相移键控(QPSK),默认通道大小为6 MHz。

将电缆调制解调器转换为SDR可能会涉及到一些深层的硬件修改,以便为每种模式使用适当的调制和带宽,并绕过大多数数字信号转换内容。本文将以Motorola MB7220为例进行介绍。

二、获得访问权限

拿到设备的第一个目标就是获得访问权限,寻找访问媒介或与设备进行通信的方式,通过对WEB的分析,没有发现什么可用的内容,并且telnet也被禁用了。

看来只能从硬件接口(UART)着手,从塑料外壳上卸下几颗螺钉,打开后即可看到电路板。通过观察,在确定了由四个通孔组成的两个候选接口后可以通过如下方法判定各个引脚的接口定义。

1.    将万用表功能开关定位到二极管(蜂鸣)档位,关闭设备电源,用黑色探头接到硬件电路板的接地引脚(这个引脚一般于较大的铜箔面相连)。

2.    用红色探头分别放在可能是UART接口的每个焊盘或者针脚上,直到听到蜂鸣器 (BEEP)的声音。

3.    听到蜂鸣器声音时即可判定设备接口的接地引脚,这种测试方法也称为通断测试。

4.    将万用表功能开关定位20V直流电压档位置,黑色探头保持接口的接地引脚(GND),用红色探头移到接口的其他引脚(GND除外)上。接通设备电源,如果看到恒定电压(3.3V或者5V)的引脚就是VCC引脚。

5.    再次重启设备并测量剩余焊盘和GND之间的电压(除了前面步骤中确定的VCC和GND)。由于在启动期间最初进行的大量数据传输,可以看到在最初的10-15秒内其中一个引脚上的电压值发生了巨大波动。这个引脚将是TXD引脚。

6.    RXD可以通过在整个过程中具有最低电压波动和最低总值的引脚来确定。

第一个UART接口似乎没有发送任何东西,而另一个则会进行数据发送,将Tx连接到树莓派上的UART Rx GPIO引脚,Rx连接到树莓派上的UART Tx GPIO引脚并连接好接地引脚。(注意:由于两个系统均为3.3V才可以这样做,如果两个系统的电压不同,则需要使用相关的适配器)。

根据经验,115200的波特率在设备中比较常见,于是使用这个波特率进行连接(如果波特率错误,则会在屏幕上显示乱码,需要重新调整波特率,然后再进行链接),在树莓派上执行命令并将设备上电,终端显示如下信息:

pi@raspberrypi:~/modem $ cu -l /dev/serial0 -s 115200
Connected.
�
B3312inim S C 84(9 m
ose_VS 8
STesldlo rh 83 rs 10
STesldhi: _h 8, _s 13
Sync: 0 
MemSize:            128 M
Chip ID:     BCM3383D-B0

BootLoader Version: 2.4.0 fyl spiboot reduced DDR drive avs
Build Date: Nov 12 2015
Build Time: 14:31:43
SPI flash ID 0xef4016, size 4MB, block size 64KB, write buffer 256, flags 0x0
Cust key size 128

Signature/PID: 3383


Image 1 Program Header:
   Signature: 3383
     Control: 0005
   Major Rev: 0003
   Minor Rev: 0000
  Build Time: 2015/11/26 08:47:57 Z
 File Length: 1692841 bytes
Load Address: 80004000
    Filename: ecram_sto.bin
         HCS: e749
         CRC: 175b753f

Found image 1 at offset 20000

Enter '1', '2', or 'p' within 2 seconds or take default...


Performing CRC on Image 1...
CRC time = 282177012
Detected LZMA compressed image... decompressing... 
Target Address: 0x80004000
decompressSpace is 0x8000000
Elapsed time 736066500

Decompressed length: 8091524

Executing Image 1...


 eCos - hal_diag_init
Ecos memory map:
BLOCK    OWNER        MIPS      SIZE      MEM
Block 0: Owner: 0 - 0x00000000 0x07e00000 0x00000000
Block 0: Owner: 0 - 0 MB 126 MB 0 MB
Block 1: Owner: 3 - 0x07e00000 0x00200000 0x07e00000
Block 1: Owner: 3 - 126 MB 2 MB 126 MB
126MB (129024KB) remaining for eCos
Init device '/dev/BrcmTelnetIoDriver'
Init device '/dev/ttydiag'
Init tty channel: 807bb020
Init device '/dev/tty0'
Init tty channel: 807bb040
Init device '/dev/haldiag'
HAL/diag SERIAL init
Init device '/dev/ser0'
BCM 33XX SERIAL init - dev: b4e00500.2
Set output buffer - buf: 0x80852408 len: 4096
Set input buffer - buf: 0x80853408 len: 4096
BCM 33XX SERIAL config
Init device '/dev/ser1'
BCM 33XX SERIAL init - dev: b4e00520.3
Set output buffer - buf: 0x80854408 len: 4096
Set input buffer - buf: 0x80855408 len: 4096
BCM 33XX SERIAL config

Init device '/dev/ser2'
InitBoard: MIPS frequency 637200000

...

Reading Permanent settings from non-vol...
Checksum for permanent settings:  0xe9d88f65
Setting downstream calibration signature to '5.7.1mp1|die temperature:70.775degC'
Settings were read and verified.


Reading Dynamic settings from non-vol...
Checksum for dynamic settings:  0x6e4a329
Settings were read and verified.

Console input has been disabled in non-vol.
Console output has been disabled in non-vol!  Goodbye...
[00:00:00 01/01/1970] [Reset/Standby Switch Thread] BcmResetStandbySwitchThread::ProcessResetSwitchEvent:  (Reset/Standby Switch Thread) Reset switch released; resetting...
[00:00:00 01/01/1970] [Reset/Standby Switch Thread] BcmResetStandbySwitchThread::ProcessResetSwitchEvent:  (Reset/Standby Switch Thread) Cant Reset pfCmDocsisCtlThread==NULL...

此输出包含大量信息,从输出的信息发现设备运行了两个MIPS处理器,其中一个是博通BCM3383的SoC,运行的系统为 eCos系统,而另一个未在调制解调器上使用。在某些设备上,第二个处理器将运行Linux以获得其他功能。

由于操作系统在启动不久后便会禁用串行控制台,在bootloader模式下,除了可以通过tftp下载新OS镜像以及用于读取和写入内存地址的实用程序外,并不能做其他多余的操作。

三、芯片中获取固件

当前的目标是启用串行控制台,这部分的参数很可能存储在引导加载程序、操作系统或配置中,这些配置数据一般是放在一个外置的存储芯片中,通过对板卡电路的分析发现了如下芯片(winbond 25Q32JV):

通过相关芯片资料可以知道芯片采用了SPI接口以及相关的管脚定义,主要的SPI管脚为VCC,片选(CS),时钟(CLK),数据输出(DO),数据输入(DI)和地。由于树莓派也存在一个SPI的控制端口,可以从芯片中读取数据,于是将导线焊接到其引脚上,并将它们连接到树莓派。地线接地(也可以使用更早的UART地线),VCC到树莓派的3.3v引脚,DO引脚连接到树莓派的SPI MISO(主机输入从机输出)引脚,DI引脚连接到MOSI引脚(主机输出从机输入)。最后,时钟连接到SCLK GPIO引脚,芯片选择连接到该CE0引脚。

要真正读取芯片,有一个很棒的工具叫做 flashrom,它支持大量芯片,并存在于树莓派的发行版本中。

通过如下命令可验证是否已检测到正确的接线。

flashrom -p linux_spi:dev=/dev/spidev0.0,spispeed=2000 --chip W25Q32.V

      如果成功了则可以进行固件转储。

flashrom -p linux_spi:dev=/dev/spidev0.0,spispeed=2000 --chip W25Q32.V --read modem.bin

四、固件数据分析

快速浏览十六进制转储,可以看到大多数数据已压缩或加密,但是在最后,配置是可见的。

...
003f00c0: ffff ffff ffff ffff ffff 0000 07b0 369a  ..............6.
003f00d0: 6336 0010 434d 4170 0002 0000 0002 0000  c6..CMAp........
003f00e0: 0000 0057 4d4c 6f67 0005 0004 7573 6572  ...WMLog....user
003f00f0: 0004 7573 6572 0005 6164 6d69 6e00 086d  ..user..admin..m
003f0100: 6f74 6f72 6f6c 6102 7465 6368 6e69 6369  otorola.technici
...

Web界面凭据以及许多其他编码的配置值清晰可见。

经过一番搜索,找到了一个名为bcm2-utils的伟大项目 ,其中包含用于转储,解析和修改Broadcom电缆调制解调器上配置的实用程序。

配置的开始位置在设备上的0x003f0000处,包括202 0xff字节,提取配置后,我可以使用bcm2cfg进行读写配置。

使用如下命令开启与telnet类似的串行控制台并设置密码。

$ ./bcm2-utils/bcm2cfg set bfc.serial_console_mode "rw"
bfc.serial_console_mode = rw
$ ./bcm2-utils/bcm2cfg set userif.remote_acc_methods 0x3
userif.remote_acc_methods = http | telnet
$ ./bcm2-utils/bcm2cfg set userif.remote_acc_pass abcd
userif.remote_acc_pass = abcd

将修改后的文件前端用零填充,使配置信息位于0x003f0000处,然后用flashrom将配置写回到芯片上。为了避免重写整个芯片,需要创建一个布局文件,内容如下:

00000000:003effff fw
003f0000:003fffff cfg

使用如下命令进行配置更新。

flashrom -p linux_spi:dev=/dev/spidev0.0,spispeed=2000 --chip W25Q32.V --layout ./layout  --image cfg  --write modem-modified.bin

打开串行控制台并再次启动后,显示和以前同样的信息,检查固件发现在初始配置之后有许多重复的配置副本。有部分内容是不同的,其中最主要的就是错误日志消息。为简化起见,通过Web界面进行了出厂重置,以消除所有错误日志消息。然后,再次转储闪存,并重复了之前的过程来修改干净的配置,只是这次把配置截断,仅包含第一个副本。然后使用dd命令,重新构造了整个配置部分,将修改后的config附加到配置副本开始的偏移处。重新刷新镜像并再次引导后终于能够查看整个引导日志,然后可以访问控制台。

...

Reading Permanent settings from non-vol...
Checksum for permanent settings:  0xe9d88f65
Setting downstream calibration signature to '5.7.1mp1|die temperature:70.775degC^@^@^@^@^@'
Settings were read and verified.


Reading Dynamic settings from non-vol...
Checksum for dynamic settings:  0x2630e508
Settings were read and verified.

[00:00:00 01/01/1970] [tStartup] BcmBfcFpmDriver::Init:  (BFC FPM Driver) Setting FPM Buffer size to: 256 Base Address: 0x87566600
[00:00:00 01/01/1970] [tStartup] BcmBfcFpmDriver::Init:  (BFC FPM Driver) fFpmLargestBufferSize: 2048 fFpmSizeShiftBits: 0x8
[00:00:00 01/01/1970] [tStartup] BcmBfcFpmDriver::Init:  (BFC FPM Driver) Pool index: 0  pool size: 2048
[00:00:00 01/01/1970] [tStartup] BcmBfcFpmDriver::Init:  (BFC FPM Driver) Pool index: 1  pool size: 1024
[00:00:00 01/01/1970] [tStartup] BcmBfcFpmDriver::Init:  (BFC FPM Driver) Pool index: 2  pool size: 512
[00:00:00 01/01/1970] [tStartup] BcmBfcFpmDriver::Init:  (BFC FPM Driver) Pool index: 3  pool size: 256
[00:00:00 01/01/1970] [tStartup] BcmBfcFpmDriver::Init:  (BFC FPM Driver) Lookup table index: 0  pool size: 3

...

[00:00:18 01/01/1970] [Scan Downstream Thread] BcmGenericCmDownstreamScanThread::ThreadMain:  (Scan Downstream Thread) Scanning for a Downstream Channel...
[00:00:18 01/01/1970] [Scan Downstream Thread] BcmGenericCmDownstreamScanThread::ScanStarting:  (Scan Downstream Thread) Scanning STD & HRC Annex B channel plan frequencies
Resetting EnergyDetected to false.
Forgetting energy frequency.
Executing fast scan algorithm...

Type 'help' or '?' for a list of commands...

CM> Scanned 489000000 Hz...
Scanned 495000000 Hz...
Scanned 501000000 Hz...
Scanned 507000000 Hz...
Scanned 513000000 Hz...

五、eCos控制台

eCos控制台包含许多配置和调试命令。可以使用以下命令停止输出的过程:

cd cm_hal
scan_stop

附带说明一下,这些“目录”不是真正的文件系统,它们只是组织命令组的一种方式。

使用telnet工具登录192.168.100.1,用户名为“ technical”,密码为配置修改时设定的密码。登陆以后发现进入的是一个受限的shell环境,但是可以使用su命令和密码来获得一个完整的shell环境。

六、逆向分析固件

根据系统启动日志可以知道操作系统位于0x20000并经过LZMA压缩。bcm2-utils工具中引用了一个外部工具库ProgramStore,用于提取操作系统镜像。通过如下命令可以提取并解压缩镜像。

./ProgramStore -f ./ecram_sto.bin -o decompressed_fw.bin -c 4 -x

现在可以使用启动日志中的基地址0x80004000,将其扔进Ghidra,并将架构设置为big endian MIPS,在Ghidra自动分析完成后即可进行进一步的工作了。

操作系统之上有大量的Broadcom代码,这些代码都是用C++编写的。由于在函数调用和多态性方面添加了大量间接调用,这使得逆向分析变得非常困难。常常会看到类似如下的代码。

case 0x24:
    uVar23 = (**(code **)(*piParm1_00 + 0x1c))(piParm1_00);

针对此种情况,可以通过eCos控制台中的write_memory命令来完成。该call命令可用于调用包含未知对象的函数,然后read_memory可用于从已知位置检索指针。

尝试调用某些函数时,会导致设备崩溃,经过仔细的分析发现它们接收了4个以上的参数,并使用t0,t1,t2和t3寄存器作为附加参数。调用约定是由ABI确定的,并且MIPS具有许多不同的ABI,Ghidra似乎不支持MIPS EABI,但在几个函数上手动设置参数寄存器并不太方便,参数似乎就是唯一的区别了。

经过一段时间的研究后,将目光投向了频谱分析仪,发现了所有其他有用的功能,例如用于设置下行(接收)通道频率的功能、套接字/绑定/监听/发送/接收功能、线程创建功能以及用于读取和写入调谐器和LNA寄存器的功能。

七、突破

经过不断的实验分析,发现了一个控制台命令,可用来执行给定频率范围内的带宽测量。

开始密切关注执行过程,以查看它对频率范围参数做了什么,发现它调用了一个非常熟悉的函数,这个函数与用于调整下行通道的函数几乎相同,但是设置频率的内存映射寄存器地址高于普通频道的地址。

测量功能将目标缓冲区的物理地址写入一个内存映射的寄存器,然后在另一个寄存器中设置一个位并循环,直到再次将其取消设置为止。之后调用传入缓冲区地址的函数,该函数可能会计算FFT。计算完成后,另一个函数对缓冲区进行了一些处理,但其他方面则保持不变。

将跳线插入同轴连接器以充当天线后,调用了bandpower函数,然后在目标缓冲区上执行read_memory操作。

CM> read_memory -n256 0x86fb3e80

86fb3e80: 00 00 06 8c  00 3f fe 48  00 00 06 41  00 20 00 3d | .....?.H...A. .=
86fb3e90: 00 00 08 56  00 20 02 11  00 00 0a b3  00 20 03 f2 | ...V. ....... ..
86fb3ea0: 00 00 0a 50  00 20 04 84  00 00 06 61  00 20 03 d7 | ...P. .....a. ..
86fb3eb0: 00 00 01 1d  00 20 02 da  00 1f fd f4  00 20 00 4d | ..... ....... .M
86fb3ec0: 00 1f fd 11  00 3f fc 20  00 1f fb 95  00 3f fa ad | .....?. .....?..
86fb3ed0: 00 1f fa 32  00 3f fd fc  00 1f fc a3  00 20 00 cb | ...2.?....... ..
86fb3ee0: 00 00 01 97  00 3f fe b5  00 00 04 0f  00 3f fb 6a | .....?.......?.j
86fb3ef0: 00 00 03 9f  00 3f fb d6  00 00 03 1d  00 3f fe 55 | .....?.......?.U
86fb3f00: 00 00 02 f8  00 3f ff a9  00 00 02 ee  00 20 01 49 | .....?....... .I
86fb3f10: 00 00 03 8f  00 20 04 87  00 00 03 94  00 20 05 09 | ..... ....... ..
86fb3f20: 00 00 01 81  00 3f ff bb  00 1f ff 14  00 3f fa 97 | .....?.......?..
86fb3f30: 00 1f fe 8d  00 3f fc 9d  00 1f ff 89  00 20 01 82 | .....?....... ..
86fb3f40: 00 00 00 be  00 20 00 09  00 00 01 8f  00 3f fa 3a | ..... .......?.:
86fb3f50: 00 00 01 78  00 3f fa 66  00 00 00 7b  00 20 01 35 | ...x.?.f...{. .5
86fb3f60: 00 1f ff 79  00 20 04 f6  00 1f fe e2  00 20 02 62 | ...y. ....... .b
86fb3f70: 00 1f fd 93  00 3f ff 4d  00 1f fa ee  00 3f fe 16 | .....?.M.....?..

FFT之后处理数据的函数检查第一个32位word的0x00200000位是否为零,如果是,则丢弃数据的第一个字和最后一个字。假设该位表示样本是I或者Q值,并且如果第一个样本是Q,则它将从开头删除不匹配的Q,从结尾删除不匹配的I。

Case 1:                Case 2:
Q IQ IQ IQ I           IQ IQ IQ IQ

     |                      | do nothing
     v                      v

  IQ IQ IQ             IQ IQ IQ IQ

通过此种方法可以获得更多的数据进行分析。

八、实践分析

为了使分析变得更加容易,应该编写一个程序,在调制解调器上运行,以调用tune和bandpower功能,然后打开侦听套接字,并通过TCP连接将缓冲区的内容发送回去。为了使程序在加载到预定的存储位置时正常工作并确保入口点位于该地址,使用的段映射与功能函数映射如下:

memset = 0x80522d7c;
memcpy = 0x80004f30;
malloc = 0x80596998;
printf = 0x8052b178;
socket = 0x80332fd0;
bind = 0x800ae7bc;
listen = 0x80412ed4;
accept = 0x80413118;
send = 0x80413240;
recv = 0x804134bc;
tune_aux_channel = 0x80082108;

SECTIONS
{
  . = 0x80810000;
  .start : { *(.start) }
  .text : { *(.text) }
  .data : { *(.data) }
  .rodata : { *(.rodata) }
}

使用如下命令进行编译:

mips-linux-gcc measure.c \
    -march=mips32 \
    -mabi=eabi \
    -msoft-float \
    -mno-abicalls \
    -fno-builtin 
    -nostdlib \
    -nodefaultlibs \
    -nostartfiles \
    -T ./script.ld

MIPS CPU没有FPU,因此使用-msoft-float。当使用-mabi=eabi选项时需要配合使用-mno-abicalls选项。使用-fno-builtin选项防止编译器通过添加对函数的调用来优化某些部分,例如memcpy将导致未定义符号的调用 。-nostdlib和-nostartfiles防止编译器使用标准c库。

使用objcopy可以从编译的elf中提取到需要关心的部分。

mips-linux-objcopy -O binary \
    -j .start \
    -j .text \
    -j .data \
    -j .rodata \
    a.out bin

最后,编写了一个Python脚本,利用pexpect远程登录到调制解调器,使用write_memory命令将二进制文件写入目标地址 。程序是通过call命令执行的。

为了查看是否可以接收FM广播,可以将其调谐到100MHz并获取数据。

使用numpy,scipy和matplotlib Python库,能够将数据解释为一个复杂的有价值的样本,计算FFT并将其绘制成图表,以查看具有明显峰值的带通滤波后的频谱。

将频谱移动到其中一个尖峰的中心,抽取频谱以隔离频率范围,并使用在网上找到的一种非常简单的复数值调频解调技术,可以清楚地看到广播的不同部分,包括19kHz导频。

九、优化

以每秒1500万个样本,每个样本占用8个字节的速度,不到一秒钟的数据可以存储在大约100MB的可用RAM中。一种明显的改进是在填充缓冲区后发送数据,然后捕获更多数据。根据处理时间和网络吞吐量计算得知两次捕获之间的间隔大约为11秒。通过实现一个新功能设置寄存器并启动捕获,从而将时间缩短至约5秒钟,从而消除了FFT计算和其他处理。在对未知寄存器值进行了一些实验之后,希望能找到一个会影响采样率的寄存器,将I和Q值限制为14个有效位的位。尽管它们每个样本仍占据8个字节,但这意味着可以将其中两个打包成一个32位字(需要注意ADC采样的位数)。编写另一个函数来确定它是否以I或Q值开头,然后遍历缓冲区,将每个I / Q对打包为单个整数并将其写入缓冲区中的下一个位置。

仅此一项并不能改善性能,但是仅通过获取第N个样本,就可以降低有效采样率,缩短处理时间并减少必须发回的字节数,从而大大提高了延迟。

使用双线程进行优化处理,一个线程将数据连续捕获到下一个可用缓冲区中,然后向另一个线程发出信号,表明已完成写入。第二个线程对数据进行打包,通过网络发送数据,然后发出可再次写入缓冲区的信号。

十、结语

本文为通过逆向分析将电缆调制解调器改装为SDR的一个初步实践,并不打算制作一个功能强悍的SDR,仅是对技术的一次挑战,希望能给后续有兴趣进行深入研究的同行一点思路上的引导,并用如下的内容结束本文:

 “With so few firsts available in life, take those that present themselves and have a crack.”

参考文献:

https://stdw.github.io/cm-sdr/

Spread the word. Share this post!

Meet The Author

Leave Comment