mirror of
https://github.com/sbousseaden/EDRUnChoker
synced 2026-06-21 14:09:16 +00:00
EDRUnChoker: fileless WMI defense against EDRChoker QoS throttling
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
*.token
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
#Requires -Version 5.1
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Reports fileless EDRChoker WMI defense subscription status.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$FilterName = 'EDRChokerDefense_QoSFilter',
|
||||||
|
[string]$ConsumerName = 'EDRChokerDefense_QoSConsumer',
|
||||||
|
[string]$TimerId = 'EDRChokerDefense_Timer'
|
||||||
|
)
|
||||||
|
|
||||||
|
$WmiNs = 'root\subscription'
|
||||||
|
$MaxThrottleBps = 1048576
|
||||||
|
|
||||||
|
$ExactTargets = @(
|
||||||
|
'amsvc', 'cb', 'cbdefense', 'cetasevc', 'cntaosmgr', 'cramtray', 'crsvc',
|
||||||
|
'cylancesvc', 'cybereasonav', 'cyserver', 'cyveraservice', 'cyvrfsflt',
|
||||||
|
'eiconnector', 'ekrn', 'elastic-agent', 'elastic-endpoint', 'endpointbasecamp',
|
||||||
|
'executionpreventionsvc', 'filebeat', 'fortiedr', 'hurukai', 'logprocessorservice',
|
||||||
|
'mpdefendercoreservice', 'msmpeng', 'mssense', 'ntrtscan', 'pccntmon', 'qualysagent',
|
||||||
|
'sensecncproxy', 'senseir', 'sensendr', 'sensesampleuploader', 'sentinelagent',
|
||||||
|
'sentinelagentworker', 'sentinelbrowsernativehost', 'sentinelhelperservice',
|
||||||
|
'sentinelservicehost', 'sentinelstaticengine', 'sentinelstaticenginescanner',
|
||||||
|
'sfc', 'taniumclient', 'taniumcx', 'taniumdetectengine', 'tmccsf', 'tmlisten',
|
||||||
|
'tmbmsrv', 'tmwscsvc', 'traps', 'winlogbeat', 'wscommunicator', 'xagt'
|
||||||
|
)
|
||||||
|
|
||||||
|
$VendorPatterns = @(
|
||||||
|
'elastic', 'sentinel', 'cybereason', 'carbonblack', 'carbon\', 'crowdstrike',
|
||||||
|
'cylance', 'defender', 'msmpeng', 'mssense', 'windows defender', 'senseir',
|
||||||
|
'tanium', 'qualys', 'fortiedr', 'mcafee', 'symantec', 'broadcom', 'trend micro',
|
||||||
|
'eset', 'kaspersky', 'bitdefender', 'sophos', 'fireeye', 'mandiant', 'traps',
|
||||||
|
'cbdefense', 'cyvera', 'hurukai', 'xagt', 'filebeat', 'winlogbeat', 'elastic-endpoint'
|
||||||
|
)
|
||||||
|
|
||||||
|
function Get-AppBaseName {
|
||||||
|
param([string]$Path)
|
||||||
|
$p = $Path.Trim().ToLowerInvariant()
|
||||||
|
if ($p.EndsWith('.exe')) { $p = $p.Substring(0, $p.Length - 4) }
|
||||||
|
$segments = $p -split '[\\/]'
|
||||||
|
return $segments[-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-KnownSecurityTarget {
|
||||||
|
param([string]$AppPath)
|
||||||
|
if ([string]::IsNullOrWhiteSpace($AppPath)) { return $false }
|
||||||
|
$lower = $AppPath.ToLowerInvariant()
|
||||||
|
$base = Get-AppBaseName -Path $lower
|
||||||
|
foreach ($t in $ExactTargets) {
|
||||||
|
if ($base -eq $t -or $base.EndsWith($t)) { return $true }
|
||||||
|
}
|
||||||
|
foreach ($p in $VendorPatterns) {
|
||||||
|
if ($lower.Contains($p)) { return $true }
|
||||||
|
}
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-MaliciousQosPolicy {
|
||||||
|
param($Policy)
|
||||||
|
$appPath = [string]$Policy.AppPathNameMatchCondition
|
||||||
|
if ([string]::IsNullOrWhiteSpace($appPath)) { return $false }
|
||||||
|
$throttle = [uint64]$Policy.ThrottleRateAction
|
||||||
|
if ($throttle -eq 0) { return $false }
|
||||||
|
if (Test-KnownSecurityTarget -AppPath $appPath) { return $true }
|
||||||
|
if ($throttle -le $MaxThrottleBps) { return $true }
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host '=== WMI subscription (root\subscription) ===' -ForegroundColor Cyan
|
||||||
|
Get-WmiObject -Namespace $WmiNs -Class __IntervalTimerInstruction -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.TimerId -eq $TimerId } |
|
||||||
|
Format-List TimerId, IntervalBetweenEvents
|
||||||
|
|
||||||
|
Get-WmiObject -Namespace $WmiNs -Class __EventFilter -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.Name -eq $filterName } |
|
||||||
|
Format-List Name, Query, EventNamespace
|
||||||
|
|
||||||
|
Get-WmiObject -Namespace $WmiNs -Class ActiveScriptEventConsumer -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.Name -eq $consumerName } |
|
||||||
|
Format-List Name, ScriptingEngine, KillTimeout
|
||||||
|
|
||||||
|
Get-WmiObject -Namespace $WmiNs -Class __FilterToConsumerBinding -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.Filter -match [regex]::Escape($filterName) } |
|
||||||
|
Format-List Filter, Consumer
|
||||||
|
|
||||||
|
Write-Host '=== Policies matching hardened detection rules ===' -ForegroundColor Cyan
|
||||||
|
$all = @(Get-CimInstance -Namespace root/standardcimv2 -ClassName MSFT_NetQosPolicySettingData -ErrorAction SilentlyContinue)
|
||||||
|
$malicious = @($all | Where-Object { Test-MaliciousQosPolicy -Policy $_ })
|
||||||
|
|
||||||
|
Write-Host "Count: $($malicious.Count) / $($all.Count) total QoS policies"
|
||||||
|
if ($malicious.Count -gt 0) {
|
||||||
|
$malicious | Select-Object Name, AppPathNameMatchCondition, ThrottleRateAction, Owner |
|
||||||
|
Format-Table -AutoSize
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host '=== Recent remediation events (Application / EDRChokerDefense) ===' -ForegroundColor Cyan
|
||||||
|
Get-WinEvent -FilterHashtable @{
|
||||||
|
LogName = 'Application'
|
||||||
|
ProviderName = 'EDRChokerDefense'
|
||||||
|
} -MaxEvents 15 -ErrorAction SilentlyContinue |
|
||||||
|
Select-Object TimeCreated, Id, LevelDisplayName, Message |
|
||||||
|
Format-Table -Wrap -AutoSize
|
||||||
@@ -0,0 +1,303 @@
|
|||||||
|
#Requires -RunAsAdministrator
|
||||||
|
#Requires -Version 5.1
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Registers a fileless permanent WMI subscription against EDRChoker QoS policies.
|
||||||
|
.DESCRIPTION
|
||||||
|
Timer-based subscription in root\subscription (no files on disk). Embedded VBScript
|
||||||
|
removes QoS policies that throttle known EDR/security agents (any rate > 0) or apply
|
||||||
|
aggressive throttling (<= 1 Mbps) to app-path policies — including paths without .exe.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$FilterName = 'EDRChokerDefense_QoSFilter',
|
||||||
|
[string]$ConsumerName = 'EDRChokerDefense_QoSConsumer',
|
||||||
|
[string]$TimerId = 'EDRChokerDefense_Timer',
|
||||||
|
[int]$PollIntervalSeconds = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$WmiNs = 'root\subscription'
|
||||||
|
$pollIntervalMs = $PollIntervalSeconds * 1000
|
||||||
|
$LogSource = 'EDRChokerDefense'
|
||||||
|
$LogName = 'Application'
|
||||||
|
|
||||||
|
function Register-DefenseEventSource {
|
||||||
|
if (-not [System.Diagnostics.EventLog]::SourceExists($LogSource)) {
|
||||||
|
New-EventLog -LogName $LogName -Source $LogSource -ErrorAction Stop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-DefenseInstallLog {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-EventLog -LogName $LogName -Source $LogSource -EventId 1000 -EntryType Information -Message $Message
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-LocalAdministratorsCreatorSid {
|
||||||
|
$sid = New-Object System.Security.Principal.SecurityIdentifier 'S-1-5-32-544'
|
||||||
|
$bytes = New-Object byte[] ($sid.BinaryLength)
|
||||||
|
$sid.GetBinaryForm($bytes, 0)
|
||||||
|
return $bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
function New-WmiSubscriptionObject {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string]$Component,
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string]$ClassName,
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[hashtable]$Properties
|
||||||
|
)
|
||||||
|
|
||||||
|
$scope = New-Object System.Management.ManagementScope("\\.\$WmiNs")
|
||||||
|
$scope.Options.EnablePrivileges = $true
|
||||||
|
$scope.Connect()
|
||||||
|
|
||||||
|
$mgmtClass = New-Object System.Management.ManagementClass(
|
||||||
|
$scope,
|
||||||
|
(New-Object System.Management.ManagementPath($ClassName)),
|
||||||
|
$null
|
||||||
|
)
|
||||||
|
$instance = $mgmtClass.CreateInstance()
|
||||||
|
|
||||||
|
foreach ($key in $Properties.Keys) {
|
||||||
|
$instance.SetPropertyValue($key, $Properties[$key])
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$instance.Put() | Out-Null
|
||||||
|
return $instance
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
$message = $_.Exception.Message
|
||||||
|
if ($_.Exception.InnerException) {
|
||||||
|
$message = $_.Exception.InnerException.Message
|
||||||
|
}
|
||||||
|
$hresult = '0x{0:X8}' -f $_.Exception.HResult
|
||||||
|
throw "Failed to create WMI $Component ($ClassName). $message (HRESULT $hresult)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Get-WmiObject -Namespace $WmiNs -Class __EventFilter -Filter "Name='$FilterName'" -ErrorAction SilentlyContinue) {
|
||||||
|
throw "Subscription '$FilterName' already exists. Run Uninstall-EdrChokerWmiDefense.ps1 first."
|
||||||
|
}
|
||||||
|
|
||||||
|
$creatorSid = Get-LocalAdministratorsCreatorSid
|
||||||
|
$wql = "SELECT * FROM __TimerEvent WHERE TimerID = '$TimerId'"
|
||||||
|
|
||||||
|
# Detection tiers (embedded — reinstall to update):
|
||||||
|
# 1) Known EDR/AV/SIEM process basename or vendor substring + any ThrottleRateAction > 0
|
||||||
|
# 2) Any app-path policy with ThrottleRateAction > 0 and <= 1 Mbps (EDRChoker evasion with non-8 values)
|
||||||
|
# Path normalization strips .exe and uses the final path segment, so "C:\...\MsMpEng" still matches.
|
||||||
|
$scriptText = @'
|
||||||
|
On Error Resume Next
|
||||||
|
|
||||||
|
Const MAX_THROTTLE_BPS = 1048576
|
||||||
|
|
||||||
|
Dim qosSvc, policies, pol
|
||||||
|
|
||||||
|
Set qosSvc = GetObject("winmgmts:\\.\root\standardcimv2")
|
||||||
|
Set policies = qosSvc.ExecQuery("SELECT * FROM MSFT_NetQosPolicySettingData")
|
||||||
|
|
||||||
|
For Each pol In policies
|
||||||
|
If IsMaliciousPolicy(pol) Then RemoveMaliciousPolicy pol
|
||||||
|
Next
|
||||||
|
|
||||||
|
Sub WriteDefenseLog(eventId, entryType, message)
|
||||||
|
Dim proc, pid, cmd, safe
|
||||||
|
safe = message
|
||||||
|
safe = Replace(safe, "'", "''")
|
||||||
|
safe = Replace(safe, """", "'")
|
||||||
|
cmd = "powershell.exe -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -Command ""Write-EventLog -LogName Application -Source EDRChokerDefense -EventId " & eventId & " -EntryType " & entryType & " -Message '" & safe & "' -ErrorAction SilentlyContinue"""
|
||||||
|
Set proc = GetObject("winmgmts:\\.\root\cimv2:Win32_Process")
|
||||||
|
proc.Create cmd, Null, Null, pid
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Sub RemoveMaliciousPolicy(pol)
|
||||||
|
Dim appPath, throttle, policyName, instanceId, tier, msg
|
||||||
|
appPath = pol.AppPathNameMatchCondition
|
||||||
|
throttle = GetNumericThrottle(pol.ThrottleRateAction)
|
||||||
|
policyName = pol.Name
|
||||||
|
instanceId = pol.InstanceID
|
||||||
|
If MatchesKnownSecurityTarget(appPath) Then
|
||||||
|
tier = "tier1-known-edr"
|
||||||
|
Else
|
||||||
|
tier = "tier2-aggressive-throttle"
|
||||||
|
End If
|
||||||
|
On Error Resume Next
|
||||||
|
pol.Delete_
|
||||||
|
If Err.Number = 0 Then
|
||||||
|
msg = "action=remediate qos_policy=" & policyName & " target=" & appPath & " throttle_bps=" & throttle & " tier=" & tier & " instance_id=" & instanceId
|
||||||
|
WriteDefenseLog 1002, "Warning", msg
|
||||||
|
Else
|
||||||
|
msg = "action=remediate_failed qos_policy=" & policyName & " target=" & appPath & " error=" & Err.Description
|
||||||
|
WriteDefenseLog 1003, "Error", msg
|
||||||
|
Err.Clear
|
||||||
|
End If
|
||||||
|
On Error GoTo 0
|
||||||
|
End Sub
|
||||||
|
|
||||||
|
Function GetNumericThrottle(val)
|
||||||
|
If IsNull(val) Or IsEmpty(val) Then
|
||||||
|
GetNumericThrottle = 0
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
If Len(CStr(val)) = 0 Then
|
||||||
|
GetNumericThrottle = 0
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
On Error Resume Next
|
||||||
|
GetNumericThrottle = CDbl(val)
|
||||||
|
If Err.Number <> 0 Then GetNumericThrottle = 0
|
||||||
|
On Error GoTo 0
|
||||||
|
End Function
|
||||||
|
|
||||||
|
Function GetBaseName(path)
|
||||||
|
Dim p, i, c
|
||||||
|
p = LCase(Trim(path))
|
||||||
|
If Len(p) = 0 Then
|
||||||
|
GetBaseName = ""
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
If Right(p, 4) = ".exe" Then p = Left(p, Len(p) - 4)
|
||||||
|
i = Len(p)
|
||||||
|
Do While i > 0
|
||||||
|
c = Mid(p, i, 1)
|
||||||
|
If c = "\" Or c = "/" Then
|
||||||
|
GetBaseName = Mid(p, i + 1)
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
i = i - 1
|
||||||
|
Loop
|
||||||
|
GetBaseName = p
|
||||||
|
End Function
|
||||||
|
|
||||||
|
Function MatchesExactTarget(base)
|
||||||
|
Dim t, i
|
||||||
|
Dim targets
|
||||||
|
targets = Array( _
|
||||||
|
"amsvc", "cb", "cbdefense", "cetasevc", "cntaosmgr", "cramtray", "crsvc", _
|
||||||
|
"cylancesvc", "cybereasonav", "cyserver", "cyveraservice", "cyvrfsflt", _
|
||||||
|
"eiconnector", "ekrn", "elastic-agent", "elastic-endpoint", "endpointbasecamp", _
|
||||||
|
"executionpreventionsvc", "filebeat", "fortiedr", "hurukai", "logprocessorservice", _
|
||||||
|
"mpdefendercoreservice", "msmpeng", "mssense", "ntrtscan", "pccntmon", "qualysagent", _
|
||||||
|
"sensecncproxy", "senseir", "sensendr", "sensesampleuploader", "sentinelagent", _
|
||||||
|
"sentinelagentworker", "sentinelbrowsernativehost", "sentinelhelperservice", _
|
||||||
|
"sentinelservicehost", "sentinelstaticengine", "sentinelstaticenginescanner", _
|
||||||
|
"sfc", "taniumclient", "taniumcx", "taniumdetectengine", "tmccsf", "tmlisten", _
|
||||||
|
"tmbmsrv", "tmwscsvc", "traps", "winlogbeat", "wscommunicator", "xagt" _
|
||||||
|
)
|
||||||
|
For i = 0 To UBound(targets)
|
||||||
|
t = targets(i)
|
||||||
|
If base = t Then
|
||||||
|
MatchesExactTarget = True
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
If Len(base) >= Len(t) Then
|
||||||
|
If Right(base, Len(t)) = t Then
|
||||||
|
MatchesExactTarget = True
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
MatchesExactTarget = False
|
||||||
|
End Function
|
||||||
|
|
||||||
|
Function MatchesVendorPattern(pathLower)
|
||||||
|
Dim p, i
|
||||||
|
Dim patterns
|
||||||
|
patterns = Array( _
|
||||||
|
"elastic", "sentinel", "cybereason", "carbonblack", "carbon\", "crowdstrike", _
|
||||||
|
"cylance", "defender", "msmpeng", "mssense", "windows defender", "senseir", _
|
||||||
|
"tanium", "qualys", "fortiedr", "mcafee", "symantec", "broadcom", "trend micro", _
|
||||||
|
"eset", "kaspersky", "bitdefender", "sophos", "fireeye", "mandiant", "traps", _
|
||||||
|
"cbdefense", "cyvera", "hurukai", "xagt", "filebeat", "winlogbeat", "elastic-endpoint" _
|
||||||
|
)
|
||||||
|
For i = 0 To UBound(patterns)
|
||||||
|
p = patterns(i)
|
||||||
|
If InStr(1, pathLower, p, 1) > 0 Then
|
||||||
|
MatchesVendorPattern = True
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
MatchesVendorPattern = False
|
||||||
|
End Function
|
||||||
|
|
||||||
|
Function MatchesKnownSecurityTarget(appPath)
|
||||||
|
Dim pathLower, baseName
|
||||||
|
pathLower = LCase(Trim(appPath))
|
||||||
|
If Len(pathLower) = 0 Then
|
||||||
|
MatchesKnownSecurityTarget = False
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
baseName = GetBaseName(pathLower)
|
||||||
|
If MatchesExactTarget(baseName) Then
|
||||||
|
MatchesKnownSecurityTarget = True
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
MatchesKnownSecurityTarget = MatchesVendorPattern(pathLower)
|
||||||
|
End Function
|
||||||
|
|
||||||
|
Function IsMaliciousPolicy(pol)
|
||||||
|
Dim appPath, throttle
|
||||||
|
appPath = pol.AppPathNameMatchCondition
|
||||||
|
throttle = GetNumericThrottle(pol.ThrottleRateAction)
|
||||||
|
|
||||||
|
If Len(Trim(appPath)) = 0 Then Exit Function
|
||||||
|
If throttle <= 0 Then Exit Function
|
||||||
|
|
||||||
|
' Tier 1: any throttle on a known EDR/AV/SIEM target (catches non-8 bps evasion).
|
||||||
|
If MatchesKnownSecurityTarget(appPath) Then
|
||||||
|
IsMaliciousPolicy = True
|
||||||
|
Exit Function
|
||||||
|
End If
|
||||||
|
|
||||||
|
' Tier 2: aggressive throttle on any app-path policy (EDRChoker-style abuse).
|
||||||
|
If throttle <= MAX_THROTTLE_BPS Then
|
||||||
|
IsMaliciousPolicy = True
|
||||||
|
End If
|
||||||
|
End Function
|
||||||
|
'@
|
||||||
|
|
||||||
|
$null = New-WmiSubscriptionObject -Component 'IntervalTimerInstruction' -ClassName __IntervalTimerInstruction -Properties @{
|
||||||
|
TimerId = $TimerId
|
||||||
|
IntervalBetweenEvents = $pollIntervalMs
|
||||||
|
SkipIfPassed = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
$filter = New-WmiSubscriptionObject -Component 'EventFilter' -ClassName __EventFilter -Properties @{
|
||||||
|
Name = $FilterName
|
||||||
|
EventNamespace = $WmiNs
|
||||||
|
QueryLanguage = 'WQL'
|
||||||
|
Query = $wql
|
||||||
|
CreatorSID = $creatorSid
|
||||||
|
}
|
||||||
|
|
||||||
|
$consumer = New-WmiSubscriptionObject -Component 'ActiveScriptEventConsumer' -ClassName ActiveScriptEventConsumer -Properties @{
|
||||||
|
Name = $ConsumerName
|
||||||
|
ScriptingEngine = 'VBScript'
|
||||||
|
ScriptText = $scriptText
|
||||||
|
KillTimeout = 60000
|
||||||
|
CreatorSID = $creatorSid
|
||||||
|
}
|
||||||
|
|
||||||
|
$null = New-WmiSubscriptionObject -Component 'FilterToConsumerBinding' -ClassName __FilterToConsumerBinding -Properties @{
|
||||||
|
Filter = $filter
|
||||||
|
Consumer = $consumer
|
||||||
|
CreatorSID = $creatorSid
|
||||||
|
}
|
||||||
|
|
||||||
|
Register-DefenseEventSource
|
||||||
|
Write-DefenseInstallLog "action=install subscription=EDRChokerDefense poll_interval_seconds=$PollIntervalSeconds log_source=$LogSource"
|
||||||
|
|
||||||
|
Write-Host 'Fileless WMI subscription installed (hardened detection, polls every' $PollIntervalSeconds 's).'
|
||||||
|
Write-Host " Timer : $TimerId (${pollIntervalMs}ms)"
|
||||||
|
Write-Host " Filter : $FilterName"
|
||||||
|
Write-Host " Consumer : $ConsumerName (ActiveScriptEventConsumer / VBScript)"
|
||||||
|
Write-Host ' Detection : known EDR targets (any throttle) + app-path throttle <= 1 Mbps'
|
||||||
|
Write-Host " Event log : $LogName / Source $LogSource (ID 1002 = remediate, 1003 = failed)"
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host 'Reinstall was required to update embedded VBScript if upgrading.'
|
||||||
|
Write-Host 'Verify: .\Get-EdrChokerDefenseStatus.ps1'
|
||||||
|
Write-Host "SOC query : Get-WinEvent -FilterHashtable @{ LogName='$LogName'; ProviderName='$LogSource' } -MaxEvents 20"
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# Unchoker
|
||||||
|
|
||||||
|
**Fileless WMI remediation for [EDRChoker](https://github.com/TwoSevenOneT/EDRChoker)** counters QoS abuse (`pacer.sys`) that throttles EDR agents to near-zero network bandwidth.
|
||||||
|
|
||||||
|
Registers a permanent subscription in `root\subscription` (no files on disk). A 5-second timer runs embedded VBScript that deletes malicious `MSFT_NetQosPolicySettingData` policies targeting known security products or aggressive app-path throttles.
|
||||||
|
|
||||||
|
## Scripts
|
||||||
|
|
||||||
|
| Script | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `Install-EdrChokerWmiDefense.ps1` | Deploy subscription (elevated) |
|
||||||
|
| `Uninstall-EdrChokerWmiDefense.ps1` | Remove subscription |
|
||||||
|
| `Get-EdrChokerDefenseStatus.ps1` | Check subscription and policy count |
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Install-EdrChokerWmiDefense.ps1
|
||||||
|
.\Get-EdrChokerDefenseStatus.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
## SOC / event log
|
||||||
|
|
||||||
|
Each successful cleanup writes a **Warning** to the **Application** log under source **EDRChokerDefense**. One event is emitted per removed policy, useful as remediation evidence and to correlate with EDRChoker activity on the host.
|
||||||
|
|
||||||
|
<img width="2077" height="1305" alt="image" src="https://github.com/user-attachments/assets/60001ea8-c1b5-4a93-8a4e-9facea3e51d1" />
|
||||||
|
|
||||||
|
|
||||||
|
| Event ID | Meaning |
|
||||||
|
|---|---|
|
||||||
|
| 1000 | Subscription installed |
|
||||||
|
| 1001 | Subscription removed |
|
||||||
|
| 1002 | Malicious QoS policy removed |
|
||||||
|
| 1003 | Remediation failed |
|
||||||
|
|
||||||
|
**1002 example:** `action=remediate qos_policy=02zxnnzr target=elastic-endpoint.exe throttle_bps=8 tier=tier1-known-edr instance_id={...}`
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Get-WinEvent -FilterHashtable @{ LogName='Application'; ProviderName='EDRChokerDefense' } -MaxEvents 50
|
||||||
|
```
|
||||||
|
|
||||||
|
Forward `ProviderName="EDRChokerDefense"` via WEF, Splunk UF, Elastic Agent, etc.
|
||||||
|
|
||||||
|
## Protect the subscription
|
||||||
|
|
||||||
|
Attackers may delete or modify the WMI objects in `root\subscription` to disable defense. **Baseline** the subscription after install and alert on changes.
|
||||||
|
|
||||||
|
**Sysmon** (recommended): enable and monitor:
|
||||||
|
|
||||||
|
| Sysmon ID | What to watch |
|
||||||
|
|---|---|
|
||||||
|
| **19** | `WmiEventFilter` created/modified/deleted |
|
||||||
|
| **20** | `WmiEventConsumer` created/modified/deleted |
|
||||||
|
| **21** | `WmiEventFilter` ↔ consumer binding changes |
|
||||||
|
|
||||||
|
Alert on any activity involving `EDRChokerDefense_QoSFilter`, `EDRChokerDefense_QoSConsumer`, or `EDRChokerDefense_Timer`, and on **new** `ActiveScriptEventConsumer` / `CommandLineEventConsumer` instances outside your change window.
|
||||||
|
|
||||||
|
**Periodic validation** (GPO/script): `Get-WmiObject -Namespace root\subscription -Class __EventFilter | Where-Object Name -like 'EDRChokerDefense*'`
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [EDRChoker](https://github.com/TwoSevenOneT/EDRChoker)
|
||||||
|
- [EDRChoker research](https://www.zerosalarium.com/2026/06/edrchoker-choking-telemetry-stream-block-edr.html)
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#Requires -RunAsAdministrator
|
||||||
|
#Requires -Version 5.1
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Removes the fileless EDRChoker WMI defense subscription.
|
||||||
|
#>
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$FilterName = 'EDRChokerDefense_QoSFilter',
|
||||||
|
[string]$ConsumerName = 'EDRChokerDefense_QoSConsumer',
|
||||||
|
[string]$TimerId = 'EDRChokerDefense_Timer'
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$WmiNs = 'root\subscription'
|
||||||
|
$LogSource = 'EDRChokerDefense'
|
||||||
|
$LogName = 'Application'
|
||||||
|
|
||||||
|
function Remove-WmiSubscriptionComponent {
|
||||||
|
param(
|
||||||
|
[string]$Class,
|
||||||
|
[string]$Filter
|
||||||
|
)
|
||||||
|
|
||||||
|
$instance = Get-WmiObject -Namespace $WmiNs -Class $Class -Filter $Filter -ErrorAction SilentlyContinue
|
||||||
|
if ($instance) {
|
||||||
|
$instance | Remove-WmiObject
|
||||||
|
Write-Host "Removed $Class ($Filter)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$bindings = @(Get-WmiObject -Namespace $WmiNs -Class __FilterToConsumerBinding -ErrorAction SilentlyContinue |
|
||||||
|
Where-Object { $_.Filter -match [regex]::Escape($FilterName) -or $_.Consumer -match [regex]::Escape($ConsumerName) })
|
||||||
|
|
||||||
|
foreach ($binding in $bindings) {
|
||||||
|
$binding | Remove-WmiObject
|
||||||
|
Write-Host 'Removed __FilterToConsumerBinding'
|
||||||
|
}
|
||||||
|
|
||||||
|
Remove-WmiSubscriptionComponent -Class ActiveScriptEventConsumer -Filter "Name='$ConsumerName'"
|
||||||
|
Remove-WmiSubscriptionComponent -Class __EventFilter -Filter "Name='$FilterName'"
|
||||||
|
Remove-WmiSubscriptionComponent -Class __IntervalTimerInstruction -Filter "TimerId='$TimerId'"
|
||||||
|
|
||||||
|
if ([System.Diagnostics.EventLog]::SourceExists($LogSource)) {
|
||||||
|
try {
|
||||||
|
Write-EventLog -LogName $LogName -Source $LogSource -EventId 1001 -EntryType Information -Message 'action=uninstall subscription=EDRChokerDefense'
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "Could not write uninstall event: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host 'Uninstall complete. WMI subscription removed; past Application log events retained for SOC.'
|
||||||
Reference in New Issue
Block a user