I thought it would be fun to wrangle together a device that acts similarly to the HAK5 USB Rubber Ducky (i.e. presents itself as a USB keyboard device and interacts with the target by sending rapid keystrokes), but is flexible w.r.t. abilities+implementation (and maybe cheaper!). In addition to just scripting keystroke commands, my bigger goal was to be able to transfer files (payloads? ¯\_(ツ)_/¯) to targets, even if restrictions are in place (like USB mass storage devices being prohibited by a policy applied in the target OS). Using a Raspberry Pi Zero (NOTE: I will include archive.org "wayback machine" snapshot links as wbm alongside all links here in an attempt to preserve-references-over-time for this post as much as possible) that I had lying around (a $15-or-less device to purchase online), an old microSD card, a leftover button, a tiny bit of solder, and a number of helpful guides/programs I found online, I cobbled something together:
And when you connect it to a target computer/device's USB and press the button, it sends your data as keystrokes on a keyboard:
In this post, I'll cover all the steps I took to get this working and share some things I learned along the way, but I'll caveat here that this isn't the most polished-of-guides. 😅
Enjoy!
Poking Around...
- setup the microSD card with a bootable Linux image
- install the microSD card into the RPi Zero, boot it, login, and update password and hostname
- setup the RPi for keystroke goodness
- setup for payload delivery
- add a button (optional!)
- use!
MicroSD Setup
- unplug the SD-to-USB card reader (or remove the microSD card slot from the built-in reader)
- replug the SD-to-USB card reader back in (or reinsert the microSD card into the built-in slot)
- note that a new /Volumes/boot drive is now mounted (this is the SD card!)
diskutil eject /Volumes/boot
RPi Boot and Setup
ssh pi@<hostname>.local
using the updated password you set. Neat!
Create the Illusion
pi@fakekeyboard:~ $ sudo poweroff
pi@fakekeyboard:~ $ wget https://github.com/girst/hardpass-passwordmanager-mirror-of-git.gir.st/archive/master.zip
pi@fakekeyboard:~ $ unzip master.zip
pi@fakekeyboard:~ $ cd hardpass-passwordmanager-mirror-of-git.gir.st-master/send_hid/
pi@fakekeyboard:~/hardpass-passwordmanager-mirror-of-git.gir.st-master/send_hid $ make
pi@fakekeyboard:~ $ echo "whoami" | sudo ./scan /dev/hidg0 1 0
pi@fakekeyboard:~/hardpass-passwordmanager-mirror-of-git.gir.st-master/send_hid $ echo "whoami" | sudo ./scan /dev/hidg0 1 0whoami
pi@fakekeyboard:~/hardpass-passwordmanager-mirror-of-git.gir.st-master/send_hid $ whoami
pi
So our one line command successfully sent scan the string "whoami" to send as keystrokes, which it did in the middle line above, which resulted in the pi output at the end (telling us we are Linux user "pi" on the system). Neato, we're a keyboard!! :)
Completing the Buildout
- base64 encode the payload you want to deliver
- setup the RPi to send that encoded payload to the scan utility (just like we did with the 'echo' command in the previous section)
- try it out!
Encoding the Payload
scp <payload filename> pi@<hostname>.local:
pi@fakekeyboard:~ $ base64 -w0 <payload filename> > payload.base64
This will give you a base64-encoded version of your file (named payload.base64), which also takes on an additional ~33% more bytes in size compared to the file size, just FWIW.
Setup to Send
#!/usr/bin/env bashSCAN_CMD='/home/pi/hardpass-passwordmanager-mirror-of-git.gir.st-master/send_hid/scan /dev/hidg0 1 0'echo "cat << EOF > /tmp/payload.base64" | sudo ${SCAN_CMD}cat /home/pi/payload.base64 | sudo ${SCAN_CMD}echo | sudo ${SCAN_CMD}echo "EOF" | sudo ${SCAN_CMD}exit $?
pi@fakekeyboard:~ $ wget https://gist.githubusercontent.com/pbarry25/50b5c409cbb14791e239790cff30b0c4/raw/2d6b6c884849daf7a88a4e5c8f408bce51e34ad5/gogokeystrokes.sh
pi@fakekeyboard:~ $ chmod +x gogokeystrokes.sh
sleep 15if [ "X`who | /bin/sed -n '/^pi/p'`" = "X" ]; then /home/pi/gogokeystrokes.sh; poweroff; fi
/usr/bin/isticktoit_usb # libcomposite configurationsleep 15if [ "X`who | /bin/sed -n '/^pi/p'`" = "X" ]; then /home/pi/gogokeystrokes.sh; poweroff; fiexit 0
These two lines we added will, at the final part of the RPi booting up, wait 15 seconds to see if anyone has logged into the RPi as the 'pi' user via ssh and, if no one has, then run our gogokeystrokes.sh script to send the payload via keystrokes and then power down the RPi. This allows the RPi to automatically deliver our payload when inserted to a target, while still allowing us to quickly ssh into the RPi on bootup if we want to update the payload or make other changes.
A Small Fly in the Ointment
pi@fakekeyboard:~ $ wget https://gist.githubusercontent.com/pbarry25/cbae67450bb5ecd16a2f5f5f99e410e5/raw/ad7b86140c5e18dbe766801bb325fe270967b0eb/main.c.diff
pi@fakekeyboard:~ $ patch -p1 < main.c.diffpatching file hardpass-passwordmanager-mirror-of-git.gir.st-master/send_hid/main.cpi@fakekeyboard:~ $ cd hardpass-passwordmanager-mirror-of-git.gir.st-master/send_hid/pi@fakekeyboard:~/hardpass-passwordmanager-mirror-of-git.gir.st-master/send_hid $ makegcc -std=c99 -Wall -Werror main.c scancodes.c -o scan
pi@fakekeyboard:~/hardpass-passwordmanager-mirror-of-git.gir.st-master/send_hid $ ~/gogokeystrokes.sh
pi@fakekeyboard:~ $ base64 -d /tmp/payload.base64 > payload
Take it for a Spin!
pi@fakekeyboard:~ $ sudo poweroff
For the Button Mashers Out There...
pi@fakekeyboard:~ $ sudo apt install python3-gpiozero
Once installed, you can grab a little script from my GitHub gists (wbm) that we'll use to read the value of our button:
pi@fakekeyboard:~ $ wget https://gist.githubusercontent.com/pbarry25/9b2e316e2c9e0818e85bc5b24e5e1579/raw/c34b22275fbe902cb88de09a72d6ad050c4a0695/button-check.py
pi@fakekeyboard:~ $ chmod +x button-check.py
This script will allowing us to initiate the payload keystroke delivery on button press! Let's update our /etc/rc.local to use it instead of the delay-and-dump mechanism. Edit the /etc/rc.local file on your RPi to remove the two lines we added in the section above, and add the following one line in their place, just before the final 'exit 0' line in the file:
/home/pi/button-check.py &
This should leave you with an /etc/rc.local file that has the last handful of lines looking like:
/usr/bin/isticktoit_usb # libcomposite configuration
/home/pi/button-check.py &
exit 0
So, as the RPi is finishing up the boot process, it will run our button-check.py script as a background process. That script will wait in a loop until the button is pressed, at which point it will playback the encoded payload as keystrokes followed by a shutdown of the RPi. You can test it here by running the script directly and then pressing the button!
pi@fakekeyboard:~ $ ./button-check.py
If everything is working as expected, the script will detect your button press (NOTE: the script is only checking the button once per second, so you may need to press it for up to 1 second to be "detected"), send the keystrokes, and then powers off the RPi (NOTE: if this doesn't work, you might try rebooting your RPi and pressing the button once it has booted back up, as I can't recall if the GPIO Zero package/deps need a reboot).
At this point you should be good-to-go for "target practice"!
Parting Thoughts...
I found this project to be pretty fun, pretty quick (a few of evenings of poking around), inexpensive, and with the potential to grow it into something cooler. A few things I thought of while working on this project that might be nice to improve/add/try:
- reduce boot-up time (disabling unnecessary services from starting up, reducing/removing timeouts, etc.)
- harden for abrupt poweroff (unplug) to remove need for clean poweroff
- try other Vendor ID (VID) and Product ID (PID) combinations to try and avoid potential target OS "configure keyboard" pop-ups on device connect
- update payloads to self-decode (and self-execute... ¯\_(ツ)_/¯ ), potentially using "hot key" shortcuts and/or control characters to open/navigate apps and/or the OS itself
- improvements to the scan tool to better/more-flexibly support large amounts of incoming stdin data and adjustable delays between keystrokes
- NOTE: on my MacBook, I see the 'hidd' process (responsible for handling keyboard and mouse events) leap to ~50% CPU with the RPi's scan cranking out keystrokes at 200 per second. If I bump the RPi rate to 400 keystrokes per second, 'hidd' jumps to ~90% CPU and my whole UI starts lagging in responsiveness.
- make the RPi enumerate as having multiple USB capabilities (composite!)