The Technical Side of Firmware Analysis
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.
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!
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.
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.
The prototype for strcpy is:
Function arguments in MIPS are
$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.
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.
User Controllable Buffer Overflow
Now let’s look at a buffer overflow that is user controllable:
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 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
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 if (2 == argument_count): NVRAM_GET(wep_encr, “WEPEncryption”) if (“WEPEncryption” in $URL) strcpy(wep_encr, $URL[“WEPEncryption”]) if (argv == 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
We also know that if
wireless.htm is requested with
WEPEncryption 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:
’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.