Reverse Engineering Firmware Primer

From Paul's Security Weekly
Jump to: navigation, search

Introduction

Reverse engineering firmware is so much fun, but also very frustrating. Using some techniques I recently discovered, I attempted to rip apart some Dlink Dir-655 firmware. This device runs MIPS and ubicom boot loader, so its weird. I was unsuccessful in mounting a file system, however the steps below can be applied to just about any firmware.

How-To Pull Apart Firmware

binwalk rules, it looks at all of the file/file system headers and spits out a map:

# binwalk --dd=gzip:gz:2 DIR655B1_FW203NAB02.bin 
DECIMAL   	HEX       	DESCRIPTION
-------------------------------------------------------------------------------------------------------
0         	0x0       	uImage header, header size: 64 bytes, header CRC: 0x29953343, created: Mon Jun 27 00:33:02 2011, image size: 6395843 bytes, Data Address: 0x40100000, Entry Point: 0x408A6270, data CRC: 0x3D73C1BC, OS: Linux, image type: OS Kernel Image, compression type: gzip, image name: Unknown - IP7160_DIR855_F_Board
64        	0x40      	gzip compressed data, from Unix, last modified: Mon Jun 27 00:33:00 2011, max compression

The "--dd" option is awesome, because it will find anything that matches "gzip", extract it, name it .gz and do that 2 times. The we get a 40.gz file to extract:

gzip -d 40.gz

The file "40" now represents the data in the firmware (basically we stripped the firmware header and uncompressed it). Now you can run strings against it:

strings -8 40 | less

I found the file system map:

Launching OS on thread: %d entering at %p
device tree alloc failed
board node alloc failed
%p: board node not attached: %d
bootargs
bootargs node alloc failed
mtdparts=ubicom32_boot_flash:128k(bootloader)ro,7552k(upgrade),384k(jffs2),64k(fw_env),64k(artblock)
start trap handler...
start usb...
start ethernet...

I need to do some more work to figure out how to map the offsets above to the data section. Some more greps that are useful:

strings -8 40 | grep password
strings -8 40 | grep backdoor
strings -8 40 | grep "\<html

Next, we can run binwalk against the data file and auto-extract all the JFFS2 filesystems:

# binwalk --dd=JFFS2:jffs2:20 40

We get a bunch of stuff, including:

DECIMAL   	HEX       	DESCRIPTION
-------------------------------------------------------------------------------------------------------
781406    	0xBEC5E   	JFFS2 filesystem data big endian, JFFS node length: 52321
812698    	0xC669A   	JFFS2 filesystem data big endian, JFFS node length: 55456
814198    	0xC6C76   	JFFS2 filesystem data big endian, JFFS node length: 1121
<snip>
7992985   	0x79F699  	JFFS2 filesystem (old) data big endian, JFFS node length: 53312
7992993   	0x79F6A1  	JFFS2 filesystem data big endian, JFFS node length: 222272

I've tried to mount JFFS2, with not much luck, but here's how:

modprobe mtdram total_size=32768 erase_size=256
modprobe mtdblock
sudo modprobe mtdchar
sudo mknod /dev/mtdblock0 b 31 0
dd if=2686A8.jffs2 of=/dev/mtdblock1
mkdir mnt
mount -t jffs2 /dev/mtdblock1 mnt/

Likely the byte order is screwing us, this command is known to work:

jffs2dump -r -e convimage -b 2686A8.jffs2 

It fails, likely my offsets are off, but you get the picture. Squashfs and Cramfs are much easier to extract, and the steps are the same, Happy Hunting!

UPDATE

Huge thanks to the author of binwalk and owner of http://www.devttys0.com Craig. He wrote in with some awesome helpful tips for pulling apart the DIR-655 firmware:

JFFS2 signatures are tricky; the signature is actually for an individual JFFS2 node (an entire JFFS2 filesystem will have many nodes). So if you only see a few JFFS2 nodes, as in the extracted gzip data from the DIR-655 firmware, they're probably false positive matches (the JFFS2 node "magic bytes" are only 2 bytes long). So the JFFS2 signatures that you were seeing were just false positive matches. What sticks out to me though is the gzip match in the gzipped data extracted from the firmware image:


    $ binwalk 40
    DECIMAL         HEX             DESCRIPTION
    -------------------------------------------------------------------------------------------------------
    781406          0xBEC5E         JFFS2 filesystem data big endian, JFFS node length: 52321
    812698          0xC669A         JFFS2 filesystem data big endian, JFFS node length: 55456
    814198          0xC6C76         JFFS2 filesystem data big endian, JFFS node length: 1121
    2425639         0x250327        LZMA compressed data, properties: 0xA0, dictionary size: 67108864 bytes, uncompressed size: 41985 bytes
    2425815         0x2503D7        LZMA compressed data, properties: 0x94, dictionary size: 67108864 bytes, uncompressed size: 41985 bytes
    ...
    2935884         0x2CCC4C        gzip compressed data, from Unix, last modified: Mon Jun 27 03:32:04 2011, max compression
    7990527         0x79ECFF        LZMA compressed data, properties: 0x84, dictionary size: 1073741824 bytes, uncompressed size: 335544320 bytes
    7992985         0x79F699        JFFS2 filesystem (old) data big endian, JFFS node length: 53312
    7992993         0x79F6A1        JFFS2 filesystem data big endian, JFFS node length: 222272
    8009671         0x7A37C7        LZMA compressed data, properties: 0x94, dictionary size: 335544320 bytes, uncompressed size: 335544320 bytes
    8015031         0x7A4CB7        LZMA compressed data, properties: 0x84, dictionary size: 872415232 bytes, uncompressed size: 536870912 bytes
    8018831         0x7A5B8F        LZMA compressed data, properties: 0xA0, dictionary size: 469893120 bytes, uncompressed size: 603979776 bytes
    8018991         0x7A5C2F        LZMA compressed data, properties: 0xB8, dictionary size: 1073872896 bytes, uncompressed size: 603979776 bytes 


The gzip match has a timestamp that is within one minute of the original gzipped file found in the firmware update image at offset 0x40, so that's a good sign. Extracting and uncompressing the gzip file at offset 0x2CCC4C above reveals that it is a CPIO archive:

    $ binwalk 40 -y gzip --dd=gzip:gz
    DECIMAL   HEX       DESCRIPTION
    -------------------------------------------------------------------------------------------------------
    2935884   0x2CCC4C   gzip compressed data, from Unix, last modified: Mon Jun 27 03:32:04 2011, max compression
    $ gunzip 2CCC4C.gz 
    gzip: 2CCC4C.gz: decompression OK, trailing garbage ignored
    $ file 2CCC4C 
    2CCC4C: ASCII cpio archive (SVR4 with no CRC)


Which can be extracted with the cpio utility to get the file system contents:

    $ cpio --no-absolute-filenames -i < 2CCC4C
    $ ls
    bin  boot  dev  etc  home  init  lib  mnt  proc  root  sbin  sys  tmp  usr  var  www


So basically the file system was built as a compressed CPIO archive, then concatenated with the kernel, then the whole thing was gzipped. Likely the device does use JFFS2, but the contents of the CPIO archive are extracted to the JFFS2 partition, rather than putting a JFFS2 image into the firmware update file.

Be sure to check out his web site and training!

Resources