Netgear Bootloader Analysis

This is the first part in a two part blog post discussing the stage 1 and stage 2 bootloaders for a specific netgear device. This first post will describe how I loaded the bootloader into ghidra, and the second post will document more extensively what I found.

I decided recently to look into the stage 1 and stage 2 boot loaders that the Netgear R9000 (x10) router utilizes during the boot process. This bootloader binary does not come bundled with the upgrade firmware and seems to be burned onto SPI ROM at manufacture. If you are interested in following along, the SPI flash can be dumped from an operating system shell using the following command:

$ cat /dev/mtd0 > /tmp/mtd0.bin
$ cd /tmp
$ tftp -p -l mtd0.bin $TFTP_SERVER_IP

Of course, getting an operating system shell is not a trivial task and is left as an exercize for the reader.

My goal is to identify the binary code for the stage 2 bootloader, then load it into Ghidra. Now that we have mtd0.bin, we run binwalk on the blob:

458824        0x70048         uImage header, header size: 64 bytes, header CRC: 0x6E51D086, created: 2016-07-22 05:32:10, image size: 77 bytes, Data Address: 0x0, Entry Point: 0x0, data CRC: 0x3379A803, OS: Firmware, CPU: ARM, image type: Script file, compression type: none, image name: "init_script"
524360        0x80048         Flattened device tree, size: 17996 bytes, version: 17
815700        0xC7254         CRC32 polynomial table, little endian
872762        0xD513A         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/iofic/al_hal_iofic.c
873806        0xD554E         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/udma/al_hal_udma_config.c
874549        0xD5835         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/udma/al_hal_udma_iofic.c
878344        0xD6708         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/include/udma/al_hal_udma.h
878920        0xD6948         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/eth/al_hal_eth_main.c
880620        0xD6FEC         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/eth/al_hal_eth_kr.c
881132        0xD71EC         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/ssm/al_hal_ssm_raid.c
883555        0xD7B63         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/pcie/al_hal_pcie.c
886603        0xD874B         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/pcie/al_hal_pcie_interrupts.c
886825        0xD8829         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/ddr/al_hal_ddr.c
887367        0xD8A47         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/ddr/al_hal_ddr_pmu.c
887611        0xD8B3B         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/pbs/al_hal_muio_mux.c
888107        0xD8D2B         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/pbs/al_hal_spi.c
888767        0xD8FBF         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/pbs/al_hal_nand_dma.c
888932        0xD9064         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/pbs/al_hal_bootstrap.c
889766        0xD93A6         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/pbs/al_hal_i2c.c
890312        0xD95C8         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/pbs/al_hal_addr_map.c
890612        0xD96F4         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/pbs/al_hal_tdm.c
890870        0xD97F6         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/ring/al_hal_pll.c
891316        0xD99B4         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/sys_services/al_hal_timer.c
891994        0xD9C5A         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/sys_fabric/al_hal_iommu.c
892486        0xD9E46         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/drivers/ring/al_hal_cmos.c
894653        0xDA6BD         Unix path: /home/ericwang/git-home/u-boot.git/tools/al_boot_v_1_65_1/src/HAL/services/pcie/al_init_pcie.c
911381        0xDE815         Copyright string: "copyright."
949496        0xE7CF8         Flattened device tree, size: 4445 bytes, version: 17

The first line of the binwalk output shows a uImage header with a type of “script” that is 77 bytes long. Due to its short length, we can be sure this is not the bootloader we are interested in. The additional binwalk results seem to show Unix paths that indicate u-boot is used as the stage 2 bootloader.

Running strings on mtd0.bin reveals that the board is a Anna Purna Labs Alpine board as well as the version of U-Boot:

 ;Annapurna Labs Alpine Dev Board
U-Boot 2015.01-gd836bbb (Jul 22 2016 - 13:32:00)

This U-Boot version string seems to suggest the bootloader was compiled back in 2016, and not updated since. This leads me to believe the bootloader is not ever, or rarely, updated in the field.

I begin reviewing mtd0.bin using the xxd binary, and it seems like there are several components embedded in the blob that are separated by blocks of 0xff bytes. I adapted a script I wrote a while back to split binary data on null characters:

#!/usr/bin/env python3

# The idea behind this file is that many times firmware images are constructed with zero'd out address regions as the delimiter.
# This script will split files in a firmware image when the embedded files are $MAX zero'd bytes in distance from each other.

import sys


print ("Analyzing File: " + FILE)

with open(FILE, 'rb') as f:
    counter = 0
    data =
    start = 0
    tripped = False
    for byte in range(len(data)):
        if data[byte] == 0xff:
            if not tripped:
                counter = counter + 1
                if counter == MAX:
                    tripped = True
                    with open('splitter-%08d-%08d.bin' % (start, byte - start - MAX + 1), 'wb') as wf:
                        wf.write(data[start:(byte - MAX + 1)])
                        print('wrote file. start: %08d. length was %08d' % (start, byte - start - MAX + 1))
                        start = byte + 1
                        counter = 0
                start = byte + 1
            tripped = False
            counter = 0

This split the binary blob up in to a bunch of smaller subdivisions. I grepped for the Unix path containing u-boot from the binwalk results, and ended up utilizing the last chunk that the binary split script generated. This file was named splitter-00589824-00450316.bin. This was the file I ended up loading into Ghidra for analysis.

The first 256 bytes of this file look like this:

  00000000: c79e 0b00 0000 0000 0500 0000 0000 0000  ................
  00000010: 0000 0000 0000 0000 4e2f 4100 0000 0000  ........N/A.....
  00000020: 0000 0000 0000 0000 c0de 0600 0000 0000  ................
  00000030: 0000 1000 0000 0000 0000 1000 0000 0000  ................
  00000040: 0000 0000 f703 0000 be00 00ea 14f0 9fe5  ................
  00000050: 14f0 9fe5 14f0 9fe5 14f0 9fe5 14f0 9fe5  ................
  00000060: 14f0 9fe5 14f0 9fe5 6000 1000 c000 1000  ........`.......
  00000070: 2001 1000 8001 1000 e001 1000 4002 1000   ...........@...
  00000080: a002 1000 efbe adde dec0 ad0b 00f0 20e3  .............. .
  00000090: 00f0 20e3 00f0 20e3 00f0 20e3 00f0 20e3  .. ... ... ... .
  000000a0: 00f0 20e3 00f0 20e3 28d0 1fe5 00e0 8de5  .. ... .(.......
  000000b0: 00e0 4fe1 04e0 8de5 13d0 a0e3 0df0 69e1  ..O...........i.
  000000c0: 0fe0 a0e1 0ef0 b0e1 48d0 4de2 ff1f 8de8  ........H.M.....
  000000d0: 5020 1fe5 0c00 92e8 4800 8de2 3450 8de2  P ......H...4P..
  000000e0: 0e10 a0e1 0f00 85e8 0d00 a0e1 1702 00fa  ................
  000000f0: 00f0 20e3 00f0 20e3 00f0 20e3 00f0 20e3  .. ... ... ... .

Now that I have the bootloader binary identified, I need to figure out the load address (CONFIG_SYS_TEXT_BASE) for the u-boot image. I don’t have console output from the boot process, but I found a post on netgear’s community forum where someone had pasted their NAS boot output. The board seemed similar.

Stage 3 2013.10-alpine_spl-1.49.d-00659-ge082932 (Nov 19 2014 - 16:21:12)

EEPROM Revision ID = 32
Device ID = a212
Device Info: AL21200-1400
Loading DT to 00100000 (16813 bytes)...
Board config ID: Netgear NAS RN20x
SRAM agent up: agent_wakeup v1.49
Loading U-Boot to 00100000 (401888 bytes)...
Executing U-Boot...

U-Boot 2013.10-alpine_db-1.49 (Jan 08 2016 - 10:41:23)


The important output is Loading U-Boot to 00100000. The u-boot version and alpine board revision are slightly older, but its as good a guess as any for a load address. I used the value 0x00100000 as the load address in Ghidra, with a File Offset value of 0x48 since the first 72 bytes of the split image turned out to be a header. Note that this is a proprietary header and thus binwalk doesn’t recognize it.

Using these values and selecting a CPU Architecture of ARMv7 32-bit default little-endian allowed me to successfully load the bootloader in Ghidra.

Once I had the bootloader loaded in Ghidra, I went looking for console output signifier strings. One thing I honed in on pretty quickly was the string NTGR. Like I mentioned above, I don’t have a console set up for this device, but I’m guessing that if you type that string in during the boot process you can break into some sort of undocumented u-boot shell. The community has seen similar “magic strings” on TP-Link devices, using the tpl string. The disassembly between the code hosting the tpl string from the TP-Link bootloader does not look comparable to the code hosting the NTGR string, and I will have to do further analysis to prove this NTGR string is what I think it is.

Other interesting tidbits I gleaned were that the U-Boot image was compiled with support for Secure Boot. Secure Boot is definitely not enabled on this device, however. These seemns to be the capability to set a “passphrase” for the board, though it is unclear whether or not such a feature is utilized.

Stay Tuned for Part 2.