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

Telemetry coverage

Rule body

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 used in Stage 5

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}")

Indicators

These rows show field, operator, and value matches.

FieldKindValuesSearch
ActionTypeeq
  • NamedPipeEvent corpus 3 (kusto 3)
field:"ActionType" kind:eq value:"NamedPipeEvent"
PipeNamematch
  • CatalogChangeListener transforms: term
  • DserNamePipe transforms: term
  • MSSE- transforms: term
  • MsFteWds transforms: term
  • PGMessagePipe transforms: term
  • SearchTextHarvester transforms: term
  • \f4c3 transforms: term
  • \f53f transforms: term
  • demoagent_ transforms: term
  • fullduplex_ transforms: term
  • halfduplex_ transforms: term
  • mojo.5688.8052. transforms: term
  • msagent_ transforms: term
  • mypipe-f transforms: term
  • mypipe-h transforms: term
  • ntsvcs transforms: term
  • ntsvcs_ transforms: term
  • postex_ transforms: term
  • postex_ssh_ transforms: term
  • scerpc transforms: term
  • scerpc_ transforms: term
  • spoolss_ transforms: term
  • status_ transforms: term
  • win_svc transforms: term
  • windows.update.manager transforms: term
  • wkssvc_ transforms: term
field:"PipeName" kind:match
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}
field:"PipeName" kind:regex_match

Output fields

These fields are emitted when the rule matches.

FieldSource
AdditionalFieldsextend
ThreadIdextend
PipeNameextend
InitiatingPIDextend
InitiatingParentPIDextend