JRP Innovations
Posted on
Reverse Engineering

Reverse Engineering a Netgear Wi-Fi 6 Router (Nighthawk RAX10 AX1800)

Author

Reverse engineering is something I have been learning for years, most reverse engineering projects when you're learning end in confusion, destroyed devices, and breaking things to figure them out...

I wish I started my journey with this project! By the end of the first day, I had a root shell, a full extracted firmware filesystem, the stock update image unpacked, the bootloader command list, the actual compressed kernel, several device trees, and a much better sense of what not to type into CFE, (CFE is Broadcom's network device firmware and bootloader for ARM SBCs).

The short version is: this router is a surprisingly capable ARM Linux system wrapped in several layers of Broadcom and Netgear boot infrastructure. Though, there is also no security??

Model caveat: My stock update image is for an RAX10 running firmware V1.0.20.174, but an early CFE boot capture identifies its board ID as RAX60 and looks for RAX60.dtb before falling back to a generic DTB. I have not yet reconciled that mismatch. For now, the claims below are about this captured BCM6755 firmware/platform combination, not necessarily every retail unit with either model number. It seems like the firmware between all RAX models may be the same with actual model-specific data passed in from CFE but that's a lofty claim to make and I'm only making it based on this weird identifier mis-match and the DTB handling behavior we will discuss later.

Netgear RAX10 base

Dissasembly

I started with the obvious steps of taking it apart, there were 3 security screw holding the case together, there is a through-hole design mounting the PCB securely into the case. Once the 3 exterior screws were removed and the case cracked, I immediately noted a 4 pin header and had a suspect for serial connection. There was 3 other hardware points with potential for debug/extensibility, though no more on that for now.

The DRAM (DDR3), the main SBC CPU package, the ethernet controller were all located under a large heatsink. The heatsink was easy to remove with 4 chunky Phillips head screws holding it down exposing EMI shielding with thermal pads for the CPU & Ethernet Controller. Fortunately the EMI shielding was pressure fit and easy to remove without severely damaging. There was an additional chip with it's own EMI shielding directly adjacent the main SBC package, (I suspect this to be radio related though I did not note down the IC label as I already identified my main points of interest for now, this will probably come back to bite me).

Hardware packages:

  • Broadcom BCM6755A1KFEBG, SoC
  • Broadcom BCM531340KFBG, Ethernet Controller
  • Winbond W634GU6NB-12, 4Gbit(512MB) DDR3L RAM RAX10_Main_CPU_BCM6755RAX10_EthController_BCM531340RAX10_DRAM_W634GU6NB

Seperate from the heatshield, we had the 256MB flash. * Macronix 2Gbit(256MB) NAND MX30LF2G18AC RAX10_NAND_FLASH_MX30LF2G18AC

That means this is not a toy microcontroller router. It is a 1.5 GHz quad-core ARMv7 Linux machine with half a gigabyte of RAM, hardware packet acceleration, two PCIe Wi-Fi radios, and a slightly cursed 256 MB NAND-backed appliance filesystem; (The NAND is being managed/controlled by the host directly).

At this point I was ready to see if what I suspected to be a serial header, really was simply a serial header. I got my multimeter and identified voltage as 3.3v, I checked to see if there was any visible voltage change from transmission drops on any of the pins, quickly I identified pin 1 for TX this way (white), I connected it to a standard serial reader after confirming pin out as p0(3.3v) P1(TX) P2(RX) P3(GND), I connected TX, RX, & GND to my Pine64 serial console. RAX10_SerialPort_UART

I rebooted and opened a standard serial terminal over the USB COM port @ 115200 Baud (115200 8N1), this caused a flood of coherent serial output leading me to re-assemble the EMI shielding and heatsink before the device got too hot.

The running hardware

The bootloader identified the platform as:

Chip ID: BCM6755_A1
ARM Cortex A7 Quad Core: 1500MHz
Total Memory: 536870912 bytes (512MB)
NAND flash: 256MB

The flash is SLC NAND with:

Page size:        2048 bytes
OOB/spare area:     64 bytes
Erase block:    131072 bytes / 128 KiB
ECC:            BCH-4

The boot chain

The serial console exposed the full chain:

Early bootstrap / BTRM
→ Broadcom CFE bootloader
→ CFE reads NAND and locates vmlinux.lz
→ CFE decompresses the Linux kernel
→ CFE loads a DTB
→ CFE patches boot arguments and NVRAM into the DTB
→ Linux boots
→ Linux mounts the SquashFS root filesystem

Identifying CFE lines were:

ubi_find_file: got vmlinux.lz  size 2756881
Decompression LZMA Image OK!
Entry at 0x00018000

cfe_fs_fetch_file: file RAX60.dtb not found
ubi_find_file: got 947622.dtb  size 4540

Setting ROOTFS: root=/dev/ubiblock0_0 ubi.mtd=0 ubi.block=0,0 rootfstype=squashfs
CFE appending "isolcpus=3" from PROFILE to boot cmdline
Appending CFE version to dtb
Appending NVRAM to dtb

This immediately explained something that confused me at first: the kernel is not stored in /boot, and it is not inside the root SquashFS filesystem. This brings me new questions though like why is the firmware expecting a different DTB than the one it provides? Why does it need to apppend the NVRAM? We find out later there is a mismatch between the DTB itself and the actual amount of memory installed, this is another thing that the CFE environment appears to be configuring. The verbosity of the serial output will help later though for seperating different firmware pieces with 0 guesswork.

Now we know CFE has its own tiny file store inside the firmware UBI image for boot files. It pulls the compressed kernel and DTB from there before Linux exists. Linux then mounts a separate SquashFS volume as /.

Storage: raw NAND is not a disk

The router’s storage stack is:

Raw NAND flash
→ MTD partitions
→ UBI
→ UBI volumes
→ either SquashFS or UBIFS

The important part:

mtd0 rootfs
→ UBI
→ ubi0:0
→ ubiblock0_0
→ SquashFS
→ immutable Linux root filesystem

mtd2 data
→ UBI
→ ubi1:0
→ UBIFS
→ mounted writable /data

This is why my initial NAND dump experiments were confusing. Raw NAND partition is not a normal disk image. It has erase blocks, bad blocks, ECC, UBI headers, wear-level metadata, and logical volumes. If a tool skips physical bad blocks while dumping, the resulting file can be perfectly useful as a stream of readable data while no longer lining up as a valid physical UBI image. This is something new to me as most of my prior successful projects with SBCs were not reverse engineered and all had more standard feeling storage stacks.

The router reported bad blocks inside the rootfs region:

nand_flash_read_buf(): Attempt to read bad nand block 480
nand_flash_read_buf(): Attempt to read bad nand block 591
nand_flash_read_buf(): Attempt to read bad nand block 593
nand_flash_read_buf(): Attempt to read bad nand block 792

That was the first major lesson:

A “raw flash dump” is only raw if you understand exactly how the dumper handled bad blocks and OOB data.

The easy firmware dump

The cleanest filesystem-level target was not /dev/mtd0.

It was:

dd if=/dev/ubiblock0_0 bs=1M | nc 10.69.69.120 9000

/dev/ubiblock0_0 is already the UBI volume presented as a block device for SquashFS. Dumping it skips the raw NAND and UBI bookkeeping layers entirely.

The first four bytes of the resulting image were:

68 73 71 73
 h  s  q  s

That is the SquashFS magic.

From there, extraction was straightforward:

unsquashfs -d RAX10-rootfs-extracted RAX10-root.squashfs

The extracted filesystem contains the normal Linux userspace:

/bin
/etc
/lib
/sbin
/usr
/www

That is where the interesting Netgear shell scripts, daemon binaries, web UI pieces, configuration helpers, and Broadcom modules live.

The official firmware image was much cleaner than expected

Running Binwalk on the stock update file showed:

0x00000080  UBI image

The first 128 bytes are a Netgear/DNI update header. Everything after that is a normal UBI image.

After stripping the 128-byte header and inspecting the UBI container, I found four volumes:

rootfs_ubifs       403 erase blocks
METADATA             1 erase block
METADATACOPY         1 erase block
filestruct_full.bin 26 erase blocks

Despite its misleading name, rootfs_ubifs starts with:

68 73 71 73
hsqs

It is a SquashFS image, not a UBIFS filesystem.

The important realization was:

rootfs_ubifs
→ SquashFS userland

filestruct_full.bin
→ CFE boot artifacts:
   kernel
   device trees
   boot metadata

Finding the actual kernel

Binwalk on filestruct_full.bin found:

0x0000001B  Device tree blob, 4540 bytes
0x000011F9  Device tree blob, 4724 bytes
0x0000248F  Device tree blob, 4724 bytes

0x0006C3E9  LZMA compressed data
            compressed size:   2757010 bytes
            uncompressed size: 7756064 bytes

That LZMA stream is the missing bootable kernel payload: vmlinux.lz.

The CFE boot log reported vmlinux.lz as 2,756,881 bytes, while Binwalk identified a 2,757,010-byte LZMA stream. The 129-byte difference is probably file-record or container overhead, but I have not fully decoded the CFE file-store format yet.

The kernel can be carved and decompressed with:

dd if=img-787591378_vol-filestruct_full.bin.ubifs \
   of=vmlinux.lz \
   bs=1 \
   skip=$((0x6C3E9)) \
   count=2757010 \
   status=progress

xz --format=lzma -dc vmlinux.lz > vmlinux
file vmlinux

The DTB was more revealing than expected

The first extracted DTB decompiled successfully:

dtc -I dtb -O dts -o dtb-0.dts dtb-0.dtb

The warnings were style warnings from Broadcom’s old device-tree naming conventions, not a failed decompile.

The DTS describes a generic Broadcom BCM947622 platform:

model = "Broadcom BCM947622";
compatible = "brcm,bcm947622";

It includes:

ARM Cortex-A7 CPUs
PL011 UART at 0xFF812000
NAND controller at 0xFF801800
I2C
PCIe
SDHCI controller
hardware RNG
crypto accelerator
watchdog
thermal support

The most interesting discovery was that the static DTB claims only 64 MB of RAM:

memory {
    reg = <0x00 0x4000000>;
};

But CFE patched the live handoff DTB to 512 MB:

/memory = 0x20000000 bytes @ 0x0

CFE also injected the actual root filesystem location, NVRAM, CFE version, and the isolcpus=3 performance setting.

So the static DTB is not the final board description. CFE turns it into one at boot.

CFE: the bootloader has a loaded gun labeled “e”

Interrupting boot drops into CFE. The discovery command was:

help

The reassuring commands include:

p               Print boot line and board parameters
cfe_version     Show CFE version
fb              Find NAND bad blocks
dn              Dump NAND contents including spare area
rn              Read NAND with ECC disabled
find            Find a string in NAND
comp            Compare NAND blocks
nmrp_flag_show  Show recovery flag
meminfo         Show system memory

The terrifying commands include:

e               Erase NAND flash
w               Write a whole image from the beginning of flash
ws              Write an image previously loaded through Kermit/JTAG
upgrade_cfe     Upgrade CFE
upgrade_image   Write a complete image
i               Erase persistent storage
factory_default Factory reset

The command name e literally means:

Erase NAND flash

Nice to know that the proprietary alternative to U-Boot is designed to be as user friendly as a .50cal rifle with a kink in the barrel.

CFE also exposes recovery hooks:

fw_recovery     start TFTP server
nmrp            start NMRP client

Those are likely the practical recovery path after a normal firmware brick. They are much more useful than attempting a blind raw-NAND restore.

Security state: unsecure does not mean unrestricted

One surprising CFE command was:

otpcfg get mode

Its result:

SoC is in unsecure mode

That is encouraging for experimentation, because the irreversible OTP secure-mode flag does not appear to be set.

It does not prove that CFE will accept arbitrary unsigned images. CFE may still enforce a Netgear header, board ID, checksum, version rule, or signature policy.

Also, this command should never be run casually:

otpcfg set mode secure

That sounds like an irreversible fuse operation, and I have no desire to discover exactly how irreversible.

The router repairs its own board data

One extracted script mirrors boarddata1 to boarddata2.

At boot, the router logged:

Start to handle the backup action, boarddata1 | boarddata2 ....
Both the data of origin and backup partition are good !!

The script checks both copies with dni_block_chksum. If the primary copy is valid and the backup differs or fails validation, the primary overwrites the backup. If the primary is bad and the backup is valid, the backup restores the primary.

The notable detail is that it copies only one NAND erase block:

nanddump ... -l 131072
flash_erase ...
nandwrite ...

That is exactly 128 KiB: one physical erase block.

This strongly suggests that the actual board-data object lives in the first good erase block of each 1 MB board-data partition. The remaining partition space is likely spare capacity or simply reserved by the vendor format.

It also means these partitions are not things to casually erase or overwrite. They likely contain device-specific identity, regional settings, MAC information, and other board configuration.

Where this leaves the project

At the end of day one, the router is no longer a black box.

I have:

✓ Root UART console
✓ Root Linux shell
✓ CFE shell
✓ Stock firmware container decoded
✓ Firmware SquashFS extracted
✓ Kernel location identified
✓ DTBs extracted and decompiled
✓ UBI volume structure mapped
✓ Boot flow understood
✓ A recovery path to investigate
✓ Several dangerous flash-writing helpers identified

Here are some of the things I may look into next:

  1. Dump the runtime DTB from /sys/firmware/fdt and compare it to the static DTB.
  2. Extract and inspect the decompressed kernel.
  3. Build a tiny static ARMv7 binary and run it from the router.
  4. Map every service listening on LAN interfaces before touching vulnerability research.

The interesting long-term idea is a hybrid system:

Stock CFE + vendor kernel/drivers
→ custom lightweight userspace
→ eventually a custom appliance-style firmware image

Ultimately since this device has it's home as a switch on my desk; the real challenge is preserving the vendor-specific pieces that make the switch, Wi-Fi radios, acceleration hardware, LEDs, watchdog, and flash layout behave.

For now, the router remains alive, bootable, and significantly less mysterious.