转储和提取SpaceX Starlink用户终端固件
在2021年5月底,Starlink在比利时推出,所以我们终于能够拿到Dishy McFlatface。在这篇博文中,我们将介绍对硬件的一些初步探索,我们将解释我们如何转储和提取固件。
请注意,这篇博文没有讨论任何具体的漏洞,我们只是记录了其他人可以用来研究Starlink用户终端(UT)的技术。在这篇博文的最后,我们将包括一些来自固件的有趣发现。请注意,SpaceX积极鼓励人们通过他们的漏洞赏金计划发现和报告安全问题:https://bugcrowd.com/spacex
我们首先将我们的终端安装在屋顶的平坦部分并玩了几个小时,让终端和路由器有机会执行固件更新。我们确实运行了一些强制性的速度测试,结果发现下载速度高达268Mbps,上传速度高达49Mbps
拆解第一层
在玩了几个小时之后,是时候弄脏我们的手并拆卸终端。
网络上有一些终端的拆解视频,但没有一个涉及我们感兴趣的细节--主要的 SoC和固件。尽管如此,这些先前的拆解视频包含许多有用的信息,使我们能够在不造成太大损坏的情况下拆开。似乎现在有一些终端的硬件修订版,拆卸过程的某些部分可能会因修订版而异,这是我们通过艰苦的方式学到的。上述拆解视频之一显示了在移除白色塑料盖之前要从主板上拆下以太网和电机控制电缆。在我们的终端上,拉动电机控制电缆将整个连接器从PCB上拉下来;幸运的是,可以修复。注意,不要拉那些电缆,而是先取下塑料后盖。取下塑料后盖后,我们可以看到覆盖 PCB的金属屏蔽层,除了一个包含以太网电缆和电机控制电缆连接器的小切口。还有一个额外的未填充连接器(4针JST SH 1.0mm),我们假设它包含一个UART调试接口,如图所示。请注意,早期的拆解视频有一个额外的连接器,但我们的终端上没有该连接器。
UART接口
连接 USB 到串口转换器后,我们可以获得有关终端启动过程的一些信息。在显示以下输出之前,输出包含有关早期引导加载程序的信息。我们可以看到终端正在使用U-Boot引导加载程序,并且提示键入“falcon”可能会中断引导过程。虽然这可以让我们访问U-Boot CLI,但我们还可以看到串行输入被配置为“nulldev”。不出所料,在启动期间通过“falcon”向串口发送信息没有任何作用
U-Boot 2020.04-gddb7afb (Apr 16 2021 - 21:10:45 +0000)
Model: Catson
DRAM: 1004 MiB
MMC: Fast boot:eMMC: 8xbit - div2
stm-sdhci0: 0
In: nulldev
Out: serial
Err: serial
CPU ID: 0x00020100 0x87082425 0xb9ca4b91
Detected Board rev: #rev2_proto2
sdhci_set_clock: Timeout to wait cmd & data inhibit
FIP1: 3 FIP2: 3
BOOT SLOT B
Net: Net Initialization Skipped
No ethernet found.
*
+
+ +
+ +
+ +
+ + + + + + +
+ + + +
+ + + +
+ + +
+ + +
+ + +
+ + + +
+ + + +
+ + + + + + + + + +
Board: SPACEX CATSON UTERM
======================================
Type 'falcon' to stop boot process
======================================
继续引导过程,我们可以看到 U-Boot从存储在嵌入式 MultiMediaCard (eMMC) 上的扁平化uImage 树 (FIT) 镜像加载内核、ramdisk 和扁平化设备树 (FDT)。我们还可以看到正在检查内核、ramdisk 和 FDT 的完整性(SHA256)和真实性(RSA 2048)。虽然我们必须执行更多测试,但似乎从早期ROM引导加载程序一直到 Linux 操作系统都实现了完整的可信引导链 (TF-A)
switch to partitions #0, OK
mmc0(part 0) is current device
MMC read: dev # 0, block # 98304, count 49152 ... 49152 blocks read: OK
Loading kernel from FIT Image at a2000000 ...
Using 'rev2_proto2@1' configuration
Verifying Hash Integrity ... sha256,rsa2048:dev+ OK
Trying 'kernel@1' kernel subimage
Description: compressed kernel
Created: 2021-04-16 21:10:45 UTC
Type: Kernel Image
Compression: lzma compressed
Data Start: 0xa20000dc
Data Size: 3520634 Bytes = 3.4 MiB
Architecture: AArch64
OS: Linux
Load Address: 0x80080000
Load Size: unavailable
Entry Point: 0x80080000
Hash algo: sha256
Hash value: 5efc55925a69298638157156bf118357e01435c9f9299743954af25a2638adc2
Verifying Hash Integrity ... sha256+ OK
Loading ramdisk from FIT Image at a2000000 ...
Using 'rev2_proto2@1' configuration
Verifying Hash Integrity ... sha256,rsa2048:dev+ OK
Trying 'ramdisk@1' ramdisk subimage
Description: compressed ramdisk
Created: 2021-04-16 21:10:45 UTC
Type: RAMDisk Image
Compression: lzma compressed
Data Start: 0xa2427f38
Data Size: 8093203 Bytes = 7.7 MiB
Architecture: AArch64
OS: Linux
Load Address: 0xb0000000
Load Size: unavailable
Entry Point: 0xb0000000
Hash algo: sha256
Hash value: 57020a8dbff20b861a4623cd73ac881e852d257b7dda3fc29ea8d795fac722aa
Verifying Hash Integrity ... sha256+ OK
Loading ramdisk from 0xa2427f38 to 0xb0000000
WARNING: 'compression' nodes for ramdisks are deprecated, please fix your .its file!
Loading fdt from FIT Image at a2000000 ...
Using 'rev2_proto2@1' configuration
Verifying Hash Integrity ... sha256,rsa2048:dev+ OK
Trying 'rev2_proto2_fdt@1' fdt subimage
Description: rev2 proto 2 device tree
Created: 2021-04-16 21:10:45 UTC
Type: Flat Device Tree
Compression: uncompressed
Data Start: 0xa23fc674
Data Size: 59720 Bytes = 58.3 KiB
Architecture: AArch64
Load Address: 0x8f000000
Hash algo: sha256
Hash value: cca3af2e3bbaa1ef915d474eb9034a770b01d780ace925c6e82efa579334dea8
Verifying Hash Integrity ... sha256+ OK
Loading fdt from 0xa23fc674 to 0x8f000000
Booting using the fdt blob at 0x8f000000
Uncompressing Kernel Image
Loading Ramdisk to 8f848000, end 8ffffe13 ... OK
ERROR: reserving fdt memory region failed (addr=b0000000 size=10000000)
Loading Device Tree to 000000008f836000, end 000000008f847947 ... OK
WARNING: ethact is not set. Not including ethprime in /chosen.
Starting kernel ...
引导过程的其余部分包含一些其他有趣的信息。例如,我们可以看到内核命令行参数以及一些分区的起始地址和长度。此外,我们可以看到 SoC包含4个CPU内核
[ 0.000000] 000: Detected VIPT I-cache on CPU0
[ 0.000000] 000: Built 1 zonelists, mobility grouping on. Total pages: 193536
[ 0.000000] 000: Kernel command line: rdinit=/usr/sbin/sxruntime_start mtdoops.mtddev=mtdoops console=ttyAS0,115200 quiet alloc_snapshot trace_buf_size=5M rcutree.kthread_prio=80 earlycon=stasc,mmio32,0x8850000,115200n8 uio_pdrv_genirq.of_id=generic-uio audit=1 SXRUNTIME_EXPECT_SUCCESS=true blkdevparts=mmcblk0:0x00100000@0x00000000(BOOTFIP_0),0x00100000@0x00100000(BOOTFIP_1),0x00100000@0x00200000(BOOTFIP_2),0x00100000@0x00300000(BOOTFIP_3),0x00080000@0x00400000(BOOTTERM1),0x00080000@0x00500000(BOOTTERM2),0x00100000@0x00600000(BOOT_A_0),0x00100000@0x00700000(BOOT_B_0),0x00100000@0x00800000(BOOT_A_1),0x00100000@0x00900000(BOOT_B_1),0x00100000@0x00A00000(UBOOT_TERM1),0x00100000@0x00B00000(UBOOT_TERM2),0x00050000@0x00FB0000(SXID),0x01800000@0x01000000(KERNEL_A),0x00800000@0x02800000(CONFIG_A),0x01800000@0x03000000(KERNEL_B),0x00800000@0x04800000(CONFIG_B),0x01800000@0x05000000(SX_A),0x01800000@0x06800000(SX_B),0x00020000@0x00F30000(VERSION_INFO_A),0x00020000@0x00F50000(VERSION_INFO_B),0x00020000
[ 0.000000] 000: audit: enabled (after initialization)
[ 0.000000] 000: Dentry cache hash table entries: 131072 (order: 9, 2097152 bytes, linear)
[ 0.000000] 000: Inode-cache hash table entries: 65536 (order: 7, 524288 bytes, linear)
[ 0.000000] 000: mem auto-init: stack:off, heap alloc:off, heap free:off
[ 0.000000] 000: Memory: 746884K/786432K available (6718K kernel code, 854K rwdata, 1648K rodata, 704K init, 329K bss, 39548K reserved, 0K cma-reserved)
[ 0.000000] 000: SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=4, Nodes=1
[ 0.000000] 000: ftrace: allocating 23664 entries in 93 pages
[ 0.000000] 000: rcu: Preemptible hierarchical RCU implementation.
[ 0.000000] 000: rcu: RCU event tracing is enabled.
[ 0.000000] 000: rcu: RCU restricting CPUs from NR_CPUS=8 to nr_cpu_ids=4.
[ 0.000000] 000: rcu: RCU priority boosting: priority 80 delay 500 ms.
[ 0.000000] 000: rcu: RCU_SOFTIRQ processing moved to rcuc kthreads.
[ 0.000000] 000: No expedited grace period (rcu_normal_after_boot).
[ 0.000000] 000: Tasks RCU enabled.
[ 0.000000] 000: rcu: RCU calculated value of scheduler-enlistment delay is 100 jiffies.
[ 0.000000] 000: rcu: Adjusting geometry for rcu_fanout_leaf=16, nr_cpu_ids=4
[ 0.000000] 000: NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0
[ 0.000000] 000: random: get_random_bytes called from start_kernel+0x33c/0x4b0 with crng_init=0
[ 0.000000] 000: arch_timer: cp15 timer(s) running at 60.00MHz (virt).
[ 0.000000] 000: clocksource: arch_sys_counter: mask: 0xffffffffffffff max_cycles: 0x1bacf917bf, max_idle_ns: 881590412290 ns
[ 0.000000] 000: sched_clock: 56 bits at 60MHz, resolution 16ns, wraps every 4398046511098ns
[ 0.008552] 000: Calibrating delay loop (skipped), value calculated using timer frequency..
[ 0.016871] 000: 120.00 BogoMIPS (lpj=60000)
[ 0.021129] 000: pid_max: default: 32768 minimum: 301
[ 0.026307] 000: Mount-cache hash table entries: 2048 (order: 2, 16384 bytes, linear)
[ 0.034005] 000: Mountpoint-cache hash table entries: 2048 (order: 2, 16384 bytes, linear)
[ 0.048359] 000: ASID allocator initialised with 32768 entries
[ 0.050341] 000: rcu: Hierarchical SRCU implementation.
[ 0.061390] 000: smp: Bringing up secondary CPUs ...
[ 0.078677] 001: Detected VIPT I-cache on CPU1
[ 0.078755] 001: CPU1: Booted secondary processor 0x0000000001 [0x410fd034]
[ 0.095799] 002: Detected VIPT I-cache on CPU2
[ 0.095858] 002: CPU2: Booted secondary processor 0x0000000002 [0x410fd034]
[ 0.112970] 003: Detected VIPT I-cache on CPU3
[ 0.113025] 003: CPU3: Booted secondary processor 0x0000000003 [0x410fd034]
[ 0.113160] 000: smp: Brought up 1 node, 4 CPUs
[ 0.113184] 000: SMP: Total of 4 processors activated.
最后,当终端完成其启动过程时,我们会看到一个登录提示
在尝试了一些密码时,我们开始意识到这个UART 接口不太可能轻松登陆进去,不得不更深入研究。
拆解第二层
终端的后金属盖围绕外边缘粘在组件上,并在金属盖中的肋骨和底层 PCB之间涂上额外的胶水。为了松开金属盖边缘的胶水,我们使用了热风枪、撬具、异丙醇和很大的耐心。具体来说,我们首先对一小部分加热,使用撬具松开该部分,添加IPA以帮助溶解胶水和另一轮撬具。取下金属盖后,映入眼帘的是一块直径约55厘米的巨大PCB
我们感兴趣的部分如下图所示。带有金属盖的倒装芯片BGA封装是该板上的主要 SoC(型号为:ST GLLCCOCA6BF)。不出所料,SoC 以 eMMC 芯片的形式连接到一些易失性DRAM存储和非易失性闪存存储
识别 eMMC 测试点
嵌入式存储芯片(eMMC) 包含闪存和控制器,与SD卡非常相似。终端包含带有封装标记 JY976 的Micron eMMC芯片,Micron 提供了一种方便的工具来将这些封装标记解码为实际部件号:https : //www.micron.com/support/tools-and-utilities/fbga。有问题的eMMC芯片的部件号为MTFC4GACAJCN-1M,并包含采用 BGA-153 封装的4GB闪存。在大多数情况下,我们会拆焊这样的 eMMC芯片,将其重新封装并使用BGA插槽进行转储。然而,在这种情况下,我们首先尝试在电路中转储eMMC,以尽量减少损坏我们的终端和eMMC芯片的几率。eMMC芯片与SD卡的相似之处在于它们共享相似的接口;eMMC芯片最多支持8条数据线,而SD卡最多支持4条数据线。eMMC芯片和SD卡都支持仅使用单条数据线,但代价是读/写速度较低。
要在线读取eMMC芯片,我们必须识别时钟(CLK)、命令(CMD)和数据 0 (D0)信号。主SoC上方的10个测试点引起了我们的注意,因为10个测试点可能是CMD、CLK和8条数据线。此外,所有这些测试点都有一个30欧姆的串联电阻连接到它们,这对于eMMC连接来说是比较常见的。我们将一条短线焊接到每个测试点,使我们能够在终端启动过程中创建逻辑分析仪捕获。使用这样的捕获,识别所需信号相对简单。CLK信号将是唯一的重复信号,CMD 是时钟开始翻转后第一个激活的信号,D0是发送数据的第一条数据线。幸运的是,确定剩余的7条数据线不需要转储eMMC内容
在主板上转存eMMC
要转存eMMC芯片,我们可以将读取器(支持1.8VIO)连接到已识别的测试点。存在主要用于手机维修的商业阅读器,并且应该可以很好地用于此目的(例如easy-JTAG和Medusa Pro)。或者,您可以使用带有集成电平转换器(例如https://shop.exploitee.rs/shop/p/low-voltage-emmc-适配器)。如果您周围有一些零件,您也可以自己动手制作一些东西。下图显示了连接到 TI TXS0202EVM 电平转换器分线板的标准USB SD卡读卡器
我们只为eMMC供电,以防止主SoC干扰。eMMC可以通过附近的两个去耦电容器供电,3.3V由SD读卡器提供,1.8V由外部电源供电。一旦一切都正确连接,我们可以创建一个磁盘映像供以后分析。
请注意,在原PCB中读取 eMMC 并不总是一件容易的事;稍微过长的电线将导致读取失败。在这种情况下,它相当简单,即使连接了这些相对较长的电线,系统似乎也能正常运行,至此,成功Dump到了终端固件
解压原始 eMMC 转存
不幸的是,Binwalk无法识别完整的文件系统,因此需要手动分析。从引导日志中可以清楚地看出,U-Boot从块98304开始加载49152个数据块。这意味着U-Boot从地址0x3000000开始读取0x1800000字节(块大小为512 (0x200)字节)。我们还从U-Boot输出中知道,这块数据是一个FIT镜像。但是,当尝试使用dumpimage工具(u-boot-tools 包的一部分)读取FIT镜像标头信息时,我们没有得到任何有用的信息。
幸运的是 SpaceX在Github上发布了他们对GPL合规性的U-Boot 的修改:https : //github.com/SpaceExplorationTechnologies
通过查看这段代码,很明显固件的某些部分以包含纠错码的自定义格式存储(ECC) 数据。
去掉 Reed-Solomon ECC 纠错码
文件spacex_catson_boot.h包含与设备如何启动有关的有趣信息。
下面的片段显示了如何从eMMC(mmc read8)读取数据以及startkernel的定义。
define SPACEX_CATSON_COMMON_BOOT_SETTINGS \
"kernel_boot_addr=" __stringify(CATS_KERNEL_BOOT_ADDR) "\0" \
"kernel_load_addr=" __stringify(CATS_KERNEL_LOAD_ADDR) "\0" \
"kernel_offset_a=" __stringify(CATS_KERNEL_A_OFFSET) "\0" \
"kernel_offset_b=" __stringify(CATS_KERNEL_B_OFFSET) "\0" \
"kernel_size=" __stringify(CATS_KERNEL_A_SIZE) "\0" \
"setup_burn_memory=mw.q " __stringify(CATS_TERM_SCRATCH_ADDR) " 0x12345678aa640001 && " \
"mw.l " __stringify(CATS_TERM_LOAD_ADDR) " 0xffffffff " __stringify(CATS_BOOTTERM1_SIZE) " && " \
"mw.l " __stringify(CATS_TERM_TOC_SER_ADDR) " " __stringify(CATS_TERM_TOC_SER_VAL) "\0" \
"startkernel=unecc $kernel_load_addr $kernel_boot_addr && bootm $kernel_boot_addr${boot_type}\0" \
"stdin=nulldev\0"
define SPACEX_CATSON_BOOT_SETTINGS \
SPACEX_CATSON_COMMON_BOOT_SETTINGS \
"_emmcboot=mmc dev " __stringify(CATS_MMC_BOOT_DEV) " " __stringify(CATS_MMC_BOOT_PART) " && " \
"mmc read8 $kernel_load_addr ${_kernel_offset} $kernel_size && " \
"run startkernel\0" \
"emmcboot_a=setenv _kernel_offset $kernel_offset_a && run _emmcboot\0" \
"emmcboot_b=setenv _kernel_offset $kernel_offset_b && run _emmcboot\0"
startkernel的定义特别有趣,因为它显示了加载内核的地址是如何被传递给一个叫做unecc的命令。从unecc命令的定义中可以看出,这个功能是对从eMMC读取的数据进行纠错。
U_BOOT_CMD(
unecc, 3, 0, do_unecc,
"Unpacks an ECC volume; increments internal ECC error counter on error",
"
该unecc命令调用在中do_unecc实现的函数unecc.c,最终这将导致调用中ecc_decode_one_pass定义的函数ecc.c
/**
- Decodes an ECC protected block of memory. If the enable_correction
- parameter is zero, it will use the MD5 checksum to detect errors and will
- ignore the ECC bits. Otherwise, it will use the ECC bits to correct any
- errors and still use the MD5 checksum to detect remaining problems.
* - @data: Pointer to the input data.
- @size: The length of the input data, or 0 to read until the
- end of the ECC stream.
- @dest: The destination for the decoded data.
- @decoded[out]: An optional pointer to store the length of the decoded
- data.
- @silent: Whether to call print routines or not.
- @enable_correction: Indicates that the ECC data should be used
- to correct errors. Otherwise the MD5 checksum
- will be used to check for an error.
- @error_count[out]: Pointer to an integer that will be incremented
- by the number of errors found. May be NULL.
- Unused if !enable_correction.
* - Return: 1 if the block was successfully decoded, 0 if we had a
- failure, -1 if the very first block didn't decode (i.e. probably
- not an ECC file)
*/
static int ecc_decode_one_pass(const void *data, unsigned long size, void *dest,
unsigned long *decoded, int silent,
int enable_correction, unsigned int *error_count)
ecc.h 包含几个相关的定义:
else /* !NPAR */
define NPAR 32
endif /* NPAR */
/*
- These options must be synchronized with the userspace "ecc"
- utility's configuration options. See ecc/trunk/include/ecc.h in the
- "util" submodule of the platform.
*/define ECC_BLOCK_SIZE 255
define ECC_MD5_LEN 16
define ECC_EXTENSION "ecc"
define ECC_FILE_MAGIC "SXECCv"
define ECC_FILE_VERSION '1'
define ECC_FILE_MAGIC_LEN (sizeof(ECC_FILE_MAGIC) - 1)
define ECC_FILE_FOOTER_LEN sizeof(file_footer_t)
define ECC_DAT_SIZE (ECC_BLOCK_SIZE - NPAR - 1)
define ECC_BLOCK_TYPE_DATA '*'
define ECC_BLOCK_TYPE_LAST '$'
define ECC_BLOCK_TYPE_FOOTER '!'
最终结论为,在这个实现中,一个受ECC保护的内存块以Magic值开始,SXECCv后跟一个版本字节(1)。这个神奇的值标志着ECC保护数据的开始,也是头块的开始。标头块本身包含(除了关键值和版本字节)215 字节数据、星号()和32字节ECC代码字。
头块后面是多个数据块。这些数据块中的每一个都有255个字节长,包含222个字节的数据,后跟一个星号()和32个字节的ECC代码字。最后一个数据块包含一个美元符号($)而不是星号,然后是最后一个页脚块。此页脚块以感叹号(!)开头,后跟ECC保护的内存块中的数据字节数(4字节)和对这些数据字节的MD5摘要。
至此,Binwalk 为何没有成功提取内核、initramfs 和FDT的原因就清楚了。Binwalk 能够获取指示特定文件开始的特殊字段值,但文件的每个块都有额外的数据,阻止Binwalk提取它。在使用Binwalk提取镜像之前,我们使用了一个简单的Python脚本来删除额外的ECC数据。同样,我们现在也可以使用 dumpimage 来获取有关FIT镜像的更多信息。
FIT 镜像和电路板修订版
以下代码段包含一些dump的镜像输出。FIT镜像包含13个引导配置,所有配置都使用相同的内核和 initramfs 镜像,但使用不同的扁平设备树(FDT)
FIT description: Signed dev image for catson platforms
Created: Fri Apr 16 23:10:45 2021
Image 0 (kernel@1)
Description: compressed kernel
Created: Fri Apr 16 23:10:45 2021
Type: Kernel Image
Compression: lzma compressed
Data Size: 3520634 Bytes = 3438.12 KiB = 3.36 MiB
Architecture: AArch64
OS: Linux
Load Address: 0x80080000
Entry Point: 0x80080000
Hash algo: sha256
Hash value: 5efc55925a69298638157156bf118357e01435c9f9299743954af25a2638adc2
Image 12 (rev2_proto2_fdt@1)
Description: rev2 proto 2 device tree
Created: Fri Apr 16 23:10:45 2021
Type: Flat Device Tree
Compression: uncompressed
Data Size: 59720 Bytes = 58.32 KiB = 0.06 MiB
Architecture: AArch64
Load Address: 0x8f000000
Hash algo: sha256
Hash value: cca3af2e3bbaa1ef915d474eb9034a770b01d780ace925c6e82efa579334dea8
Image 15 (ramdisk@1)
Description: compressed ramdisk
Created: Fri Apr 16 23:10:45 2021
Type: RAMDisk Image
Compression: lzma compressed
Data Size: 8093203 Bytes = 7903.52 KiB = 7.72 MiB
Architecture: AArch64
OS: Linux
Load Address: 0xb0000000
Entry Point: 0xb0000000
Hash algo: sha256
Hash value: 57020a8dbff20b861a4623cd73ac881e852d257b7dda3fc29ea8d795fac722aa
Default Configuration: 'rev2_proto2@1'
Configuration 0 (utdev@1)
Description: default
Kernel: kernel@1
Init Ramdisk: ramdisk@1
FDT: utdev3@1
Sign algo: sha256,rsa2048:dev
Sign value: bb34cc2512d5cd3b5ffeb5acace0c1b3dd4d960be3839c88df57c7aeb793ad73a74e87006efece4e9f1e31edbb671e2c63dc4cdcb1a2f55388d83a11f1074f21a1e48d81884a288909eb0c9015054213e5e74cbcc6a6d2617a720949dcac3166f1d01e3c2465d8e7461d14288f1a0abef22f80e2745e7f8499af46e8c007b825d72ab494f104df57433850f381be793bfe06302473269d2f45ce2ff2e8e4439017c0a94c5e7c6981b126a2768da555c86b2be136d4f5785b83193d39c9469bd24177be6ed3450b62d891a30e96d86eee33c2cbfc549d3826e6add36843f0933ced7c8e23085ee6106e3cc2af1e04d2153af5f371712854e91c8f33a4ea434269
从U-Boot 代码 ( spacex_catson_uterm.c)可以清楚地看出,引导配置是根据5个GPIO引脚的状态决定的
/**
- Check board ID GPIOs to find board revision.
- The board IDs are mapped as follows
- id_b0:pio12[2]
- id_b1:pio12[3]
- id_b2:pio12[0]
- id_b3:pio12[1]
- id_b4:pio20[4]
*/
u32 pio12 = readl(BACKBONE_PIO_A_PIO2_PIN);
u32 pio20 = readl(BACKBONE_PIO_B_PIO0_PIN);
u32 board_id = (((pio12 >> 2) & 1) << 0) |
(((pio12 >> 3) & 1) << 1) |
(((pio12 >> 0) & 1) << 2) |
(((pio12 >> 1) & 1) << 3) |
(((pio20 >> 4) & 1) << 4);
/*
switch (board_id)
{
case 0b11111:
board_rev_string = BOARD_REV_1_1P3;
break;
case 0b11100:
board_rev_string = BOARD_REV_1_2P1;
break;
case 0b11000:
board_rev_string = BOARD_REV_1_2P2;
break;
case 0b10100:
board_rev_string = BOARD_REV_1_3P0;
break;
case 0b10000: /* rev1 pre-production */
board_rev_string = BOARD_REV_1_PRE_PROD;
break;
case 0b11110: /* rev1 production */
board_rev_string = BOARD_REV_1_PROD;
break;
case 0b00001:
board_rev_string = BOARD_REV_2_0P0;
break;
case 0b00010:
board_rev_string = BOARD_REV_2_1P0;
break;
case 0b00011:
board_rev_string = BOARD_REV_2_2P0;
break;
}
}
printf("Detected Board rev: %s\n", board_rev_string);
下图显示了这些引脚被拉高/拉低以指示电路板修订的位置。请注意,从早期的串行引导日志中,我们的终端使用rev2_proto2配置( case 0b00011)进行引导。在最近的视频中,Colin O'Flynn 将其中一些引脚拉高/拉低,可以观察到终端尝试使用不同的FIT配置启动,因此使用不同的设备树。我们比较了一些FDT,但没有发现任何从安全角度有趣的差异
对固件的初步了解
登录提示
回想一下,在启动过程完成后,我们会看到一个登录提示。对于进一步的研究,获得登录的能力会很有用,允许我们与实时系统进行交互。但是,通过查看镜像文件,很明显不允许任何用户登录。在启动期间,终端确实读取保险丝以确定它是否是测试硬件。如果触点未融合,它将为 root 用户设置一个密码,允许登录。出售给消费者的Starlink 终端当然是融合的,禁用登录提示。
root::10933:0:99999:7:::
bin::10933:0:99999:7:::
daemon::10933:0:99999:7:::
sync::10933:0:99999:7:::
halt::10933:0:99999:7:::
uucp::10933:0:99999:7:::
operator::10933:0:99999:7:::
ftp::10933:0:99999:7:::
nobody::10933:0:99999:7:::
sshd::::::::
开发硬件
SpaceX 的工程师为了防止终端设备被人拆开破解这种情况,似乎试图主动检测不再受他们控制的未融合开发硬件。开发硬件受地理坐标围栏限制,只能在某些预定义的区域工作,其中大部分显然是SpaceX的位置。如果在这些预定义的地理围栏之外使用开发硬件,SpaceX 可能会收到通知。
有趣的是,其中一些地理围栏似乎与SpaceX没有明确的联系。虽然我们不会在这里透露这些位置,但我会说这SNOW_RANCH看起来是一个玩开发硬件的好地方。
安全要素
从固件中的参考可以清楚地看出(我们的修订版)终端包含STMicroelectronicsSTSAFE安全芯片。安全芯片的用途尚不完全清楚,但可用于远程验证终端合法性。
芯片系统
有人肯定问StarLink使用哪种处理器:答案是四核的Cortex-A53,每个核都被分配了一个特定的任务。
下一步计划
暂时就这样,如果有兴趣,我们可能会继续研究Starlink终端,并在未来的文章中提供更多详细信息。在撰写本文时,我们能够在终端上获得一个root shell,但是现在公开分享有关此事的更多信息还为时过早。
仅提供技术分享和交流使用,转载出处:https://www.esat.kuleuven.be/cosic/blog/dumping-and-extracting-the-spacex-starlink-user-terminal-firmware/