Detection rules › Kusto
Suspicious office child process created
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
| Tactic | Techniques |
|---|---|
| Execution | T1204 User Execution |
Event coverage
| Provider | Event/ActionType | Title |
|---|---|---|
| Sysmon | Event ID 1 | Process creation |
| Sysmon | Event ID 3 | Network connection |
| Sysmon | Event ID 11 | FileCreate |
| Security-Auditing | Event ID 4663 | An attempt was made to access an object. |
| Security-Auditing | Event ID 4688 | A new process has been created. |
| Security-Auditing | Event ID 5156 | The Windows Filtering Platform has permitted a connection. |
| Defender-DeviceFileEvents | FileCreated | File created |
| Defender-DeviceNetworkEvents | ConnectionSuccess | Connection succeeded |
| Defender-DeviceProcessEvents | ProcessCreated | Process created |
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.
| Field | Kind | Excluded values |
|---|---|---|
FileName | in | AcroRd32.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 |
FileName | in | chrome.exe, firefox.exe, iexplore.exe, msedge.exe, msedgewebview2.exe |
FileName | in | excel.exe, powerpnt.exe, winword.exe |
FolderPath | regex_match | C:\Program Files (x86)\TechSmith\Camtasia Studio \d+\TscHelp.exe |
ProcessCommandLine | regex_match | ^""?rundll32""? C:\WINDOWS\system32\spool\DRIVERS\((x64)|(x86))\\d\hpmsn\d+.((dll)|(DLL)), |
ProcessCommandLine | regex_match | ^""?verclsid.exe""? \/S \/C {00020D0B-0000-0000-C000-000000000046} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$ |
ProcessCommandLine | regex_match | ^""?verclsid.exe""? \/S \/C {B801CA65-A1FC-11D0-85AD-444553540000} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$ |
ProcessCommandLine | regex_match | ^""?verclsid.exe""? \/S \/C {48123BC4-99D9-11D1-A6B3-00C04FD91555} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$ |
ProcessCommandLine | regex_match | ^""?verclsid.exe""? \/S \/C {D3E34B21-9D75-101A-8C3D-00AA001A1652} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$ |
ProcessCommandLine | regex_match | ^""?verclsid.exe""? \/S \/C {E88DCCE0-B7B3-11D1-A9F0-00AA0060FA31} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$ |
ProcessCommandLine | regex_match | ^""?verclsid.exe""? \/S \/C {25336920-03F9-11CF-8FD0-00AA00686F13} \/I {00000112-0000-0000-C000-000000000046} \/X 0x5$ |
FolderPath | starts_with | C:\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.
| Field | Kind | Values |
|---|---|---|
ActionType | eq |
|
InitiatingProcessFileName | in |
|
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.
| Field | Source |
|---|---|
DeviceProcessEvents_InitiatingProcessCommandLine | project-rename |
SHA1 | extend |