Detection rules › Kusto

Suspicious office child process created

Author
FalconForce
Source
github.com/FalconForceTeam/FalconFriday

This query obtains a list of downloaded Office documents (doc, xls, etc.) by looking at files written by commonly used web browsers. It then searches for invocations of an Office program by double-clicking on these files. If these processes spawn an uncommon child process this is reported as suspicious.

MITRE ATT&CK coverage

TacticTechniques
ExecutionT1204 User Execution

Event coverage

Rule body kusto

let timeframe = 2*1d;
let browsers = dynamic(["iexplore.exe", "chrome.exe", "firefox.exe", "msedge.exe", "msedgewebview2.exe"]);
let ext = dynamic([".docm", ".xlsm", ".xls", ".doc", ".pptm", ".ppt"]);
let officeApps = dynamic(["winword.exe", "excel.exe", "powerpnt.exe"]);
let whitelist = dynamic(["MSOSYNC.exe", "splwow64.exe", "csc.exe", "outlook.exe", "AcroRd32.exe", "Acrobat.exe", "explorer.exe", "DW20.exe",
"Microsoft.Mashup.Container.Loader.exe", "Microsoft.Mashup.Container.NetFX40.exe", "WerFault.exe", "CLVIEW.exe", "wermgr.exe"]);
let whitelistedDomains = dynamic([""]);
let binPeriodForSearch = 1h;
let timeDiffFileCreateNetworkEvent = 15; // In seconds. Don't make this more than 15s because of comparison later on.
// List all filecreate events where the filename has a known Office extension which can contain macros.
let fileDownloads = materialize(
  DeviceFileEvents
  | where ingestion_time() >= ago(timeframe)
  // We need to have FileCreated and FileRenamed here because some browsers first download the file under a different name and rename it when it's done.
  // For example, the Chrome .crdownload files are all renamed to the intended name after the download has finished.
  | where ActionType in~ ("FileCreated", "FileRenamed") and InitiatingProcessFileName in~ (browsers) and FileName has_any (ext)
  // We need to do this to limit the search of deviceNetworkEvents. Otherwise, the dataset becomes too big to join in MDE.
  | extend period=bin(Timestamp, binPeriodForSearch)
  // Optimizations to keep MDE happy. Otherwise the database gets too big.
  | project DeviceId, InitiatingProcessFileName, InitiatingProcessId, period, FileName, Timestamp
  // The renames are meant to avoid confusions as there will be a lot of FileNames from different tables.
  | project-rename DeviceFileEvents_InitiatingProcessFileName = InitiatingProcessFileName,
                   DeviceFileEvents_FileName = FileName,
                   DeviceFileEvents_Timestamp = Timestamp
);
// Now we need to find the network event that triggered the the filewrite.
// This is an approximation based on timestamp, deviceid, pid and process name.
let downloadSource = materialize(
  DeviceNetworkEvents
  | where ingestion_time() >= ago(timeframe)
  | where DeviceId in~ ((fileDownloads | project DeviceId)) and RemotePort in (80, 443)
  | extend period=bin(Timestamp, binPeriodForSearch)
  // Exclude allow-listed domains. Here you want to allow-list your internal Sharepoint environment.
  // This can be useful if you want to use this rule for external attacks.
  // There is a trade-off, as an internal attacker might abuse your Sharepoint for malware.
  | where parse_url(RemoteUrl).Host !in~ (whitelistedDomains)
  | lookup kind=inner fileDownloads on DeviceId, InitiatingProcessId, $left.InitiatingProcessFileName == $right.DeviceFileEvents_InitiatingProcessFileName, period
  | extend TimeDiff = datetime_diff('second', Timestamp, DeviceFileEvents_Timestamp)
  // The filecreate and network event should happen within max 15 second of each other.
  | where  -timeDiffFileCreateNetworkEvent < TimeDiff and TimeDiff < timeDiffFileCreateNetworkEvent
  // We're now only interested in the unique filenames of the downloads and the location they're possibly downloaded from.
  | summarize possibelURLs=make_set(RemoteUrl) by DeviceFileEvents_FileName
);
// Final step in tying everything together.
// Find Office applications that create a child process which is in our previously generated list.
// We don't filter on devicename because we want to see all instances of this file being run.
DeviceProcessEvents
| where ingestion_time() >= ago(timeframe)
| where ActionType =~ "ProcessCreated"
| where InitiatingProcessFileName in~ (officeApps) and FileName !in~ (officeApps) and FileName !in~ (whitelist) and FileName !in~ (browsers)
| project-rename DeviceProcessEvents_InitiatingProcessCommandLine = InitiatingProcessCommandLine
| where DeviceProcessEvents_InitiatingProcessCommandLine has_any (( downloadSource | project DeviceFileEvents_FileName))
| where not(FolderPath matches regex @"C:\\Program Files \(x86\)\\TechSmith\\Camtasia Studio \d+\\TscHelp\.exe")
| where not(ProcessCommandLine matches regex @"^""?rundll32""? C:\\WINDOWS\\system32\\spool\\DRIVERS\\((x64)|(x86))\\\d\\hpmsn\d+.((dll)|(DLL)),")
// Verclsid cannot be filtered as it's a LOLBIN which allows creating arbitrary COM objects.
// We can only filter the execution of very specific COM objects.
// Embedded Outlook item in an Office file.
//00020D0B-0000-0000-C000-000000000046 == Outlook 97-2003 Object. 00000112-0000-0000-C000-000000000046 == IOLEInterface 0x5 == CLSCTX_INPROC_SERVER	| CLSCTX_LOCAL_SERVER
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{00020D0B-0000-0000-C000-000000000046\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// Embedded Adobe document in Office file.
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{B801CA65-A1FC-11D0-85AD-444553540000\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// Embedded XML file in Office file.
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{48123BC4-99D9-11D1-A6B3-00C04FD91555\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// Bitmap image.
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{D3E34B21-9D75-101A-8C3D-00AA001A1652\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// ZIP folder.
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{E88DCCE0-B7B3-11D1-A9F0-00AA0060FA31\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// HTML document.
| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{25336920-03F9-11CF-8FD0-00AA00686F13\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")
// FileProfile is case-sensitive and works on lower-case hashes.
| extend SHA1=tolower(SHA1)
| invoke FileProfile(SHA1, 1000)
| where not(FolderPath startswith @"C:\Windows\System32\spool\drivers\" and IsCertificateValid and IsRootSignerMicrosoft) // All drivers signed by Microsoft are trusted.
// Begin environment-specific filter.
// End environment-specific filter.

Stages and Predicates

Parameters

let timeframe = 2*1d;
let browsers = dynamic(["iexplore.exe", "chrome.exe", "firefox.exe", "msedge.exe", "msedgewebview2.exe"]);
let ext = dynamic([".docm", ".xlsm", ".xls", ".doc", ".pptm", ".ppt"]);
let officeApps = dynamic(["winword.exe", "excel.exe", "powerpnt.exe"]);
let whitelistedDomains = dynamic([""]);
let binPeriodForSearch = 1h;
let timeDiffFileCreateNetworkEvent = 15;

Let binding: whitelist

let whitelist = dynamic(["MSOSYNC.exe", "splwow64.exe", "csc.exe", "outlook.exe", "AcroRd32.exe", "Acrobat.exe", "explorer.exe", "DW20.exe",
"Microsoft.Mashup.Container.Loader.exe", "Microsoft.Mashup.Container.NetFX40.exe", "WerFault.exe", "CLVIEW.exe", "wermgr.exe"]);

Let binding: fileDownloads

let fileDownloads = materialize(
  DeviceFileEvents
  | where ingestion_time() >= ago(timeframe)
  | where ActionType in~ ("FileCreated", "FileRenamed") and InitiatingProcessFileName in~ (browsers) and FileName has_any (ext)
  | extend period=bin(Timestamp, binPeriodForSearch)
  | project DeviceId, InitiatingProcessFileName, InitiatingProcessId, period, FileName, Timestamp
  | project-rename DeviceFileEvents_InitiatingProcessFileName = InitiatingProcessFileName,
                   DeviceFileEvents_FileName = FileName,
                   DeviceFileEvents_Timestamp = Timestamp
);

Derived from timeframe, browsers, ext, binPeriodForSearch.

Let binding: downloadSource

let downloadSource = materialize(
  DeviceNetworkEvents
  | where ingestion_time() >= ago(timeframe)
  | where DeviceId in~ ((fileDownloads | project DeviceId)) and RemotePort in (80, 443)
  | extend period=bin(Timestamp, binPeriodForSearch)
  | where parse_url(RemoteUrl).Host !in~ (whitelistedDomains)
  | lookup kind=inner fileDownloads on DeviceId, InitiatingProcessId, $left.InitiatingProcessFileName == $right.DeviceFileEvents_InitiatingProcessFileName, period
  | extend TimeDiff = datetime_diff('second', Timestamp, DeviceFileEvents_Timestamp)
  | where  -timeDiffFileCreateNetworkEvent < TimeDiff and TimeDiff < timeDiffFileCreateNetworkEvent
  | summarize possibelURLs=make_set(RemoteUrl) by DeviceFileEvents_FileName
);

Derived from timeframe, whitelistedDomains, binPeriodForSearch, timeDiffFileCreateNetworkEvent, fileDownloads.

Stage 1: source

DeviceProcessEvents

Stage 2: where

| where ingestion_time() >= ago(timeframe)

Stage 3: where

| where ActionType =~ "ProcessCreated"

Stage 4: where

| where InitiatingProcessFileName in~ (officeApps) and FileName !in~ (officeApps) and FileName !in~ (whitelist) and FileName !in~ (browsers)

References whitelist (defined above).

Stage 5: project-rename

| project-rename DeviceProcessEvents_InitiatingProcessCommandLine = InitiatingProcessCommandLine

Stage 6: where

| where DeviceProcessEvents_InitiatingProcessCommandLine has_any (( downloadSource | project DeviceFileEvents_FileName))

Stage 7: where

| where not(FolderPath matches regex @"C:\\Program Files \(x86\)\\TechSmith\\Camtasia Studio \d+\\TscHelp\.exe")

Stage 8: where

| where not(ProcessCommandLine matches regex @"^""?rundll32""? C:\\WINDOWS\\system32\\spool\\DRIVERS\\((x64)|(x86))\\\d\\hpmsn\d+.((dll)|(DLL)),")

Stage 9: where

| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{00020D0B-0000-0000-C000-000000000046\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")

Stage 10: where

| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{B801CA65-A1FC-11D0-85AD-444553540000\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")

Stage 11: where

| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{48123BC4-99D9-11D1-A6B3-00C04FD91555\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")

Stage 12: where

| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{D3E34B21-9D75-101A-8C3D-00AA001A1652\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")

Stage 13: where

| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{E88DCCE0-B7B3-11D1-A9F0-00AA0060FA31\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")

Stage 14: where

| where not(ProcessCommandLine matches regex @"^""?verclsid\.exe""? \/S \/C \{25336920-03F9-11CF-8FD0-00AA00686F13\} \/I \{00000112-0000-0000-C000-000000000046\} \/X 0x5$")

Stage 15: extend

| extend SHA1=tolower(SHA1)

Stage 16: invoke

| invoke FileProfile(SHA1, 1000)

Stage 17: where

| where not(FolderPath startswith @"C:\Windows\System32\spool\drivers\" and IsCertificateValid and IsRootSignerMicrosoft)

Exclusions

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

FieldKindExcluded values
FileNameinAcroRd32.exe, Acrobat.exe, CLVIEW.exe, DW20.exe, MSOSYNC.exe, Microsoft.Mashup.Container.Loader.exe, Microsoft.Mashup.Container.NetFX40.exe, WerFault.exe, csc.exe, explorer.exe, outlook.exe, splwow64.exe, wermgr.exe
FileNameinchrome.exe, firefox.exe, iexplore.exe, msedge.exe, msedgewebview2.exe
FileNameinexcel.exe, powerpnt.exe, winword.exe
FolderPathregex_matchC:\Program Files (x86)\TechSmith\Camtasia Studio \d+\TscHelp.exe
ProcessCommandLineregex_match^""?rundll32""? C:\WINDOWS\system32\spool\DRIVERS\((x64)|(x86))\\d\hpmsn\d+.((dll)|(DLL)),
ProcessCommandLineregex_match^""?verclsid.exe""? \/S \/C {00020D0B-0000-0000-C000-000000000046} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$
ProcessCommandLineregex_match^""?verclsid.exe""? \/S \/C {B801CA65-A1FC-11D0-85AD-444553540000} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$
ProcessCommandLineregex_match^""?verclsid.exe""? \/S \/C {48123BC4-99D9-11D1-A6B3-00C04FD91555} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$
ProcessCommandLineregex_match^""?verclsid.exe""? \/S \/C {D3E34B21-9D75-101A-8C3D-00AA001A1652} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$
ProcessCommandLineregex_match^""?verclsid.exe""? \/S \/C {E88DCCE0-B7B3-11D1-A9F0-00AA0060FA31} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$
ProcessCommandLineregex_match^""?verclsid.exe""? \/S \/C {25336920-03F9-11CF-8FD0-00AA00686F13} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$
FolderPathstarts_withC:\Windows\System32\spool\drivers\

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
  • ProcessCreated corpus 10 (kusto 10)
InitiatingProcessFileNamein
  • excel.exe corpus 8 (elastic 8)
  • powerpnt.exe corpus 7 (elastic 7)
  • winword.exe corpus 8 (elastic 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
DeviceProcessEvents_InitiatingProcessCommandLineproject-rename
SHA1extend