Why do this?
Reports of successful hacks against Internet of Things (IoT) devices have been on the rise. Most of these efforts have involved demonstrating how to gain access to such a device or to break through its security barrier. Most of these attacks are considered relatively inconsequential because the devices themselves contain no real data of value (such as credit card numbers or PII). The devices in question generally don't provide much value to a botnet owner as they tend to have access to lots bandwidth, but have very little in terms of CPU and RAM.
However these devices get more interesting to sophisticated attackers when they can be used to establish a persistent point of access in a network. Putting a callback backdoor into a webcam, for example, gives a hacker full-time access to the network without having to rely on infecting a laptop, workstation or a server, all of which are usually under high scrutiny and may often be patched. On a tiny device, there is no anti-virus and no endpoint protection. In fact, no one thinks of the device as having software on it at all. This makes these devices potentially inviting for persistent attackers who rely on stealthy channels of command-and-control to manage their attacks.
The downside for the attacker is that this class of devices doesn't usually have any persistent storage that is really usable. Instead, they use nvram to store configuration and the flash ROM to store the running code. So the attacker's hope for real persistence rests on being able to control what will be in the flash ROM. In this blog, we will explore how difficult it is to create a new flash image that could contain all the tools we need to have a real persistent backdoor to the network on which the device is installed. Once we have such a flash image, putting it in place could involve "updating" an already deployed device or installing the backdoor onto the device somewhere in the delivery chain - i.e. before it is received and installed by the end customer. For this experiment to be meaningful, it is imperative that the device continue to perform its normal function otherwise it would immediately raise suspicion or cause the customer to replace the device with a working version.
In this scenario, the Vectra Threat Labs team purchased a consumer-grade D-Link WiFi web camera for roughly $30 USD*. Cracking open that little wonderful plastic case was an amazing experience with a kind reminder that a Leatherman is not the right tool for every job ...
A quick look at the circuit board shows:
- WiFi antenna
- Ralink RT5350F
- SDram M12L2561616a-6t
- Flash Rom MX25L3205
Dumping the flash memory
Given the presence of the flash memory chip on the PCB, we surmise that this is where the data/code is likely kept for persistence. So the first thing to do is to dump the content of that chip to see what is stored on it.
After hooking up a Bus Pirate to the board, we can use flashrom to dump the content.
#flashrom -p buspirate_spi:dev=/dev/ttyUSB0,spispeed=1M
flashrom v0.9.7-r1782 on Linux 4.0.0-kali1-amd64 (x86_64)
flashrom is free software, get the source code at http://www.flashrom.org
Calibrating delay loop... OK.
Found Macronix flash chip "MX25L3205(A)" (4096 kB, SPI) on buspirate_spi.
Found Macronix flash chip "MX25L3205D/MX25L3208D" (4096 kB, SPI) on buspirate_spi.
Found Macronix flash chip "MX25L3206E" (4096 kB, SPI) on buspirate_spi.
Multiple flash chip definitions match the detected chip(s): "MX25L3205(A)", "MX25L3205D/MX25L3208D", "MX25L3206E"
Please specify which chip definition to use with the -c <chipname> option.
Now we can dump the content of the flash chips for further analysis.
#flashrom -p buspirate_spi:dev=/dev/ttyUSB0,spispeed=1M -c 'MX25L3205(A)' -r MX25L3205-A
Analyzing the flash dump
Once we have a nice dump of the flash we can use binwalk to determine what is inside of it.
#binwalk -Me MX25L3205-A
DECIMAL HEXADECIMAL DESCRIPTION
0 0x0 uImage header, header size: 64 bytes, header CRC: 0x11BEF629, created: Tue Feb 3 11:07:53 2015, image size: 111116 bytes, Data Address: 0x80200000, Entry Point: 0x80200000, data CRC: 0xCD95F789, OS: Linux, CPU: MIPS, image type: Standalone Program, compression type: none, image name: "SPI Flash Image"
91040 0x163A0 U-Boot version string, "U-Boot 1.1.3"
105424 0x19BD0 HTML document header
105777 0x19D31 HTML document footer
105780 0x19D34 HTML document header
105979 0x19DFB HTML document footer
106140 0x19E9C HTML document header
106840 0x1A158 HTML document footer
210495 0x3363F PEM certificate
211671 0x33AD7 PEM RSA private key
327680 0x50000 uImage header, header size: 64 bytes, header CRC: 0xABF213A9, created: Tue Feb 3 11:07:48 2015, image size: 3730981 bytes, Data Address: 0x80000000, Entry Point: 0x8038B000, data CRC: 0x2829F3C1, OS: Linux, CPU: MIPS, image type: OS Kernel Image, compression type: lzma, image name: "Linux Kernel Image"
327744 0x50040 LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 6394309 bytes327744 0x50040 LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 6394309 bytes
So the format of this firmware consists of a u-boot and a Linux kernel and image.
We could have used dd, lzma or cpio to extract the content of the firmware or we can let binwalk do this work. We still need to extract the last step of the cpio image to see the content of the image.
#cpio -ivd --no-absolute-filenames -F. 0.cpio
Once this last step is done, we can access the Linux image filesystem.
One interesting binary in the filesystem is the /bin/upgradefw, this seems to be the executable used to performed verification and update of the firmware.
./bin/upgradefw: ELF 32-bit LSB executable, MIPS, MIPS-II version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, stripped
Analyzing the upgradefw binary
For this section we are going to rely on IDA Pro as the tool of choice to reverse-engineer the upgrade binary.
IDA is able to take a very good first pass at the binary which makes it way easier to analyze. Following the code path from the main function brings us pretty rapidly to a function named "check" that does most of the work of verifying if the flash image is valid before sending it to mtd_write.
The validation done by the upgradefw binary seems to include a few crc32 checks, memmem/strstr checks and some loop that computes a value and compares it to a fixed value or two.
The logic flow of the check function between the entry point and a success return looks roughly like this:
- Check if file opened correctly
- Check size of the file
- Load file and check that read worked
- Check the "Signature: "
- Check the "Release: "
- Compare the Release to the current Release
- Uboot/uimage check routine
switching to x86 here for the 55AA55AA checksum.
Adding a backdoor
At this point, adding a backdoor roughly devolves to adding a service inside a Linux system - in our case, all we want is a simple connect-back Socks proxy. This can either be accomplished with a srelay and netcat in the startup script or more optimized C code, or one could go with a simple callback backdoor with a shell using netcat and busybox which are already present on the system.
It's always a good idea to be as lightweight as possible on the features added - after all, we don't have a full laptop to work with here, but rather a tiny webcam with 4MB of ROM. So adding too much functionality is just going to break the software. While we are making the modification, we can also remove the capacity to reflash the device in the future. This would prevent an administrator-initiated firmware update which would remove our backdoor.
After spending some time building a repackaging script, we found a nice script on the Ralink website that ended up being pretty useful.
use it with:
make -f Makefile.4M
After that, all that is needed is a fix to the checksum in the file. This can either be achieved with a RaLink utility name addchecksum or by manually fixing the checksum. The offset/range the checksum use can be discovered in both the upgradefw or addchecksum binary. And as usual... check your endianness.
Testing a connect back
Using the telnetd / busybox / netcat we can bring back a telnet socket to an outside host to have remote persistence to the webcam. With the webcam acting as a proxy, the attacker can now send control traffic into the network to advance his attack, and likewise use the webcam to siphon out stolen data.
Escape character is '^]'.
(none) login: admin
BusyBox v1.12.1 (2015-11-11 05:41:04 UTC) built-in shell (ash)
Enter 'help' for a list of built-in commands.
# ls /bin
iwpriv pcmcmd nvram_daemon ntpclient sounddb ipush touch pwd ls cp
ov7740 switch mii_mgr mtd_write notifystream schedule sync ps login chmod
uvc_stream gpio ated msmtp mydlinkevent lanconfig sleep ping kill cat
mail nvram_set reg mDNSResponder imagetp iperf sh mount grep ash
i2c nvram_get pppoecd lld2d upgradefw inadyn sed mknod echo busybox
swing ralink_init openssl disablebonjour audiopush umount rm mkdir date alphapd
So does all this mean that D-Link's web camera has a major security issue? Not necessarily - we get what we pay for, and asking a vendor who sells a webcam on Amazon for $30 to provide safe firmware update features which would require a TPM or a specialized chip to verify the content and signature of a software update is not very realistic. Rather the point of this demonstration is to highlight the real impact that IoT devices pose to the attack surface of a network. As we've shown, the barriers to hacking these devices are relatively low, and even the most basic devices can provide the plumbing for a persistent command-and-control channel. While these devices are low-value in terms of hard costs, they still matter to the security of the network, and teams need to keep an eye on them to reveal any signs of malicious behavior.
*Vectra disclosed the issue to D-LInk in early December 2015. As of January 7, 2016, the company has not provided a fix.