Bypassing password protection and getting a shell through UART in NEC Aterm WR8165N Wi-Fi router
We started out by trying to connect to the router via UART and found out that it asks for a username and password in order to log in. We made many attempts using well-known credentials, but we decided that it was time to resort to static analysis in an attempt to find out the correct password.
Obtaining the firmware
As usual, we downloaded the firmware from the vendor’s website and ran Binwalk on it. Unfortunately Binwalk was unable to recognize any file format, so we get the impression that it might be encrypted somehow. In order to verify it, we decided to run Binwalk’s built-in entropy analysis on the downloaded image. The results are shown below:
This means that the image had a constant (and high) entropy value, which means that the image was most likely encrypted. But, since we did have physical access to the device, Emilio was able to desolder the EEPROM chip from the board and read its contents. With this newly dumped firmware image, we made one more attempt to run Binwalk on it and, sure enough, it was now able to recognize the contents and extract the entire filesystem!
Figuring out the password
The first thing we did was to dump the
/etc/shadow file and try to crack the hash. We succeeded but this password was not accepted by the UART login prompt. Judging from this behavior we concluded that there must be something else going on with this firmware, so we decided to take a look at the rest of the filesystem and see if we could find anything suspicious.
Login credentials’ validation
After taking a look at
/etc/init.d/rcS we found that a binary called
console_password runs after the initiation sequence is over. We were surprised to find that this binary had not been stripped from its symbols. In fact, all binaries had symbols! (thanks NEC for making our job easier). Here is a simplified version of the decompiled binary (the main function only):
This might seem like a lot of code but most of it is really straightforward (we can safely ignore everything between the comments (3) and (4) for now). The gist of it is that a username gets read from
stdin (1) and then checked against the string
“root_cheeper” (2), just before reading a password from
stdin (3) and checking it against some buffer (4). If any of the checks fail, then the program goes into an infinite loop (at the
HANG label). Of course, what we would like to know is the exact value of the string in
valid_pwd, which will be compared to the password read from
We now have to bring our attention to what’s going on between (3) and (4). Long story short, it is just executing
pwcrypt with some parameters obtained from
apmib_get and reading its output into a buffer. What this means is that there must be a binary called
pwcrypt within the filesystem whose function is to generate a valid password based on some input data.
Sure enough, we found the
pwcrypt binary in the
/bin directory of the filesystem. Moreover, we found the following strings defined within it:
This seems to imply that if we could run this binary using both the product name and its WAN MAC address (both of which are known to us), we would be able to generate the correct password ourselves! It sounds good on paper, but of course, it doesn’t actually work. This is due to the fact that the binary runs the following code in order to generate the password:
So this process of password-generation is dependent on one more binary, called
flash, which is supposed to retrieve a random hardware key from flash memory (we don’t actually know any of this for sure, but given such suggestive symbol and program names given here we can assume so, at least for the moment being).
If we continued the analysis as we’ve been doing so far, we would now turn our attention to the
flash binary. But reverse engineering the
flash binary is kind of tiresome, since it involves many indirect function calls and a lot of code (for reference,
console_password was about 4KB in size and
pwcrypt 9KB, but
flash is a 84KB binary). Clearly, just attempting to read the code from start to end wouldn’t work here, so let’s digress for a while.
Let’s turn our attention once more to the code listing for
console_password, the one given earlier. We now know that it is called
pwcrypt, and that
pwcrypt expects to be given both the device’s model name and its MAC address. But how exactly does
console_password get that information? The relevant code is included here once again for convenience:
What this is telling us is that
apmib_get is somehow in charge of retrieving device-related values from non-volatile memory. This functionality is oddly related to the one we expected from
flash, isn’t it? On top of that,
flash is linked against
libapmib too, which is the library that defines this
apmib_get function. Given all of that context it does make sense to go and look at
libapmib first, in an attempt to try to figure out how all of these values are being read from memory (and what type of memory they are being read from!).
Digging into libapmib
We decided to take a look at
libapmib.so and see how
apmib_get works. Luckily, as we had debug symbols, we were able to see this at the beginning of the function (note that we have named the parameters because we can infer their semantics from the previous usages of this function):
With this information we noticed that there is a table in memory which stores information related to the integer keys we saw previously (for instance, from looking at
console_password we know that the key for the mac address is
202). Moreover, there are multiple tables, and they are searched in a specific order when
apmib_get begins looking for a requested id.
After inspecting how these tables are laid out in memory we noticed the following pattern:
Note that we’ve already added some typing information, which we’ve figured out by taking a look at how the elements of this table were being used within
After defining the appropriate types, the code ended up looking like this:
We added a lot of information to this listing, so we will need to talk a little bit about where all of this came from.
- We knew that the 3rd field of each entry in the table indicated the type of the entry because, depending on its value, the value of
resultgets set in different ways.
- We also know that the first field is the id because it is the value being compared with the first input parameter (which if you recall, indicates the key to be searched).
- Finally, as you can see from the 7th case inside the switch, the data being copied to
resultcomes from adding the 4th field of the entry to a given pointer that we named
data_tbl_ptr. This is why we decided to call this field `offset`.
In the snippet above, we included only the 7th case of the switch because this is the entry type of
RANDOM_KEY, which looks suspiciously similar to
HW_RANDOM_KEY from earlier (in fact, when you use
flash get to retrieve
HW_RANDOM_KEY, flash stripes the
HW_ prefix and just looks for
After figuring all of this out, we were only missing the actual value of
RANDOM_KEY. We knew that it is located at offset
0xD in the table
pHwSetting, but we were missing that table’s contents.
In order to get the contents of that buffer, we took a look at all the functions where
pHwSetting is being used, and found that it was being referenced from
apmib_init_HW. Out of those functions, the ones that seemed more likely to give us a hint about the contents of
pHwSetting were the ones that contained
init in the name. We quickly realized that they were both calling a function named
apmib_hwconf, which seemed to be populating the buffer.
After reverse engineering
apmib_hwconf for a while we managed to discover that the contents of the buffer were being uncompressed from flash and copied to RAM. After managing to decompress this image, we were able to read the value of
RANDOM_KEY using the previously defined offset. In order to automate this process, we wrote a custom tool which you can find here: https://github.com/infobyte/nec_aterm_tools/tree/main/nec-aterm-firmware-config-extractor.
Putting it all together
Let’s do a recap of what we have done so far:
- To get a shell on the device, a binary called
console_passwordchecks the password entered by the user against the one returned by
pwcrypt(which gets called using two known values).
pwcrypt, in turn, calls
flash get HW_RANDOM_KEYto retrieve the random key from flash.
- And we knew the value that
flash get HW_RANDOM_KEYshould return in order to generate the valid password, because we managed to uncompress the settings stored in flash memory and calculate the offset at which the 4-byte random key is supposed to reside.
In other words, we have everything we need to calculate the correct password ourselves. We just needed to set up an environment which allows us to execute MIPS binaries, and some way to fake flash memory access.
The first task was easily achieved using qemu user mode and
chroot, but the second one was a bit more hacky. Since
pwcrypt only uses
flash once to get the hardware random key, we replaced
flash with a bash script that always returns the correct value for
HW_RANDOM_KEY. Then, we executed
pwcrypt with the device name and WAN mac as arguments to get the correct password.
The exact process can be seen in this video:
After all this effort, Emilio noticed that there was a QR code on the back of the device. It turns out that people upload photos of their devices and redact the information they consider sensitive, for example the default WiFi password.
But Emilio found out that the QR code contains a lot of information. If you scan it with your cell phone, it will lead to a page designed to speed up your setup: Easy QR start (らくらくQRスタート).
The thing is that in order to set up the device, the URL conveys a lot of information. Guess what information is there, among other things… The
HW_RANDOM_KEY, the WAN mac address and the device name! So only by scanning this QR Code, we could have generated the shell password without dumping any firmware. We made another tool to automate this process, which you can find here: https://github.com/infobyte/nec_aterm_tools/tree/main/rakuraku-nec-shell.