-
Notifications
You must be signed in to change notification settings - Fork 525
[Osquery_manager] Startup Items artifact saved query #16078
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: temporary-osquery-artifacts-branch
Are you sure you want to change the base?
[Osquery_manager] Startup Items artifact saved query #16078
Conversation
|
I can see you have used |
From my investigation:
However we might add this table anyway, and just use UNION to gather more results. Is this what you'd prefer @raqueltabuyo ? |
|
I think it is better if we combine the tables. Try to leverage a little bit more filtering for known good processes. |
Implements comprehensive Windows persistence detection via startup_items table with two detection approaches: - NON_WHITELISTED: Filters known-good tasks, flags unknown binaries - LOTL_INDICATOR: Detects Living off the Land patterns (PowerShell -e, certutil, wscript, mshta, regsvr32/rundll32 abuse) Includes hash/signature enrichment via authenticode table join. MITRE ATT&CK: T1547.001, T1059.001, T1105
Implements comprehensive Linux persistence detection combining multiple sources (systemd_units, crontab, XDG autostart, startup_items) with two detection approaches: - NON_WHITELISTED: User-created services in /etc/systemd/system, @reboot cron jobs, user XDG autostart entries - LOTL_INDICATOR: Detects bash -c, curl|bash, base64 -d, /dev/shm execution, netcat, /dev/tcp redirection patterns Cross-distro compatible with expanded known-good allowlist. MITRE ATT&CK: T1543.002, T1053.003, T1547.013, T1059.004, T1105
Implements comprehensive macOS persistence detection via launchd and startup_items tables with two detection approaches: - NON_WHITELISTED: Non-Apple signed LaunchAgents/Daemons, filters /System/Library and /Library/Apple paths - LOTL_INDICATOR: Detects bash -c, curl|bash, osascript -e abuse, base64 -D, /tmp execution, netcat patterns Uses signature table for code signing verification on macOS. MITRE ATT&CK: T1543.001, T1547.015, T1059.004, T1105
Adds accessed_time (atime) and created_time (btime) columns from the file table to all three startup items queries (Windows, Linux, macOS). Maps to file.accessed and file.created ECS fields for complete file timestamp coverage in forensic investigations.
| "id": "startup_items_darwin_elastic", | ||
| "interval": "3600", | ||
| "platform": "darwin", | ||
| "query": "-- Dual-detection macOS persistence query:\n-- 1. NON_WHITELISTED: Non-Apple signed LaunchAgents/Daemons (signature-based filtering)\n-- 2. LOTL_INDICATOR: Living off the Land patterns (bash -c, curl | bash, osascript, etc.)\n-- Uses TRIM() on paths and extracts executable path from program_arguments\n-- MITRE ATT&CK: T1543.001, T1547.015, T1059.004, T1105\n\nWITH non_whitelisted_launchd AS (\n SELECT\n l.label AS name,\n TRIM(COALESCE(NULLIF(l.program, ''), l.program_arguments)) AS path,\n l.path AS source,\n l.program_arguments AS args,\n l.username,\n CASE\n WHEN l.disabled = '1' OR l.disabled = 1 THEN 'disabled'\n WHEN l.run_at_load = 'true' OR l.run_at_load = '1' OR l.run_at_load = 1 THEN 'enabled'\n ELSE 'unknown'\n END AS status,\n 'Launch Agent/Daemon' AS type,\n 'NON_WHITELISTED' AS detection_method,\n 'Non-Apple signed LaunchAgent/Daemon' AS detection_reason\n FROM launchd AS l\n WHERE (\n (l.program IS NOT NULL AND TRIM(l.program) != '')\n OR (l.program_arguments IS NOT NULL AND TRIM(l.program_arguments) != '')\n )\n AND l.path NOT LIKE '/System/Library/%'\n AND l.path NOT LIKE '/Library/Apple/%'\n AND (l.run_at_load = 'true' OR l.run_at_load = '1' OR l.run_at_load = 1 OR l.run_at_load IS NOT NULL)\n),\nnon_whitelisted_startup AS (\n SELECT\n si.name,\n TRIM(si.path) AS path,\n si.source,\n si.args,\n si.username,\n si.status,\n CASE\n WHEN si.type = 'Startup Item' THEN 'Legacy Startup Item'\n WHEN si.type = 'Login Item' THEN 'Login Item'\n ELSE si.type\n END AS type,\n 'NON_WHITELISTED' AS detection_method,\n 'Legacy startup/login item' AS detection_reason\n FROM startup_items AS si\n WHERE TRIM(si.path) IS NOT NULL\n AND TRIM(si.path) != ''\n AND TRIM(si.path) NOT LIKE '/System/Library/%'\n AND TRIM(si.path) NOT LIKE '/Library/Apple/%'\n),\nlotl_launchd AS (\n SELECT\n l.label AS name,\n TRIM(COALESCE(NULLIF(l.program, ''), l.program_arguments)) AS path,\n l.path AS source,\n l.program_arguments AS args,\n l.username,\n CASE\n WHEN l.disabled = '1' OR l.disabled = 1 THEN 'disabled'\n WHEN l.run_at_load = 'true' OR l.run_at_load = '1' OR l.run_at_load = 1 THEN 'enabled'\n ELSE 'unknown'\n END AS status,\n 'Launch Agent/Daemon (LotL)' AS type,\n 'LOTL_INDICATOR' AS detection_method,\n CASE\n WHEN l.program_arguments LIKE '%bash -c%' OR l.program_arguments LIKE '%sh -c%' THEN 'Shell command execution via -c flag'\n WHEN l.program_arguments LIKE '%curl%|%bash%' OR l.program_arguments LIKE '%curl%|%sh%' THEN 'Download and pipe to shell'\n WHEN l.program_arguments LIKE '%curl%http%' OR l.program_arguments LIKE '%wget%http%' THEN 'Download utility abuse'\n WHEN l.program_arguments LIKE '%base64 -D%' OR l.program_arguments LIKE '%base64 --decode%' THEN 'Base64 decode for obfuscation'\n WHEN l.program_arguments LIKE '%osascript%' AND l.program_arguments LIKE '%-e%' THEN 'AppleScript execution abuse'\n WHEN l.program_arguments LIKE '%python%-%c%' OR l.program_arguments LIKE '%perl%-%e%' THEN 'Scripting language one-liner'\n WHEN l.program_arguments LIKE '%/tmp/%' OR l.program_arguments LIKE '%/private/tmp/%' THEN 'Execution from temp directory'\n WHEN l.program_arguments LIKE '%/dev/tcp/%' THEN 'Bash TCP socket redirection'\n WHEN l.program_arguments LIKE '%nc %' OR l.program_arguments LIKE '%netcat%' THEN 'Netcat reverse shell'\n WHEN l.program_arguments LIKE '%nohup%&%' OR l.program_arguments LIKE '%disown%' THEN 'Background process persistence'\n WHEN l.program_arguments LIKE '%.sh%' AND (TRIM(COALESCE(NULLIF(l.program, ''), l.program_arguments)) LIKE '/tmp/%' OR TRIM(COALESCE(NULLIF(l.program, ''), l.program_arguments)) LIKE '/private/tmp/%') THEN 'Shell script from temp directory'\n ELSE 'Unknown LotL pattern in LaunchAgent'\n END AS detection_reason\n FROM launchd AS l\n WHERE (\n (l.program IS NOT NULL AND TRIM(l.program) != '')\n OR (l.program_arguments IS NOT NULL AND TRIM(l.program_arguments) != '')\n )\n AND (l.run_at_load = 'true' OR l.run_at_load = '1' OR l.run_at_load = 1 OR l.run_at_load IS NOT NULL)\n AND (\n l.program_arguments LIKE '%bash -c%'\n OR l.program_arguments LIKE '%sh -c%'\n OR l.program_arguments LIKE '%curl%|%bash%'\n OR l.program_arguments LIKE '%curl%|%sh%'\n OR l.program_arguments LIKE '%curl%http%'\n OR l.program_arguments LIKE '%wget%http%'\n OR l.program_arguments LIKE '%base64 -D%'\n OR l.program_arguments LIKE '%base64 --decode%'\n OR (l.program_arguments LIKE '%osascript%' AND l.program_arguments LIKE '%-e%')\n OR l.program_arguments LIKE '%python%-%c%'\n OR l.program_arguments LIKE '%perl%-%e%'\n OR l.program_arguments LIKE '%/tmp/%'\n OR l.program_arguments LIKE '%/private/tmp/%'\n OR l.program_arguments LIKE '%/dev/tcp/%'\n OR l.program_arguments LIKE '%nc %'\n OR l.program_arguments LIKE '%netcat%'\n OR l.program_arguments LIKE '%nohup%&%'\n OR l.program_arguments LIKE '%disown%'\n OR (l.program_arguments LIKE '%.sh%' AND (TRIM(COALESCE(NULLIF(l.program, ''), l.program_arguments)) LIKE '/tmp/%' OR TRIM(COALESCE(NULLIF(l.program, ''), l.program_arguments)) LIKE '/private/tmp/%'))\n )\n),\ncombined AS (\n SELECT * FROM non_whitelisted_launchd\n UNION ALL\n SELECT * FROM non_whitelisted_startup\n UNION ALL\n SELECT * FROM lotl_launchd\n)\nSELECT\n c.name,\n c.path,\n -- Extract executable path (first space-separated token if path contains arguments)\n CASE\n WHEN INSTR(c.path, ' ') > 0 AND SUBSTR(c.path, 1, 1) != '-'\n THEN SUBSTR(c.path, 1, INSTR(c.path, ' ') - 1)\n ELSE c.path\n END AS exe_path,\n c.type,\n c.status,\n c.source,\n c.args,\n c.username,\n c.detection_method,\n c.detection_reason,\n s.signed AS is_signed,\n s.identifier AS signature_signer,\n h.sha256,\n h.sha1,\n h.md5,\n f.size,\n datetime(f.mtime, 'unixepoch') AS modified_time,\n datetime(f.ctime, 'unixepoch') AS changed_time,\n datetime(f.atime, 'unixepoch') AS accessed_time,\n datetime(f.btime, 'unixepoch') AS created_time,\n f.uid,\n f.gid,\n f.mode\nFROM combined AS c\nLEFT JOIN signature AS s ON s.path = CASE\n WHEN INSTR(c.path, ' ') > 0 AND SUBSTR(c.path, 1, 1) != '-'\n THEN SUBSTR(c.path, 1, INSTR(c.path, ' ') - 1)\n ELSE c.path\nEND\nLEFT JOIN hash AS h ON h.path = CASE\n WHEN INSTR(c.path, ' ') > 0 AND SUBSTR(c.path, 1, 1) != '-'\n THEN SUBSTR(c.path, 1, INSTR(c.path, ' ') - 1)\n ELSE c.path\nEND\nLEFT JOIN file AS f ON f.path = CASE\n WHEN INSTR(c.path, ' ') > 0 AND SUBSTR(c.path, 1, 1) != '-'\n THEN SUBSTR(c.path, 1, INSTR(c.path, ' ') - 1)\n ELSE c.path\nEND\nWHERE (\n c.detection_method = 'LOTL_INDICATOR'\n OR s.signed IS NULL\n OR s.signed = 0\n OR (\n s.identifier IS NOT NULL\n AND s.identifier NOT LIKE 'com.apple.%'\n AND s.identifier NOT LIKE 'Apple Inc.%'\n )\n)\nORDER BY\n CASE WHEN c.detection_method = 'LOTL_INDICATOR' THEN 0 ELSE 1 END,\n CASE WHEN s.signed = 0 OR s.signed IS NULL THEN 0 ELSE 1 END,\n c.detection_reason,\n c.name", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this query:
AND (l.run_at_load = 'true' OR l.run_at_load = '1' OR l.run_at_load = 1 OR l.run_at_load IS NOT NULL) can be reduced to l.run_at_load IS NOT NULL is that your intention?
Also, I think the match patterns need work. This seems to apply to the Linux patterns too though I didn't have an FPs. Examples I saw on my computer are below. Though I'm not sure how to address them...
WHEN l.program_arguments LIKE '%nc %' OR l.program_arguments LIKE '%netcat%' THEN 'Netcat reverse shell'flags on/System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Versions/A/Support/mdsync -s mdworker-scan -c MDSSyncScanWorker -m com.apple.metadata.mds.scanl.program_arguments LIKE '%/tmp/%' OR l.program_arguments LIKE '%/private/tmp/%' THEN 'Execution from temp directory' flags on/usr/libexec/kdumpd /var/tmp/PanicDumps
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I figured this would work - what do you think?
| Issue | Fix Applied |
|---|---|
| run_at_load simplification | Removed redundant IS NOT NULL, kept explicit true checks |
| %nc % → mdsync FP | Changed to % nc % (space before nc) |
| /tmp/ → kdumpd FP | Added NOT LIKE '%/var/tmp/%' exclusion |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also worth noting: I did not change to just IS NOT NULL as suggested because that would incorrectly match run_at_load = 'false'.
| } | ||
| }, | ||
| { | ||
| "key": "file.code_signature.signed", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
file.code_signature.signed doesn't exist in ECS. Can the table columb be changed to signature_status (and have a text value in it that matches other tables rather than an int?) then file.code_signature.status be used?
| { | ||
| "key": "file.directory", | ||
| "value": { | ||
| "field": "source" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Source looks like the source plist not the directory. Drop?
| { | ||
| "key": "process.name", | ||
| "value": { | ||
| "field": "name" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
name isn't the process name. Drop?
| "key": "file.directory", | ||
| "value": { | ||
| "field": "source" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
source is the source service file not the directory. Drop?
| { | ||
| "key": "process.name", | ||
| "value": { | ||
| "field": "name" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
name is not the process name. Drop?
| { | ||
| "key": "registry.path", | ||
| "value": { | ||
| "field": "source" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
source can contain non-registry paths. Drop this?
| "key": "process.name", | ||
| "value": { | ||
| "field": "name" | ||
| } | ||
| }, | ||
| { | ||
| "key": "process.executable", | ||
| "value": { | ||
| "field": "path" | ||
| } | ||
| }, | ||
| { | ||
| "key": "process.command_line", | ||
| "value": { | ||
| "field": "args" | ||
| } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw "process" details for desktop.ini which included the fact that it was signed. I wonder what's going on. Is there a bug in the query or is this accurate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There also some strange exe_path , like 0, 1 showing while enumerating HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunNotification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, what do you suggest? Should we add a filter to exclude non-executable files from here?
| "key": "process.name", | ||
| "value": { | ||
| "field": "name" | ||
| } | ||
| }, | ||
| { | ||
| "key": "process.executable", | ||
| "value": { | ||
| "field": "path" | ||
| } | ||
| }, | ||
| { | ||
| "key": "process.command_line", | ||
| "value": { | ||
| "field": "args" | ||
| } | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There also some strange exe_path , like 0, 1 showing while enumerating HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunNotification.
| { | ||
| "key": "process.executable", | ||
| "value": { | ||
| "field": "path" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me, path shows as the systemd service file path in some cases, or the /etc/init.d script
In both cases this points to a service, not a process: should this ECS field be removed in favor of using service.id with the name field?
| { | ||
| "key": "file.path", | ||
| "value": { | ||
| "field": "path" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
path here makes more sense to me IMHO
- Remove event.kind, event.module, event.dataset static mappings - Replace with event.action mapping for consistency - Remove host.os.type static mapping (redundant with Elastic Agent) - Remove threat.* fields (framework, tactic, technique mappings) - Simplify tags array to core identifiers only - Apply changes to all 3 platform queries (Windows, Linux, macOS)
- Remove incorrect ECS mappings per reviewer comments: - Drop process.name (name column is item label, not process name) - Drop file.directory→source (source is file path, not directory) - Drop registry.path→source (source can contain non-registry paths) - Drop process.executable on Linux (path shows service files) - Fix macOS false positives: - Change %nc % to % nc % to prevent mdsync FP - Exclude /var/tmp/ from /tmp/ pattern to prevent kdumpd FP - Fix Windows RunNotification entries: - Filter out numeric path values (0, 1, 4) from RunNotification - Simplify macOS run_at_load condition (remove redundant IS NOT NULL)
💚 Build Succeeded
History
cc @tomsonpl |
Startup Items Artifact
The Startup Items artifact provides comprehensive visibility into persistence mechanisms across Windows, macOS, and Linux systems. These queries employ a dual-detection approach: (1) identifying non-whitelisted/non-signed persistence entries and (2) detecting Living off the Land (LotL) attack indicators using legitimate OS tools. This enables security teams to identify both custom malware persistence AND sophisticated attacks that abuse native system utilities.
Core Forensic Artifacts Coverage
Queries by Platform
🪟 Windows - Registry Run Keys & Startup Folder Persistence
Description
Detects Windows persistence via startup items using a dual-detection approach: (1) Non-whitelisted legitimate binaries and (2) Living off the Land (LotL) attack indicators. Identifies both unsigned/unknown binaries AND abuse of legitimate Windows tools (PowerShell -e, certutil, wscript, etc.) for malicious persistence. Filters out high-volume known-good tasks while flagging suspicious patterns regardless of code signature.
Detection Focus:
-e,-enc,-EncodedCommand)MITRE ATT&CK Mapping:
Result
Query results include startup item name, path, type, registry source, command arguments, detection method (NON_WHITELISTED or LOTL_INDICATOR), detection reason, code signature status, file hashes (SHA256/SHA1/MD5), and file metadata.
Platform
windowsInterval
3600seconds (1 hour)Query ID
startup_items_windows_elasticECS Field Mappings
event.category→["configuration"]event.type→["info"]event.kind→stateevent.module→osqueryevent.dataset→osquery.startup_itemshost.os.type→windowsprocess.name→nameprocess.executable→pathprocess.command_line→argsfile.path→pathfile.hash.sha256→sha256file.hash.sha1→sha1file.hash.md5→md5file.size→sizefile.mtime→modified_timefile.ctime→changed_timefile.accessed→accessed_timefile.created→created_timefile.directory→directoryuser.name→usernamerule.category→typeservice.state→statusregistry.path→sourcefile.code_signature.subject_name→signature_signerfile.code_signature.status→signature_statusrule.description→detection_reasonrule.name→detection_methodthreat.framework→MITRE ATT&CKthreat.tactic.id→["TA0003", "TA0002"]threat.tactic.name→["Persistence", "Execution"]threat.technique.id→["T1547.001", "T1059.001", "T1105"]SQL Query
🍎 macOS - LaunchAgents/Daemons & Login Items Persistence
Description
Detects macOS persistence via a dual-detection approach: (1) Non-Apple signed LaunchAgents/Daemons and (2) Living off the Land (LotL) attack indicators using legitimate macOS tools. Identifies both unsigned persistence AND abuse of bash, curl/osascript, base64, etc. Filters out Apple system paths while detecting suspicious patterns regardless of code signature. Note: macOS 10.13+ login items may not be fully captured due to Apple's backgrounditems.btm format.
Detection Focus:
-cflag (bash -c, sh -c)MITRE ATT&CK Mapping:
Result
Query results include LaunchAgent/Daemon label, executable path, plist source, program arguments, detection method (NON_WHITELISTED or LOTL_INDICATOR), detection reason, code signature status, file hashes (SHA256/SHA1/MD5), and file metadata.
Platform
darwinInterval
3600seconds (1 hour)Query ID
startup_items_darwin_elasticECS Field Mappings
event.category→["configuration"]event.type→["info"]event.kind→stateevent.module→osqueryevent.dataset→osquery.startup_itemshost.os.type→macosprocess.name→nameprocess.executable→exe_pathprocess.command_line→argsfile.path→pathfile.hash.sha256→sha256file.hash.sha1→sha1file.hash.md5→md5file.size→sizefile.mtime→modified_timefile.ctime→changed_timefile.accessed→accessed_timefile.created→created_timefile.uid→uidfile.gid→gidfile.mode→modeuser.name→usernameservice.state→statusrule.category→typefile.directory→sourcefile.code_signature.signed→is_signedfile.code_signature.subject_name→signature_signerrule.description→detection_reasonrule.name→detection_methodthreat.framework→MITRE ATT&CKthreat.tactic.id→["TA0003", "TA0002"]threat.tactic.name→["Persistence", "Execution"]threat.technique.id→["T1543.001", "T1547.015", "T1059.004", "T1105"]SQL Query
🐧 Linux - Systemd, Cron & XDG Autostart Persistence
Description
Detects Linux persistence via a dual-detection approach: (1) User-created persistence mechanisms (systemd services, cron @reboot jobs, XDG autostart entries, startup_items) and (2) Living off the Land (LotL) attack indicators using legitimate Linux tools. Combines startup_items table with fine-grained systemd_units/crontab queries for comprehensive coverage. Identifies both custom persistence AND abuse of bash, curl/wget, base64 decoding, /dev/shm execution, etc. Maintains cross-distro compatibility with location-based filtering and expanded known-good allowlist.
Detection Focus:
-cflag (bash -c, sh -c)MITRE ATT&CK Mapping:
Result
Query results include service/cron name, service ID, executable path, source location, command arguments, username, status, detection method (NON_WHITELISTED or LOTL_INDICATOR), detection reason, file hashes (SHA256/SHA1/MD5), and file metadata including permissions.
Platform
linuxInterval
3600seconds (1 hour)Query ID
startup_items_linux_elasticECS Field Mappings
event.category→["configuration"]event.type→["info"]event.kind→stateevent.module→osqueryevent.dataset→osquery.startup_itemshost.os.type→linuxprocess.name→nameprocess.executable→pathprocess.command_line→argsfile.path→pathfile.hash.sha256→sha256file.hash.sha1→sha1file.hash.md5→md5file.size→sizefile.mtime→modified_timefile.ctime→changed_timefile.accessed→accessed_timefile.created→created_timefile.uid→uidfile.gid→gidfile.mode→modeuser.name→usernameservice.id→service_idservice.state→statusrule.category→typefile.directory→sourcerule.description→detection_reasonrule.name→detection_methodthreat.framework→MITRE ATT&CKthreat.tactic.id→["TA0003", "TA0002"]threat.tactic.name→["Persistence", "Execution"]threat.technique.id→["T1543.002", "T1053.003", "T1547.013", "T1059.004", "T1105"]SQL Query