Introduction
This is the post related to a work I presented at the 2023 Ekoparty Security Conference’s Hardware Hacking Village, where I talked about a personal vulnerability research project that started out as a reverse engineering effort in order to understand more about an old, generic FPV drone I had, which could be controlled and viewed through a mobile app that was no longer supported in modern android versions. The story starts with some years after I changed my phone since when I first bought and used this quadcopter drone.
One day I wanted to fly said drone, which required me to open the app that shows the drone camera feed (which its crucial for actually flying it outside view-range). However the app never fully opened and it crashed after showing a black screen. After some web search, it seemed I wasn’t the only one experiecing this, as seen on the Google Play page comments for said mobile app. This prompted a mission in my near-future tasklist: bring a dead android app back to life using the powers of RE, sharing the efforts to the open-source community some day (while learning on the way).
RE Process
For this, I figured that I would need first to get some grasp or understanding about the inner workings of the app and its way of communicating with the drone and controlling it, but also knowing how to send commands such as: taking a video, storing said video on the drone’s filesystem, taking a screeshot from the video feed, shuting down the drone, and changing other settings. I first started reversing the APK, reading the decompiled Dex classes written in Java, but then I noticed that most (if not all) the interesting functionality was implemented on embedded native libraries (shared objects) that came with the APK, which shifted me to go back to my comfort zone with a twist: understanding ASM binaries BUT in ARM’s Thumb ISA.
After a while getting close with both lewei shared objects (liblewei63.so
and liblewei-2.5.so
), and some the few thousand method names, I started filtering for symbol names that started with java_com
, as those were closer in meaning to the high-level functionalities of the drone (start record, download file, to name a few). Some of these functions made sense in respect to what could be seen on the App GUI, but some others weren’t. Then, after doing the static analysis of these libs and eventually getting stuck, I resorted to something more dynamic for understanding the custom protocol. This consisted in connecting a rooted Android phone (which was old enough to run the app) to the drone’s WiFi AP and then extract the network traffic.
In order to “see” said traffic, I used tcpdump
while storing packet sessions inside the android device for each command that I was trying to recognize/characterize, in order to properly keep measurements clean. For drone controlling packets, would slowly lift just one of the two joysticks on the app GUI, changing one axis, and then stopping the network capture. With more hypothesis-based experiments (e.g. a certain byte-field constitutes X position), I ended up discovering the fundamentals in how the drone communicates from within the app, how it sends/receives the video-feed with the drone camera, and also how it sends/receives commands. There are three type of packets: client metadata requests, drone metadata responses and drone data stream packets. The metadata packets from the client contain which command to trigger and the size of command data, following the data for the command (if it requires it). The metadata response (sent from the drone) basically specifies how much bytes the drone will send inside the response. Of course, data stream packets consists of the response data from the drone after some command is triggered.
With this I had enough information to develop an app that no longer needed to have a library for sending raw bytes to pilot the drone, which meant the most likely source for the crash of the app shouldn’t persist when one runs it in a newer android smartphone. Or rather, it would be way easier to maintain and upgrade in a modern mobile-app development framework, like Flutter. Then, the app wouldn’t require native libraries compiled for a specific target architecture, and no weird FFI functions, as all drone interactions could be wrapped inside a function that sends the raw TCP and UDP bytes through the sockets bound to the ports for controlling (50000/UDP), and for triggering commands (7060/TCP) on the drone itself.
What else could I do now with that info about the communication protocols? That’s right, trying to find interesting low-level vulnerabilities and non-documented functionalities present on the drone firmware. But in order to make this feasible, I needed to somehow get the binaries that were running on the drone first. That was enough justification for prying open this device, reverse engineering the PCB and attempting to dump the firmware through simple yet absolute methods: finding a forgotten protocol interface port (UART, SPI, I2C, JTAG, SWD) that connects us to a TTY or gives us hardware debugging powers, reading flash memory from a discrete IC, or reading flash memory from a SoC that has known vulnerabilities. Sadly, there were no easy-paths to take from here, so I reassembled the device and kept on.
Afterwards, I made a little research about drone security on academia, and I found that the same company (UdiRC) that sold these drones already had some appearances on a paper 1 that talked about vulnerable firmware that was shared between many drone families, and was also sold by different companies besides UdiRC (under different plastic-cases and brand names). Such a very complex chain-of-supply for RC toys (a racconto of what we’ll be encountering again). Anyway, after nmap scanning the drone, I found that one of the vulnerabilities that was mentioned in that paper was also present on this drone at the exposed FTP service. What’s more funny is that this drone ran a full Linux OS (no RTOS) on the ARMv5-based SoC which was its brain.
With this in mind, I overwrote the /etc/shadow
with a known root password and got inside with telnet. This is where the software RE and vuln search phase started. I followed the boot-time’s path of execution for all scripts and binaries, looking for the mention of even more scripts and binaries inside files at /etc/init.d
. The main binary that handled the commands and was bound to the ports mentioned before was lewei_cam
. I also found other binaries that weren’t running as processes, but appeared as commented calls inside init.d
scripts (where some didn’t exist at those given paths, and some weren’t mentioned but had binaries under common dirs). Other interesting pieces of the Anyka firmware were kernel drivers that weren’t loaded in the kernel, which were found at the /root/
home dir.
When analyzing the lewei_cam
binary I immediately went searching for vulnerable calls to system
. Sadly, all of them had hardcoded parameters and thus were not useful. Then, I went searching for possible functions that would handle the raw-byte commands, in order to see how where they being parsed and processed, and then at which other functions would this tainted data go to. For this intro post, I skipped a lot of other steps and paths I took, but mainly the relevant thing to mention is that I reached a function that was widely used inside the command handler function when receiving data from the mobile phone. That’s where I found then a BOF on a custom recv
function that would write size bytes into a local buffer in stack. The thing was that size was a number that the attacker controlled as a field in the client metadata packets sent while communicating. So basically any user could trigger that BOF if connected to the drone’s AP.
In order to actually validate this hypothesis, which was at that moment just pure static RE of the firmware, I needed a way to actually test it in a powered-on drone. Dynamic Reverse engineering was possible through the usage of gdbserver
after being compiled from source for this old architecture (ARMv5) target. However, finding a toolchain that worked for this specific target (ARMv5) took me a lot of time. Eventually I found the exact same toolchain used to compile the binary (lewei_cmd
) when searching for compiler strings on the drone binaries. After compiling, uploading and succesfully running gdbserver
inside the drone, I could finally have a clear vision of a process memory (yippieee).
With gdb on my side, I ended up validating the previous hypothesis of being able to change metadata for raw command packets, which was necessary for the local BOF. This was it. After changing the size for the sent command data, any sequence of command data that was over 20 bytes would corrupt the stack (first writing over BP, then reaching PC). The firmware on the drone didn’t check for any kind of max value violation, or mismatch between the metadata packet size field, and the command data packet size. Eventually I found a specific path inside the command handler function that didn’t require many favorable conditions in order to steal the program execution without crashing the program afterward, so I followed it. This required to successfully survive a call to free with a pointer value that was being corrupted on our BOF payload, however this could be easily done by choosing any address inside a writable segment, or using a null pointer.
After this, returning from lewei_handle_cmd
in the routine epilogue (pop
) would set the PC register with a value from within our payload. Now, in order to fully exploit the drone, first a ROP-chain for ARM must be crafted through the usage of pop
gadgets and other ARM ASM instructions. The good thing was that the SoC works with 16-bit addresses and the server binary didn’t have ASLR enabled (it’s a really old kernel).
I also talked a little about chain-supply implications at the end of the presentation, as there are at least 3 different companies involved for the final product: Le-wei, related to the camera module and the mobile app’s native library; Anyka, programming the firmware that runs on the drone itself (with some stuff from Le-wei); and UdiRC, selling the final RC toy that includes the mobile app, the drone firwmware and a hard plastic case re-skin of the quadcopter.
One can see from the past paper1, and also by searching for terms like “lewei_cam”, “admin”, “172.16.10.1” and other strings inside the firmware and android native libraries, that these packages and libraries are not tailor-made, but more of an IP that contains different functionalities that the drone maker should try to put to use (in the mobile app) if the hardware supports it. Evidence for this is that the drone server itself (lewei_cam
) had more command paths that the UdiRC FPV drone uses in the mobile app.
Anyway, there’s more pictures with nitty-gritty details in the slides section. So I encourage you to skim or read the more interesting parts.
Slides
These are the slides I used at the 2023 presentation. They have way more details (or so I hope) than the previous introduction, in case you want to go in-depth with some specific part of the story.
Featured at PwnLive’s Twitch
After some months, local Vulnerability Researcher and old-time CTF player Nox, invited me for one episode in PwnLive, a Twitch program that he hosts where he invites Latin-american based hackers and cybersecurity researchers to talk about specific topics that they come with (e.g. malware analysis, reverse engineering, exploit development), while having a casual conversation with both the presenter and the chat.
I really enjoyed having a talk at the program. It felt like having a chat with an old-time friend while discussing even more topics that my slides didn’t touch or went in-depth. The whole episode ended-up being like 2 hours, but I hope some people find the extra bits discussed in there useful for their practical research endeavors.
YouTube Video
Note: the whole video is in Spanish