
How I built KQL detection rules to hunt unauthorized browsers and rogue RMM tools across endpoints, then automated silent remediation with PowerShell scripts.
Published on April 29, 2026 by Kyle S
kql microsoft-defender powershell remediation threat-hunting rogue-rmm pua detection-engineering soc
9 min READ
Unauthorized software on corporate endpoints is one of those problems that sits in the gap between security and IT operations. Users install personal browsers. Free tools bundle PUAs. And sometimes, rogue remote management tools show up with no clear explanation — running as SYSTEM, calling home to relay servers, and sitting unnoticed for weeks.
This post walks through how I built a detection and remediation pipeline to find and remove unauthorized software across an enterprise environment using Microsoft Defender for Endpoint, KQL, and PowerShell.
Remote management tools like AnyDesk, TeamViewer, and RustDesk are legitimate software — but when they show up on endpoints without authorization, they’re effectively backdoors. Attackers use them for persistence because they blend in with normal IT operations and don’t trigger traditional malware detections.
PUA browsers like Wave Browser, Shift Browser, and OneStart are a different flavor of the same problem. They bundle themselves with other software, resist uninstallation, create scheduled tasks to relaunch themselves, and often ship with telemetry that sends browsing data to third parties.
Both categories need to be detected, investigated, and removed.
The foundation of this project is a KQL query that runs in Microsoft Defender Advanced Hunting. The goal was to detect unauthorized software based on three signals:
DeviceProcessEvents
| where FileName has_any (
"shift.exe",
"wavebrowser.exe",
"onestart.exe",
"oneai.exe",
"anydesk.exe",
"teamviewer.exe",
"TeamViewer_Service.exe",
"SplashtopStreamer.exe",
"rustdesk.exe",
"meshagent.exe",
"supremo.exe"
)
or FolderPath has_any (
"Shift Technologies",
"WaveBrowser",
"OneStart",
"OneAI",
"AnyDesk",
"TeamViewer"
)
or ProcessVersionInfoProductName has_any (
"Shift",
"Wave Browser",
"OneStart",
"OneAI",
"AnyDesk",
"TeamViewer"
)
| summarize ExecutionCount = count(), LastSeen = max(Timestamp) by DeviceName, AccountName, FileName
| sort by ExecutionCount desc
The ProcessVersionInfoProductName field is the key differentiator. It’s embedded in the binary’s metadata — even if someone renames wavebrowser.exe to chrome.exe, the product name still reads “Wave Browser.”
Our environment uses ScreenConnect as the approved RMM tool. Any RMM tool that isn’t ScreenConnect is unauthorized. The query naturally handles this because ScreenConnect isn’t in the detection list.
When the query returned results, the first hit was AnyDesk running on an endpoint. The process details told a story:
"AnyDesk.exe" --finish-update --silent — it was silently updating itselfThe next step was checking what AnyDesk was actually doing on the network:
DeviceNetworkEvents
| where DeviceName == "affected-endpoint"
| where InitiatingProcessFileName has "anydesk"
| project Timestamp, RemoteIP, RemotePort, RemoteUrl, InitiatingProcessCommandLine
| sort by Timestamp desc
This revealed active outbound connections to relay-e24ffb83.net.anydesk.com on port 443 — a legitimate AnyDesk relay server. The binary hash checked clean on VirusTotal, confirming it was the real AnyDesk software, not a trojanized version.
The concern wasn’t malware. It was an unauthorized remote access channel running as a system service that nobody in IT had deployed or was monitoring.
A query you run manually is useful for investigations. A query that runs itself is useful for operations. I converted the detection query into a Custom Detection Rule in Defender:
DeviceName, User → AccountName, File → FileNameNow every time an unauthorized tool executes on any endpoint, an alert lands in the Incidents & Alerts queue automatically.
Detection without remediation is just a notification. I built PowerShell scripts designed to run through ScreenConnect Backstage (or Defender Live Response) to silently remove unauthorized software from endpoints.
The first version was straightforward — kill the process, delete the folder, remove the scheduled task:
$ErrorActionPreference = "SilentlyContinue"
Get-Process -Name "shift" | Stop-Process -Force
$UserProfiles = Get-ChildItem "C:\Users" -Directory
foreach ($Profile in $UserProfiles) {
$ShiftPath = Join-Path $Profile.FullName "AppData\Local\Shift"
if (Test-Path $ShiftPath) {
Remove-Item -Path $ShiftPath -Recurse -Force
}
}
Unregister-ScheduledTask -TaskName "ShiftLaunchTask" -Confirm:$false
It ran successfully and reported everything was removed — but the files were still there. The issue was that $ErrorActionPreference = "SilentlyContinue" was swallowing the removal errors, and Remove-Item -Recurse was failing silently on locked files.
The improved version addressed three problems:
ShiftLaunchTask was relaunching the process before the script could delete the filescmd /c rd /s /q as the primary removal method — more aggressive than Remove-Item for stubborn directories$ErrorActionPreference = "SilentlyContinue"
$Log = "C:\Windows\Temp\ShiftRemoval.log"
function Write-Log ($msg) {
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') - $msg" | Out-File -Append -FilePath $Log
}
try {
Write-Log "Starting Shift Browser removal"
# Remove the scheduled task first so Shift can't relaunch
Unregister-ScheduledTask -TaskName "ShiftLaunchTask" -Confirm:$false
Write-Log "Removed ShiftLaunchTask scheduled task"
# Double kill with delay
Stop-Process -Name "shift" -Force
Start-Sleep -Seconds 2
Stop-Process -Name "shift" -Force
Write-Log "Stopped Shift processes"
$UserProfiles = Get-ChildItem "C:\Users" -Directory
foreach ($Profile in $UserProfiles) {
$ShiftPath = Join-Path $Profile.FullName "AppData\Local\Shift"
if (Test-Path $ShiftPath) {
cmd /c rd /s /q "$ShiftPath"
if (Test-Path $ShiftPath) {
Remove-Item -Path $ShiftPath -Recurse -Force
}
if (Test-Path $ShiftPath) {
Write-Log "FAILED to remove $ShiftPath"
} else {
Write-Log "Removed $ShiftPath"
}
}
}
Write-Log "Shift Browser removal complete"
} catch {
Write-Log "ERROR: $_"
}
Key design decisions:
$ErrorActionPreference = "SilentlyContinue" with no Write-Output, so nothing is visible to the end userC:\Windows\Temp\ where only administrators have accessC:\Users, not just the logged-in userOne detail that required hands-on investigation was identifying the exact scheduled task names. Rather than using wildcards (which my manager flagged as too broad), I checked two machines with Shift installed:
Get-ScheduledTask | Where-Object {$_.TaskName -like "Shift*"} | Select-Object TaskName, TaskPath, State
Both machines only had ShiftLaunchTask at the root level. The update tasks (ShiftUpdateTaskMachineCore, ShiftUpdateTaskMachineUA) that a full installation would create weren’t present — because Shift was installed as a PUA through software bundling, not a deliberate system-wide install.
Removing software after it’s installed is reactive. Preventing it from running in the first place is the long-term goal.
The quickest win is blocking by file hash in Defender:
Block the download sources:
*.anydesk.com)For persistent prevention that survives software updates:
Building this pipeline reinforced a few things:
Detection needs multiple signals. File names catch the obvious cases. Folder paths catch renamed binaries. Product metadata catches everything else.
Automated alerts beat manual hunting. A Custom Detection Rule that runs every hour catches what a weekly hunting session would miss.
Remediation scripts need iteration. The first version looked correct and reported success — but the files were still there. Silent error suppression is a double-edged sword. Always add verification.
Prevention is layered. Hash indicators for immediate blocking, web filtering for download prevention, AppLocker for long-term enforcement. No single layer covers everything.
PUAs are harder than malware. Malware gets flagged by EDR. PUAs are technically legitimate software — they won’t trigger antivirus detections. You need custom detection rules to find them.
The scripts and KQL queries from this project are available in my PowerShell Scripts and KQL Queries repositories.