It was recently brought to my attention that the firmware updates for the Linksys WRT120N were employing some unknown obfuscation. I thought this sounded interesting and decided to take a look.
The latest firmware update for the WRT120N didn’t give me much to work with:
As you can see, there is a small LZMA compressed block of data; this turned out to just be the HTML files for the router’s web interface. The majority of the firmware image is unidentified and very random. With nothing else to go on, curiosity got the best of me and I ordered one (truly, Amazon Prime is not the best thing to ever happen to my bank account).
Looking to get some more insight into the device’s boot process, I started with the serial port:
I’ve talked about serial ports in detail elsewhere, so I won’t dwell on the methods used here. However, with a quick visual inspection and a multimeter it was easy to identify the serial port’s pinout as:
- Pin 2 – RX
- Pin 3 – TX
- Pin 5 – Ground
The serial port runs at 115200 baud and provided some nice debug boot info:
$ sudo miniterm.py /dev/ttyUSB0 115200 --- Miniterm on /dev/ttyUSB0: 115200,8,N,1 --- --- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H --- ======================================================================= Wireless Router WG7005G11-LF-88 Loader v0.03 build Feb 5 2009 15:59:08 Arcadyan Technology Corporation ======================================================================= flash MX25L1605D found. Copying boot params.....DONE Press Space Bar 3 times to enter command mode ... Flash Checking Passed. Unzipping firmware at 0x80002000 ... [ZIP 3] [ZIP 1] done In c_entry() function ... install_exception install exception handler ... install interrupt handler ... ulVal: 0x484fb Set GPIO #11 to OUTPUT Set GPIO #1 to OUTPUT Set GPIO #0 to OUTPUT Set GPIO #6 to INPUT Set GPIO #12 to INPUT Timer 0 is requested ##### _ftext = 0x80002000 ##### _fdata = 0x80447420 ##### __bss_start = 0x804D5B04 ##### end = 0x81869518 ##### Backup Data from 0x80447420 to 0x81871518~0x818FFBFC len 583396 ##### Backup Data completed ##### Backup Data verified [INIT] HardwareStartup .. [INIT] System Log Pool startup ... [INIT] MTinitialize .. CPU Clock 350000000 Hz init_US_counter : time1 = 270713 , time2 = 40272580, diff 40001867 US_counter = 70 cnt1 41254774 cnt2 41256561, diff 1787 Runtime code version: v1.0.04 System startup... [INIT] Memory COLOR 0, 1600000 bytes .. [INIT] Memory COLOR 1, 1048576 bytes .. [INIT] Memory COLOR 2, 2089200 bytes .. [INIT] tcpip_startup .. Data size: 1248266 e89754967e337d9f35e8290e231c9f92 Set flash memory layout to Boot Parameters found !!! Bootcode version: v0.03 Serial number: JUT00L602233 Hardware version: 01A ...
The firmware looked to have been made by Arcadyan, and the ‘Unzipping firmware…’ message was particularly interesting; a bit of Googling turned up this post on reversing Arcadyan firmware obfuscation, though it appears to be different from the obfuscation used by the WRT120N.
The only interaction with the serial port was via the bootloader menu. During bootup you can break into the bootloader menu (press the space bar three times when prompted) and perform a few actions, like erasing flash and setting board options:
Press Space Bar 3 times to enter command mode ...123 Yes, Enter command mode ... [WG7005G11-LF-88 Boot]:? ====================== [U] Upload to Flash [E] Erase Flash [G] Run Runtime Code [A] Set MAC Address [#] Set Serial Number [V] Set Board Version [H] Set Options [P] Print Boot Params [I] Load ART From TFTP  Set SKU Number  Set PIN Number ======================
Unfortunately, the bootloader doesn’t appear to provide any options for dumping the contents of RAM or flash. Although there is a JTAG header on the board, I opted for dumping the flash chip directly since JTAG dumps tend to be slow, and interfacing directly with SPI flash is trivial.
$ sudo spiflash --read=flash.bin --size=$((0x200000)) --verify FT232H Future Technology Devices International, Ltd initialized at 15000000 hertz Reading 2097152 bytes starting at address 0x0...saved to flash.bin. Verifying...success.
The flash chip contains three LZMA compressed blocks and some MIPS code, but the main firmware image is still unknown:
The first two blocks of LZMA compressed data are part of an alternate recovery image, and the MIPS code is the bootloader. Besides some footer data, the rest of the flash chip simply contains a verbatim copy of the firmware update file.
The bootloader, besides being responsible for de-obfuscating and loading the firmware image into memory, contains some interesting tidbits. I’ll skip the boring parts in which I find the bootloader’s load address, manually identify standard C functions, resolve jump table offsets, etc, and get to the good stuff.
First, very early in the boot process, the bootloader checks to see if the reset button has been pressed. If so, it starts up the “Tiny_ETCPIP_Kernel” image, which is the small LZMA-compressed recovery image, complete with a web interface:
This is nice to know; if you ever end up with a bad firmware update, holding the reset button during boot will allow you to un-brick your router.
There is also a hidden administrator mode in the bootloader’s UART menu:
Entering an option of ! will enable “administrator mode”; this unlocks a few other options, including the ability to read and write to memory:
[WG7005G11-LF-88 Boot]:! Enter Administrator Mode ! ====================== [U] Upload to Flash [E] Erase Flash [G] Run Runtime Code [M] Upload to Memory [R] Read from Memory [W] Write to Memory [Y] Go to Memory [A] Set MAC Address [#] Set Serial Number [V] Set Board Version [H] Set Options [P] Print Boot Params [I] Load ART From TFTP  Set SKU Number  Set PIN Number ====================== [WG7005G11-LF-88 Boot]:
The most interesting part of the bootloader, of course, is the code that loads the obfuscated firmware image into memory.
De-obfuscation is performed by the load_os function, which is passed a pointer to the obfuscated image as well as an address where the image should be copied into memory:
The de-obfuscation routine inside load_os is not complicated:
Basically, if the firmware image starts with the bytes 04 01 09 20 (which our obfuscated firmware image does), it enters the de-obfuscation routine which:
- Swaps the two 32-byte blocks of data at offsets 0×04 and 0×68.
- Nibble-swaps the first 32 bytes starting at offset 0×04
- Byte-swaps each of the adjacent 32 bytes starting at offset 0×04
At this point, the data at offset 0×04 contains a valid LZMA header, which is then decompressed.
Implementing a de-obfuscation tool was trivial, and the WRT120N firmware can now be de-obfuscated and de-compressed:
$ ./wrt120n ./firmware/FW_WRT120N_1.0.07.002_US.bin ./deobfuscated.bin Doing block swap... Doing nibble-swap... Doing byte-swap... Saving data to ./deobfuscated.bin... Done!
The de-obfuscation utility can be downloaded here for those interested.