Encoded or obfuscated use of the Start-Sleep cmdlet can be an indicator of malicious activity.
The Start-Sleep
cmdlet can be used to pause the running of malicious code in attempts to evade detections by antivirus software. This is an older technique that many solutions now detect, but some threat actors continue to include this practice by encoding or obfuscating the Start-Sleep
command.
Prerequisites for this KQL query
The table for this query (DeviceProcessEvents
) is available in Microsoft Defender. Perform query usingĀ Microsoft Defender > Hunting > Advanced hunting. Advanced hunting has previous 30 days of logs available.
KQL query to identify encoded or obfuscated use of the Start-Sleep cmdlet
The following query identifies events indicative of malicious Start-Sleep
usage.
// Look for PowerShell using sleep timers to evade antivirus and detect sandbox environments.
let _regex = @"^[Ss]tart-[Ss]leep\s-([Ss]econds|[Ss])\s[0-9]{1,2}$";
DeviceProcessEvents
| where Timestamp > ago(30d)
| where ProcessCommandLine has "-enc"
| extend B64String = tostring(parse_command_line(ProcessCommandLine, "windows")[-1])
| extend B64String = base64_decode_toarray(B64String)
| where isnotempty(B64String)
| mv-apply B64String on (
summarize Fixed = make_list_if(B64String, B64String != 0) by ReportId, DeviceId
| extend B64String = make_string(Fixed))
| where B64String matches regex _regex
| summarize arg_max(Timestamp, ReportId) by DeviceName,DeviceId,InitiatingProcessCommandLine,B64String,InitiatingProcessAccountUpn,InitiatingProcessParentFileName
| project Timestamp,DeviceName,DeviceId,InitiatingProcessCommandLine,B64String,InitiatingProcessAccountUpn,InitiatingProcessParentFileName,ReportId
| sort by Timestamp desc
// credit: Microsoft Threat Intelligence
Deconstructing the KQL query
Read on to understand HOW the query above works.
By focusing on encoded commands and checking them against a specific pattern, it effectively narrows down potential security incidents involving the Start-Sleep
command. This process highlights how encoded malicious activities can be detected and analyzed within a dataset of device process events.
The objective of this KQL query is to identify potentially suspicious PowerShell activities over the last 30 days. Specifically, it looks for encoded commands within PowerShell scripts and checks if they match a pattern indicative of the Start-Sleep
command, which can be used to delay execution and potentially evade detection. The query decodes these commands and filters the results to present relevant information about the events.
1. Define a Regular Expression
let _regex = @"^[Ss]tart-[Ss]leep\s-([Ss]econds|[Ss])\s[0-9]{1,2}$";
This line defines a regular expression pattern stored in the variable _regex
. The pattern matches strings that represent the Start-Sleep
command with specific formats, regardless of case, and with a sleep duration of 1-99 seconds.
2. Select the Table
DeviceProcessEvents
This specifies the table DeviceProcessEvents
, which contains events related to processes on devices.
3. Filter by Time
| where Timestamp > ago(30d)
This filters the data to include only events that occurred in the last 30 days.
4. Filter for Encoded Commands
| where ProcessCommandLine has "-enc"
This further filters the results to include only those events where the ProcessCommandLine
contains the -enc
flag, which indicates that the command line is using Base64-encoded PowerShell commands.
5. Extract the Base64 String
| extend B64String = tostring(parse_command_line(ProcessCommandLine, "windows")[-1])
Here, the parse_command_line
function is used to parse the ProcessCommandLine
, and tostring
converts the last element of the parsed command line (which is expected to be the Base64 string) into a string. This new field is named B64String
.
6. Decode the Base64 String
| extend B64String = base64_decode_toarray(B64String)
This line decodes the Base64 string into an array of bytes.
7. Filter Out Empty Decoded Strings
| where isnotempty(B64String)
This filters out rows where the B64String
field is empty, ensuring only events with a valid decoded string are considered.
8. Handle Multi-Valued Arrays
| mv-apply B64String on (
summarize Fixed = make_list_if(B64String, B64String != 0) by ReportId, DeviceId
| extend B64String = make_string(Fixed))
This mv-apply
operator applies operations on each element of the B64String
array. It uses summarize
to create a list of non-zero elements (Fixed
), then converts this list back into a string. This step is necessary to clean up the decoded strings by removing any zero bytes.
9. Match the Decoded String Against the Regular Expression
| where B64String matches regex _regex
This filters the data to include only those events where the decoded B64String
matches the regular expression _regex
, identifying potential Start-Sleep
commands.
10. Summarize and Select Latest Event
| summarize arg_max(Timestamp, ReportId) by DeviceName, DeviceId, InitiatingProcessCommandLine, B64String, InitiatingProcessAccountUpn, InitiatingProcessParentFileName
The summarize
operator groups the results by several fields (DeviceName
, DeviceId
, InitiatingProcessCommandLine
, B64String
, InitiatingProcessAccountUpn
, InitiatingProcessParentFileName
) and selects the latest event (arg_max(Timestamp, ReportId)
) for each group.
11. Project Selected Columns
| project Timestamp, DeviceName, DeviceId, InitiatingProcessCommandLine, B64String, InitiatingProcessAccountUpn, InitiatingProcessParentFileName, ReportId
This selects the columns to be included in the final output, focusing on the most relevant details.
12. Sort the Results
| sort by Timestamp desc
Finally, the results are sorted by Timestamp
in descending order, ensuring that the most recent events appear first.