D-Link: A Firmware Security Analysis – Part 2

by Aug 23, 2019

The Technical Side of Firmware Analysis

In part one of the firmware security analysis we talked about choosing a target, downloading the firmware, and submitting it to Centrifuge which was pretty simple.

Centrifuge provided a lot of useful information about the firmware, most importantly the potential flaws in the code analysis section.

In this post we will explore the technical side of firmware security analysis to help identify which of the flaws are potentially exploitable flaws, and which flaws are simply poor programming practices.


Starting the Search

The best place to begin the search: the administration server that runs at boot. The administration server will likely be exposed to the internet. Therefore, it’s our best starting point in my opinion.

If you can access to the actual camera, you can simply connect a UART cable, access the shell, and get a process listing to identify the server. But I prefer not to buy hardware until I have an actual vulnerability identified, and I actually have all the information I need at my fingertips from the unpacked firmware.

Centrifuge identified a startup script. I’ll start with that. But if you don’t have access to Centrifuge, then a good place to start is the /etc/init.d script and see what services run at boot.

Startup Commands

Startup Commands
At this point I like to use binwalk to unpack the firmware locally for easier browsing. Looking through the startup scripts requires reading comments and looking at function calls to identify what runs at boot. The start of /etc_ro/rcS is shown below. These files are usually commented pretty well, which is helpful with the identification process.

Working through all the scripts eventually leads to a mention of a webserver, alpahpd. The path to get there was:

/etc_ro/rcS -> /sbin/internet.sh -> /sbin/lan.sh -> /sbin/web.sh

So now we have our starting point!


The Target: alphapd Web Server

We can either dive right into disassembling alphapd in our tool of choice (IDA, Ghidra, Radare2, Binary Ninja, etc) or look at the Code Analysis section in Centrifuge to greatly narrow down our choices.

Centrifuge will call out both potential buffer overflows (if the destination is a stack variable) and command injections (if the parameter is a non-static string). This significantly reduces the number of function calls we need to examine from 300+ down to a respectable 32, which is a pretty substantial time save. The Code Analysis view gives all the information we need to start our analysis of the function calls in our disassembler of choice, which for me is IDA.

Code Analysis of alphapd
Code Analysis of alphapd

The server is already running by the time our exploit would be processed so we can skip any calls in main and calls prior to the message processing loop.


Buffer Overflow Requirements

For buffer overflows we look for two things. First, the destination must be a stack variable no matter the function call. If this is not true, then we won’t be able to overwrite the return address and control execution (more on this later). Second, we need to control some portion of the source data. Let’s look at an example.

Non-vulnerable strcpy Centrifuge
Non-vulnerable strcpy Centrifuge
Non-vulnerable strcpy IDA

Non-vulnerable strcpy IDA
The prototype for strcpy is:
strcpy(destination, source);
Function arguments in MIPS are $a0, $a1, $a2, $a3 for the first four args, then the stack if more arguments are required. To identify if the destination is on the stack, we need to see how $a0 is set.

MIPS executes instructions based on a 5 stage pipeline. As a call is executed, what comes after that call (the jalr) is also executed. This operation takes place in what is known as the delay slot.

In this example, the delay slot is where the first argument is set. It is simply $a0 = STACK ADDRESS + OFFSET. Therefore, the destination is on the stack.

Next is whether or not the source, $a1, is a user controllable string. From the code snippet noyes_select, it appears the string “No” or “Yes” will be the source. Since this is not a user controllable string, it will never result in a buffer overflow. This is simply a poor coding practice.


Command Injections

The same process is used to identify valid command injections. In this case, there is no destination. But we still need to control the source.

alphapd has a nice function, doSystem, that combines vsnprintf and system which means we are looking for an $a0 with some kind of format string in it. This method makes it quick and easy to identify potential vulnerabilities.

The example below is automatically discounted because a static string is passed as the argument with no chance of user controlled data.

doSystem Centrifuge
doSystem Centrifuge
doSystem IDA
doSystem IDA


User Controllable Buffer Overflow

Now let’s look at a buffer overflow that is user controllable:

strcpy Centrifuge
strcpy Centrifuge

strcpy Centrifuge
In this example, our destination is on the stack. The second parameter is set by the $s1 register. So now we travel back up the disassembly to see if it’s controllable.

$s1 Regsiter

$s1 Regsiter
The $s1 register is set to the contents of the $v0 register, which is used for return values from functions. In this case, the most recent function call is websGetVar, an alphapd function that retrieves variables from the URL. After some thought, we might end up with some pseudo code that looks like this.
sub_443138(request, argument_count, argument_value):
 char wep_encr[4]
 if (2 == argument_count):
 NVRAM_GET(wep_encr, “WEPEncryption”)
 if (“WEPEncryption” in $URL)
 strcpy(wep_encr, $URL[“WEPEncryption”])
 if (argv[1] == wep_encr):
 return “checked”
 return “”
Basically this function checks if supplied arguments are equal to the value of WEPEncryption from non-volatile random access memory or the URL. If they are equal, it returns the string “checked” or an empty string if they are not equal. If we can cause this function to be executed with WEPEncryption in the URL, then the contents of that value will be copied directly to the stack.

So the question is: who calls this function?

Lucky for us, there is only one reference to this function call.


Not a lot of information to go on here other than the string RadioOfWEPEncryWay. Doing a quick grep for that string shows it in one location.


By referencing our pseudo code, we can assume that the returned string “checked” or an empty string will be used to set the radio buttons on the wireless.htm page.

We also know that if wireless.htm is requested withWEPEncryption in the URL, it should copy that string directly to the stack.

There’s a little bit of handwaving going on here, but I did look through a lot of disassembly to figure out that websParaDefine function correlates the strings between %% and an associated processing function. Add all this together, and we end up with a request that looks something like this:

Those ’A’s should end up on the function’s stack.


To Be Continued…

I cherry picked some nice examples for the purpose of this security analysis. But the reality is, most of the potential vulnerabilities in firmware you examine are pretty complicated and take some time to understand, at least for me.

A good rule of thumb I follow: if it looks too complicated to execute the code path, then skip it. Have I missed vulnerabilities because of this rule? Probably. But I’m all about that low- to middle-of-the-tree-hanging-fruit.

Now that we have identified a potentially user controlled buffer overflow, next time we’ll dive in to how to emulate the server to determine if we can call this function and crash it.


About the Author

Evan Walls is a vulnerability analyst and developer at Tactical Network Solutions, focusing on embedded system exploitation. When he isn’t searching for new exploits, he teaches training courses developed by TNS, including IoT Firmware Exploitation. Prior to working at TNS he was a Windows developer for the Department of Defense. Now that he’s seen the glory and freedom that is Linux he vowed never to use another windows development machine again.

You can follow him on LinkedIn or GitHub.

Recent Posts
Embrace IoT Security Compliance or Die a Slow Death

Embrace IoT Security Compliance or Die a Slow Death

IoT Security Compliance. IoT Security Standards. IoT Security Frameworks. All new buzzwords that are picking up steam. So imagine our surprise when we talk to IoT device manufacturers about why they continue to ship products with bad security and the reason they don’t fix it.

How to Compare Two Different Binary Files

How to Compare Two Different Binary Files

One of our favorite new capabilities in the Centrifuge Spring ‘20 release is Firmware Differencing. This is how to compare two binary files quickly and efficiently for Linux, QNX, and VxWorks. But that’s not all it compares!

How to Enforce IoT Security Standards and Compliance

How to Enforce IoT Security Standards and Compliance

With all of these certification standards and compliance regulations, conducting product cyber-security assessments quickly becomes very complicated and expensive. Here’s how to save time and money.