Skip to content

Conversation

@tomsonpl
Copy link
Contributor

@tomsonpl tomsonpl commented Nov 21, 2025

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

# Artifact OS Query File Description
1 Startup Items Windows startup_items_windows_elastic d4e5f6a7 Windows persistence via registry Run keys, startup folders with LotL detection
2 Startup Items macOS startup_items_darwin_elastic f6a7b8c9 macOS persistence via LaunchAgents/Daemons with signature and LotL detection
3 Startup Items Linux startup_items_linux_elastic e5f6a7b8 Linux persistence via systemd, cron, XDG autostart with LotL detection

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:

  • PowerShell base64 encoded command execution (-e, -enc, -EncodedCommand)
  • Download utility abuse (Invoke-WebRequest, certutil -urlcache, bitsadmin)
  • Execution from suspicious paths (C:\Users\Public, C:\ProgramData, Temp directories)
  • Windows Script Host abuse (wscript.exe, cscript.exe, mshta.exe)
  • Proxy execution via regsvr32/rundll32
  • Script file execution (.hta, .vbs, .js)

MITRE ATT&CK Mapping:

  • T1547.001 - Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder
  • T1059.001 - Command and Scripting Interpreter: PowerShell
  • T1105 - Ingress Tool Transfer

Result

Screenshot 2025-12-05 at 19 37 07

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

windows

Interval

3600 seconds (1 hour)

Query ID

startup_items_windows_elastic

ECS Field Mappings

  • event.category["configuration"]
  • event.type["info"]
  • event.kindstate
  • event.moduleosquery
  • event.datasetosquery.startup_items
  • host.os.typewindows
  • process.namename
  • process.executablepath
  • process.command_lineargs
  • file.pathpath
  • file.hash.sha256sha256
  • file.hash.sha1sha1
  • file.hash.md5md5
  • file.sizesize
  • file.mtimemodified_time
  • file.ctimechanged_time
  • file.accessedaccessed_time
  • file.createdcreated_time
  • file.directorydirectory
  • user.nameusername
  • rule.categorytype
  • service.statestatus
  • registry.pathsource
  • file.code_signature.subject_namesignature_signer
  • file.code_signature.statussignature_status
  • rule.descriptiondetection_reason
  • rule.namedetection_method
  • threat.frameworkMITRE ATT&CK
  • threat.tactic.id["TA0003", "TA0002"]
  • threat.tactic.name["Persistence", "Execution"]
  • threat.technique.id["T1547.001", "T1059.001", "T1105"]

SQL Query

-- Dual-detection Windows startup items query:
-- 1. NON_WHITELISTED: Filters out known-good high-volume tasks, flags everything else
-- 2. LOTL_INDICATOR: Detects Living off the Land attack patterns (powershell -e, certutil, etc.)
-- Uses TRIM() on paths and extracts .exe path for proper hash lookups
-- MITRE ATT&CK: T1547.001, T1059.001, T1105

WITH non_whitelisted AS (
    SELECT
        si.name,
        TRIM(si.path) AS path,
        si.type,
        si.status,
        si.source,
        si.args,
        si.username,
        'NON_WHITELISTED' AS detection_method,
        'Startup item not in known-good allowlist' AS detection_reason
    FROM startup_items AS si
    WHERE si.type IN ('Startup Item', 'Run Group Policy', 'RunOnce')
        AND TRIM(si.path) IS NOT NULL
        AND TRIM(si.path) != ''
        -- Filter 1a: Exclude Microsoft system tasks in System32 (unless LotL indicators present)
        AND NOT (
            si.source LIKE 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\%'
            AND TRIM(si.path) LIKE 'C:\Windows\System32\%'
            AND si.args NOT LIKE '%powershell% -e%'
            AND si.args NOT LIKE '% -enc %'
            AND si.args NOT LIKE '% -EncodedCommand %'
            AND si.args NOT LIKE '%Invoke-WebRequest%'
            AND si.args NOT LIKE '%IWR %'
            AND si.args NOT LIKE '%certutil% -urlcache%'
            AND si.args NOT LIKE '%bitsadmin%'
        )
        -- Filter 1b: Exclude specific known-good third-party updaters (name + path match required)
        AND NOT (
            si.name = 'GoogleUpdateTaskMachineUA'
            AND TRIM(si.path) LIKE '%GoogleUpdate.exe%'
        )
        AND NOT (
            si.name LIKE 'Adobe Acrobat Update Task%'
            AND TRIM(si.path) LIKE '%Adobe%'
        )
        AND NOT (
            si.source LIKE '%Microsoft\Office%'
            AND TRIM(si.path) LIKE 'C:\Program Files\Microsoft Office\%'
        )
),
lotl_indicators AS (
    SELECT
        si.name,
        TRIM(si.path) AS path,
        si.type,
        si.status,
        si.source,
        si.args,
        si.username,
        'LOTL_INDICATOR' AS detection_method,
        CASE
            WHEN si.args LIKE '%powershell% -e%' OR si.args LIKE '% -enc %' OR si.args LIKE '% -EncodedCommand %' THEN 'PowerShell base64 encoded command'
            WHEN si.args LIKE '%Invoke-WebRequest%' OR si.args LIKE '%IWR %' OR si.args LIKE '%curl %' OR si.args LIKE '%wget %' THEN 'Download command detected'
            WHEN si.args LIKE '%certutil% -urlcache%' OR si.args LIKE '%certutil% -f%' THEN 'CertUtil download abuse'
            WHEN si.args LIKE '%bitsadmin% /transfer%' THEN 'BITSAdmin download abuse'
            WHEN TRIM(si.path) LIKE '%C:\Users\Public\%' OR TRIM(si.path) LIKE '%C:\ProgramData\%' THEN 'Suspicious file path (writable by low-priv users)'
            WHEN TRIM(si.path) LIKE '%\Temp\%' OR TRIM(si.path) LIKE '%\AppData\Local\Temp\%' THEN 'Execution from Temp directory'
            WHEN si.args LIKE '%wscript.exe%' OR si.args LIKE '%cscript.exe%' THEN 'Windows Script Host abuse'
            WHEN si.args LIKE '%mshta.exe%' THEN 'MSHTA.exe abuse'
            WHEN si.args LIKE '%regsvr32%' OR si.args LIKE '%rundll32%' THEN 'Proxy execution via regsvr32/rundll32'
            WHEN si.args LIKE '%.hta%' OR si.args LIKE '%.vbs%' OR si.args LIKE '%.js%' THEN 'Script file execution'
            ELSE 'Unknown LotL pattern'
        END AS detection_reason
    FROM startup_items AS si
    WHERE si.type IN ('Startup Item', 'Run Group Policy', 'RunOnce')
        AND TRIM(si.path) IS NOT NULL
        AND TRIM(si.path) != ''
        AND (
            -- PowerShell encoded commands (highest priority)
            si.args LIKE '%powershell% -e%'
            OR si.args LIKE '% -enc %'
            OR si.args LIKE '% -EncodedCommand %'
            -- Download utilities abuse
            OR si.args LIKE '%Invoke-WebRequest%'
            OR si.args LIKE '%IWR %'
            OR si.args LIKE '%curl %'
            OR si.args LIKE '%wget %'
            OR si.args LIKE '%certutil% -urlcache%'
            OR si.args LIKE '%certutil% -f%'
            OR si.args LIKE '%bitsadmin% /transfer%'
            -- Suspicious file paths
            OR TRIM(si.path) LIKE '%C:\Users\Public\%'
            OR TRIM(si.path) LIKE '%C:\ProgramData\%'
            OR TRIM(si.path) LIKE '%\Temp\%'
            OR TRIM(si.path) LIKE '%\AppData\Local\Temp\%'
            -- Script execution abuse
            OR si.args LIKE '%wscript.exe%'
            OR si.args LIKE '%cscript.exe%'
            OR si.args LIKE '%mshta.exe%'
            OR si.args LIKE '%regsvr32%'
            OR si.args LIKE '%rundll32%'
            OR si.args LIKE '%.hta%'
            OR si.args LIKE '%.vbs%'
            OR si.args LIKE '%.js%'
        )
),
combined AS (
    SELECT * FROM non_whitelisted
    UNION
    SELECT * FROM lotl_indicators
)
SELECT
    c.name,
    c.path,
    -- Extract .exe path from command line for proper hash/signature lookups
    CASE
        WHEN INSTR(LOWER(c.path), '.exe ') > 0
        THEN SUBSTR(c.path, 1, INSTR(LOWER(c.path), '.exe ') + 3)
        WHEN INSTR(LOWER(c.path), '.exe"') > 0
        THEN REPLACE(SUBSTR(c.path, 1, INSTR(LOWER(c.path), '.exe"') + 3), '"', '')
        ELSE c.path
    END AS exe_path,
    c.type,
    c.status,
    c.source,
    c.args,
    c.username,
    c.detection_method,
    c.detection_reason,
    a.subject_name AS signature_signer,
    a.result AS signature_status,
    h.sha256,
    h.sha1,
    h.md5,
    f.size,
    datetime(f.mtime, 'unixepoch') AS modified_time,
    datetime(f.ctime, 'unixepoch') AS changed_time,
    datetime(f.atime, 'unixepoch') AS accessed_time,
    datetime(f.btime, 'unixepoch') AS created_time,
    f.directory
FROM combined AS c
LEFT JOIN authenticode AS a ON a.path = CASE
    WHEN INSTR(LOWER(c.path), '.exe ') > 0
    THEN SUBSTR(c.path, 1, INSTR(LOWER(c.path), '.exe ') + 3)
    WHEN INSTR(LOWER(c.path), '.exe"') > 0
    THEN REPLACE(SUBSTR(c.path, 1, INSTR(LOWER(c.path), '.exe"') + 3), '"', '')
    ELSE c.path
END
LEFT JOIN hash AS h ON h.path = CASE
    WHEN INSTR(LOWER(c.path), '.exe ') > 0
    THEN SUBSTR(c.path, 1, INSTR(LOWER(c.path), '.exe ') + 3)
    WHEN INSTR(LOWER(c.path), '.exe"') > 0
    THEN REPLACE(SUBSTR(c.path, 1, INSTR(LOWER(c.path), '.exe"') + 3), '"', '')
    ELSE c.path
END
LEFT JOIN file AS f ON f.path = CASE
    WHEN INSTR(LOWER(c.path), '.exe ') > 0
    THEN SUBSTR(c.path, 1, INSTR(LOWER(c.path), '.exe ') + 3)
    WHEN INSTR(LOWER(c.path), '.exe"') > 0
    THEN REPLACE(SUBSTR(c.path, 1, INSTR(LOWER(c.path), '.exe"') + 3), '"', '')
    ELSE c.path
END
ORDER BY
    CASE WHEN c.detection_method = 'LOTL_INDICATOR' THEN 0 ELSE 1 END,
    c.detection_reason,
    c.name

🍎 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:

  • Shell command execution via -c flag (bash -c, sh -c)
  • Download and pipe to shell patterns (curl | bash)
  • AppleScript execution abuse (osascript -e)
  • Base64 decode for obfuscation
  • Execution from temp directories (/tmp, /private/tmp)
  • Netcat reverse shells and bash TCP socket redirection
  • Background process persistence (nohup, disown)

MITRE ATT&CK Mapping:

  • T1543.001 - Create or Modify System Process: Launch Agent
  • T1547.015 - Boot or Logon Autostart Execution: Login Items
  • T1059.004 - Command and Scripting Interpreter: Unix Shell
  • T1105 - Ingress Tool Transfer

Result

Screenshot 2025-12-05 at 19 37 25

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

darwin

Interval

3600 seconds (1 hour)

Query ID

startup_items_darwin_elastic

ECS Field Mappings

  • event.category["configuration"]
  • event.type["info"]
  • event.kindstate
  • event.moduleosquery
  • event.datasetosquery.startup_items
  • host.os.typemacos
  • process.namename
  • process.executableexe_path
  • process.command_lineargs
  • file.pathpath
  • file.hash.sha256sha256
  • file.hash.sha1sha1
  • file.hash.md5md5
  • file.sizesize
  • file.mtimemodified_time
  • file.ctimechanged_time
  • file.accessedaccessed_time
  • file.createdcreated_time
  • file.uiduid
  • file.gidgid
  • file.modemode
  • user.nameusername
  • service.statestatus
  • rule.categorytype
  • file.directorysource
  • file.code_signature.signedis_signed
  • file.code_signature.subject_namesignature_signer
  • rule.descriptiondetection_reason
  • rule.namedetection_method
  • threat.frameworkMITRE ATT&CK
  • threat.tactic.id["TA0003", "TA0002"]
  • threat.tactic.name["Persistence", "Execution"]
  • threat.technique.id["T1543.001", "T1547.015", "T1059.004", "T1105"]

SQL Query

-- Dual-detection macOS persistence query:
-- 1. NON_WHITELISTED: Non-Apple signed LaunchAgents/Daemons (signature-based filtering)
-- 2. LOTL_INDICATOR: Living off the Land patterns (bash -c, curl | bash, osascript, etc.)
-- Uses TRIM() on paths and extracts executable path from program_arguments
-- MITRE ATT&CK: T1543.001, T1547.015, T1059.004, T1105

WITH non_whitelisted_launchd AS (
    SELECT
        l.label AS name,
        TRIM(COALESCE(NULLIF(l.program, ''), l.program_arguments)) AS path,
        l.path AS source,
        l.program_arguments AS args,
        l.username,
        CASE
            WHEN l.disabled = '1' OR l.disabled = 1 THEN 'disabled'
            WHEN l.run_at_load = 'true' OR l.run_at_load = '1' OR l.run_at_load = 1 THEN 'enabled'
            ELSE 'unknown'
        END AS status,
        'Launch Agent/Daemon' AS type,
        'NON_WHITELISTED' AS detection_method,
        'Non-Apple signed LaunchAgent/Daemon' AS detection_reason
    FROM launchd AS l
    WHERE (
        (l.program IS NOT NULL AND TRIM(l.program) != '')
        OR (l.program_arguments IS NOT NULL AND TRIM(l.program_arguments) != '')
    )
        AND l.path NOT LIKE '/System/Library/%'
        AND l.path NOT LIKE '/Library/Apple/%'
        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)
),
non_whitelisted_startup AS (
    SELECT
        si.name,
        TRIM(si.path) AS path,
        si.source,
        si.args,
        si.username,
        si.status,
        CASE
            WHEN si.type = 'Startup Item' THEN 'Legacy Startup Item'
            WHEN si.type = 'Login Item' THEN 'Login Item'
            ELSE si.type
        END AS type,
        'NON_WHITELISTED' AS detection_method,
        'Legacy startup/login item' AS detection_reason
    FROM startup_items AS si
    WHERE TRIM(si.path) IS NOT NULL
        AND TRIM(si.path) != ''
        AND TRIM(si.path) NOT LIKE '/System/Library/%'
        AND TRIM(si.path) NOT LIKE '/Library/Apple/%'
),
lotl_launchd AS (
    SELECT
        l.label AS name,
        TRIM(COALESCE(NULLIF(l.program, ''), l.program_arguments)) AS path,
        l.path AS source,
        l.program_arguments AS args,
        l.username,
        CASE
            WHEN l.disabled = '1' OR l.disabled = 1 THEN 'disabled'
            WHEN l.run_at_load = 'true' OR l.run_at_load = '1' OR l.run_at_load = 1 THEN 'enabled'
            ELSE 'unknown'
        END AS status,
        'Launch Agent/Daemon (LotL)' AS type,
        'LOTL_INDICATOR' AS detection_method,
        CASE
            WHEN l.program_arguments LIKE '%bash -c%' OR l.program_arguments LIKE '%sh -c%' THEN 'Shell command execution via -c flag'
            WHEN l.program_arguments LIKE '%curl%|%bash%' OR l.program_arguments LIKE '%curl%|%sh%' THEN 'Download and pipe to shell'
            WHEN l.program_arguments LIKE '%curl%http%' OR l.program_arguments LIKE '%wget%http%' THEN 'Download utility abuse'
            WHEN l.program_arguments LIKE '%base64 -D%' OR l.program_arguments LIKE '%base64 --decode%' THEN 'Base64 decode for obfuscation'
            WHEN l.program_arguments LIKE '%osascript%' AND l.program_arguments LIKE '%-e%' THEN 'AppleScript execution abuse'
            WHEN l.program_arguments LIKE '%python%-%c%' OR l.program_arguments LIKE '%perl%-%e%' THEN 'Scripting language one-liner'
            WHEN l.program_arguments LIKE '%/tmp/%' OR l.program_arguments LIKE '%/private/tmp/%' THEN 'Execution from temp directory'
            WHEN l.program_arguments LIKE '%/dev/tcp/%' THEN 'Bash TCP socket redirection'
            WHEN l.program_arguments LIKE '%nc %' OR l.program_arguments LIKE '%netcat%' THEN 'Netcat reverse shell'
            WHEN l.program_arguments LIKE '%nohup%&%' OR l.program_arguments LIKE '%disown%' THEN 'Background process persistence'
            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'
            ELSE 'Unknown LotL pattern in LaunchAgent'
        END AS detection_reason
    FROM launchd AS l
    WHERE (
        (l.program IS NOT NULL AND TRIM(l.program) != '')
        OR (l.program_arguments IS NOT NULL AND TRIM(l.program_arguments) != '')
    )
        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)
        AND (
            l.program_arguments LIKE '%bash -c%'
            OR l.program_arguments LIKE '%sh -c%'
            OR l.program_arguments LIKE '%curl%|%bash%'
            OR l.program_arguments LIKE '%curl%|%sh%'
            OR l.program_arguments LIKE '%curl%http%'
            OR l.program_arguments LIKE '%wget%http%'
            OR l.program_arguments LIKE '%base64 -D%'
            OR l.program_arguments LIKE '%base64 --decode%'
            OR (l.program_arguments LIKE '%osascript%' AND l.program_arguments LIKE '%-e%')
            OR l.program_arguments LIKE '%python%-%c%'
            OR l.program_arguments LIKE '%perl%-%e%'
            OR l.program_arguments LIKE '%/tmp/%'
            OR l.program_arguments LIKE '%/private/tmp/%'
            OR l.program_arguments LIKE '%/dev/tcp/%'
            OR l.program_arguments LIKE '%nc %'
            OR l.program_arguments LIKE '%netcat%'
            OR l.program_arguments LIKE '%nohup%&%'
            OR l.program_arguments LIKE '%disown%'
            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/%'))
        )
),
combined AS (
    SELECT * FROM non_whitelisted_launchd
    UNION ALL
    SELECT * FROM non_whitelisted_startup
    UNION ALL
    SELECT * FROM lotl_launchd
)
SELECT
    c.name,
    c.path,
    -- Extract executable path (first space-separated token if path contains arguments)
    CASE
        WHEN INSTR(c.path, ' ') > 0 AND SUBSTR(c.path, 1, 1) != '-'
        THEN SUBSTR(c.path, 1, INSTR(c.path, ' ') - 1)
        ELSE c.path
    END AS exe_path,
    c.type,
    c.status,
    c.source,
    c.args,
    c.username,
    c.detection_method,
    c.detection_reason,
    s.signed AS is_signed,
    s.identifier AS signature_signer,
    h.sha256,
    h.sha1,
    h.md5,
    f.size,
    datetime(f.mtime, 'unixepoch') AS modified_time,
    datetime(f.ctime, 'unixepoch') AS changed_time,
    datetime(f.atime, 'unixepoch') AS accessed_time,
    datetime(f.btime, 'unixepoch') AS created_time,
    f.uid,
    f.gid,
    f.mode
FROM combined AS c
LEFT JOIN signature AS s ON s.path = CASE
    WHEN INSTR(c.path, ' ') > 0 AND SUBSTR(c.path, 1, 1) != '-'
    THEN SUBSTR(c.path, 1, INSTR(c.path, ' ') - 1)
    ELSE c.path
END
LEFT JOIN hash AS h ON h.path = CASE
    WHEN INSTR(c.path, ' ') > 0 AND SUBSTR(c.path, 1, 1) != '-'
    THEN SUBSTR(c.path, 1, INSTR(c.path, ' ') - 1)
    ELSE c.path
END
LEFT JOIN file AS f ON f.path = CASE
    WHEN INSTR(c.path, ' ') > 0 AND SUBSTR(c.path, 1, 1) != '-'
    THEN SUBSTR(c.path, 1, INSTR(c.path, ' ') - 1)
    ELSE c.path
END
WHERE (
    c.detection_method = 'LOTL_INDICATOR'
    OR s.signed IS NULL
    OR s.signed = 0
    OR (
        s.identifier IS NOT NULL
        AND s.identifier NOT LIKE 'com.apple.%'
        AND s.identifier NOT LIKE 'Apple Inc.%'
    )
)
ORDER BY
    CASE WHEN c.detection_method = 'LOTL_INDICATOR' THEN 0 ELSE 1 END,
    CASE WHEN s.signed = 0 OR s.signed IS NULL THEN 0 ELSE 1 END,
    c.detection_reason,
    c.name

🐧 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:

  • Shell command execution via -c flag (bash -c, sh -c)
  • Download and pipe to shell patterns (curl | bash, wget | sh)
  • Base64 decode for obfuscation
  • Execution from world-writable directories (/dev/shm, /tmp)
  • Netcat reverse shells and bash TCP socket redirection
  • Background process persistence (nohup, disown)
  • Download and execute patterns (chmod +x with http URLs)
  • Scripting language one-liners (python -c, perl -e)

MITRE ATT&CK Mapping:

  • T1543.002 - Create or Modify System Process: Systemd Service
  • T1053.003 - Scheduled Task/Job: Cron
  • T1547.013 - Boot or Logon Autostart Execution: XDG Autostart Entries
  • T1059.004 - Command and Scripting Interpreter: Unix Shell
  • T1105 - Ingress Tool Transfer

Result

Screenshot 2025-12-05 at 19 37 38

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

linux

Interval

3600 seconds (1 hour)

Query ID

startup_items_linux_elastic

ECS Field Mappings

  • event.category["configuration"]
  • event.type["info"]
  • event.kindstate
  • event.moduleosquery
  • event.datasetosquery.startup_items
  • host.os.typelinux
  • process.namename
  • process.executablepath
  • process.command_lineargs
  • file.pathpath
  • file.hash.sha256sha256
  • file.hash.sha1sha1
  • file.hash.md5md5
  • file.sizesize
  • file.mtimemodified_time
  • file.ctimechanged_time
  • file.accessedaccessed_time
  • file.createdcreated_time
  • file.uiduid
  • file.gidgid
  • file.modemode
  • user.nameusername
  • service.idservice_id
  • service.statestatus
  • rule.categorytype
  • file.directorysource
  • rule.descriptiondetection_reason
  • rule.namedetection_method
  • threat.frameworkMITRE ATT&CK
  • threat.tactic.id["TA0003", "TA0002"]
  • threat.tactic.name["Persistence", "Execution"]
  • threat.technique.id["T1543.002", "T1053.003", "T1547.013", "T1059.004", "T1105"]

SQL Query

-- Dual-detection Linux persistence query:
-- 1. NON_WHITELISTED: User-created systemd/cron/XDG autostart/startup_items (location-based filtering)
-- 2. LOTL_INDICATOR: Living off the Land patterns (bash -c, curl | bash, base64 -d, etc.)
-- Uses TRIM() on paths for proper matching
-- MITRE ATT&CK: T1543.002, T1053.003, T1547.013, T1059.004, T1105

WITH non_whitelisted_systemd AS (
    SELECT
        su.id AS name,
        su.id AS service_id,
        TRIM(su.fragment_path) AS path,
        TRIM(su.fragment_path) AS source,
        su.description AS args,
        su.user AS username,
        CASE
            WHEN su.unit_file_state = 'enabled' THEN 'enabled'
            WHEN su.unit_file_state = 'disabled' THEN 'disabled'
            ELSE su.unit_file_state
        END AS status,
        'Systemd Service (Custom)' AS type,
        'NON_WHITELISTED' AS detection_method,
        'User-created systemd service' AS detection_reason
    FROM systemd_units AS su
    WHERE TRIM(su.fragment_path) LIKE '/etc/systemd/system/%'
        AND su.id LIKE '%.service'
        AND su.unit_file_state IN ('enabled', 'static', 'linked')
        AND su.id NOT IN (
            'ssh.service', 'sshd.service', 'cron.service', 'crond.service',
            'rsyslog.service', 'syslog.service', 'systemd-timesyncd.service',
            'chronyd.service', 'ntpd.service', 'NetworkManager.service',
            'systemd-networkd.service', 'networking.service', 'docker.service',
            'containerd.service', 'podman.service', 'snapd.service',
            'flatpak-system-helper.service', 'ufw.service', 'firewalld.service',
            'iptables.service', 'nftables.service', 'auditd.service',
            'journald.service', 'systemd-journald.service', 'polkit.service',
            'dbus.service', 'dbus-broker.service', 'gdm.service', 'lightdm.service',
            'sddm.service', 'cups.service', 'cups-browsed.service',
            'avahi-daemon.service', 'bluetooth.service', 'ModemManager.service',
            'accounts-daemon.service', 'udisks2.service', 'upower.service',
            'thermald.service', 'fwupd.service', 'packagekit.service',
            'unattended-upgrades.service', 'apt-daily.service',
            'apt-daily-upgrade.service', 'dnf-makecache.service',
            'elastic-agent.service', 'filebeat.service', 'metricbeat.service',
            'auditbeat.service', 'osqueryd.service'
        )
        AND su.id NOT LIKE 'getty@%'
        AND su.id NOT LIKE 'dbus-%'
        AND su.id NOT LIKE 'systemd-%'
        AND su.id NOT LIKE 'user@%'
        AND su.id NOT LIKE 'session-%'
),
non_whitelisted_cron AS (
    SELECT
        SUBSTR(c.command, 1, 100) AS name,
        '' AS service_id,
        TRIM(c.path) AS path,
        TRIM(c.path) AS source,
        c.command AS args,
        CASE
            WHEN TRIM(c.path) LIKE '/var/spool/cron/crontabs/%' THEN REPLACE(TRIM(c.path), '/var/spool/cron/crontabs/', '')
            WHEN TRIM(c.path) LIKE '/var/spool/cron/%' THEN REPLACE(REPLACE(TRIM(c.path), '/var/spool/cron/', ''), 'crontabs/', '')
            ELSE 'root'
        END AS username,
        'enabled' AS status,
        'Cron @reboot' AS type,
        'NON_WHITELISTED' AS detection_method,
        'Cron @reboot job' AS detection_reason
    FROM crontab AS c
    WHERE c.event = '@reboot'
        AND c.command NOT LIKE '%/usr/lib/apt/%'
        AND c.command NOT LIKE '%unattended-upgrade%'
        AND c.command NOT LIKE '%/usr/sbin/anacron%'
        AND c.command NOT LIKE '%run-parts%/etc/cron%'
),
non_whitelisted_xdg AS (
    SELECT
        REPLACE(f.filename, '.desktop', '') AS name,
        '' AS service_id,
        f.path,
        f.directory AS source,
        '' AS args,
        SUBSTR(SUBSTR(f.path, 7), 1, INSTR(SUBSTR(f.path, 7), '/') - 1) AS username,
        'enabled' AS status,
        'XDG Autostart (User)' AS type,
        'NON_WHITELISTED' AS detection_method,
        'User-specific XDG autostart entry' AS detection_reason
    FROM file AS f
    WHERE f.path LIKE '/home/%/.config/autostart/%.desktop'
),
non_whitelisted_startup AS (
    SELECT
        si.name,
        '' AS service_id,
        TRIM(si.path) AS path,
        si.source,
        si.args,
        si.username,
        si.status,
        'Startup Item (Generic)' AS type,
        'NON_WHITELISTED' AS detection_method,
        'Startup item from startup_items table' AS detection_reason
    FROM startup_items AS si
    WHERE TRIM(si.path) IS NOT NULL
        AND TRIM(si.path) != ''
        AND TRIM(si.path) NOT LIKE '/usr/lib/systemd/%'
        AND TRIM(si.path) NOT LIKE '/lib/systemd/%'
        AND TRIM(si.path) NOT LIKE '/usr/share/dbus-1/%'
        AND TRIM(si.path) NOT LIKE '/etc/init.d/%'
        AND TRIM(si.path) NOT LIKE '/run/systemd/generator/%'
        AND TRIM(si.path) NOT LIKE '/run/systemd/generator.late/%'
        AND TRIM(si.path) NOT LIKE '/run/systemd/transient/%'
        AND TRIM(si.path) NOT LIKE '/run/systemd/system/%'
        AND TRIM(si.path) NOT LIKE '/etc/systemd/system/%'
        AND si.source NOT LIKE '/etc/xdg/autostart/%'
        AND si.source NOT LIKE '/usr/share/gnome/autostart/%'
        AND si.source NOT LIKE '/usr/share/autostart/%'
        AND si.source NOT LIKE '/usr/share/gdm/autostart/%'
        AND si.source NOT LIKE '/usr/share/applications/%'
        AND si.name NOT LIKE 'snap-%.mount'
        AND si.name NOT LIKE 'snap%.mount'
        AND si.name NOT LIKE 'session-%.scope'
        AND si.name NOT LIKE 'user-%.slice'
        AND si.name NOT LIKE 'user@%.service'
        AND si.name NOT LIKE '%.mount'
        AND si.name NOT LIKE '%.automount'
        AND si.name NOT LIKE '%.socket'
        AND si.name NOT LIKE '%.timer'
        AND si.name NOT LIKE '%.path'
        AND si.name NOT LIKE '%.target'
        AND si.name NOT LIKE '%.slice'
        AND si.name NOT LIKE '%.swap'
        AND si.name NOT LIKE '%.device'
        AND si.name NOT LIKE 'systemd-%'
        AND si.name NOT LIKE 'dbus-%'
        AND si.name NOT LIKE 'getty@%'
        AND si.name NOT LIKE 'GNOME %'
        AND si.name NOT LIKE 'gsd-%'
        AND si.name NOT IN (
            'sshd', 'ssh.service', 'sshd.service', 'cron', 'cron.service',
            'crond', 'crond.service', 'rsyslogd', 'rsyslog.service',
            'chronyd', 'chronyd.service', 'ntpd', 'ntpd.service',
            'NetworkManager', 'NetworkManager.service', 'dockerd', 'docker.service',
            'containerd', 'containerd.service', 'snapd', 'snapd.service',
            'polkitd', 'polkit.service', 'gdm', 'gdm.service', 'gdm3',
            'lightdm', 'lightdm.service', 'sddm', 'sddm.service',
            'cupsd', 'cups.service', 'cups-browsed', 'cups-browsed.service',
            'avahi-daemon', 'avahi-daemon.service', 'bluetoothd', 'bluetooth',
            'bluetooth.service', 'ModemManager', 'ModemManager.service',
            'udisksd', 'udisks2.service', 'upowerd', 'upower.service',
            'thermald', 'thermald.service', 'fwupd', 'fwupd.service',
            'packagekitd', 'packagekit.service', 'elastic-agent', 'elastic-agent.service',
            'filebeat', 'filebeat.service', 'metricbeat', 'metricbeat.service',
            'auditbeat', 'auditbeat.service', 'osqueryd', 'osqueryd.service',
            'dbus', 'dbus.service', 'accounts-daemon.service', 'apport.service',
            'apparmor.service', 'ufw.service', 'firewalld.service'
        )
),
lotl_systemd AS (
    SELECT
        su.id AS name,
        su.id AS service_id,
        TRIM(su.fragment_path) AS path,
        TRIM(su.fragment_path) AS source,
        su.description AS args,
        su.user AS username,
        CASE
            WHEN su.unit_file_state = 'enabled' THEN 'enabled'
            WHEN su.unit_file_state = 'disabled' THEN 'disabled'
            ELSE su.unit_file_state
        END AS status,
        'Systemd Service (LotL)' AS type,
        'LOTL_INDICATOR' AS detection_method,
        CASE
            WHEN su.description LIKE '%bash -c%' OR su.description LIKE '%sh -c%' THEN 'Shell command execution via -c flag'
            WHEN su.description LIKE '%curl%|%bash%' OR su.description LIKE '%wget%|%sh%' THEN 'Download and pipe to shell'
            WHEN su.description LIKE '%curl%http%' OR su.description LIKE '%wget%http%' THEN 'Download utility abuse'
            WHEN su.description LIKE '%base64 -d%' OR su.description LIKE '%base64 --decode%' THEN 'Base64 decode for obfuscation'
            WHEN su.description LIKE '%/dev/shm/%' OR su.description LIKE '%/tmp/%' THEN 'Execution from world-writable directory'
            WHEN su.description LIKE '%nc %' OR su.description LIKE '%netcat%' OR su.description LIKE '%ncat %' THEN 'Netcat reverse shell'
            WHEN su.description LIKE '%/dev/tcp/%' THEN 'Bash TCP socket redirection'
            WHEN su.description LIKE '%nohup%&%' OR su.description LIKE '%disown%' THEN 'Background process persistence'
            WHEN su.description LIKE '%.sh%' AND TRIM(su.fragment_path) LIKE '/tmp/%' THEN 'Shell script from temp directory'
            WHEN su.description LIKE '%python% -c%' OR su.description LIKE '%perl% -e%' THEN 'Scripting language one-liner'
            WHEN su.description LIKE '%chmod +x%' AND su.description LIKE '%http%' THEN 'Download and execute pattern'
            ELSE 'Unknown LotL pattern in systemd'
        END AS detection_reason
    FROM systemd_units AS su
    WHERE TRIM(su.fragment_path) IS NOT NULL
        AND su.id LIKE '%.service'
        AND su.unit_file_state IN ('enabled', 'static', 'linked')
        AND (
            su.description LIKE '%bash -c%'
            OR su.description LIKE '%sh -c%'
            OR su.description LIKE '%curl%|%bash%'
            OR su.description LIKE '%wget%|%sh%'
            OR su.description LIKE '%curl%http%'
            OR su.description LIKE '%wget%http%'
            OR su.description LIKE '%base64 -d%'
            OR su.description LIKE '%base64 --decode%'
            OR su.description LIKE '%/dev/shm/%'
            OR su.description LIKE '%/tmp/%'
            OR su.description LIKE '%nc %'
            OR su.description LIKE '%netcat%'
            OR su.description LIKE '%ncat %'
            OR su.description LIKE '%/dev/tcp/%'
            OR su.description LIKE '%nohup%&%'
            OR su.description LIKE '%disown%'
            OR (su.description LIKE '%.sh%' AND TRIM(su.fragment_path) LIKE '/tmp/%')
            OR su.description LIKE '%python% -c%'
            OR su.description LIKE '%perl% -e%'
            OR (su.description LIKE '%chmod +x%' AND su.description LIKE '%http%')
        )
),
lotl_cron AS (
    SELECT
        SUBSTR(c.command, 1, 100) AS name,
        '' AS service_id,
        TRIM(c.path) AS path,
        TRIM(c.path) AS source,
        c.command AS args,
        CASE
            WHEN TRIM(c.path) LIKE '/var/spool/cron/crontabs/%' THEN REPLACE(TRIM(c.path), '/var/spool/cron/crontabs/', '')
            WHEN TRIM(c.path) LIKE '/var/spool/cron/%' THEN REPLACE(REPLACE(TRIM(c.path), '/var/spool/cron/', ''), 'crontabs/', '')
            ELSE 'root'
        END AS username,
        'enabled' AS status,
        'Cron (LotL)' AS type,
        'LOTL_INDICATOR' AS detection_method,
        CASE
            WHEN c.command LIKE '%bash -c%' OR c.command LIKE '%sh -c%' THEN 'Shell command execution via -c flag'
            WHEN c.command LIKE '%curl%|%bash%' OR c.command LIKE '%wget%|%sh%' THEN 'Download and pipe to shell'
            WHEN c.command LIKE '%curl%http%' OR c.command LIKE '%wget%http%' THEN 'Download utility abuse'
            WHEN c.command LIKE '%base64 -d%' OR c.command LIKE '%base64 --decode%' THEN 'Base64 decode for obfuscation'
            WHEN c.command LIKE '%/dev/shm/%' OR c.command LIKE '%/tmp/%' THEN 'Execution from world-writable directory'
            WHEN c.command LIKE '%nc %' OR c.command LIKE '%netcat%' OR c.command LIKE '%ncat %' THEN 'Netcat reverse shell'
            WHEN c.command LIKE '%/dev/tcp/%' THEN 'Bash TCP socket redirection'
            WHEN c.command LIKE '%nohup%&%' OR c.command LIKE '%disown%' THEN 'Background process persistence'
            WHEN c.command LIKE '%python% -c%' OR c.command LIKE '%perl% -e%' THEN 'Scripting language one-liner'
            WHEN c.command LIKE '%chmod +x%' AND c.command LIKE '%http%' THEN 'Download and execute pattern'
            ELSE 'Unknown LotL pattern in cron'
        END AS detection_reason
    FROM crontab AS c
    WHERE c.command IS NOT NULL
        AND (
            c.command LIKE '%bash -c%'
            OR c.command LIKE '%sh -c%'
            OR c.command LIKE '%curl%|%bash%'
            OR c.command LIKE '%wget%|%sh%'
            OR c.command LIKE '%curl%http%'
            OR c.command LIKE '%wget%http%'
            OR c.command LIKE '%base64 -d%'
            OR c.command LIKE '%base64 --decode%'
            OR c.command LIKE '%/dev/shm/%'
            OR c.command LIKE '%/tmp/%'
            OR c.command LIKE '%nc %'
            OR c.command LIKE '%netcat%'
            OR c.command LIKE '%ncat %'
            OR c.command LIKE '%/dev/tcp/%'
            OR c.command LIKE '%nohup%&%'
            OR c.command LIKE '%disown%'
            OR c.command LIKE '%python% -c%'
            OR c.command LIKE '%perl% -e%'
            OR (c.command LIKE '%chmod +x%' AND c.command LIKE '%http%')
        )
),
lotl_startup AS (
    SELECT
        si.name,
        '' AS service_id,
        TRIM(si.path) AS path,
        si.source,
        si.args,
        si.username,
        si.status,
        'Startup Item (LotL)' AS type,
        'LOTL_INDICATOR' AS detection_method,
        CASE
            WHEN si.args LIKE '%bash -c%' OR si.args LIKE '%sh -c%' THEN 'Shell command execution via -c flag'
            WHEN si.args LIKE '%curl%|%bash%' OR si.args LIKE '%wget%|%sh%' THEN 'Download and pipe to shell'
            WHEN si.args LIKE '%curl%http%' OR si.args LIKE '%wget%http%' THEN 'Download utility abuse'
            WHEN si.args LIKE '%base64 -d%' OR si.args LIKE '%base64 --decode%' THEN 'Base64 decode for obfuscation'
            WHEN TRIM(si.path) LIKE '%/dev/shm/%' OR TRIM(si.path) LIKE '%/tmp/%' THEN 'Execution from world-writable directory'
            WHEN si.args LIKE '%nc %' OR si.args LIKE '%netcat%' OR si.args LIKE '%ncat %' THEN 'Netcat reverse shell'
            WHEN si.args LIKE '%/dev/tcp/%' THEN 'Bash TCP socket redirection'
            WHEN si.args LIKE '%nohup%&%' OR si.args LIKE '%disown%' THEN 'Background process persistence'
            WHEN si.args LIKE '%python% -c%' OR si.args LIKE '%perl% -e%' THEN 'Scripting language one-liner'
            WHEN si.args LIKE '%chmod +x%' AND si.args LIKE '%http%' THEN 'Download and execute pattern'
            ELSE 'Unknown LotL pattern in startup_items'
        END AS detection_reason
    FROM startup_items AS si
    WHERE TRIM(si.path) IS NOT NULL
        AND TRIM(si.path) != ''
        AND (
            si.args LIKE '%bash -c%'
            OR si.args LIKE '%sh -c%'
            OR si.args LIKE '%curl%|%bash%'
            OR si.args LIKE '%wget%|%sh%'
            OR si.args LIKE '%curl%http%'
            OR si.args LIKE '%wget%http%'
            OR si.args LIKE '%base64 -d%'
            OR si.args LIKE '%base64 --decode%'
            OR TRIM(si.path) LIKE '%/dev/shm/%'
            OR TRIM(si.path) LIKE '%/tmp/%'
            OR si.args LIKE '%nc %'
            OR si.args LIKE '%netcat%'
            OR si.args LIKE '%ncat %'
            OR si.args LIKE '%/dev/tcp/%'
            OR si.args LIKE '%nohup%&%'
            OR si.args LIKE '%disown%'
            OR si.args LIKE '%python% -c%'
            OR si.args LIKE '%perl% -e%'
            OR (si.args LIKE '%chmod +x%' AND si.args LIKE '%http%')
        )
),
combined AS (
    SELECT * FROM non_whitelisted_systemd
    UNION ALL
    SELECT * FROM non_whitelisted_cron
    UNION ALL
    SELECT * FROM non_whitelisted_xdg
    UNION ALL
    SELECT * FROM non_whitelisted_startup
    UNION ALL
    SELECT * FROM lotl_systemd
    UNION ALL
    SELECT * FROM lotl_cron
    UNION ALL
    SELECT * FROM lotl_startup
)
SELECT
    c.name,
    c.service_id,
    c.path,
    c.type,
    c.status,
    c.source,
    c.args,
    c.username,
    c.detection_method,
    c.detection_reason,
    h.sha256,
    h.sha1,
    h.md5,
    f.size,
    datetime(f.mtime, 'unixepoch') AS modified_time,
    datetime(f.ctime, 'unixepoch') AS changed_time,
    datetime(f.atime, 'unixepoch') AS accessed_time,
    datetime(f.btime, 'unixepoch') AS created_time,
    f.uid,
    f.gid,
    f.mode
FROM combined AS c
LEFT JOIN hash AS h ON h.path = c.path
LEFT JOIN file AS f ON f.path = c.path
ORDER BY
    CASE WHEN c.detection_method = 'LOTL_INDICATOR' THEN 0 ELSE 1 END,
    c.detection_reason,
    c.type,
    c.name

@tomsonpl tomsonpl self-assigned this Nov 21, 2025
@tomsonpl tomsonpl added documentation Improvements or additions to documentation. Applied to PRs that modify *.md files. Integration:osquery_manager Osquery Manager labels Nov 21, 2025
@tomsonpl tomsonpl marked this pull request as ready for review November 21, 2025 12:48
@tomsonpl tomsonpl requested a review from a team as a code owner November 21, 2025 12:48
@tomsonpl tomsonpl requested review from ashokaditya and szwarckonrad and removed request for a team November 21, 2025 12:48
@raqueltabuyo
Copy link

I can see you have used startup_items for Windows and MacOS but not Linux, what was the reason for that? Are we confident that there is no data missing using another table that it is not startup_items?

@tomsonpl
Copy link
Contributor Author

I can see you have used startup_items for Windows and MacOS but not Linux, what was the reason for that? Are we confident that there is no data missing using another table that it is not startup_items?

From my investigation:

  1. Limited Linux coverage: The startup_items table on Linux primarily exposes systemd units, but with less granularity
    than querying systemd_units directly
  2. Missing persistence mechanisms: startup_items doesn't capture:
    - Cron @reboot jobs
    - XDG autostart entries (~/.config/autostart/*.desktop)
    - User-specific systemd services in detail
  3. Dual-detection: We needed fine-grained access to systemd_units.fragment_path and
    systemd_units.description for LotL pattern detection, which startup_items doesn't provide

However we might add this table anyway, and just use UNION to gather more results.

Is this what you'd prefer @raqueltabuyo ?

@andrewkroh andrewkroh added the Team:Defend Workflows Security team for Endpoint and OSQuery workflows [elastic/security-defend-workflows] label Nov 25, 2025
@raqueltabuyo
Copy link

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.
@tomsonpl tomsonpl requested review from calladoum-elastic and removed request for ashokaditya and szwarckonrad December 8, 2025 14:26
"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",
Copy link
Contributor

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...

  1. 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.scan
  2. l.program_arguments LIKE '%/tmp/%' OR l.program_arguments LIKE '%/private/tmp/%' THEN 'Execution from temp directory' flags on /usr/libexec/kdumpd /var/tmp/PanicDumps

Copy link
Contributor Author

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

Copy link
Contributor Author

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",
Copy link
Contributor

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"
Copy link
Contributor

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"
Copy link
Contributor

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?

Comment on lines 158 to 161
"key": "file.directory",
"value": {
"field": "source"
}
Copy link
Contributor

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"
Copy link
Contributor

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"
Copy link
Contributor

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?

Comment on lines 44 to 60
"key": "process.name",
"value": {
"field": "name"
}
},
{
"key": "process.executable",
"value": {
"field": "path"
}
},
{
"key": "process.command_line",
"value": {
"field": "args"
}
},
Copy link
Contributor

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?

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.

Copy link
Contributor Author

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?

Comment on lines 44 to 60
"key": "process.name",
"value": {
"field": "name"
}
},
{
"key": "process.executable",
"value": {
"field": "path"
}
},
{
"key": "process.command_line",
"value": {
"field": "args"
}
},

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"

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"

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)
@elasticmachine
Copy link

💚 Build Succeeded

History

cc @tomsonpl

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation. Applied to PRs that modify *.md files. Integration:osquery_manager Osquery Manager Team:Defend Workflows Security team for Endpoint and OSQuery workflows [elastic/security-defend-workflows]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants