Detection rules › Kusto

Script Interpreter Loading DotNet Assembly From Memory

Author
FalconForce
Source
github.com/FalconForceTeam/FalconFriday

The query searches for script interpreters (mmc.exe, mshta.exe, wscript.exe, and cscript.exe) loading .NET assemblies from memory. In the case of the MMC executable, the query also checks for the MSC file that was loaded, as some legitimate MSC files are known to load .NET assemblies via MMC.

MITRE ATT&CK coverage

References

Event coverage

ProviderEvent/ActionTypeTitle
Defender-DeviceEventsClrUnbackedModuleLoadedCLR unbacked module loaded
DotNETRuntimeEvent ID 152ModuleID=ModuleID.

Rule body kusto

let timeframe = 2*1h;
DeviceEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType == "ClrUnbackedModuleLoaded"
| where InitiatingProcessFileName in~ ("mmc.exe","mshta.exe","wscript.exe","cscript.exe")
| extend DotNetAssemblyName = tostring(parse_json(AdditionalFields).ModuleILPathOrName)
| extend ParsedCommandLine=parse_command_line(InitiatingProcessCommandLine, "windows")
// When a .msc file is opened in MMC, the file path is passed as an argument to MMC.
// Based on testing this is the first argument in the command line. In some cases a command-line switch /32 is passed as the first argument
// and the file path is the second argument. This is handled by the iif statement below.
| extend MscFile=ParsedCommandLine[1]
| extend MscFile=iif(MscFile startswith "/", ParsedCommandLine[2], MscFile)
// Some Microsoft MSC files are known to load .NET assemblies via MMC.
| where not(InitiatingProcessFileName =~ "mmc.exe" and (MscFile startswith @"c:\windows\system32\" or MscFile startswith @"C:\Program Files\Update Services\" or MscFile startswith @"C:\ProgramData\Microsoft\Windows\Start Menu\"))
// Do not alert when MMC is started without any arguments, since in that case it is unknown which MSC file was loaded.
| where not(InitiatingProcessFileName =~ "mmc.exe" and (array_length(ParsedCommandLine) == 1))
// Begin environment-specific filter.
// End environment-specific filter.

Stages and Predicates

Parameters

let timeframe = 2*1h;

Stage 1: source

DeviceEvents

Stage 2: where

| where ingestion_time() >= ago(timeframe)

Stage 3: where

| where ActionType == "ClrUnbackedModuleLoaded"

Stage 4: where

| where InitiatingProcessFileName in~ ("mmc.exe","mshta.exe","wscript.exe","cscript.exe")

Stage 5: extend (4 consecutive steps)

| extend DotNetAssemblyName = tostring(parse_json(AdditionalFields).ModuleILPathOrName)
| extend ParsedCommandLine=parse_command_line(InitiatingProcessCommandLine, "windows")
| extend MscFile=ParsedCommandLine[1]
| extend MscFile=iif(MscFile startswith "/", ParsedCommandLine[2], MscFile)

Stage 6: where

| where not(InitiatingProcessFileName =~ "mmc.exe" and (MscFile startswith @"c:\windows\system32\" or MscFile startswith @"C:\Program Files\Update Services\" or MscFile startswith @"C:\ProgramData\Microsoft\Windows\Start Menu\"))

Stage 7: where

| where not(InitiatingProcessFileName =~ "mmc.exe" and (array_length(ParsedCommandLine) == 1))

Exclusions

Top-level NOT(...) conjuncts: predicates this rule actively suppresses.

FieldKindExcluded values
MscFilestarts_withC:\Program Files\Update Services\
MscFilestarts_withC:\ProgramData\Microsoft\Windows\Start Menu\
MscFilestarts_withc:\windows\system32\
InitiatingProcessFileNameeqmmc.exe
InitiatingProcessFileNameeqmmc.exe
ParsedCommandLineeq1

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
ActionTypeeq
  • ClrUnbackedModuleLoaded transforms: cased
InitiatingProcessFileNamein
  • cscript.exe corpus 7 (elastic 6, splunk 1)
  • mmc.exe corpus 5 (elastic 3, splunk 1, kusto 1)
  • mshta.exe corpus 12 (elastic 10, splunk 2)
  • wscript.exe corpus 11 (elastic 10, splunk 1)

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
DotNetAssemblyNameextend
ParsedCommandLineextend
MscFileextend