[Reverse Engineering Primer] Unpacking cramfs firmware file systems

Reverse engineering firmware from the point of view of an attacker involves levering as much as possible what is accessible from the physical board. Of course the classic question this strives to answer is, if someone gets hold of my board what could go wrong? Reverse engineering some devices in the wild often exposes security keys, default passwords and other forms of security failures that can expose an unfair escalation of privilege or perhaps also allow a complete take over of the device right down to boot loader level - all of this sometimes also possibly learned by analyzing the firmware.

I'm going to talk about some of the more basic skill in getting toward exposing the code and other sensitive artifacts used by the device. Before you can find remote code execs and admin auth by pass vulns you need to get to grips with firmware image formats and embedded file systems.

Setting up the environment 

Before getting super in depth and writing our own file format parsers, machine learning classifiers and unpackers we can rely on some pre-built tools developed by the awesome security community. To get this done we're gonna need the following stuff:

Installing binwalk for ubuntu 

Pretty straight forward binwalk should be available from the standard list of sources
sudo apt-get install binwalk

Installing firmware-mod-kit  

sudo apt-get install build-essential zlib1g-dev liblzma-dev python-magic
git clone https://github.com/rampageX/firmware-mod-kit.git

You can then begin cd /src/[command]/ making everything but I do that during the tutorial below too. We are only going to need the uncramfs utility so you only really need to make that right now.

Basic Binwalk 

Once we have a firmware image we would like to look at; what you need to know is how to detail what is inside it. A firmware image is just a stream of bytes like any other that has its own boundaries and segments. The firmware image is essentially what allows control to branch to the kernel or it contains the kernel itself sometimes, and sometimes its almost all of the software that will run on an embedded board. Booting can be done a number of ways, there are a couple recipes for making the process simpler like grub but for now we will make no assumptions about what is in the image and unpack it as far as we can.

To list the contents of a boot image all you need to do is fire off this command:

binwalk [image file]

binwalk is pretty good at picking up the headers and magic numbers that mark different assets in the image. This is a very pretty and perhaps misleading view of the image file, lets take a look at the hexdump of the image file. Running hexdump -C [image file name] shows us the following:

binwalk was pretty accurate, it said that the first couple of byte streams were from an executable script supposedly named after one of my favorite Ricky Martin songs. Just kidding not sure why its labeled that way beyond indicating that it is to be executed by a born shell variant.

How do we actually get hold of the contents of the filesystem and firmware? Well thats why I made a massive header for the next section. So you can read it and realize stuff!

Unpacking and mounting Cramfs

If you need to get something out of a firmware image you must rely pretty much on what the board itself does - a range of bytes to extract. We need to essentially be able to say where to begin extracting and where to stop. The binwalk output above serves exactly this purpose - tell us where certain sections of memory begin with respect to the contents they hold. The data duplicator command is amazing at copying things out given a byte range - it takes a couple of other intuitive input parameters (possibly more than I mention here but in terms of getting stuff out of firmware you should be good with just these flew flags and options):

dd of=outputfile if=inputfile bs=blocksize skip=offset count=N 

  • of means output file
  • if means input file
  • bs means block size which is just a fancy way of saying "copy this many blocks at a time
  • count means copy N bs size blocks
  • skip means start copying after this many bytes or from that offset in the file. 

Lets try to strip out some files. That shebang file looks interesting lets see whats in it:

dd bs=16751 skip=0 count=1 if=[firmware image] of=output_file.sh 

I used a block size of 16751 bytes because this is where the CRC polynomial table begins, and will definitely include all of the script contents. It strips out a little more than that though.

This little script we are looking at extracts firmware that is part of the script file (a friend of mine helped me figure that out coz I'm a total noob! Thanks Galen! Anyway back to our analysis. The fact this script is a self extractor is made obvious by the rest of the file contents - a couple bytes down you should see this in the file:

Clearly the line extract $self is a dead give away! The ddPack program I suspect allows them to skip over script bytes until it reaches this "Firmware Boundary" string in the file.

We can also of course take a look at the file system. To get the cramfs system mounted we need to first pull it from the binary, then optionally change the endianness with cramfsswap (probably not necessary but try it if you get anything funky without it - super easy to install as well should be in the default apt list for ubuntu). Anyway after extraction you then need to mount it using well the mount utility here's how you do that:

  1. extract cramfs image from the binary: dd bs=1 skip=1071495 count=6082560 if=[firmware] of=dcs-1130L.cramfs 
  2. make a directory to mount the cramfs image onto: mkdir /tmp/dcs-1130_cramfs/
  3. (optionally compile uncramfs in firmware-mod-kit) 
  4. run uncramfs on the filesystem archive: uncramfs /tmp/dcs-1130_cramfs dcs-1130L.cramfs 

And here's the mounted file system ready to inspect. I can already smell all the low hanging fruit :) 

Reading list and references

  1. https://milo2012.wordpress.com/category/reversing-firmwares/
  2. https://wiki.securityweekly.com/Reverse_Engineering_Firmware_Primer