How-To: Extracting Decryption Keys for D-Link

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 SHRS header:

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 prog.cgi and fota – two binaries which probably handle these encrypted firmware images in some way or another.

In prog.cgi, we can quite easily track down the firmware upload routine, based on where we find the encrpted_img string:


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: gj_decode().


Jumping into the Library

gj_decode() is defined in It’s a really small function, but critically it calls two functions with encryption-related names: aes_set_key and aes_cbc_decrypt. These are also defined within the same 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 and 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 00031bd5.

Indeed, at 00031ba3, we can see that there is an array of bytes:


A very similar pattern can be seen at 00031bc5.

As such, we can probably guess that the AES key itself is at 00031ba3, and the IV is at 00031bc5.

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

We can test the hypothesis quickly and easily by using CyberChef. I tend to open this up most times I want to quickly workshop anything cryptography-related. Yes, it’s written by the UK’s GCHQ. But if you’re the kind of person who is suspicious of things written by civil servants, it’s written in pure JavaScript, and can be run in whatever browser you want, on whatever air gapped box you want 😊.

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.