How to Test Embedded Systems Security Easily with Mayhem
Professionals working with embedded or low-level software often face challenges in setting up effective security testing, especially with the complexities of IoT firmware.
In this blog post, we will demonstrate how Mayhem can be used to easily set up fuzzing campaigns in these types of embedded systems, using IoTGoat’s dnsmasq as an example.
The IoTGoat project is a deliberately insecure firmware project based on OpenWrt and maintained by OWASP as a platform to educate software developers and security professionals with testing commonly found vulnerabilities in IoT devices. The vulnerability challenges are based on the OWASP IoT Top 10, as well as "easter eggs" from project contributors. (For a list of vulnerability challenges, see the IoTGoat challenges wiki page. )
In this post, we will be trying out the binary runtime analysis section of the challenge and assess how easy it is to find vulnerabilities in it.
Getting Started
There are a few suggested ways to get started with trying out IoTGoat, including: (1) downloading the raw firmware package, (2) downloading a custom virtual machine, or (3) building from source. In this write up, we'll skip on custom build instructions and we will be testing out IoTGoat using the raw firmware package as downloaded from the releases page.
Extracting and Dockerizing our target: dnsmasq
Dnsmasq is a popular open-source lightweight DNS forwarder and DHCP server used in IoTGoat's firmware. The installed Dnsmasq package (version 2.73) is vulnerable to a stack-based buffer overflow when using the DHCPv6 service - CVE-2017-14493. See Google Security's blog post for a few more details on the vulnerability.
So, in theory, all we have to do is set up an automatic testing campaign against dnsmasq's DHCP service. If that binary was already in a runnable docker image we would be done, since Mayhem can take these in with zero effort. However, in this particular case, the dnsmasq target is embedded in the released firmware package, a problem quite common when trying to analyze IoT firmware.
Luckily, there are excellent firmware extraction/rehosting systems, including Binwalk, Firmadyne, or the more recent Greenhouse. Let's try them out and see if we can auto-extract the IoTGoat firmware. Our goal: autodockerize the IoTGoat target so that it's runnable by Mayhem.
We decided to use the simplest of the tools above for extraction to limit the "magic" done behind the scenes. We put together a 10-line python extractor:
def main():
with tempfile.TemporaryDirectory() as tmpdirname:
os.chdir(tmpdirname)
subprocess.check_output(["binwalk", "-e", "-M", "-0", "root", "/input"])
# look for the following directory: "/etc" and copy "/" to "/output"
for root, dirs, files in os.walk(tmpdirname):
if "etc" in dirs:
subprocess.check_output(["cp", "-ra", os.path.join(root, '.'), "/output"])
break
with open("/output/Dockerfile", "w") as f:
f.write("FROM scratch\n")
f.write("COPY . /\n")
and planted it in an iot-dockerizer image to have a complete tool doing the dockerization for us:
FROM ubuntu:24.04 as extractor
RUN apt update
RUN DEBIAN_FRONTEND=noninteractive apt install -fy git binwalk unzip build-essential liblzma-dev liblzo2-dev zlib1g-dev wget
RUN git clone https://github.com/ethan42/sasquatch.git && cd sasquatch && ./build.sh
COPY extract.py /usr/bin/extract
CMD ["extract"]
After building the above, we're ready to try auto-dockerizing IoTGoat:
$ docker pull index.docker.io/ethan42/iot-dockerizer:1
...
$ mkdir output
$ docker run -v `pwd`/IoTGoat-x86.img:/input -v `pwd`/output:/output index.docker.io/ethan42/iot-dockerizer:1
WARNING: Symlink points outside of the extraction directory: /tmp/tmpe8c4f_56/_input.extracted/squashfs-root-0/usr/bin/ssh -> /usr/sbin/dropbear; changing link target to /dev/null for security purposes.
...
WARNING: Symlink points outside of the extraction directory: /tmp/tmpe8c4f_56/_input.extracted/squashfs-root/etc/ppp/resolv.conf -> /tmp/resolv.conf.ppp; changing link target to /dev/null for security purposes.
$ cd output/
~/output$ ls
Dockerfile bin dev dnsmasq_setup.sh etc lib mnt overlay proc rom root sbin sys tmp usr var www
~/output$ find . -name dnsmasq
./usr/sbin/dnsmasq
./etc/init.d/dnsmasq
~/output$ file ./usr/sbin/dnsmasq
./usr/sbin/dnsmasq: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-i386.so.1, no section header
Perfect! Looks like we have an extracted filesystem + Dockerfile + a script to setup the dnsmasq service! Let's set up the dockerfile to run dnsmasq:
FROM scratch
COPY . /
COPY ./etc/config/network.bak /etc/config/network
COPY ./etc/dnsmasq.conf.bak /etc/dnsmasq.conf
CMD /usr/sbin/dnsmasq --no-daemon -k
Let's build and push it:
~/output$ docker build -t index.docker.io/ethan42/iotgoat:dnsmasq .
STEP 1/5: FROM scratch
STEP 2/5: COPY . /
--> Using cache d78ae62d0e183d15151a5f4c9e71083375962b8e3affeccb514327e0c8220a4e
--> d78ae62d0e18
...
--> 7572d40b5edc
Successfully tagged docker.io/ethan42/iotgoat:dnsmasq
Successfully tagged docker.io/library/iotgoat:dnsmasq
7572d40b5edcaacffc6063068507bfe77970abcdd838093fe02c294d09380668
~/output$ docker push index.docker.io/ethan42/iotgoat:dnsmasq
Testing IoTGoat's dnsmasq With Mayhem
Now all we have left to do is to configure the Mayhemfile to get testing started. The application is using DHCP over IPv6, so we need to use a udp://[::1] url for our network block. The dnsmasq exercise docs have several lengthy steps, but this configuration option should be all that's needed for Mayhem. Note that this step is typically very cumbersome to set up with most fuzzing engines but in Mayhem it comes out of the box. Our configured Mayhemfile looks as follows:
project: ethan42/iotgoat
target: dnsmasq
image: ethan42/iotgoat:dnsmasq
cmds:
- cmd: /usr/sbin/dnsmasq --no-daemon -k
network:
url: udp://[::1]:547
client: false
timeout: 4.0
All we have to do is start our run:
$ mayhem run .
/var/folders/hq/wbzzm4791rv1tr0kz_xtpzpm0000gn/T/tmpiqxfqb9y/Mayhemfile 100% |###############| Time: 0:00:00 314.4 B/s
Run started: ethan42/iotgoat/dnsmasq/1
Run URL: https://app.mayhem.security:443/ethan42/iotgoat/dnsmasq/1
ethan42/iotgoat/dnsmasq/1
That's it! Now we let Mayhem run its auto-analysis and we can check results after a break.
Checking out Mayhem's Results
We accidentally left Mayhem analyzing dnsmasq
over the weekend and when we came back we saw that it identified multiple defects and it was making steady progress generating test cases that identify new code coverage across the entire duration of the run:
Looking at one of the first High Severity (8.6) discovered by Mayhem we see:
Huh! A backtrace starting at address 0x4f4f4f4f
. That looks suspicious enough. Double clicking on the contents of the test case, we see that 4f4f4f4f
is a byte sequence within the test case and also happens to be the contents of eip
:
The definition of a control flow hijack! Let's repro this locally by starting up the server:
BusyBox v1.28.4 () built-in shell (ash)
/ # /usr/sbin/dnsmasq -k --no-daemon --port 547 dnsmasq: started, version 2.73 cachesize 150
dnsmasq: compile time options: IPv6 GNU-getopt no-DBus no-i18n no-IDN DHCP DHCPv6 no-Lua TFTP no-conntrack no-ipset no-a
uth no-DNSSEC loop-detect inotify
dnsmasq-dhcp: DHCPv6, IP range fdca:1:2:3:4::2 -- fdca:1:2:3:4::1233, lease time 1h
dnsmasq-dhcp: router advertisement on fdca:1:2:3::
dnsmasq-dhcp: IPv6 router advertisement enabled
dnsmasq: no servers found in /etc/resolv.conf, will retry
dnsmasq: read /etc/hosts - 4 addresses
and then sending over the payload:
nc -u ::1 547 < ./testsuite/a8495b8b14c03301580c2b647cd5912050518f9778e93920b7dd7132b30965da
which results in:
dnsmasq: no servers found in /etc/resolv.conf, will retry
dnsmasq: read /etc/hosts - 4 addresses
Segmentation fault (core dumped)
/ # dmesg | grep segfault
[252480.591397] dnsmasq[15183]: segfault at 4f4f4f4f ip 000000004f4f4f4f sp 00000000ffbe2530 error 14 in libgcc_s.so.1[ea961000+13000] likely on CPU 0 (core 0, socket 0)
There it is! Mayhem generated a control hijack for IoTGoat (CVE-2017-14493 repro) with minimal effort on our end!
This demonstrates Mayhem’s effectiveness in detecting critical vulnerabilities in embedded systems and OT devices with minimal setup.
Conducting this kind of security testing in embedded systems is important to protect your components from attacks and potential platform takeovers. It ensures that vulnerabilities like CVE-2017-14493 can be identified and addressed proactively.
Mayhem for OT Security
Developed by professional hackers, Mayhem automates the detection of vulnerabilities in software and embedded systems. By generating thousands of tests and providing automated triage, Mayhem ensures comprehensive security coverage, helping you identify and mitigate risks efficiently. Get a demo to see how Mayhem can make OT security easy.
(All code and configuration used in this blog post is available on GitHub.)
Add Mayhem to Your DevSecOps for Free.
Get a full-featured 30 day free trial.