Category Archives: Firmware

Breaking secure boot on the Meraki Z3 and Meraki Go GX20

Meraki used to be friendly to open-source. Older devices and Meraki firmwares offered a root shell over UART, and it was relatively easy to flash custom firmware to the device. Later, they began locking down devices so that people could no longer repurpose the hardware. Their latest “innovations” include the use of secure boot, ostensibly to improve device security but with the side effect of making claimed and EoL devices e-waste.


“Z3 is a remote work gateway with integrated Wi-Fi, secure access to corporate and multi-cloud resources, and features that delight SD-branch cloud platform users.”

Today we are looking at the Meraki Z3 and Meraki Go GX20. Both the Z3 and GX20 ship with secure boot enabled, meaning you cannot boot an alternative firmware like OpenWrt on the devices, and they are useless if the previous owner has not “unclaimed” them in Meraki’s dashboard.

Meraki chose to use the same u-boot release for their Qualcomm IPQ40xx based devices (MR33, MR30H, Z3, Go GX20). The MR33 ships without secure boot. However, since secure boot is enabled on the Z3 and Meraki Go GX20, u-boot on those devices must be signed with a certificate matching the one burned into the QFPROM fuses, or the device will not boot. In the case of the Z3, u-boot is signed with the fuzzy-cricket attestation ca1 certificate:

California1
San Francisco
Cisco Meraki1%0#
fuzzy-cricket attestation ca1
Product Security0
170728184745Z
170827184745Z0

The boot flow on an ipq40xx device with secure boot enabled can be summarized in the following diagram (adapted from the LineageOS docs):

Since Meraki use the same u-boot for devices with and without secure boot, u-boot must determine at some point whether it should enforce signature verification on the payload (a Flattened Image Tree Image). That check happens here:

static int do_meraki_qca_boot(cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[])
{
   /* unsupported boards */
   switch(get_meraki_product_id()) {
      case MERAKI_BOARD_STINKBUG:
      case MERAKI_BOARD_LADYBUG:
      case MERAKI_BOARD_NOISY_CRICKET:
      case MERAKI_BOARD_YOWIE:
      case MERAKI_BOARD_BIGFOOT:
      case MERAKI_BOARD_SASQUATCH:
      case MERAKI_BOARD_WOOKIE:
         return 0;
      default:
         break;
   }

   /* Check 0: check/force secure boot on */
   force_secboot();

   /* Check 1: boot diagnostic */
   run_command("run check_boot_diag_img", 0);

   /* Check 2: boot part.new if upgrading */
   do_upgrade_boot();

   /* Check 3: if node specific unlock exists, chainload u-boot */
   if (devel_crt_valid()) {
      do_chainload_uboot();
   }

   setenv("part","part.safe");
   run_command("run boot_signedpart",0);

   setenv("part","part.old");
   run_command("run boot_signedpart",0);

   run_command("reset",0);

   return 0;
}

Note that if the board is STINKBUG (MR33), LADYBUG (MR74), NOISY_CRICKET (MR30H), YOWIE (MR42), BIGFOOT (MR52), SASQUATCH (MR53), or WOOKIE (MR84) then verification of the FIT image is skipped, as these devices don’t have secure boot enabled by default.


The 2017.07 u-boot update for the MR33 is a special case, as that doesn’t actually enable secure boot, it just maliciously bricks the device if the user attempts to interrupt boot.

How this practice is permitted under the EU Unfair Commercial Practices Directive (Directive 2005/29/EC of 2005) is quite frankly a mystery to me, but perhaps someone with more legal expertise can weigh in.


u-boot determines which devices its running on by reading the “Board major number” from an I2C EEPROM (24c64; silkscreen U32) on the PCB. Modifying the contents of the EEPROM from a device model with secure boot to a device without secure boot is enough to disable signature verification of payloads in u-boot. You can find the “board major number” byte at offset 0x4a and a backup copy at 0x104a in the EEPROM.

Original Z3 boot output:

Format: Log Type - Time(microsec) - Message - Optional Info
Log Type: B - Since Boot(Power On Reset),  D - Delta,  S - Statistic
S - QC_IMAGE_VERSION_STRING=BOOT.BF.3.1.1-00096
S - IMAGE_VARIANT_STRING=DAACANAZA
S - OEM_IMAGE_VERSION_STRING=CRM
S - Boot Config, 0x00000025
S - Core 0 Frequency, 0 MHz
B -       261 - PBL, Start
B -      1340 - bootable_media_detect_entry, Start
B -      2615 - bootable_media_detect_success, Start
B -      2629 - elf_loader_entry, Start
B -      7204 - auth_hash_seg_entry, Start
B -   1382243 - auth_hash_seg_exit, Start
B -   1447600 - elf_segs_hash_verify_entry, Start
B -   1569288 - PBL, End
B -   1569312 - SBL1, Start
B -   1657984 - pm_device_init, Start
D -         6 - pm_device_init, Delta
B -   1659502 - boot_flash_init, Start
D -     87535 - boot_flash_init, Delta
B -   1751079 - boot_config_data_table_init, Start
D -     14006 - boot_config_data_table_init, Delta - (419 Bytes)
B -   1767784 - clock_init, Start
D -      7575 - clock_init, Delta
B -   1778758 - CDT version:2,Platform ID:8,Major ID:1,Minor ID:0,Subtype:1
B -   1782246 - sbl1_ddr_set_params, Start
B -   1787231 - cpr_init, Start
D -         2 - cpr_init, Delta
B -   1791720 - Pre_DDR_clock_init, Start
D -         5 - Pre_DDR_clock_init, Delta
D -     13140 - sbl1_ddr_set_params, Delta
B -   1804998 - pm_driver_init, Start
D -         2 - pm_driver_init, Delta
B -   1875945 - sbl1_wait_for_ddr_training, Start
D -        27 - sbl1_wait_for_ddr_training, Delta
B -   1893463 - Image Load, Start
D -   1311048 - QSEE Image Loaded, Delta - (268504 Bytes)
B -   3205010 - Image Load, Start
D -      2116 - SEC Image Loaded, Delta - (2048 Bytes)
B -   3215195 - Image Load, Start
D -   1307290 - APPSBL Image Loaded, Delta - (292616 Bytes)
B -   4522910 - QSEE Execution, Start
D -        56 - QSEE Execution, Delta
B -   4529089 - SBL1, End
D -   2961858 - SBL1, Delta
S - Flash Throughput, 1970 KB/s  (563587 Bytes,  285975 us)
S - DDR Frequency, 672 MHz


U-Boot 2017.07-RELEASE-g39cabb9bf3 (May 24 2018 - 14:07:32 -0700)

DRAM:  242 MiB
machid : 0x8010001
Product: meraki_Fuzzy_Cricket
NAND:  ONFI device found
ID = 1d80f101
Vendor = 1
Device = f1
128 MiB
Using default environment

In:    serial
Out:   serial
Err:   serial
machid: 8010001
ubi0: attaching mtd1
ubi0: scanning is finished
ubi0: attached mtd1 (name "mtd=0", size 112 MiB)
ubi0: PEB size: 131072 bytes (128 KiB), LEB size: 126976 bytes
ubi0: min./max. I/O unit sizes: 2048/2048, sub-page size 2048
ubi0: VID header offset: 2048 (aligned 2048), data offset: 4096
ubi0: good PEBs: 896, bad PEBs: 0, corrupted PEBs: 0
ubi0: user volume: 5, internal volumes: 1, max. volumes count: 128
ubi0: max/mean erase counter: 124/51, WL threshold: 4096, image sequence number: 1218587189
ubi0: available PEBs: 337, total reserved PEBs: 559, PEBs reserved for bad PEB handling: 20


Secure boot enabled.

Read 0 bytes from volume part.safe to 84000000
No size specified -> Using max size (16584704)
Valid image
## Loading kernel from FIT Image at 84000028 ...
   Using 'config@1' configuration
   Trying 'kernel@1' kernel subimage
     Description:  wired-arm-qca Kernel
     Type:         Kernel Image
     Compression:  uncompressed
     Data Start:   0x84000134
     Data Size:    1962384 Bytes = 1.9 MiB
     Architecture: ARM
     OS:           Linux
     Load Address: 0x80208000
     Entry Point:  0x80208000
     Hash algo:    sha1
     Hash value:   1a716f7999511396baa166ef3986165f40c4c1c7
   Verifying Hash Integrity ... sha1+ OK
## Loading ramdisk from FIT Image at 84000028 ...
   Using 'config@1' configuration
   Trying 'ramdisk@1' ramdisk subimage
     Description:  wired-arm-qca Ramdisk
     Type:         RAMDisk Image
     Compression:  uncompressed
     Data Start:   0x841df3b8
     Data Size:    14496712 Bytes = 13.8 MiB
     Architecture: ARM
     OS:           Linux
     Load Address: 0x82200000
     Entry Point:  0x82200000
     Hash algo:    sha1
     Hash value:   6c03ebf4feff11ed19dadcd2b6afe329d74e6671
   Verifying Hash Integrity ... sha1+ OK
   Loading ramdisk from 0x841df3b8 to 0x82200000
## Loading fdt from FIT Image at 84000028 ...
   Using 'config@1' configuration
   Trying 'fdt@1' fdt subimage
     Description:  Fuzzy Cricket Device Tree
     Type:         Flat Device Tree
     Compression:  uncompressed
     Data Start:   0x84fb2874
     Data Size:    38216 Bytes = 37.3 KiB
     Architecture: ARM
     Hash algo:    sha1
     Hash value:   25733212c52b5e5803c8fe02a64fd229f30a2ac4
   Verifying Hash Integrity ... sha1+ OK
   Loading fdt from 0x84fb2874 to 0x89000000
   Booting using the fdt blob at 0x89000000
   Loading Kernel Image ... OK
   Using Device Tree in place at 89000000, end 8900c547
Using machid 0x8010001 from environment

Starting kernel ...

After modifying the Z3 EEPROM to have the board major number of the MR33:

Format: Log Type - Time(microsec) - Message - Optional Info
Log Type: B - Since Boot(Power On Reset),  D - Delta,  S - Statistic
S - QC_IMAGE_VERSION_STRING=BOOT.BF.3.1.1-00096
S - IMAGE_VARIANT_STRING=DAACANAZA
S - OEM_IMAGE_VERSION_STRING=CRM
S - Boot Config, 0x00000025
S - Core 0 Frequency, 0 MHz
B -       261 - PBL, Start
B -      1340 - bootable_media_detect_entry, Start
B -      2615 - bootable_media_detect_success, Start
B -      2629 - elf_loader_entry, Start
B -      7204 - auth_hash_seg_entry, Start
B -   1382292 - auth_hash_seg_exit, Start
B -   1447925 - elf_segs_hash_verify_entry, Start
B -   1569633 - PBL, End
B -   1569657 - SBL1, Start
B -   1658338 - pm_device_init, Start
D -         6 - pm_device_init, Delta
B -   1659859 - boot_flash_init, Start
D -     87566 - boot_flash_init, Delta
B -   1751466 - boot_config_data_table_init, Start
D -     14002 - boot_config_data_table_init, Delta - (419 Bytes)
B -   1768163 - clock_init, Start
D -      7570 - clock_init, Delta
B -   1779133 - CDT version:2,Platform ID:8,Major ID:1,Minor ID:0,Subtype:1
B -   1782622 - sbl1_ddr_set_params, Start
B -   1787606 - cpr_init, Start
D -         2 - cpr_init, Delta
B -   1792095 - Pre_DDR_clock_init, Start
D -         5 - Pre_DDR_clock_init, Delta
D -     13141 - sbl1_ddr_set_params, Delta
B -   1805373 - pm_driver_init, Start
D -         2 - pm_driver_init, Delta
B -   1876249 - sbl1_wait_for_ddr_training, Start
D -        27 - sbl1_wait_for_ddr_training, Delta
B -   1893981 - Image Load, Start
D -   1311108 - QSEE Image Loaded, Delta - (268504 Bytes)
B -   3205588 - Image Load, Start
D -      2122 - SEC Image Loaded, Delta - (2048 Bytes)
B -   3215674 - Image Load, Start
D -   1307310 - APPSBL Image Loaded, Delta - (292616 Bytes)
B -   4523411 - QSEE Execution, Start
D -        56 - QSEE Execution, Delta
B -   4529587 - SBL1, End
D -   2962011 - SBL1, Delta
S - Flash Throughput, 1969 KB/s  (563587 Bytes,  286173 us)
S - DDR Frequency, 672 MHz


U-Boot 2017.07-RELEASE-g39cabb9bf3 (May 24 2018 - 14:07:32 -0700)

DRAM:  242 MiB
machid : 0x8010001
Product: meraki_Stinkbug
NAND:  ONFI device found
ID = 1d80f101
Vendor = 1
Device = f1
128 MiB
Using default environment

In:    serial
Out:   serial
Err:   serial
machid: 8010001
ubi0: attaching mtd1
ubi0: scanning is finished
ubi0: attached mtd1 (name "mtd=0", size 112 MiB)
ubi0: PEB size: 131072 bytes (128 KiB), LEB size: 126976 bytes
ubi0: min./max. I/O unit sizes: 2048/2048, sub-page size 2048
ubi0: VID header offset: 2048 (aligned 2048), data offset: 4096
ubi0: good PEBs: 896, bad PEBs: 0, corrupted PEBs: 0
ubi0: user volume: 5, internal volumes: 1, max. volumes count: 128
ubi0: max/mean erase counter: 124/52, WL threshold: 4096, image sequence number: 1218587189
ubi0: available PEBs: 337, total reserved PEBs: 559, PEBs reserved for bad PEB handling: 20
Read 0 bytes from volume part.safe to 84000000
No size specified -> Using max size (16584704)
Wrong Image Format for bootm command
ERROR: can't get kernel image!
Read 0 bytes from volume part.old to 84000000
No size specified -> Using max size (16547840)
Wrong Image Format for bootm command
ERROR: can't get kernel image!
resetting ...

Note the change in output, the device was previously meraki_Fuzzy_Cricket but is now identified as meraki_Stinkbug, so the EEPROM modification worked. Unfortunately, the format of the FIT images differs between secure boot and non-secure boot, so the device no longer boots the stock firmware.

The MR33 board major number was chosen as u-boot selects config@1 from the FIT image for the MR33, which is the same config index used by default for the Z3. It would be possible to select any of the device models mentioned above that don’t implement secure boot, but doing so would mean u-boot attempts to boot from a different config@ entry.

Since the Z3 and Meraki Go GX20 have secure boot enabled, we cannot replace u-boot or the device will fail to boot.

0x000000000000-0x000000100000 : "sbl1"
0x000000100000-0x000000200000 : "mibib"
0x000000200000-0x000000300000 : "bootconfig"
0x000000300000-0x000000400000 : "qsee"
0x000000400000-0x000000500000 : "qsee_alt"
0x000000500000-0x000000580000 : "cdt"
0x000000580000-0x000000600000 : "cdt_alt"
0x000000600000-0x000000680000 : "ddrparams"
0x000000700000-0x000000900000 : "u-boot"
0x000000900000-0x000000b00000 : "u-boot-backup"
0x000000b00000-0x000000b80000 : "ART"
0x000000c00000-0x000007c00000 : "ubi"

However, by modifying the device major number, the (signed) u-boot will now allow booting unsigned payloads. The easiest way to proceed is to then build our own modified u-boot as a FIT image, and put it into the ubivol part.safe. After we do this, the boot process now looks like:

Format: Log Type - Time(microsec) - Message - Optional Info
Log Type: B - Since Boot(Power On Reset),  D - Delta,  S - Statistic
S - QC_IMAGE_VERSION_STRING=BOOT.BF.3.1.1-00096
S - IMAGE_VARIANT_STRING=DAACANAZA
S - OEM_IMAGE_VERSION_STRING=CRM
S - Boot Config, 0x00000025
S - Core 0 Frequency, 0 MHz
B -       261 - PBL, Start
B -      1340 - bootable_media_detect_entry, Start
B -      2615 - bootable_media_detect_success, Start
B -      2629 - elf_loader_entry, Start
B -      7270 - auth_hash_seg_entry, Start
B -   1382352 - auth_hash_seg_exit, Start
B -   1447639 - elf_segs_hash_verify_entry, Start
B -   1569353 - PBL, End
B -   1569377 - SBL1, Start
B -   1658051 - pm_device_init, Start
D -         6 - pm_device_init, Delta
B -   1659570 - boot_flash_init, Start
D -     87594 - boot_flash_init, Delta
B -   1751205 - boot_config_data_table_init, Start
D -     14010 - boot_config_data_table_init, Delta - (419 Bytes)
B -   1767914 - clock_init, Start
D -      7572 - clock_init, Delta
B -   1778887 - CDT version:2,Platform ID:8,Major ID:1,Minor ID:0,Subtype:1
B -   1782376 - sbl1_ddr_set_params, Start
B -   1787361 - cpr_init, Start
D -         2 - cpr_init, Delta
B -   1791851 - Pre_DDR_clock_init, Start
D -         5 - Pre_DDR_clock_init, Delta
D -     13143 - sbl1_ddr_set_params, Delta
B -   1805130 - pm_driver_init, Start
D -         2 - pm_driver_init, Delta
B -   1876340 - sbl1_wait_for_ddr_training, Start
D -        27 - sbl1_wait_for_ddr_training, Delta
B -   1893870 - Image Load, Start
D -   1312117 - QSEE Image Loaded, Delta - (268504 Bytes)
B -   3206486 - Image Load, Start
D -      2118 - SEC Image Loaded, Delta - (2048 Bytes)
B -   3216578 - Image Load, Start
D -   1308369 - APPSBL Image Loaded, Delta - (292616 Bytes)
B -   4525372 - QSEE Execution, Start
D -        56 - QSEE Execution, Delta
B -   4531549 - SBL1, End
D -   2964253 - SBL1, Delta
S - Flash Throughput, 1969 KB/s  (563587 Bytes,  286154 us)
S - DDR Frequency, 672 MHz


U-Boot 2017.07-RELEASE-g39cabb9bf3 (May 24 2018 - 14:07:32 -0700)

DRAM:  242 MiB
machid : 0x8010001
Product: meraki_Stinkbug
NAND:  ONFI device found
ID = 1d80f101
Vendor = 1
Device = f1
128 MiB
Using default environment

In:    serial
Out:   serial
Err:   serial
machid: 8010001
ubi0: attaching mtd1
ubi0: scanning is finished
ubi0: attached mtd1 (name "mtd=0", size 112 MiB)
ubi0: PEB size: 131072 bytes (128 KiB), LEB size: 126976 bytes
ubi0: min./max. I/O unit sizes: 2048/2048, sub-page size 2048
ubi0: VID header offset: 2048 (aligned 2048), data offset: 4096
ubi0: good PEBs: 896, bad PEBs: 0, corrupted PEBs: 0
ubi0: user volume: 5, internal volumes: 1, max. volumes count: 128
ubi0: max/mean erase counter: 214/99, WL threshold: 4096, image sequence number: 2086049366
ubi0: available PEBs: 0, total reserved PEBs: 896, PEBs reserved for bad PEB handling: 20
Read 0 bytes from volume part.safe to 84000000
No size specified -> Using max size (507904)
## Loading kernel from FIT Image at 84000000 ...
   Using 'config@1' configuration
   Trying 'kernel-1' kernel subimage
     Description:  Kernel
     Type:         Kernel Image
     Compression:  uncompressed
     Data Start:   0x840000d0
     Data Size:    381216 Bytes = 372.3 KiB
     Architecture: ARM
     OS:           Linux
     Load Address: 0x87300000
     Entry Point:  0x87300000
     Hash algo:    sha1
     Hash value:   89c319b76738e71147631f87311fc8f31e8ac8aa
   Verifying Hash Integrity ... sha1+ OK
## Loading fdt from FIT Image at 84000000 ...
   Using 'config@1' configuration
   Trying 'fdt-1' fdt subimage
     Description:  Insect DTB
     Type:         Flat Device Tree
     Compression:  uncompressed
     Data Start:   0x8405d2d4
     Data Size:    235 Bytes = 235 Bytes
     Architecture: ARM
     Hash algo:    sha1
     Hash value:   86c47255d86f2bd6301e7772ca65b3548493875b
   Verifying Hash Integrity ... sha1+ OK
   Booting using the fdt blob at 0x8405d2d4
   Loading Kernel Image ... OK
   Using Device Tree in place at 8405d2d4, end 840603be
Using machid 0x8010001 from environment

Starting kernel ...



U-Boot 2017.07-DEVEL (Apr 01 2024 - 14:50:01 +0000)

DRAM:  242 MiB
machid : 0x8010001
Product: meraki_Stinkbug
NAND:  ONFI device found
ID = 1d80f101
Vendor = 1
Device = f1
128 MiB
Using default environment

In:    serial
Out:   serial
Err:   serial
machid: 8010001
Net:   MAC0 addr:e0:cb:bc:11:22:33
PHY ID1: 0x4d
PHY ID2: 0xd0b1
ipq40xx_ess_sw_init done
eth0
Autoboot in 5 seconds
FUZZY CRICKET #

We could proceed by putting OpenWrt directly in part.safe however if we ever had a bad flash there would be no way to recover except by manually reflashing NAND. Chain loading a u-boot build does delay booting by a few seconds, but offers a way to recover from a bad flash with just UART and tftpboot.

With the modified u-boot on flash, we can now tftpboot the OpenWrt initramfs and install it to flash with sysupgrade.


“Meraki Go offers simple networks for serious business.”

The Meraki Go GX20 uses the same PCB as the Z3, but without the WiFi radios populated (the lack of WiFi means you won’t find the GX20 in the FCC database). So, does the above technique work for the Meraki Go GX20?

U-Boot 2012.07-g03cdfe19e00f [local,local] (Aug 29 2017 - 11:59:45)

DRAM:  498 MiB
machid : 0x8010001
ERROR: Unknown board
NAND:  ONFI device found
ID = 1d80f101
Vendor = 1
Device = f1
128 MiB
Using default environment

In:    serial
Out:   serial
Err:   serial
machid: 8010001
Hit any key to stop autoboot:  0
Creating 1 MTD partitions on "nand0":
0x000000c00000-0x000007f00000 : "mtd=0"
UBI: attaching mtd1 to ubi0
UBI: physical eraseblock size:   131072 bytes (128 KiB)
UBI: logical eraseblock size:    126976 bytes
UBI: smallest flash I/O unit:    2048
UBI: VID header offset:          2048 (aligned 2048)
UBI: data offset:                4096
UBI: attached mtd1 to ubi0
UBI: MTD device name:            "mtd=0"
UBI: MTD device size:            115 MiB
UBI: number of good PEBs:        920
UBI: number of bad PEBs:         0
UBI: max. allowed volumes:       128
UBI: wear-leveling threshold:    4096
UBI: number of internal volumes: 1
UBI: number of user volumes:     7
UBI: available PEBs:             364
UBI: total number of reserved PEBs: 556
UBI: number of PEBs reserved for bad PEB handling: 9
UBI: max/mean erase counter: 2/0
Read 0 bytes from volume part.safe to 84000000
No size specified -> Using max size (3284992)
## Booting kernel from FIT Image at 84000000 ...
   Using 'config@1' configuration
   Verifying Hash Integrity ... sha384,secp384r1:wired-arm-qca-RT-SECP384R1_1-rel+ OK
   Trying 'kernel@1' kernel subimage

No, unfortunately for u-boot 2012.07 which shipped on the Meraki Go GX20 I purchased, changing the device major results in ERROR: Unknown board and we can see from the sha384,secp384r1:wired-arm-qca-RT-SECP384R1_1-rel+ OK output that signature checking is still enabled.

However, the Meraki Go GX20 is a very similar device to the Z3. Is it possible that there is another way?

Examining the u-boot region of the Meraki Go GX20 with strings reveals the following:

California1
San Francisco
Cisco Meraki1%0#
fuzzy-cricket attestation ca1
Product Security0
170728184745Z
170827184745Z0

So the u-boot binary on the Meraki Go GX20 is signed with the same signing cert as u-boot on the Z3. What if we replace u-boot on the Meraki Go GX20 with the signed u-boot from the Z3 dump?

U-Boot 2017.07-RELEASE-g39cabb9bf3 (May 24 2018 - 14:07:32 -0700)

DRAM:  242 MiB
machid : 0x8010001
Product: meraki_Stinkbug
NAND:  ONFI device found
ID = 1d80f101
Vendor = 1
Device = f1
128 MiB
Using default environment

Bingo. And since the Z3 2017.07 u-boot release contains the secure boot downgrade bug, we can exploit it on the Meraki Go GX20 as well.

I am curious to see how u-boot 2012.07 on the Meraki Go GX20 implements signature validation, since unlike u-boot 2017.07 changing the device major does not disable signature verification of the payload.

Unfortunately, this is not possible. At the time of writing it has been 16 months since I requested the GPL source code for the Meraki Go GX20 and Meraki have yet to provide the u-boot source code for the device.


The EU and other jurisdictions claim to take the right to repair and e-waste seriously, but their actions thus far have ignored the elephant in the room. Many of the devices being sold in the past 5 years come with secure boot enabled, and thus are locked to running the OEM’s software.

In the case of the Meraki prodcuts, you have purchased the device and Meraki are selling a license only for the cloud management and hardware replacement service. But, if you stop paying Meraki, buy a “claimed” second-hand device, or Meraki discontinue support for the device you now have a worthless brick.

“Cisco Meraki may find it necessary to discontinue products for a number of reasons, including product line enhancements, market demand, technology innovation, or if the product simply matures over time and needs to be replaced by something functionally richer.”

To be clear, you are not leasing the hardware, and this is not “Hardware as a Service” you have bought the device in question and own it.

There are hundreds of SMD resistors on the device’s PCB. Any one of them could be tied to a GPIO which is polled by the SoC BootROM to enable or disable secure boot. Incorporating this into the hardware design would cost nothing, and would allow consumers the choice to re-use devices that were resold without proper decommissioning, from companies that were liquidated, from devices that were recycled as e-waste, or from devices where the manufacturer has decided to “maximize shareholder value” and end support. Physically opening the device and adding or removing a component to disable secure boot does not compromise user security in any way.

Routers like the Z3 and Meraki Go GX20 may appear to the untrained eye to be highly specialized devices, but in reality they are very similar to a Raspberry Pi with additional network interfaces, e.g. they are general purpose embedded computers.

It is past time that regulators start considering the user-harmful practices of companies in enforcing secure boot on these devices with no way for consumers to exercise their rights. This anti-consumer behaviour increases costs and creates more e-waste, solely for the benefit of the bottom line of companies that are (often) not even based in the same economic area. We should not, and cannot, accept such a status quo. There is no reason for these devices to become e-waste!


Impact: Device owners with physical access to their device and the appropriate hardware flashing tools can install a custom firmware.

Could this be accomplished remotely? No, not unless it is chained with another exploit providing Remote Code Execution (RCE) on the device. Anyone with an RCE on Meraki devices would not waste it on flashing a FOSS firmware, they would use it to build a botnet.

Can Meraki patch this? Yes, it is trivial for them to add a check in u-boot 2017.07 whether secure boot is enabled and enforce signature verification. Patching this is left as an exercise to the reader.

Will they patch it? Meraki announced the EoL of the Z3 in March 2024. They have previously been known to update u-boot to brick devices when users attempt to flash a custom firmware. It remains to be seen if they will continue this anti-consumer practice on the Z3 and Meraki Go GX20.

Responsible disclosure: Yes, this has been responsibly disclosed to you, the device owner.

Fujitsu TX1320 M3

As with many purchases, this one began with a thread on ServeTheHome. Given my previous work on the Fujitsu TX140 S2 motherboard, how could I refuse the offer of a barebones Fujitsu TX1320 M3 for only 29€?

Fujitsu TX1320 M3

The TX1320 M3 is slightly too tall to fit within 2U, measuring at 398x340x98mm. Those wishing to place it on a rack shelf, you will need to budget 2.5U for the unit.


My TX1320 M3 came with BIOS 1.19.0, which does not support Xeon v5 CPUs. Not knowing the iRMC S4 password, it was not possible to flash a newer BIOS via iRMC. But, the SOIC8 that contains the BIOS was easily located (on the front of the motherboard, near to the INTR header and internal USB3 port).

The BIOS update D3373-B1.ROM file provided by Fujitsu is exactly 8388608 bytes. So, updating the BIOS requires only a ch341a programmer and a SOIC8 chip-clip. Dump the current BIOS, replace the last 8MB with D3373-B1.ROM downloaded from Fujitsu, and reflash to have support for a Xeon v5 CPU:

$ sudo flashrom -p ch341a_spi -c "N25Q128..3E" -r TX1320.bin
$ dd if=D3373-B1.ROM of=TX1320.bin bs=1M seek=8
$ sudo flashrom -p ch341a_spi -c "N25Q128..3E" -w TX1320.bin

After booting Linux, it is easy to reset the iRMC S4 password using ipmitool.


iRMC S4 on the TX1320 M3 motherboard has the UART routed to an unpopulated header and has the same parameters as the TX140 S2: 38400n8.

The pin beside UART Tx is not a ground, it’s a GPIO from the Pilot III. Pin 3 of the INTR header (closest to the USB port) is GND and fits a 2.54mm prototype wire. The iRMC S4 bootlog is available in this gist.

UART access does not instantly make you god of iRMC S4. After booting, remmman is running on the uart, so without knowing a valid username and password, you can’t gain access. u-boot is built with the dhcp and tftpboot commands, so you could potentially boot a modified image from the network that would give you root access (untested).

The GPL components of iRMC S4 are available on GitHub.


The Fujitsu 250W power supply (Model: CPB09-045E, S26113-E564-V71-01) has the dimensions 93x71x187mm, which should allow for the installation of a TFX PSU (85x65x175mm) in the chassis with minimal modification. Being a Fujitsu workstation, the power supply pin-out is identical to the TX140 S2.

The SATA power connector on the motherboard is a Molex 5559 2×4 with the following pinout (top view, looking toward PCB):

GND* 5V GND 12V*
GND* 5V* GND* 12V*

The 2.5″ backplane (Fujitsu P/N: A3C40176096) has a Molex 5559 2×2 power connector with the following pinout (top view, looking toward PCB):

GND GND*
12V 12V*

Note that only the the conductors marked with * are populated in the Fujitsu wiring harness.


The 16 pin front-panel header has the same pinout as the TX140 S2:

As expected, we find two I2C EEPROMs (TSSOP8) and a Texas Instruments LM75 temperature sensor (VSSOP8) on the front-panel PCB (Fujitsu P/N: A3C40167342).

TX1320 M3 front-panel PCB


The TX1320 M3 noise profile is excellent, it is slightly audible during POST, but inaudible once booted into the OS (Linux).

The CPU cooler (V26898-B1003-V1/A3C40175673/A3C40175674) is a small dual heat pipe design with two towers with a fan (Delta AFB0712HHB) sandwiched between. The hot-swap 2.5″ drive bay has a single fan AVC DA07020B12M installed in a toolless plastic bracket. The design expects one fan per backplane, so if you install a second backplane to expand to eight 2.5″ drives, a second fan and bracket may be necessary.


lspci output:

00:00.0 Host bridge: Intel Corporation Xeon E3-1200 v5/E3-1500 v5/6th Gen Core Processor Host Bridge/DRAM Registers (rev 07)
00:14.0 USB controller: Intel Corporation 100 Series/C230 Series Chipset Family USB 3.0 xHCI Controller (rev 31)
00:14.2 Signal processing controller: Intel Corporation 100 Series/C230 Series Chipset Family Thermal Subsystem (rev 31)
00:16.0 Communication controller: Intel Corporation 100 Series/C230 Series Chipset Family MEI Controller #1 (rev 31)
00:16.1 Communication controller: Intel Corporation 100 Series/C230 Series Chipset Family MEI Controller #2 (rev 31)
00:17.0 SATA controller: Intel Corporation Q170/Q150/B150/H170/H110/Z170/CM236 Chipset SATA Controller [AHCI Mode] (rev 31)
00:1c.0 PCI bridge: Intel Corporation 100 Series/C230 Series Chipset Family PCI Express Root Port #5 (rev f1)
00:1c.5 PCI bridge: Intel Corporation 100 Series/C230 Series Chipset Family PCI Express Root Port #6 (rev f1)
00:1c.6 PCI bridge: Intel Corporation 100 Series/C230 Series Chipset Family PCI Express Root Port #7 (rev f1)
00:1f.0 ISA bridge: Intel Corporation C236 Chipset LPC/eSPI Controller (rev 31)
00:1f.2 Memory controller: Intel Corporation 100 Series/C230 Series Chipset Family Power Management Controller (rev 31)
00:1f.4 SMBus: Intel Corporation 100 Series/C230 Series Chipset Family SMBus (rev 31)
01:00.0 VGA compatible controller: Matrox Electronics Systems Ltd. MGA G200e [Pilot] ServerEngines (SEP1) (rev 05)
01:00.1 Co-processor: Emulex Corporation ServerView iRMC HTI
02:00.0 Ethernet controller: Intel Corporation I210 Gigabit Network Connection (rev 03)
03:00.0 Ethernet controller: Intel Corporation I210 Gigabit Network Connection (rev 03)

For those looking to install 32GB unbuffered DIMMs, unfortunately there is no support for that on the TX1320 M3. The capacity is recognized, and I was able to open UEFI Setup, as well as boot the Arch Linux installer, however if the CPU addresses beyond 16GB a non-maskable interrupt (NMI) is generated and the system halts.


The TX1320 M3 is not a compelling upgrade for those who already have something like the TX140 S2: performance and power consumption are quite similar between the two generations. Only if you need more bandwidth than PCIe 2.0 offers would upgrading to the TX1320 M3 (PCIe 3.0 x8/x8/x4/x1) make sense over the TX140 S2 (PCIe 3.0 x8, PCIe 2.0 x8/x4/x1).

Power consumption:

  • 4W when powered off (iRMC powered, management NIC connected at 1000MBit)
  • 16W when idle in Linux (Xeon E3-1220 v5, 2x16GB DIMMs, 16GB boot SSD, 1xEthernet, iRMC management Ethernet)

If you do not already have a Fujitsu and are interested in a low-power server, then the TX1320 M3 (or TX1330 M2 which also uses the D3373 motherboard) is a good choice. Of course, iRMC S4 is onboard, so with a little effort you can have an Advanced license 😉

Given that the D3373 motherboard is mATX compatible, I consider it a worthwhile purchase for the chassis alone. Note that the IO shield is integrated into the chassis, so you would need to remove this with a rotary saw to install another motherboard.

D3373-B1.ROM: 8cf71990597df6561b9c7c3e2c1b7e4c4b373a7a63271ba1a93bab9f50e0903f


(The following content was added in January 2024)

If you want to expand the capacity of the system to 8 drives, you will need to purchase another backplane as the one shipped in the system only supports four 2.5″ drives.

I can confirm that the Fujitsu 8 port backplane (P/N: A3C40173252) fits mechanically with zero modifications.

The 8 port backplane can be found for the same price (29€) as most sellers are asking for the 4 port model, and simplifies the cabling. You only need one power cable and one I2C cable with the 8 port backplane, although two of each cable are provided in the TX1320. You will need an additional SATA/SAS controller though, as the D3373 has only one Mini-SAS HD connector for up to 4 devices.

You can use the original Molex 5559 power cable (2×2 positions; 2 populated) from the TX1320 with the 8 port backplane without modification; below is the pinout of the original power cable (2×3 positions; 6 populated) marked on the PCB as X40:

A3C40173252 SAS backplane power receptacle (X40)

Older revisions of the backplane also have a Micro-Fit 3.0 (Molex 430450412) populated on the PCB as X17 which offers 12V and 5V voltage outputs. Note that the below pinout is for the cable, not the X17 receptacle:

A3C40173252 X17 cable pinout

Note that you still need to provide cooling for the additional drive bays, which cannot easily be done as the plastic fan duct has no official part number (as noted by Artur in the comments).

Meraki MX84 overview

I became aware of the Meraki MX84 from Lathe Abusaid’s blog post about tearing down the hardware. After setting up an eBay alert and waiting, I finally won a job lot which included an MX84.


Here is a quick summary of the MX84 specs:

  • Intel Atom C2358 CPU (2C/2T, 1.74GHz)
  • 4GB DDR3 ECC RAM (H5TC4G83CFR-PBA)
  • Internal SATA port (1TB Western Digital Green)
  • External USB2.0 port
  • 13 Network interfaces (Vitesse VSC7425: 11 Gigabit Ethernet, 2 SFP)
  • 16MB SPI flash, 1GB NAND flash (Phison PS2251, USB on motherboard)
  • Fanless
  • Open frame 12V 2.5A power supply

The device runs Linux 3.18.131.

00:00.0 Host bridge: Intel Corporation Atom processor C2000 SoC Transaction Router (rev 02)
00:01.0 PCI bridge: Intel Corporation Atom processor C2000 PCIe Root Port 1 (rev 02)
00:03.0 PCI bridge: Intel Corporation Atom processor C2000 PCIe Root Port 3 (rev 02)
00:0b.0 Co-processor: Intel Corporation Atom processor C2000 QAT (rev 02)
00:0e.0 Host bridge: Intel Corporation Atom processor C2000 RAS (rev 02)
00:0f.0 IOMMU: Intel Corporation Atom processor C2000 RCEC (rev 02)
00:13.0 System peripheral: Intel Corporation Atom processor C2000 SMBus 2.0 (rev 02)
00:14.0 Ethernet controller: Intel Corporation Ethernet Connection I354 1.0 GbE Backplane (rev 03)
00:14.1 Ethernet controller: Intel Corporation Ethernet Connection I354 1.0 GbE Backplane (rev 03)
00:14.2 Ethernet controller: Intel Corporation Ethernet Connection I354 1.0 GbE Backplane (rev 03)
00:14.3 Ethernet controller: Intel Corporation Ethernet Connection I354 1.0 GbE Backplane (rev 03)
00:16.0 USB controller: Intel Corporation Atom processor C2000 USB Enhanced Host Controller (rev 02)
00:17.0 SATA controller: Intel Corporation Atom processor C2000 AHCI SATA2 Controller (rev 02)
00:18.0 SATA controller: Intel Corporation Atom processor C2000 AHCI SATA3 Controller (rev 02)
00:1f.0 ISA bridge: Intel Corporation Atom processor C2000 PCU (rev 02)
00:1f.3 SMBus: Intel Corporation Atom processor C2000 PCU SMBus (rev 02)

The MX84 uses coreboot as the bootloader (coreboot-af6fa06-dirty-Liteon_GRM1001_MFG_v4.0.0; bootlog) and the ROM has the following layout:

00000000:00010000 reserved
00010000:0070ffff bk1
00710000:00dfffff bk2
00e00000:00ffffff coreboot

The cbfs contains the following:

FMAP REGION: COREBOOT
mx84.bin: 16384 kB, bootblocksize 1024, romsize 16777216, offset 0xe00000
alignment: 64 bytes, architecture: x86

Name                           Offset     Type           Size   Comp
cmos_layout.bin                0xe00000   cmos_layout      1352 none
fallback/romstage              0xe00580   (unknown)       25820 none
fallback/ramstage              0xe06ac0   (unknown)       61965 none
fallback/payload               0xe15d40   simple elf      20349 none
config                         0xe1ad00   raw              4310 none
revision                       0xe1be00   raw               712 none
(empty)                        0xe1c100   null          1261208 none
mrc.cache                      0xf4ffc0   mrc_cache       65536 none
cpu_microcode_blob.bin         0xf60000   microcode       84992 none
(empty)                        0xf74c40   null            45912 none
fsp.bin                        0xf7ffc0   spd            389120 none
(empty)                        0xfdf000   null           134040 none

coreboot was built with an ELF payload (miles) which by default loads and jumps into the bootkernel FIT image (dts here) located at 0x10000. A secondary bootkernel exists on flash at offset 0x710000.


Let us revisit those Intel I354 interfaces. As a networking appliance, the MX84 has a lot of network interfaces.

There are 13 network interfaces on the front (Management, Internet 1 & 2, Ethernet 3-10, and 2 SFP cages) so there should be a switch inside the MX84 or we would expect to see more than four interfaces in lspci.

In this case, the switch is the VSC7425, and even if you use the 3.18.131 kernel from Meraki, you won’t have any connectivity because all four of the I354 interfaces connect directly to the VSC7425

The stock Meraki firmware uses a binary called vtss_poca_d to initialise and configure the VSC7425, which does so using a proprietary Vitesse framework (PDF).

vtss_poca_d is a static binary, so could we use it with a newer kernel such as 5.10.146 found in OpenWrt 22.03?

$ vtss_poca_d
mdio_write16: SIOCSMIIREG on eth0 phy:0 reg:0 failed: Not supported
mdio_write16: SIOCSMIIREG on eth0 phy:0 reg:1 failed: Not supported
mdio_write16: SIOCSMIIREG on eth0 phy:0 reg:2 failed: Not supported
mdio_write16: SIOCSMIIREG on eth0 phy:0 reg:3 failed: Not supported
mdio_write16: SIOCSMIIREG on eth0 phy:0 reg:0 failed: Not supported
mdio_write16: SIOCSMIIREG on eth0 phy:0 reg:1 failed: Not supported
mdio_write16: SIOCSMIIREG on eth0 phy:0 reg:2 failed: Not supported
mdio_write16: SIOCSMIIREG on eth0 phy:0 reg:3 failed: Not supported
mdio_write16: SIOCSMIIREG on eth0 phy:0 reg:0 failed: Not supported
mdio_write16: SIOCSMIIREG on eth0 phy:0 reg:1 failed: Not supported
mdio_read16: SIOCGMIIREG on eth0 phy:0 reg:2 failed: Not supported
mdio_read16: SIOCGMIIREG on eth0 phy:0 reg:3 failed: Not supported
mdio_read16: SIOCGMIIREG on eth0 phy:0 reg:2 failed: Not supported
mdio_read16: SIOCGMIIREG on eth0 phy:0 reg:3 failed: Not supported

Nope! As such, it is very unlikely this device will ever be supported by OpenWrt.


You may have noticed that the MX84 is based on the Atom C2000, a CPU which suffers from the AVR54 errata. When I first received my MX84, there was no output on UART and the power consumption was a suspiciously consistent 6W, all the hallmarks of a device dead from AVR54. There are numerous instructions for how to revive a Synology NAS with a dead Atom, but no such instructions exist for the MX84.

Fortunately for me, there was a photo from Lathe Abusaid’s blog post which provided a crucial hint. It appears that the MX84 unit photographed in their teardown includes a 100 Ohm resistor between pins 1 (LPC clock) and 9 (3.3V) of header J7.

This solution appears to have been chosen because it was the most convenient for Cisco, however note that pin 9 of J7 appears to be a GPIO output which, depending on the coreboot payload, may not always be active high. I would suggest instead soldering to pin 8 of the unpopulated SOIC8 nearby (U47), which will provide 3.3V regardless of the payload GPIO configuration.

That being said, here is a photo of the resistor fix to J7 that I just advised against doing, taken before realizing pin 9 was a GPIO

After soldering the pull-up resistor, the LPC clock is back on pin 1 of J7:

Oscilloscope output of the LPC clock on pin 1 of J7 after adding a 100Ω resistor


There is an unpopulated footprint for a Micro-USB port on the left side of the motherboard. By default the D-/D+ are not connected, as the 0 Ohm resistors are unpopulated (they are instead populated on R467/R468 connecting the Phison ps2303q to the Atom CPU). I believe this port was used during development to easily swap the USB drive connected to the SoC.


One question I had about the MX84 was: why coreboot? It seems that this design is based on Intel’s “Mohon Peak” reference platform. From the Intel customer reference board (CRB) documentation (PDF):

The embedded firmware ecosystem has developed an example boot loader solution
for the CRB that uses the FSP kit. This solution is based on the open source Coreboot
project at coreboot.org. While Intel does not endorse or support boot loader solutions
based on the Coreboot project, the example Coreboot-based boot loader provides a
good teaching model for how to integrate the Intel FSP into a complete boot loader
solution.

Now it is clear why Meraki chose to use coreboot, that is simply the bootloader reference provided by Intel for Mohon Peak. Other manufacturers who made Atom C2000 products also used coreboot (such as the VeloCloud 520-AC).

Meraki provided the coreboot source code in December 2022, after a delay of more than 12 months. The coreboot source code for the MX84 is available on GitHub.


Meraki hardware commanding the premium that it does, if you are considering buying an MX84: don’t. The VeloCloud 520-AC (C2358) and 540-AC (C2558) are available for ~$30 on eBay and have the C0 revision which doesn’t suffer from AVR54.

If you already own an MX84 and want to poke around, here is a buildroot based firmware that you flash to SPI. The firmware will boot, initialize the switch, DHCP, and start an SSH server (the root password is the device serial without hyphens). Note that it is initramfs based, so no changes are persisted.

Caveat emptor: VeloCloud devices have an issue with the igb/I354 compatibility, meaning that only the two SFP cages are functional. However, that is two more interfaces than you will get from the MX84 (zero) with any other kernel.