Microsoft Secure Tech Accelerator
Apr 03 2024, 07:00 AM - 11:00 AM (PDT)
Microsoft Tech Community
Hunting tip of the month: PowerShell commands
Published Jun 29 2018 03:01 PM 13.3K Views
Microsoft

PowerShell scripts have clearly become one of the weapons of choice for attackers who want to stay extremely stealthy. Like other scripts, they are easily obfuscated, downloaded, tucked away in the registry and among other benign-looking content, and launched using a legitimate process—the scripting engine. As the de facto scripting standard for administrative tasks on Windows, PowerShell trumps other scripting languages because it can easily invoke system APIs and access a variety of .Net classes and objects. PowerShell is loved by system administrators and defenders, but unfortunately it is just as appealing to attackers.

 

In this post, I’d like to share a few queries that can make it much easier for you to find suspicious PowerShell activity in your network…

 

Explaining PowerShellCommand events

This event type reports the names of commands executed by the PowerShell engine, including PowerShell cmdlets and scripts, as well as other executables.

It does not matter if that command is specified in the commandline or not – the command is reported nonetheless.

Similarly, it’s reported whether executed by powershell.exe, by wsmprovhost.exe (used for remote PowerShell) or by some other process that loads the PowerShell engine.

This event is part of the MiscEvents table, and its ActionType is "PowerShellCommand".

The columns starting with InitiatingProcess contain the details of the processes that has executed the command.

 

The AdditionalFields column contains the Command name in a JSON structure, in example:

     {"Command":"Create-WmiClass"}

This column is where we keep the long tail of fields – the uncommon ones that are unique to just a few event types.

You can parse it by calling parse_json or extractjson, or you could simply run a “contains” where filter on it.

If your organization is a large one, we suggest you speed up your queries by applying more filters before you parse the JSON column.

 

Query #1: Find all executions of a specific command

Have you found a suspicious or malicious command in your network, and want to find other instances of it? Or perhaps you have read a new blog post about some attacker or attack framework using some PowerShell cmdlet?

You can now easily find out if it was executed in your organization and on which machines.

 

// Taken from https://github.com/Microsoft/WindowsDefenderATP-Hunting-Queries/blob/master/Execution/PowershellComm...

let powershellCommandName = "Invoke-RickAscii";

MiscEvents | where ActionType == "PowerShellCommand" and AdditionalFields contains powershellCommandName

| extend PowerShellCommand=extractjson("$.Command", AdditionalFields, typeof(string))

| where PowerShellCommand =~ powershellCommandName

| project EventTime, ComputerName, InitiatingProcessCommandLine, InitiatingProcessParentFileName

 

Note: Not familiar with Invoke-RickAscii? Well, it’s not something you should hunt for, but we still recommend you read all about it as it’s the coolest PowerShell cmdlet ever.

 

Query #2: Find all commands run on a machine in some time-range

Noticed some interesting activities on a machine? Try this query to quickly check for PowerShell activity around the same time.

 

MiscEvents

| where MachineId == "8707b4da675a244f5be391a74b9141896b83bc77" and EventTime between (datetime(2018-06-26T23:48:51.5401469Z) .. 1h) and ActionType == "PowerShellCommand"

// Parse Command field from AdditionalFields column

| extend PowerShellCommand=extractjson("$.Command", AdditionalFields, typeof(string))

 

Query 3: Same query, but without common powershell commands

Got too much noise? Let’s filter out the commands that are common in your organization.

In this query we use a let statement, which enables you to define tabular data, scalar data or user-defined functions that will later be used (and reused) in the query.

 

// This query was taken from https://github.com/Microsoft/WindowsDefenderATP-Hunting-Queries/blob/master/Execution/PowershellComm...

let powershellCommands =

    MiscEvents

    | where ActionType == "PowerShellCommand"

    | extend PowerShellCommand=extractjson("$.Command", AdditionalFields, typeof(string))

    | where PowerShellCommand !endswith ".ps1" and PowerShellCommand !endswith ".exe";

let commonCommands =

    powershellCommands

    | summarize MachineCount=dcount(MachineId) by PowerShellCommand

    | where MachineCount > 20;

powershellCommands

| where MachineId == "8707b4da675a244f5be391a74b9141896b83bc77" and EventTime between (datetime(2018-06-26T23:48:51.5401469Z) .. 1h)

| join kind=leftanti (commonCommands) on PowerShellCommand

 

Query 4: pivot around an alert & parameterize your query

Typing a full datetime timestamp for every lookup might be time-consuming.

To speed up things, in the example below we query for the details of an alert, and then pivot around it – but you could do that for any other event just as well.

When saving a shared query for future use by your team, it’s best to define constants at the top of the query using let statements. This makes it much easier to identify configurable parameters.

 

// Query for alert details

let alertIdParameter = "636641078490537577_-1905871543";

let alert = AlertEvents | where AlertId == alertIdParameter | summarize AlertFirstEventTime=min(EventTime) by MachineId;

let machineId = toscalar(alert | project MachineId);

let timestamp = toscalar(alert | project AlertFirstEventTime);

// Same query as above - query for cmdlets executed by the PowerShell engine

let powershellCommands =

    MiscEvents

    | where ActionType == "PowerShellCommand"

    | extend PowerShellCommand=extractjson("$.Command", AdditionalFields, typeof(string))

    | where PowerShellCommand !endswith ".ps1" and PowerShellCommand !endswith ".exe";

powershellCommands | where MachineId == machineId and EventTime between ((timestamp-5min) .. 10min)

| join kind=leftanti (powershellCommands | summarize MachineCount=dcount(MachineId) by PowerShellCommand | where MachineCount > 20) on PowerShellCommand

 

One last tip – machine ID and alert ID

In case you are not familiar with MachineId or AlertId, so those are WDATP unique identifiers for a machine or an alert.

You can copy-paste them from the URL of the machine page or the alert page, or can get them from the Advanced hunting tables, from our APIs or from PowerBI reports.

https://securitycenter.windows.com/machine/c42046e157d641a6ac9444a002cb2b4ee56a1ddf/2018-06-18T02:15...

https://securitycenter.windows.com/alert/636656435044189870_28344485

 

Wrap up

So, next time you investigate an alert and see PowerShell running, you could easily check which uncommon PowerShell commands ran on that machine – and if malicious, check on which other machines these commands were executed.

Don’t forget to bookmark our GitHub repository of Advanced hunting queries. It’s a great place to get guidance on how to hunt for specific threats as well as explore beautifully crafted queries that return insight into possible breach activities in your network.

 

Thanks for reading, and awaiting your feedback and suggestions for the next posts

Windows Defender ATP team

1 Comment
Microsoft

This is awesome. Thanks !

Version history
Last update:
‎Nov 14 2019 02:41 PM
Updated by: