Read more about what happened when we reported Devil's Ivy and how it affects millions of devices on our blog
Axis M3004 security camera
When we began a security analysis of remote configuration services last year, we had no idea it would lead us to uncover vulnerabilities that affect so many users. We have been studying the prevalence and nature of the vulnerabilities that arise in remote configuration services, so when we approached the M3004, we specifically sought out such a service. We began by enumerating all open ports and examined the code which handled incoming data. The service we found was wsd, a service that imports a third party library from gSOAP by Genivia. We discovered the vulnerability by using IDA Pro, a reverse engineering tool, to examine all code that wrote incoming data to stack buffers.
A function displayed in IDA Pro
We reached the vulnerable code by sending a POST command to the ONVIF service available on port 80. We were able to observe the internals of the service and the results of our code by activating ssh through the webserver and then remotely debugging the service with gdbserver, which was already installed on the camera. After verifying the vulnerability by observing it crash at a value we set when the overflow overwrote the stored return pointer on the stack, our next challenge was to gain code execution.
Although there was no limit to how many bytes we could write to the stack, we could not execute code on it, and a filter required that all the values we sent be greater than 31. We used a technique called Return Oriented Programming (ROP) to write addresses to the stack to force the program to execute code snippets in libc so we could circumvent the non-executable stack. Through this technique, we allocated executable space, copied our shellcode to that space, and directed execution to it. Although the requirement that all the addresses we used needed to have values greater than 31 restricted us somewhat, we were still able to gain code execution. Watch the video below for an introduction to ROP.
Once we reached this step, we wrote shellcode, which again contained all values greater than 31, to open a port and allow a remote user to connect to a shell. At this point we had gained code execution by exploiting Devil's Ivy (CVE-2017-9765). Due to Axis's security settings, this exploit only grants access to a shell as an unprivileged user on the M3004. However, we were able to execute commands from the ONVIF specification that only a privileged user would normally be allowed. We were able to reset the camera to its factory defaults and take control of the camera, reboot it to prevent an operator from viewing the feed, and change network settings.
Read on for the full technical details or scroll to the bottom to see a video demonstration.
We began our process by downloading the latest firmware for the M3004 camera from Axis’s website. They do require an account to download firmware, but did not verify that we were a legitimate customer. We became Nate Johnson, reachable at a throwaway email address, and immediately had access to the camera’s firmware. We extracted the file system and Linux kernel using binwalk with the Jefferson extractor for JFFS2 file systems.
The output from binwalk
We ran nmap to scan the camera for open ports and found 1900 (upnp), 3702 (ws-discover) and 5353 (mdns) open. After some analysis of the file system, we found that ws-discover was associated with wsd, a service which handled the SOAP protocol. wsd imports libsoap.so (from gSOAP by Genivia) to parse incoming SOAP messages, which we scrutinized for any code which wrote incoming data to the stack. We used IDA Pro to look for stack buffers, and then manually traced back the source of any data copied into them. Using this technique, it only took one day of looking through assembly code to spot this vulnerability.
Imported functions from libsoap.so
One piece of code caught our attention in the function soap_get() that wrote incoming data to an 0x40 byte stack buffer. The code operates in a loop that checks for the end character ‘?’ or an end of data indicator as an end condition rather than counting the number of bytes it has written to the 0x40 byte stack buffer.
The vulnerable code loop in soap_get()
In the screenshot above, R6 is the data counter, set to the size of the stack buffer, R9 is the stack buffer pointer, and R5 is the incoming byte read from the network. If the data counter in R6 is less than zero, the function skips writing to the buffer, but continues to read in data using j_soap_getchar(). By writing enough data to wsd to wrap the counter around to a positive number again, we could write data to the stack past the 0x40 byte limit. This takes a few minutes, but there is no limit on the amount of incoming data, and it is simple enough to send using netcat. We calculated that we needed to send 0x8000000 bytes to wrap the counter around to a positive number, 0x40 to fill up the fixed length stack buffer, and another 0x30 more before we could overwrite the stored return address.
We reached this vulnerability by sending a POST command to “/onvif/device_service” on port 80, which was handed off from the webserver to the wsd service. To send 0x80000070 bytes, we generated a text file beginning with “POST /onvif/device_service” followed by a new line and “<?” to indicate the beginning of a SOAP message. We filled the rest of the file with junk bytes and then used netcat to send the file with the command “nc [camera_ip] 80 < postpwn.txt”
We needed more information to determine whether we were really able to exploit the vulnerability, since all we could tell at this point was that the service was unresponsive immediately after we completed sending all the data. To gain ssh access to the camera, we followed the directions we found in Axis’s support center.Using the camera’s embedded webserver, we navigated to the advanced menu and enabled ssh by editing /etc/conf.d/ssh. After restarting the camera, we were able to ssh in with the set username and password. We discovered that gdbserver was already installed on the camera, so we used a version of gdb compiled for ARM on our local machine to monitor the service when we hit the vulnerability. Sure enough, we saw it crash at the value we supplied in the overflow.
wsd crash with gdb attached. R4-R11 and the current PC were stored on the stack. The output shows the new values we have given them.
The next challenge we faced was gaining code execution, since the stack was non-executable. Unlike other devices we’ve looked at lately, incoming data was not stored at a fixed value on an executable heap, which was great to see and naturally slowed us down. However, we could write as many bytes to the stack as we needed, and libc was in a static location. We put together a ROP chain that used snippets of code in libc to gain execution. The tricky part of this was that we could not use any addresses which contained bytes with a value below 0x20 or 0x3F or 0xFF. Values lower than 0x20 were replaced with the byte 0x20, and 0x3F or 0xFF would mark the end of our buffer. Fortunately, libc was based at an address that allowed us a large section of code to use in the ROP chain.
/proc/[pid]/maps gave us the base of libc for wsd
We found our ROP addresses manually, using IDA and the regex feature in find. We appended the ROP chain to our large text file, and wrote a script to check for any bad values. In all, it took us a few days of steady work to put together the entire 19-address long chain. We first called pvalloc() to allocate a page-aligned memory buffer then copied our shellcode from lower on the stack into the buffer using strcpy(). We finished up by calling mprotect() to mark the buffer executable and then jumped to the executable buffer to begin our shellcode.
An example of the technique we used to write value-restricted Thumb code
Surprisingly, writing shellcode presented the most trouble. We set out to to bind to a socket and allow a remote user to connect to a shell. Because we were restricted in values, we first worked on xor encoding the bulk of our shellcode and decoding it in place with a section of restricted value shellcode. ARM processors cache instructions and data, which you can clear with the ISB or an MCR instruction, neither of which we could use with our restrictions. Although there is an example online of changing the data portion of the MCR instruction to clear the instruction path, this did not work for us. We also learned that, on some chips, you can simply branch to your code, since the processor only caches sequential instructions. However, this was not effective either. Finally, we wrote value restricted Thumb shellcode, which only took about an hour after weeks of research into other possibilities.
To write value-restricted shellcode, we relied heavily on our ability to execute code in libc. We set up the arguments in our code, then called the functions in libc that performed the system calls we required. For example, to make the socket system call, we wrote the snippet of code you see to the right.
At this point we had gained code execution and a shell on the camera by exploiting the Devil’s Ivy vulnerability. While other devices may run the service using gSOAP as a root user, this particular device only grants access to a shell as the unprivileged wsd user. Despite this, we were able to execute commands from the ONVIF specification that only a privileged user would normally be allowed. The permissions settings were located in a text file on the camera which is writeable by the wsd user. We used sed, available through busybox on the camera, to change the permissions of the SystemReboot command to allow an anonymous user to run it with the following line:
sed -i /SystemReboot=8/SystemReboot=f/ access_policy
After closing the connection, which caused wsd to restart and reload the access_policy file, we were able to send in the SystemReboot command and reboot the camera. An attacker could continually reboot the camera or change its network settings to prevent access to the feed.
Part of the response to the SystemReboot command, sent after modifying the permissions
There were many other commands available to us, including SetSystemFactoryDefaults, which allows an attacker to reset the system to its factory defaults. After the camera resets to factory defaults, it prompts the attacker to change the credentials, allowing the attacker alone to view the camera feed.
The camera prompts for a new password after being reset to factory defaults
View a demonstration of Devil's Ivy on the Axis M3004 security camera below or visit our blog to learn more about what happened when we reported it and how it affects millions of devices.