In our last post we talked about a vulnerability discovered in the D-Link DCS-930L Cloud Camera. Since then the Senrio Research Team has been working closely with the D-Link Security Incident Report Team. Below we disclose technical details of our efforts.
Entire Product Categories at Risk According to D-Link 39 models across Connected Home Products, including wifi cameras, camera recorders and storage, are affected by this single vulnerability. What does that mean in terms of exposure to consumers? In a collaboration with Shodan we discovered 400,000 devices publicly accessible that could be affected by this 0day. The DCS-930L camera alone made up 55,000 of the search results. Consumers are encouraged to update their devices with the latest firmware. | 39 models across several D-Link Connected Home Product families are affected by this flaw, including WiFi Cameras, Camera Recorders, and Cloud Storage devices |
Read the Senrio/Shodan exclusive on this vulnerability here.
How We Did It
What follows is a categorical description of how we found and exploited the vulnerability. You can find more information on the tools and industry-leading training on how to find and mitigate these types of vulnerabilities here.

STEP 1. Physical Access
We started out by examining the camera for physical access that might assist with later software-focused vulnerability analysis. We removed the casing and decided to target the TSOP Flash memory for data extraction and the pads at JP-2 for a possible interface. After some analysis using proprietary Senrio software and popular logic analyzer tools, we were able to determine that block JP-2 contained pins that transmitted data during boot. We were able to determine that this was a UART connection and reverse engineered the correct pinouts. We then proceeded with making these connections more accessible by soldering leads onto the JP-2 pads.
We started out by examining the camera for physical access that might assist with later software-focused vulnerability analysis. We removed the casing and decided to target the TSOP Flash memory for data extraction and the pads at JP-2 for a possible interface. After some analysis using proprietary Senrio software and popular logic analyzer tools, we were able to determine that block JP-2 contained pins that transmitted data during boot. We were able to determine that this was a UART connection and reverse engineered the correct pinouts. We then proceeded with making these connections more accessible by soldering leads onto the JP-2 pads.

Soon, we had reliable physical access to the unprotected UART. We calculated the baud rates manually using the period of the pulses. This is calculated by measuring signals from the wires that we connected. We examine the duration or width of the smallest "pulse" (in this case 17.25μs). Since 17.25μs is 17.25 millionths of a second, we can calculate the baud rate by doing this math: (1/17.25)*1000000 = 57971
The calculated baud rate is thus 57971, which is close to the standard: 57600 (8 None 1). With this data we are able to decode the data transmitted by the device within the logic analyzer software and see that it was, in fact, a boot log. This data could also be trivially exported from the logic analyzer and parsed.
The next steps were to gain interactive access to the UART terminal with the calculated baud rate and the determined UART pinouts. For this, we used our custom developed tool called “The Shikra” which functions as an “Interface Swiss Army Knife.” Using the Shikra for interactive access, we were then able to begin focusing on finding the first vulnerabilities and begin analysis of the firmware and executables on the filesystem.
STEP 2. Vulnerability Discovery
The first vulnerability we found was a technique to trivially exfiltrate files from the system. We discovered that we could download files via the web interface started by the system during boot. We used the UART interface to move files to the web server’s api directory, “/etc_ro/web/api/“, renamed them to a “.jar” file extension (so the browser would not try to render them), and then logged into the web interface, browsed to the files, and downloaded them over HTTP. While D-Link posts firmware publicly on their support site, and we easily extracted the filesystem with binwalk, this technique was incredibly valuable when we needed to extract files generated during runtime, like core dumps, from the system.
The first vulnerability we found was a technique to trivially exfiltrate files from the system. We discovered that we could download files via the web interface started by the system during boot. We used the UART interface to move files to the web server’s api directory, “/etc_ro/web/api/“, renamed them to a “.jar” file extension (so the browser would not try to render them), and then logged into the web interface, browsed to the files, and downloaded them over HTTP. While D-Link posts firmware publicly on their support site, and we easily extracted the filesystem with binwalk, this technique was incredibly valuable when we needed to extract files generated during runtime, like core dumps, from the system.
Using the Shikra, we got a list of running processes, then searched through the executables for those that could receive and parse data from the network, based on whether they imported functions like recvfrom() and strcpy(). We targeted the executable dcp for vulnerability analysis. This service is part of the mydlink agent package, common to more than 120 D-Link products.
We began our analysis of dcp in IDA Pro. dcp is an ELF file running on Linux installed on a MIPS core. If you’ve worked with ARM before, MIPS is an easy transition. The registers and instructions are similar and MIPS is also a RISC architecture. The biggest catch to a novice MIPS reverser is delay slots. On MIPS the instruction following a transition of execution, like a branch or a call, is executed without being affected by the previous instruction. You can think of it as the following instruction being executed first. So when looking at the static disassembly, you will see an instruction loading the first argument into $a0 for a function call immediately after that function call, as at address 0x403a0c in the screenshot below
We began our analysis of dcp in IDA Pro. dcp is an ELF file running on Linux installed on a MIPS core. If you’ve worked with ARM before, MIPS is an easy transition. The registers and instructions are similar and MIPS is also a RISC architecture. The biggest catch to a novice MIPS reverser is delay slots. On MIPS the instruction following a transition of execution, like a branch or a call, is executed without being affected by the previous instruction. You can think of it as the following instruction being executed first. So when looking at the static disassembly, you will see an instruction loading the first argument into $a0 for a function call immediately after that function call, as at address 0x403a0c in the screenshot below
We located where dcp receives and parses incoming data and then reversed the dcp protocol. We discovered that dcp listens on port 5978 for incoming requests that allow a remote user to run 10 different commands that execute a shell command, change the time, or modify and retrieve other system settings. The protocol consists of a header indicating the length and command type, followed by a series of key=value pairs that provide the command arguments. It was very helpful that the code had some symbols and frequent debug prints. While the debug prints did not print to the console by default, we enabled them by killing the running process and starting it back up with the extra argument ‘-d 1’ to enable debug mode.

We then wrote a short python script to try out a few commands. Of the ten dcp commands, eight required a signature generated from the command string and the user password, so we focused on the two commands that did not and those that processed data before checking the signature. This led us to a series of insecure strcpy()s called in dcp_class6_handler() (shown in the screenshot to the right), which has no signature requirement and copies the values from the commands five key=value arguments to fixed length buffers on the stack. It does check an ID string, passed in as the value for key S, which is blank by default and unused.
We expanded our script to bombard the dcp process with invalid requests to find out how the parser would perform and whether any of the strcpy()s would result in a stack buffer overflow. Over UART, we turned on core dumps with ‘ulimit -c unlimited’ and then sent the custom dcp commands over the network with 0x200 bytes of data in each value. Sure enough, dcp crashed and produced a core dump, which we downloaded over HTTP using the web server technique we had previously discovered.
We expanded our script to bombard the dcp process with invalid requests to find out how the parser would perform and whether any of the strcpy()s would result in a stack buffer overflow. Over UART, we turned on core dumps with ‘ulimit -c unlimited’ and then sent the custom dcp commands over the network with 0x200 bytes of data in each value. Sure enough, dcp crashed and produced a core dump, which we downloaded over HTTP using the web server technique we had previously discovered.
Using GDB compiled for MIPS from Sourcery Code Bench to analyze the core dump, we saw that dcp had segfaulted at a return address matching the junk values we’d supplied for the value of key ‘I’ (0x200 bytes of 0x43, or ‘C’).
STEP 3. Exploiting the Vulnerability
Exploiting the overflow was straightforward once we realized that the command arguments were all written to an executable, hard-coded, address in process memory! IDA claimed the section was only configured to allow read/write access, but when we double checked and examined process memory over UART with ‘cat /proc/<pid>/maps’ we saw it was actually executable. This meant we could simply place shellcode in one of the command’s arguments, point the overwritten return address ($ra) to our shellcode, and not bother with a ROP chain.
For a Proof of Concept, we decided to change the device password. We wrote up a few lines of MIPS assembly code to call tdb_set_device_passwd() with a new password of our choosing (we chose ‘senrio’). The only tricky part was that we couldn’t use any nulls (as the overwrite is caused by an unbounded string copy that stops copying when it encounters a null) and the executable was based at 0x00400000. This was not a problem for overwriting the stored $ra with the address of our shell code because the system is little endian and 0x00 was the last byte we needed to write, but we did need the address for tdb_set_device_passwd() and a pointer to the string containing our new password.
Exploiting the overflow was straightforward once we realized that the command arguments were all written to an executable, hard-coded, address in process memory! IDA claimed the section was only configured to allow read/write access, but when we double checked and examined process memory over UART with ‘cat /proc/<pid>/maps’ we saw it was actually executable. This meant we could simply place shellcode in one of the command’s arguments, point the overwritten return address ($ra) to our shellcode, and not bother with a ROP chain.
For a Proof of Concept, we decided to change the device password. We wrote up a few lines of MIPS assembly code to call tdb_set_device_passwd() with a new password of our choosing (we chose ‘senrio’). The only tricky part was that we couldn’t use any nulls (as the overwrite is caused by an unbounded string copy that stops copying when it encounters a null) and the executable was based at 0x00400000. This was not a problem for overwriting the stored $ra with the address of our shell code because the system is little endian and 0x00 was the last byte we needed to write, but we did need the address for tdb_set_device_passwd() and a pointer to the string containing our new password.

As is common, MIPS has nonvolatile registers that are, in this function, stored on the stack and restored before returning to its caller function, which expects to resume execution with the same nonvolatile registers as before the call. Our stack overflow, before overwriting the stored value of $ra, had also overwritten the stored values of $s0-$s8 (shown in the screenshot of the function’s stack to the right). We used the values of these registers to calculate the values of tdb_set_device_passwd() and our password string.
We set the stored value of $s1 to the address of tdb_set_device_passwd() + 0x43434343 (“CCCC”) and the stored value of $s2 to the pointer to our new password + 0x43434343, and the stored value of $s0 to 0x43434343. We wrote shell code to subtract $s0 from $s1 and $s2 to get the correct addresses.
We crafted a new command message, assigning each key=value pair to hold a different part of the exploit, consisting of the shellcode, the password, and the overflow values. We only used 4 of the 5 keys, there is a key the command uses which we could have included but had no use for. Its absence prevented the function from actually trying to add a volume, lessening the footprint we left on the device.
We set the stored value of $s1 to the address of tdb_set_device_passwd() + 0x43434343 (“CCCC”) and the stored value of $s2 to the pointer to our new password + 0x43434343, and the stored value of $s0 to 0x43434343. We wrote shell code to subtract $s0 from $s1 and $s2 to get the correct addresses.
We crafted a new command message, assigning each key=value pair to hold a different part of the exploit, consisting of the shellcode, the password, and the overflow values. We only used 4 of the 5 keys, there is a key the command uses which we could have included but had no use for. Its absence prevented the function from actually trying to add a volume, lessening the footprint we left on the device.
The end result was that our command, once injected into the device, forced the device to accept our new admin password the next time we contacted the device’s web server:
Step 4. Impact of Research
After successfully exploiting the camera, we gathered information on how many users this would affect. We went to D-Link’s support site and downloaded old firmware releases for the 930L to find out when the bug was introduced. We extracted dcp from each release using binwalk and found that the class 6 parser was vulnerable when it was first introduced in version 1.12, released over a year ago on 03/16/2015.
After successfully exploiting the camera, we gathered information on how many users this would affect. We went to D-Link’s support site and downloaded old firmware releases for the 930L to find out when the bug was introduced. We extracted dcp from each release using binwalk and found that the class 6 parser was vulnerable when it was first introduced in version 1.12, released over a year ago on 03/16/2015.
To find out how many publicly available 930Ls the bug affected, we turned to Shodan for the camera’s unique web server response, “DCS-930L” and came up with 55,559 results. We assumed that not all the devices on Shodan were running the most recent version, so we looked for any updates to the web server response that could help identify firmware versions. According to their release notes, D-Link also introduced support for TLS in v1.12. |
We used Shodan’s feature to filter by supported SSL protocols and added “ssl.version:tlsv1” to our search, which resulted in 14,400 vulnerable devices. This is a conservative estimate. If accurate, this means that only about 25% of people are updating their camera firmware, which means they aren’t vulnerable to this exploit but they’re vulnerable to everything else that D-Link has patched!
This isn’t a problem that affects just the 930L, D-Link communicated that this bug affects over 120 models in their product line. The vendor later corrected that estimate and listed the following models under affected products:
Affected Products: DCS-2132L, DCS-2136L, DCS-2210L, DCS-2230L, DCS-2310L, DCS-2330L, DCS-2332L, DCS-2630L, DCS-5009L, DCS-5010L, DCS-5020L, DCS-5029L, DCS-5030L, DCS-5222L, DCS-6004L, DCS-6010L, DCS-6212L, DCS-700L, DCS-7010L, DCS-800L, DCS-820L, DCS-825L, DCS-850L, DCS-855L, DCS-930L, DCS-931L, DCS-932L, DCS-933L, DCS-934L, DCS-935L, DCS-936L, DCS-942L, DCS-960L, DNR-202L, DNR-312L, DNR-322L, DNS-320L, DNS-327L, DNS-340L
Affected Products: DCS-2132L, DCS-2136L, DCS-2210L, DCS-2230L, DCS-2310L, DCS-2330L, DCS-2332L, DCS-2630L, DCS-5009L, DCS-5010L, DCS-5020L, DCS-5029L, DCS-5030L, DCS-5222L, DCS-6004L, DCS-6010L, DCS-6212L, DCS-700L, DCS-7010L, DCS-800L, DCS-820L, DCS-825L, DCS-850L, DCS-855L, DCS-930L, DCS-931L, DCS-932L, DCS-933L, DCS-934L, DCS-935L, DCS-936L, DCS-942L, DCS-960L, DNR-202L, DNR-312L, DNR-322L, DNS-320L, DNS-327L, DNS-340L
Code Reuse Also Means Vulnerability Reuse
Manufacturers have become very efficient in designing hardware and software platforms that can be used across multiple product families. This saves costs (economies of scale) and development time (reducing time to market). However, this also means that a vulnerability can span across the entire product offering, as in the case of D-Link. The same tools that were used to identify this vulnerability in a consumer product can be applied to devices in high-value networks; such as, programmable logic controllers (PLCs) or remote power managers (RPMs). These control systems are used in everything from traffic lights to nuclear power plants. A bug found in an inexpensive drug pump (which can be easily obtained and used for research purposes) may also be present in a high-end MRI machine from the same vendor - all thanks to code reuse in firmware components.
"Low-hanging" simple-to-exploit stack overflows have largely disappeared from traditional endpoints (computers and servers) thanks to better protections like stack cookies, ASLR, smarter compilers and other advanced mitigations. However, bugs like this are still alive and well in the embedded devices, ICS, and IoT that we use every day in our infrastructure, hospitals, and businesses. While our exploit code only changes the password, "remote code execution" means complete control of the device. It only takes a little more work to modify our proof-of-concept to do something different like install a backdoor, allowing an attacker persistent access to high value networks. | Stack overflow bugs like this are still alive and well in the embedded devices, ICS, and IoT that we use every day in our infrastructure, hospitals, and businesses. |
Once an attacker gains access to an embedded device, like a WiFi camera, they can abuse it for malicious purposes; for example, gain access to internal data, use it to form botnet of embedded devices, or tamper with the device to impact its performance.
The first step in addressing a compromise is identifying affected devices. For that you need an IoT aware security solution that provides visibility and actionable insights. With a strong heritage in embedded device research and training, Senrio is dedicated to aiding organizations in identifying, protecting and responding to issues as their number of IoT assets grows.