This post covers why I decided to build Uberducky and the technicial details on how it's actually built.
BackstoryI recently found myself in a situation where I needed a shell on a laptop. The usual methods wouldn't work: all network-accessible services were disabled on the laptop, and the owner of the laptop was a hard target with good screen locking discipline so a USB Rubber Ducky wouldn't do the trick. I did identify one weak point: the monitor that the laptop was regularly plugged into had USB ports hidden on the back. Ideally I'd be able to plug in something like a USB Rubber Ducky that I could trigger remotely. Distract the target while their screen is unlocked, send the signal, and the backconnect shell drops.
This is not a new idea: the Cactus WHID is an inexpensive tool for doing exactly this. In its default configuration, it broadcasts a WiFi network named "Exploit" that allows you to configure a duckyscript payload and trigger it remotely. Out of the box it's not the stealthiest option, but it's very cool and definitely worth a look.
I've worked extensively with Ubertooth (having written most of the BLE sniffing code) and I thought the platform would be a great fit for something a little stealthier. It has a USB microcontroller on which it is possible to implement the USB HID spec, and it has a generic radio that can be coerced to speak something akin to BLE.
Building UberduckyThe project started out as an empty firmware for Ubertooth because the main repo is too crufty. I did submodule in the main repo, as well as copying the firmware Makefiles into the root of the project. Quite a bit goes into bringing up the platform, but it's entirely hidden behind ubertooth_init(). While the Ubertooth repo wasn't really designed for out-of-tree builds like this, it was surprisingly painless to make it all work. Check out Makefile and common.mk if you're interested.
As a starting point, I made a very simple PoC that would inject a random keystroke at intervals. LPCUSB is the USB stack used by Ubertooth, and it comes with rudimentary HID example code. In no time flat I was injecting random keystrokes every few seconds.
I ran into my first snag at this point: if you insert the example HID keyboard into an Apple computer, it pops open a dialog asking you to press certain keys so it can identify the layout. This obviously won't work for stealthy keystroke injection, so I needed to find a way to prevent this from happening. I knew that this dialog does not open on plugging in Apple branded keyboards, so I theorized that the OS uses the USB vendor and product IDs to determine whether the keyboard has a known layout. Fortunately these values can be set within LPCUSB, and sure enough by changing them to an Apple keyboard (05ac:2227) the dialog never opened.
Just injecting one keystroke wasn't very interesting, so I set to expanding to a complete alphabet. I've implemented HID before over BLE so it wasn't completely new to me. See my BLE Slide Quacker and some older work I did hacking Bluetooth keyboards. General HID is extremely flexible, but keyboards can be implemented very simply. Every time a key is pressed or released, the keyboard sends an 8 byte report via a USB interrupt descriptor. The first byte encodes modifiers (such as control and shift) using a bitfield, and the third byte encodes all other keys. I'm not sure what the other six bytes are used for, so I left them all set to 0 and was never an issue!
In order to learn the HID keycodes as well as perform general debugging of my implementation, I used Linux's debugfs capabilities. Under most kernels from the last few years, this is mounted under /sys/kernel/debug. HID devices show up under /sys/kernel/debug/hid/[hex_string]/, and you can watch the reports come in live by cat'ing the events file. I used a normal keyboard and pressed each key, making note of the value of the report descriptor. I later found a handful of HID keycode tables online and the values matched up.
At this point it was a SMOP (small matter of programming) to convert a given key and set of modifiers into a valid HID report. You can see the code in hid.c.
Injecting DuckyscriptThe goal, of course, was to inject a sequence of keystrokes that would cause the target system to perform some malicious action. The people behind the USB Rubber Ducky invented a scripting language called Duckyscript to simplify this process and make it possible to share such scripts.
Duckyscript is designed for humans to read and write, but trying to implement a bulky parser on the microcontroller doesn't make much sense. Instead I wrote a Python parser to compile Duckyscript down into a binary language that's suitable for use on the MCU. The parser outputs a C file with an array that gets compiled into the firmware.
On the microcontroller, I use a state machine driven by the LPC1752's timer peripheral to run the script and actually do the keystroke injection. TIMER0 is configured to tick with a 1 millisecond period, and by using the match registers it is possible to cause an interrupt to occur at any given time. I chose 1 ms because Duckyscript encodes delays in units of milliseconds, and a single keypress takes around 10 ms to complete. The state machine lives in uberducky.c.
The compiled version of Duckyscript uses four opcodes: key, delay, string, and repeat. Key is a single keypress usually combined with a modifier, such as control-v, windows, or any printable ASCII character. Delay pauses the script for a specified number of ms. String is a sequence of characters all typed in a row, such as "echo hello". Repeat will repeat the previous opcode one or more times.
With the Duckyscript injector completed, the Ubertooth hardware was acting effectively as a clone of a real USB Rubber Ducky. As soon as it was inserted to a computer, it would run the script and inject the keystrokes. Now I just needed a way to trigger it wirelessly.
Triggering via BLEFor initial testing, I abused some of the range testing code from Ubertooth to trigger the Duckyscript injection using a second Ubertooth. While I personally have a lot of Uberteeth, most people probably only have one and wouldn't like to spend an extra $120 just for triggering their Uberducky. Ultimately I wanted a mechanism that would be accessible for most people with a single Ubertooth as well as being nearly impossible to accidentally trigger.
|The author owns many Uberteeth
I initially planned to lift some of the BLE code from Ubertooth, but like the rest of the firmware it is not written modularly and is overly complex for the task at hand. Instead I implemented it as simply and stupidly as possible using the CC2400's built-in FIFO. When a packet is received, the 32 byte FIFO fills. Once it is full, the radio stops receiving and goes into idle mode. Normally you might parse the packet header to read the length and turn off the radio when the full packet is received, but that would take more lines of code! Instead, Uberducky blindly receives 32 bytes no matter what, even if we're just decoding random radio noise. All the BLE code fits into fewer than 100 lines, check it out in ble.c.
The FIFO trick allows us to receive any advertising packet sent on a given channel, but we still need a trigger. Instead of even attempting to parse the packet, Uberducky searches for a magic 128-bit string of bytes anywhere within the packet. The magic string is defined at compile time and can be changed to suit the user's needs.
That only leaves us with how to send the trigger. Once again, a second Ubertooth is the simplest approach, but normal BLE devices also have a mechanism for sending 128-bit values: by including a list of 128-bit UUIDs in the AD data section of the advertising packet. The only trick is that the UUID needs to be within the first 32 bytes of the packet.
On Linux, it's possible to do this with the following commands:
sudo btmgmt add-adv -D 1 -u fd123ff9-9e30-45b2-af0d-b85b7d2dc80c 1 &&
sudo btmgmt clr-adv
This very briefly sets the system to advertise with the specified UUID and then disables advertising. The only caution is that if the script is very short it can be triggered multiple times.
It is possible to similar tricks on other OSes, but that is left as an exercise to the reader.
WishlistThere are a few things I'd love to implement in the medium to long term. Primarily I'd like the ability to update the Duckyscript or trigger UUID without reflashing the entire firmware. It would also be nice to change the magic UUID in the same way.
I envision two possible approaches. The first is to implement a USB serial port. While this reduces stealthiness, it is very straightforward and accessible. A second (and cooler) approach would be to implement a wireless protocol that could be managed using a second Ubertooth. The dream, of course, is to drive it entirely from BLE, but that would entail building an entire link layer to run on Ubertooth. Easier said than done!
Finally, I would love to see ways of triggering it from other platforms. In particular, OS X or Android would be stupendous and I imagine not much code.
If these ideas or any others strike your fancy, I will happily accept pull requests on GitHub.