Reversing the WRT120N’s Firmware Obfuscation

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:

Binwalk firmware update analysis

Binwalk firmware update analysis

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

Hardware Analysis


A first glance at the hardware showed that the WRT120N had a Atheros AR7240 SoC, a 2MB SPI flash chip, 32MB of RAM, and what appeared to be some serial and JTAG headers:

WRT120N PCB

WRT120N PCB

Looking to get some more insight into the device’s boot process, I started with the serial port:

UART Header

UART Header

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 
 [1] Set SKU Number 
 [2] 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.

Pretty much anything that can speak SPI can be used to read the flash chip; I used an FTDI C232HM cable and the spiflash.py utility included with libmpsse:

$ 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:

Flash analysis

Flash analysis

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.

Bootloader Analysis


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:

Unzipping Tiny Kernel

Unzipping Tiny Kernel

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:

Hidden bootloader menu

Hidden bootloader 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 
 [1] Set SKU Number 
 [2] 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.

Obfuscation Analysis


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:

load_os(0xBF040000, 0x80002000);

The de-obfuscation routine inside load_os is not complicated:

De-obfuscation routine

De-obfuscation routine

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!
Analysis of de-obfuscated firmware

Analysis of de-obfuscated firmware

The de-obfuscation utility can be downloaded here for those interested.

Bookmark the permalink.

34 Responses to Reversing the WRT120N’s Firmware Obfuscation

  1. Loic says:

    Is there some GPL violations in there? We should sue them

  2. axet says:

    i hardly can call it obfuscation, it is encryption.

  3. none says:

    Zachary Cutlip did a talk about reverse-engineering BT routers that you might like:

    http://baythreat.org/speakers.html#cutlip
    http://www.youtube.com/watch?v=Ogyr6ZUpcpk

  4. Aloysius says:

    Found anything interesting inside?

  5. andrewq says:

    Excellent job and writeup! Thanks!

  6. Evert says:

    Nice post! Could you tell me which program you used to determine the entropy of the bin? And how did you made the picture of the graphical de-obfuscation routine? :)

  7. Michael says:

    How did you get from MIPS assembly to C code with comments? Did you convert it yourself manually in the tool, for better readability in the article?

  8. michael thorne says:

    What’s crazy about this is – how long did it take you to do the RE?
    Why doesn’t the company just plain text the code and build their reputation with in the community. So sad that they “just don’t get it!”

    • chris says:

      AFAIK, most of the boards are carbon copies of reference designs – the companies spend very little on building their own hardware. Most of their own IP lies in the firmware, which is why they go to such pains to keep it closed – low-end consumer hw is very cut-throat.

  9. Pingback: Деобфускация прошивки Linksys WRT120N | XAKEPA.HET

  10. xort says:

    Nice writeup brosive, keep tearing into the guts of those poor bastards hahah

  11. Pingback: Reversing the WRT120N’s Firmware Obfuscation – /dev/ttyS0 | Paul D. Parisi

  12. Pingback: Re-enabling JTAG and Debugging the WRT120N - /dev/ttyS0

  13. Chris says:

    Is the final entropy analysis correct? Is the de-obfuscated image compressed? The entropy image doesn’t seem to be particularly useful if that is the case.

    • Craig says:

      Yes, the final entropy analysis is correct, and the resulting de-obfuscated image is compressed. The de-obfuscation routine is just swapping bytes around to re-construct a valid LZMA compressed file; the decompression can then be performed separately with standard LZMA utilities.

      The entropy image serves two purposes:

      1) For purposes of the article, it provides a frame of reference which the reader can compare to the first (obfuscated) entropy analysis.

      2) LZMA signature analysis is particularly prone to false positives; showing the signature analysis results on top of the entropy graph helps confirm that both LZMA results are likely valid, and that there does not appear to be anything else of interest in the firmware image that the signature analysis missed.

  14. Pingback: Hacking the Linksys WRT120N

  15. Pingback: Hacking the Linksys WRT120N | Hack The Planet

  16. Edward says:

    I have one of those boxes. What add on is useful? Too little RAM – can the chip be replaced?

  17. nobody says:

    I don’t use IDA’s graph mode much at all, but now I’m curious–how do you go about collapsing the disassembly inside a node and replacing it with some comments of your choosing?

  18. Pingback: Cracking Linksys “Encryption” - /dev/ttyS0

  19. Pingback: WRT120N fprintf Stack Overflow - /dev/ttyS0

  20. Pingback: Reverzné inžinierstvo: nástroj pre analýzu obfuskovaného firmware-kódu pre Linksys WRT120N - Linux Mint CZ&SK

  21. Chris says:

    Craig, kudos to you. I recently discovered the UART on an Apple Express (2nd gen) and am able to get a root console, but am struggling how I can back up the bootloader of the device.

    You can watch the device boot up here,
    http://www.youtube.com/watch?v=5IY1mX3HqXQ

    • Craig says:

      At the very beginning of the boot process it gives you the option to “Press any key to stop…”. This usually will drop you into the bootloader shell, which commonly provides some mechanism to read/write flash/RAM. You could then dump the bootloader contents to the serial console (typically it’s printed out as a hex dump), and write a little script to convert the ASCII hex dump into the actual binary data.

      You can also look for JTAG, or try dumping the firmware directly from the flash chip itself.

  22. keivy says:

    I’m curious about the “boring parts”,such as find the bootloader’s load address, manually identify standard C functions.Could you tell me how to do it?

  23. Anka says:

    I was reading this article specifically hoping to get tips on how to do the “boring parts”. :-(
    Interesting read anyways. :)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>