Detection rules › Kusto

Suspicious named pipes

Status
available
Severity
medium
Time window
1h
Source
github.com/Azure/Azure-Sentinel

This query looks for Named Pipe events that either contain one of the known IOCs or make use of patterns that can be linked to CobaltStrike usage.

MITRE ATT&CK coverage

Event coverage

Rule body kusto

id: ddf7c669-db26-4215-acaf-11e2953a04e6
name: Suspicious named pipes
description: |
  This query looks for Named Pipe events that either contain one of the known IOCs or make use of patterns that can be linked to CobaltStrike usage.
severity: Medium
status: Available
requiredDataConnectors:
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceEvents
queryFrequency: 1h
queryPeriod: 1h
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Execution
  - DefenseEvasion
relevantTechniques:
  - T1559
  - T1055
query: |
  let timeframe=1h;
  let CobaltStrikeDefaults= dynamic([@"msagent_", @"MSSE-", @"postex_", @"status_", @"mypipe-f", @"mypipe-h",@"ntsvcs_",@"scerpc_", @"mojo.5688.8052."]);
  let CobaltStrikeMallable= dynamic([@"win_svc", @"ntsvcs", @"scerpc", @"status_", @"SearchTextHarvester", @"DserNamePipe",@"wkssvc_",@"scerpc_", @"spoolss_",@"CatalogChangeListener",@"fullduplex_",@"demoagent_",@"PGMessagePipe",@"MsFteWds",@"postex_ssh_",@"windows.update.manager",@"\f4c3",@"\f53f",@"halfduplex_"]);
  DeviceEvents
  | where Timestamp >= ago(timeframe)
  | where ActionType == "NamedPipeEvent"
  | extend AdditionalFields=parse_json(AdditionalFields)
  | extend ThreadId=tostring(AdditionalFields.ThreadId)
  | extend PipeName=tostring(AdditionalFields.PipeName)
  // creating string based variants of the processIDs for matching several times later
  | extend InitiatingPID=tostring(InitiatingProcessId)
  | extend InitiatingParentPID=tostring(InitiatingProcessParentId)
  // Begin allow-list.
  // End allow-list.
  | where PipeName has_any (CobaltStrikeDefaults) or
  // Mojo is generated by Chrome(ium) browsers and teams and have distinct pattern including the (parent)ProcessId and ThreadId plus a random character string, CobaltStrike generates hex.
        (PipeName matches regex @"\\mojo\.\d+\.\d+\." and not(PipeName matches regex @"\\mojo\.\d+\.\d+\.\d+$" or PipeName has InitiatingPID or PipeName has InitiatingParentPID or PipeName has ThreadId)) or
  // Chrome(ium) browsers sync processes have distinct pattern including the (parent)ProcessId and ThreadId plus a random character string, CobaltStrike generates hex.
        (PipeName matches regex @"\\(edge|chrome)\.sync\.\d+\.\d+\." and not(PipeName matches regex @"\\(edge|chrome|edge\.sync|chrome\.sync)\.\d+\.\d+\.\d+$" or PipeName has InitiatingPID or PipeName has InitiatingParentPID or PipeName has ThreadId)) or
  // PSHost is generated by PowerShell and has a distinct pattern including the (parent)ProcessId.
        (PipeName matches regex @"\\PSHost\.\d+\." and not(PipeName matches regex @"\\PSHost\.\d+\.\d+\." or PipeName has InitiatingPID or PipeName has InitiatingParentPID)) or
  // Crashpad pipes have a distinct pattern including the ProcessId and a string of upper case characters.
        (PipeName matches regex @"\\crashpad_" and not(PipeName matches regex @"\\crashpad_\d+_[A-Z]+" or PipeName has InitiatingPID or PipeName has InitiatingParentPID)) or
  // Firefox pipes have a distinct pattern including the ProcessId and 1-3 digits which are sequential for each new pipe.
        (PipeName matches regex @"\\cubeb-pipe-" and not(PipeName matches regex @"\\cubeb-pipe-\d+_[0-9]{1-3}+" or PipeName has InitiatingPID)) or
  // Based on a list of public mallable profiles and a suffix that is a random HEX string.
        (PipeName has_any (CobaltStrikeMallable) and PipeName matches regex @"[a-fA-F0-9]{2,10}$") or
        (PipeName matches regex @"\\pipe\\[0-9a-f]{7,10}" or PipeName matches regex @"\\pipe\\[0-9a-f]{8}")
entityMappings:
  - entityType: Account
    fieldMappings:
      - identifier: Sid
        columnName: AccountSid
      - identifier: Name
        columnName: AccountName
      - identifier: NTDomain
        columnName: AccountDomain
  - entityType: Host
    fieldMappings:
      - identifier: FullName
        columnName: DeviceName
  - entityType: Process
    fieldMappings:
      - identifier: CommandLine
        columnName: ProcessCommandLine
version: 1.0.0
kind: Scheduled

Stages and Predicates

Parameters

let timeframe = 1h;
let CobaltStrikeDefaults = dynamic([@"msagent_", @"MSSE-", @"postex_", @"status_", @"mypipe-f", @"mypipe-h",@"ntsvcs_",@"scerpc_", @"mojo.5688.8052."]);

Let binding: CobaltStrikeMallable

let CobaltStrikeMallable = dynamic([@"win_svc", @"ntsvcs", @"scerpc", @"status_", @"SearchTextHarvester", @"DserNamePipe",@"wkssvc_",@"scerpc_", @"spoolss_",@"CatalogChangeListener",@"fullduplex_",@"demoagent_",@"PGMessagePipe",@"MsFteWds",@"postex_ssh_",@"windows.update.manager",@"\f4c3",@"\f53f",@"halfduplex_"]);

Stage 1: source

DeviceEvents

Stage 2: where

| where Timestamp >= ago(timeframe)

Stage 3: where

| where ActionType == "NamedPipeEvent"

Stage 4: extend (5 consecutive steps)

| extend AdditionalFields=parse_json(AdditionalFields)
| extend ThreadId=tostring(AdditionalFields.ThreadId)
| extend PipeName=tostring(AdditionalFields.PipeName)
| extend InitiatingPID=tostring(InitiatingProcessId)
| extend InitiatingParentPID=tostring(InitiatingProcessParentId)

Stage 5: where

| where PipeName has_any (CobaltStrikeDefaults) or
      (PipeName matches regex @"\\mojo\.\d+\.\d+\." and not(PipeName matches regex @"\\mojo\.\d+\.\d+\.\d+$" or PipeName has InitiatingPID or PipeName has InitiatingParentPID or PipeName has ThreadId)) or
      (PipeName matches regex @"\\(edge|chrome)\.sync\.\d+\.\d+\." and not(PipeName matches regex @"\\(edge|chrome|edge\.sync|chrome\.sync)\.\d+\.\d+\.\d+$" or PipeName has InitiatingPID or PipeName has InitiatingParentPID or PipeName has ThreadId)) or
      (PipeName matches regex @"\\PSHost\.\d+\." and not(PipeName matches regex @"\\PSHost\.\d+\.\d+\." or PipeName has InitiatingPID or PipeName has InitiatingParentPID)) or
      (PipeName matches regex @"\\crashpad_" and not(PipeName matches regex @"\\crashpad_\d+_[A-Z]+" or PipeName has InitiatingPID or PipeName has InitiatingParentPID)) or
      (PipeName matches regex @"\\cubeb-pipe-" and not(PipeName matches regex @"\\cubeb-pipe-\d+_[0-9]{1-3}+" or PipeName has InitiatingPID)) or
      (PipeName has_any (CobaltStrikeMallable) and PipeName matches regex @"[a-fA-F0-9]{2,10}$") or
      (PipeName matches regex @"\\pipe\\[0-9a-f]{7,10}" or PipeName matches regex @"\\pipe\\[0-9a-f]{8}")

References CobaltStrikeMallable (defined above).

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
  • NamedPipeEvent transforms: cased corpus 2 (kusto 2)
PipeNamematch
  • CatalogChangeListener
  • DserNamePipe
  • MSSE-
  • MsFteWds
  • PGMessagePipe
  • SearchTextHarvester
  • \f4c3
  • \f53f
  • demoagent_
  • fullduplex_
  • halfduplex_
  • mojo.5688.8052.
  • msagent_
  • mypipe-f
  • mypipe-h
  • ntsvcs
  • ntsvcs_
  • postex_
  • postex_ssh_
  • scerpc
  • scerpc_
  • spoolss_
  • status_
  • win_svc
  • windows.update.manager
  • wkssvc_
PipeNameregex_match
  • [a-fA-F0-9]{2,10}$
  • \(edge|chrome).sync.\d+.\d+.
  • \PSHost.\d+.
  • \crashpad_
  • \cubeb-pipe-
  • \mojo.\d+.\d+.
  • \pipe\[0-9a-f]{7,10}
  • \pipe\[0-9a-f]{8}

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
AdditionalFieldsextend
ThreadIdextend
PipeNameextend
InitiatingPIDextend
InitiatingParentPIDextend