www.esat.kuleuven.be Open in urlscan Pro
134.58.64.16  Public Scan

URL: https://www.esat.kuleuven.be/cosic/blog/dumping-and-extracting-the-spacex-starlink-user-terminal-firmware/
Submission: On June 13 via api from LU — Scanned from DE

Form analysis 1 forms found in the DOM

GET https://www.esat.kuleuven.be/cosic/

<form role="search" method="get" class="search-form" action="https://www.esat.kuleuven.be/cosic/">
  <label>
    <span class="screen-reader-text">Search for:</span>
    <input type="search" class="search-field" placeholder="Search …" value="" name="s">
  </label>
  <input type="submit" class="search-submit" value="Search">
</form>

Text Content

RESEARCH GROUP COSIC, KU LEUVEN

Menu
 * About us
   * Research
   * Media
   * Honors and awards
 * Info for
   * Students
   * Researchers
   * Industry
 * People
   * People
   * Former people
   * Teams
 * Research
   * Blog
   * Publications
   * Running projects
   * Finished Projects
 * Open positions
   * Open positions
   * Stages (NL)
   * Internships (ENG)
   * Student jobs
 * News
 * Calendar
 * Contact


DUMPING AND EXTRACTING THE SPACEX STARLINK USER TERMINAL FIRMWARE

Towards the end of May 2021 Starlink launched in Belgium so we were finally able
to get our hands on a Dishy McFlatface. In this blog post we will cover some
initial exploration of the hardware and we will explain how we dumped and
extracted the firmware.
Note that this blog post does not discuss any specific vulnerabilities, we
merely document techniques that can be used by others to research the Starlink
User Terminal (UT). Towards the end of this blog post we will include some
interesting findings from the firmware. Note that SpaceX actively encourages
people to find and report security issues through their bug bounty program:
https://bugcrowd.com/spacex

We first set up our UT on a flat section of our university building’s roof and
played around with it for a few hours, giving the UT and router the chance to
perform a firmware update. We did run a few mandatory speed tests and were
seeing as much as 268 Mbps download and 49 Mbps upload.


TEARDOWN: LEVEL 1

After a few hours of playing around it was time to get our hands dirty and
disassemble the UT.

There have been a few teardown videos of the UT but none of them went into the
details we were interested in: the main SoC and firmware. Nevertheless, these
prior teardowns ([1, 2, 3]) of the dish contain a lot of useful information that
allowed us to disassemble our dish without too much damage.
It appears that there a few hardware revisions of the UT out there by now,
certain parts of the teardown process can differ depending on the revision,
something we learned the hard way. One of the aforementioned teardown videos
shows the Ethernet and motor control cables to be detached from the main board
before the white plastic cover is removed. On our UT, a tug on the motor control
cables pulled the entire connector from the PCB; luckily it appears we can
repair the damage. In other words, do not pull on those cables but first remove
the back plastic cover, for those of you in the same boat: JST BM05B-ZESS-TBT.

After removing the back plastic cover we can see a metal shield covering the
PCB, with the exception of a small cut-out containing the connectors for the
Ethernet cable and motor control cable. There is one additional, unpopulated
connector (4 pin JST SH 1.0mm), that we assumed would contain a UART debug
interface as was shown in [4]. Note that the early teardown videos had an
additional connector that is no longer present on our UT.

 * 
 * 


THE UART INTERFACE

After hooking up a USB to serial converter we could get some information on the
UT’s boot process.
The output contains information regarding the early stage bootloaders before
showing the following output.

We can see that the UT is using the U-Boot bootloader, and that typing ‘falcon’
may interrupt the boot process. While this may give us access to a U-Boot CLI we
can also see that the serial input is configured as ‘nulldev’. Unsurprising,
spamming the serial interface with ‘falcon’ during boot did not yield any
result.

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 =
======================================

Continuing through the boot process we can see that U-Boot loads a kernel,
ramdisk and Flattened Device Tree (FDT) from a Flattened uImage Tree (FIT) image
that is stored on an embedded MultiMediaCard (eMMC).
We can also see that the integrity (SHA256) and authenticity (RSA 2048) of the
kernel, ramdisk and FDT is being checked. While we would have to perform some
more tests it appears that a full trusted boot chain (TF-A) is implemented from
the early stage ROM bootloader all the way down to the Linux operating system.

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 ...

The remainder of the boot process contains some other interesting pieces of
information.
For example, we can see the kernel command line arguments and with that the
starting addresses and lengths of some partitions. Additionally, we can see that
the SoC contains 4 CPU cores.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[    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.

Finally, when the UT completes its boot process we are greeted with a login
prompt:

Development login enabled: no
 
SpaceX User Terminal.
user1 login:

While making a few attempts at guessing valid login credentials we started
realising that this UART interface would be unlikely to result in an easy win.
We had to go deeper.


TEARDOWN: LEVEL 2

The back metal cover of the UT is glued to the assembly around the outer edge
and additional glue is applied between the ribs in the metal cover and the
underlying PCB. To loosen the glue at the edge of the metal cover we used a heat
gun, prying tools, isopropyl alcohol and a lot of patience. Specifically, we
first applied heat to a small section, used a prying tool to loosen that
section, added IPA to help dissolve the glue and another round of the prying
tool. Having removed the metal cover we are greeted by an enormous PCB measuring
approximately 55 cm in diameter.

The parts of interest to us are shown in the picture below. The flip-chip BGA
package with the metal lid is the main SoC on this board (marking: ST
GLLCCOCA6BF). Unsurprisingly the SoC is connected to some volatile DRAM storage
and non-volatile flash storage in the form of an eMMC chip.


IDENTIFYING EMMC TEST POINTS

An embedded MultiMediaCard (eMMC) contains flash storage and a controller and is
quite similar to an SD-card. The UT contains a Micron eMMC chip with package
marking JY976, Micron offers a convenient tool to decode these package markings
to the actual part number:
https://www.micron.com/support/tools-and-utilities/fbga. The eMMC chip in
question has part number MTFC4GACAJCN-1M and contains 4GB of flash storage in a
BGA-153 package. In most scenarios we would desolder such an eMMC chip, reball
it and dump it using a BGA socket. However, in this case we first attempted to
dump the eMMC in-circuit to minimize the odds of damaging our UT and the eMMC
chip.

eMMC chips are similar to SD cards in that they share a similar interface; the
eMMC chip does support up to 8 data lines whereas SD-cards support up to 4 data
lines. Both eMMC chips and SD-Cards support the use of only a single data line
at the cost of lower read/write speeds.

To read the eMMC chip in-circuit we have to identify the clock (CLK), command
(CMD) and data 0 (D0) signals. The 10 test points above the main SoC drew our
attention, as 10 test points could be a CMD, CLK and 8 data lines. Additionally,
all of these test points have a 30 Ohm series resistor connected to them which
is relatively common for eMMC connections. We soldered a short wire to each test
point, allowing us to create a logic analyser capture during the UT boot
process. Using such a capture it is relatively straightforward to identify the
required signals. The CLK signal will be the only repetitive signal, CMD is the
signal that is first active after the clock starts toggling and D0 is the first
data line to send out data. Determining the remaining 7 data lines is luckily
unnecessary to dump the eMMC contents.

 * 
 * 


DUMPING THE EMMC IN-CIRCUIT

To dump the eMMC chip we can connect a reader (that supports 1.8V IO) to the
identified test points. Commercial readers that are mostly aimed at phone repair
exist and should work well for this purpose (e.g. easy-JTAG and Medusa Pro).
Alternatively you can use a regular USB SD-card reader (one that supports 1-bit
mode) with an SD-card breakout with integrated level-shifters (e.g.
https://shop.exploitee.rs/shop/p/low-voltage-emmc-adapter ). You can also whip
something up yourself if you have some parts laying around. The picture below
shows a standard USB SD-card reader connected to a TI TXS0202EVM level-shifter
breakout board.

We only provide power to the eMMC to prevent the main SoC from interfering. The
eMMC can be powered through two nearby decoupling capacitors, 3.3V is provided
by the SD card reader and 1.8V is provided using a lab power supply. Once
everything is hooked up properly we can create a disk image for later analysis.

Note that reading eMMC in circuit is not always an easy task; wires that are
slightly too long can already prevent reading from succeeding. In this case it
was rather straightforward and the system appears to function normally even with
these relatively long wires attached.

 * 
 * 


UNPACKING THE RAW EMMC DUMP

Unfortunately, Binwalk was not able to extract the full filesystem hence a
manual analysis was required.

From the boot log it was clear that U-Boot was loading 49152 blocks of data
starting at block 98304. Meaning that U-Boot is reading 0x1800000 bytes
(blocksize of 512 (0x200) bytes) starting from address 0x3000000. We also know
from the U-Boot output that this chunk of data is a FIT image. However, when
trying to read the FIT image header information using the dumpimage tool (part
of the u-boot-tools package) we weren’t getting any useful information.

Luckily SpaceX released their modifications to U-Boot on Github for GPL
compliance: https://github.com/SpaceExplorationTechnologies
By looking at this code it became clear that certain parts of the firmware are
stored in a custom format that contains Error Correcting Code (ECC) data.


STRIPPING REED-SOLOMON ECC WORDS

The file spacex_catson_boot.h contains interesting information related to how
the device boots.
The following snippets show how data is being read from eMMC (mmc read8) and the
definition for startkernel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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"

The definition of startkernel is particularly interesting as it shows how the
address where the kernel was loaded is being passed to a command called unecc.
From the unecc command definition it is quite clear that this functionality is
performing error correction on the data read from the eMMC.

1
2
3
4
5
6
7
8
9
10
U_BOOT_CMD(
    unecc, 3, 0, do_unecc,
    "Unpacks an ECC volume; increments internal ECC error counter on error",
    "<source> <target>\n"
    "\tReturns successfully if the given source was successfully\n"
    "\tunpacked to the target. This will fail if the given source\n"
    "\tis not an ECC volume. It will succeed if bit errors were\n"
    "\tsuccessfully fixed.\n"
    "\t<source> and <target> should both be in hexadecimal.\n"
);

The unecc command calls the do_unecc function implemented in unecc.c. Eventually
this will result in calling the ecc_decode_one_pass function defined in ecc.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
 * 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 contains several relevant definitions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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   '!'

In the end what it boils down to is that, in this implementation, an ECC
protected block of memory starts with the magic header value SXECCv followed by
a version byte (1). This magic value marks the start of the ECC protected data,
but also the start of the header block. The header block itself contains (in
addition to the magic value and version byte), 215 bytes of data, an asterisk
(*), and 32 bytes of ECC code words.

The header block is followed by multiple data blocks. Each of these data blocks
is 255 bytes long and contains 222 bytes of data followed by an asterisk symbol
(*) and 32 bytes of ECC code words. The last data block contains a dollar sign
($) instead of the asterisk and is followed by a final footer block. This footer
block starts with and exclamation mark (!) that is followed by the number of
data bytes in the ECC protected block of memory (4-bytes) and MD5 digest over
those data bytes.

At this point it should be clear why Binwalk did not succeed in extracting the
kernel, initramfs and FDT. Binwalk is able to pick up on the magic values that
indicate the start of a particular file, but each block of the file had
additional data that prevented Binwalk from extracting it. We used a simple
Python scrip to remove the extra ECC data before using Binwalk to extract the
image. Similarly, we can now also use dumpimage to get more information on the
FIT image.


THE FIT IMAGE AND BOARD REVISIONS

The following snippet contains some of the dumpimage output. The FIT image
contains 13 boot configurations, all configurations use the same kernel and
initramfs images but a different Flattened Device Tree (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

From the U-Boot code (spacex_catson_uterm.c) it becomes clear that the boot
configuration is decided based on the state of 5 GPIO pins.

142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
  /**
   * 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);
  /*
   * https://confluence/display/satellites/User+Terminal%3A+Catson+ID+Bits
   */
  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);

The following picture shows where these pins are being pulled high/low to
indicate the board revision. Note from the earlier serial bootlog that our UT
boots using the rev2_proto2 configuration (case 0b00011). In a recent video
Colin O’Flynn pulled some of these pins high/low and could observe that the UT
tried booting using a different FIT configuration and thus different devicetree
[5]. We compared some of the FDTs but did not spot any differences that would be
interesting from a security perspective.


A FIRST LOOK AT THE FIRMWARE


THE LOGIN PROMPT

Recall that after the boot process has completed we are greeted with a login
prompt. For further research it would be useful to gain the ability to log in,
allowing us to interact with the live system. However, by looking at the shadow
file it becomes clear that none of the users are allowed to log in.
During boot the UT does read a fuse to determine if it is development hardware
or not. If the UT is unfused it will set a password for the root user, allowing
log in.
Starlink UTs that are sold to consumers are of course production fused,
disabling the login prompt.

1
2
3
4
5
6
7
8
9
10
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:*:::::::


DEVELOPMENT HARDWARE

Development hardware often finds its way into the wrong hands [6, 7]. The
engineers at SpaceX considered this scenario and appear to try to actively
detect unfused development hardware that is no longer under their control.
Development hardware is geofenced to only work in certain predefined areas, most
of which are clearly SpaceX locations. SpaceX is likely notified if development
hardware is used outside these predefined geofences.

 * 
 * 

Interestingly, some of these geofences do not seem to have a clear connection to
SpaceX. While we will not disclose these locations here, I will say that the
SNOW_RANCH looks like a nice location to play with development hardware.


SECURE ELEMENT

From references in the firmware it became clear that (our revision of) the UT
contains a STMicroelectronics STSAFE secure element. The purpose of the secure
element is not entirely clear yet, but it may be used to remotely authenticate
the UT.


THE SOC

Some people have asked which processor is being used: the answer is a Quad-Core
Cortex-A53 and each core has been assigned a specific task.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
############################
# System Information
############################
#
# The user terminal phased-array computers are Catson SoCs with a quad-core
# Cortex-A53.
#
# We dedicate one core to control, while leaving the other three to handle
# interrupts and auxiliary processes.
#
#   CPU 0: Control process.
#   CPU 1: Lower-MAC RX process.
#   CPU 2: Lower-MAC TX process.
#   CPU 3: PhyFW and utility core - interrupts, auxiliary processes,
miscellaneous


WHAT’S NEXT

That’s it for now. We will likely continue looking into the Starlink UT and
provide more details in future blog posts if there is interest. At the time of
writing we were able to obtain a root shell on the UT, but it’s too early to
publicly share more information on that matter.


REFERENCES

[1] MikeOnSpace – Starlink Dish TEARDOWN! – Part 1 –
https://youtu.be/QudtSo5tpLk
[2] Ken Keiter – Starlink Teardown: DISHY DESTROYED! –
https://youtu.be/iOmdQnIlnRo
[3] The Signal Path – Starlink Dish Phased Array Design, Architecture & RF
In-depth Analysis – https://youtu.be/h6MfM8EFkGg
[4] MikeOnSpace – Starlink Dish TEARDOWN! – Part 2 –
https://youtu.be/38_KTq8j0Nw
[5] Colin O’Flynn – Starlink Dishy (Rev2 HW) Teardown Part 1 – UART, Reset, Boot
Glitches – https://youtu.be/omScudUro3s
[6] Brendan I. Koerner – The Teens Who Hacked Microsoft’s Xbox Empire –
https://www.wired.com/story/xbox-underground-videogame-hackers/
[7] Jack Rhysider – Darknet Diaries EP 45: XBOX UNDERGROUND (PART 1) –
https://darknetdiaries.com/episode/45/

SHARE

XFacebookLinkedIn

Posted in COSIC Cryptography BlogTagged Lennert Wouters, Level: Advanced,
Research blogging, SpaceX, Starlink
Posted on 06/07/202109/06/2022 by Lennert Wouters in COSIC Cryptography Blog


POST NAVIGATION

Previous: Bart Preneel becomes APPROACH Board Member
Next: Ingrid Verbauwhede named IACR Fellow


COSIC COURSE 2024

We are organizing the COSIC Course on Cryptography and Cyber Security (1-4 July
2024)
Co-organized with enCRYPTON school on “Fast and Efficient Implementation of
Homomorphic Encryption for Privacy Enhancing Technologies” (3-4 July 2024)

More info and registration


FOLLOW US

 * X @CosicBe
 * Bluesky @cosic.bsky.social
 * Mastodon @CosicBe
 * LinkedIn
 * YouTube


MOST USED TAGS

Alice Pellet-Mary Asiacrypt Asiacrypt2019 Aysajan Abidin Bart Preneel Carl
Bootland Cathedral CCS CCS2019 Claudia Diaz Co6GC COED Conference blogging Cosic
Guide To Crypto Daniele Cozzo EC22 Enrique Argones Rúa ERC Erik Pohle EuroCrypt
EuroCrypt 2024 FHE H2020 Ilaria Chillotti Ingrid Verbauwhede Karim Baghery
Kelong Cong Lennert Wouters Level: Advanced Level: Easy Level: Intermediate MPC
Mustafa A. Mustafa Privacy Prizes & Awards Research blogging Roel Peeters RWC
2022 Shahla Atapoor Thomas Decru Titouan Tanguy Vincent Rijmen Ward Beullens
Wouter Castryck Younes Talibi Alaoui


ADVANCED MASTER CYBERSECURITY

COSIC and DistriNet organize a 60-ECTS Advanced Master Cybersecurity programme
together, aimed at graduates from electrical engineering, computer science and
mathematics. The English-taught programme can be completed on a one-year
full-time basis.


SPOTLIGHT PROJECTS

 * ERC ISOCRYPT
 * ERC Belfort


UPCOMING EVENTS

Jun 14
11:00 - 12:00 CEST


COSIC SEMINAR – A SYSTEMATIC EXPLORATION OF EVOLUTIONARY COMPUTATION FOR THE
DESIGN OF HARDWARE-ORIENTED NON-CRYPTOGRAPHIC HASH FUNCTIONS – MUJTABA HASSAN
(KU LEUVEN)

Jun 19
10:00 - 11:00 CEST


COSIC SEMINAR – GLITCH-STOPPING CIRCUITS: HARDWARE SECURE MASKING WITHOUT
REGISTERS – ZHENDA ZHANG (KU LEUVEN)

Jun 26
13:00 - 14:00 CEST


COSIC SEMINAR – MIXMATCH: FLOW MATCHING FOR MIXNET TRAFFIC – LENNART OLDENBURG
(KU LEUVEN)

View Calendar


SEARCH THIS SITE

Search for:


RECENT BLOG POSTS

 * VNC nr. 2 10/06/2024
 * Fast Evaluation of S-boxes with Garbled Circuits 05/06/2024
 * Eurocrypt 2024 (CrossFyre workshop): MPC With Delayed Parties Over Star-Like
   Networks 03/06/2024




 * 2024 © KU Leuven
 * Disclaimer
 * Cookiebeleid
 * Privacybeleid
 * COSIC webmaster

Follow COSIC