Unsafe at any Altitude : The Parrot AR Drone 2.0

About a year ago, I did a co-op work term with Aeryon Labs of Waterloo.
I must have caught the drone bug working there, because while I was in Palo Alto working for Zugata, I bought a Parrot AR Drone 2.0 Elite Edition on impulse. For the most part, I’ve been pretty pleased with the AR Drone 2.0. The design’s showing its age, and the control gains could probably be tuned to take care of some of the excessive pitching I’ve noticed, but overall it’s a solid platform that records decent video at a decent price.

But that’s when you’re using the drone as intended. When you start sending it commands that it wasn’t intended to handle, it gets more interesting.

First, some background. The AR Drone 2.0 is essentially a flying Linux-based WiFi router. Clients connect to the unsecured WiFi access point that the drone broadcasts. Flight control is handled by a program titles program.elf. The drone is controlled with “AT Commands” — UDP datagrams containing plaintext commands separated by the carriage return character. These commands are received and processed by a program on the drone named program.elf. The protocol and control program are proprietary and specific to the AR Drone 2.0. Further details can be found in Parrot’s SDK documentation, but this should be enough context for the two bugs I’m going to describe.

program.elf occasionally needs to start subprocesses to perform certain tasks. The mechanism it uses is interesting: program.elf starts a shell subprocess, which it calls the Bash proxy. When it needs to start another subprocess, it writes to the bash proxy’s stdin. I’m not sure what advantages this pattern has compared to traditional fork-and-exec, it does introduce a potential vulnerability if user input is passed to the shell unsanitized.

The AR Drone 2.0’s lone network security measure is a pairing mode.
When enabled or disabled, the app uses the AT*CONFIG command to set the value of the config parameter “network:owner_mac” to either the phone’s MAC address, or “00:00:00:00:00:00” to disable it. The drone then invokes the script “/bin/pairing_setup.sh” with the supplied value, which reconfigures the drone’s iptables rules to drop incoming packets with other MAC addresses, or clear the iptables rules if disabled. As others (such as Mark Szabo) have noted, this means that this this security measure can be trivially circumvented with MAC spoofing. But the input is unsanitized, and the command that’s sent to the bash proxy is generated with simple string concatenation, so if you send a MAC address post-concatenated with shell commands, they will be executed.

# ls /
bin etc home mnt root tmp var
data factory lib proc sbin update
dev firmware licenses sys usr
# strace -fitp $(pidof program.elf) -s 2048 -e trace=recv,recvfrom,recvmsg,send,sendto,sendmsg,write 2>&1 | grep “mac\|pair”
[pid 883] 00:10:55 [4007a344] recvfrom(20, “AT*CONFIG_IDS=1,\”5ee79c02\”,\”465d213c\”,\”96e3654b\”\rAT*CONFIG=1,\”network:owner_mac\”,\”20:82:c0:86:b1:62; touch /proofofconcept\”\r”, 2048, 0, {sa_family=AF_INET, sin_port=htons(41256), sin_addr=inet_addr(“”)}, [16]) = 124
[pid 884] 00:10:55 [40267b54] write(49, “/bin/pairing_setup.sh 20:82:c0:86:b1:62; touch /proofofconcept\n”, 63 <unfinished …>
^C# ls /
bin etc home mnt root tmp var
data factory lib proc sbin update
dev firmware licenses proofofconcept sys usr

So, not only does the pairing measure not provide any meaningful security, but it also introduces a command injection vulnerability with full root privileges.

But there’s another bug with a more serious failure mode. I’ve nicknamed it the 4097 bug, because it occurs whenever a datagram of 4097 bytes or larger is received on the AT Command port. It starts in this procedure, which is called to service incoming messages on the AT command socket. Unfortunately, this procedure has a bug, shown here in this IDA callgraph snippet.

Before receiving the packet proper, the handler routine checks its size by doing recvfrom with length 0 and the flags MSG_PEEK and MSG_TRUNC. The return value is the full message size, and the message is not removed from the queue. If the size is less than 4096, it goes into a block that receives the message, removing it from the queue. If the size is greater than 4096, it logs an error message (“UDP packet is larger than maximum authorized size. Dropping packet.”) and returns. But it returns without actually dropping the message from the receive queue.

(Aside: I don’t really understand the reasoning behind checking the size of the incoming message in this manner. recvfrom already truncates incoming datagrams larger than the buffer size, so I don’t see why this bounds check would be necessary. At most, you’d avoid the cost of copying 1024 bytes into process space, but I don’t believe that the performance hit would be that much worse. Using recvfrom directly without the MSG_PEEK flag would ensure that the incoming datagram is always dequeued, even if it’s larger than the maximum buffer size, and using MSG_TRUNC you could still determine if the message was truncated based on the return value)

This bug would be bad enough on its own, making the drone unresponsive to new commands due to the message stuck in the queue, but it’s worse than that. The communication server thread of program.elf is built around an epoll loop. If there is data available on the socket corresponding to the AT Command port, program.elf branches to the above procedure. But because it never actually drops the message from the queue, the next time it proceeds to the epoll_wait call, it immediately returns because there’s data available to be read on the AT command port. Execution enters this handler routine again, and returns again without removing the packet from the queue, and again proceeds to epoll_wait, which returns immediately. The execution thus enters an infinite loop.

This infinite loop prevents program.elf’s communications thread from performing any tasks and pegs the microcontroller’s CPU at 100%. The drone will stop transmitting navigational data and stop sending its camera video streams. Worse, in this state the drone stops sending output to the motor controllers. Without new input, the motor controllers will happily continue to operate at their current speeds, putting the drone in an uncontrolled flight pattern.

This error state does not trigger the drone’s emergency state or cause any watchdog timer to time out, so the drone will continue on its uncontrolled flight pattern. Fortunately, other safety features like the excessive angle motor cutout remain operational, meaning that you can stop your drone if you’re within arm’s reach to hit it. Still, the end result is that this bug puts the drone in an unrecoverable, uncontrolled flight pattern that will end in a crash of some kind.

Finding these was fun, and diagnosing the issues was a good opportunity to practice with strace and IDA. But do these really matter? The AR Drone 2.0 is a toy that used to advertise itself as “The Flying Video Game”. No organization that I am aware of is using the AR Drone 2.0 as a platform for any “real” work. Still, the existence of these vulnerabilities speaks to design decisions made in the development of the drone, and the attitudes that influenced and enabled these decisions.

Network security clearly wasn’t a design concern for the AR Drone 2.0. By default, it broadcasts an unsecured WiFi access point with no option to use WPA/WPA2, and has an open telnet service. This isn’t entirely a bad thing — Though it does leave the drone completely open to attack, it also means that the AR Drone 2.0 is open for extension out of the box, and having this kind of access made finding these faults significantly easier. From a security standpoint these bugs admittedly aren’t particularly useful. The command injection vulnerability doesn’t give an attacker access to anything telnet didn’t already provide. It’s more the principle of the thing: this vulnerability isn’t very far removed from the textbook system-and-string-concatenation command injection.

Likewise, the 4097 bug could be an effective denial of service attack, but using telnet or even sending normal AT commands an attacker could just as easily cut power to the drone’s motors or turn it off mid-flight. What the 4097 bug demonstrates is a lack of robustness. The 4097 bug’s failure mode represents the AR Drone 2.0 failing in an incredibly unsafe manner.

These bugs are relatively simple, and I feel that stricter code validation and more rigorous testing could have caught them. I also feel that this wasn’t done because the AR Drone was conceived of as a toy. That’s a failing — software that controls anything that flies, even if it’s meant to be used by hobbyists (especially if it’s to be used by untrained hobbyists, and the AR Drone 2.0 is) should be robust enough to not go into an uncontrolled flight pattern from a single instruction.

I attempted to disclose these vulnerabilities to Parrot’s engineering team via their support line and developer forum, but it doesn’t seem like they’re supporting the AR Drone 2.0 anymore. This isn’t too surprising, given that the AR Drone 2.0 has been succeeded by the Parrot Bebop and Parrot Bebop 2. To Parrot’s credit, the Bebop and Bebop 2 have WPA2 security on their access points, so if similar flaws exist in these models, at least they cannot be freely exploited. That said, if Parrot wants to hook me up with a Bebop 2 so I can play around with it, I’ll gladly take it.