MacOS.UnifiedLogHunter

This artifact allows for live hunting through Apple’s Unified Logs using the native log command.

The Unified Logs can be a great resource for learning about system events. There are many logging subsystems that can provide a wealth of data for investigators.

However, users should ensure their searches are scoped appropriately (date/time/event type/etc), as a lot of data can be returned, which could affect the ability to review the collected data or potentially impact client performance.

The Predicate parameter can be used to filter logs. Example filters are included as artifact parameters.

Users may need to adjust the Length parameter to accomodate a large number of events being returned.

If you would like to perform an offline collection, or only care about collecting the raw files associated with this data, consider using Exchange.MacOS.UnifiedLogParser .


name: MacOS.UnifiedLogHunter
description: |
        This artifact allows for live hunting through Apple's Unified Logs using the native `log` command.
        
        The Unified Logs can be a great resource for learning about system events. There are many logging subsystems that can provide a wealth of data for investigators.
        
        However, users should ensure their searches are scoped appropriately (date/time/event type/etc), as a lot of data can be returned, which could affect the ability to review the collected data or potentially impact client performance.
        
        The `Predicate` parameter can be used to filter logs. Example filters are included as artifact parameters.
        
        Users may need to adjust the `Length` parameter to accomodate a large number of events being returned.
        
        If you would like to perform an offline collection, or only care about collecting the raw files associated with this data, consider using [Exchange.MacOS.UnifiedLogParser](https://docs.velociraptor.app/exchange/artifacts/pages/macos.unifiedlogparser/).
reference:
  - https://github.com/jamf/jamfprotect/tree/main/unified_log_filters
  - https://www.mandiant.com/resources/blog/reviewing-macos-unified-logs
  - https://skartek.dev/2022/05/04/unified-logging-for-macos-an-introduction/
  - https://www.crowdstrike.com/blog/how-to-leverage-apple-unified-log-for-incident-response/
  - https://devstreaming-cdn.apple.com/videos/wwdc/2016/721wh2etddp4ghxhpcg/721/721_unified_logging_and_activity_tracing.pdf
type: CLIENT
author: Wes Lambert - @therealwlambert|@weslambert@infosec.exchange
parameters:
  - name: StartDate
    default: timestamp
    type: timestamp
  - name: EndDate
    default:
    type: timestamp  
  - name: Predicate
    description: Use a custom filter
    default:
    type: string
  - name: RunAllQueries
    description: Run all preconfigured filters.  You may need to increase the default timeout of 600s.
    type: bool
  - name: Configuration Profile - Manual Install
    description: Look for manual install of a configuration profile
    type: bool
  - name: Configuration Profile - Manual Removal
    description: Look for anual removal of a configuration profile
    type: bool
  - name: DNS configuration changes
    description: Look for modifications made to host DNS settings
    type: bool
  - name: Failed Lock Screen Unlock
    description: Look for failures to unlock the lock screen
    type: bool
  - name: Failed Local Password Login
    description: Look for failures for local logins using a password
    type: bool 
  - name: Successful Local Password Login
    description: Look for successful logins using a password
    type: bool
  - name: Failed Local TouchID Login
    description: Look for local TouchID logins
    type: bool
  - name: Failed sudo access
    description: Look for failed usage of 'sudo'
    type: bool
  - name: Gatekeeper File Access Rejections and User Bypasses
    description: Look for Gatekeeper file access rejections and user bypasses
    type: bool
  - name: Gatekeeper File Access Scan Activity
    description: Look for Gatekeeper file access scan activity
    type: bool
  - name: Inbound screen sharing
    description: Look for inbound screen sharing
    type: bool
  - name: Kernel Extension Additions
    description: Look for changes made to kernel extensions
    type: bool
  - name: Keychain DB Unlock
    description: Look for keychain database unlock attempts
    type: bool    
  - name: Permissions and Access Violations
    description: Looks for TCC permisssions and access violations 
    type: bool
  - name: Session Creation and Destruction
    description: Looks for session creation and Destruction
    type: bool
  - name: SSH Login Activity
    description: Look for SSH login failures and successes
    type: bool
  - name: Successful Local TouchID Login
    description: Look for successful TouchID logins
    type: bool
  - name: Sudo access
    type: bool
    description: Look for general 'sudo' usage
  - name: MDM Profile - Manual Removal
    type: bool
    description: Look for the removal of MDM profiles
  - name: Network server connection attempts inbound
    type: bool
    description: Look for inbound network connection attempts
  - name: Root user enabled or password changed
    type: bool
    description: Look for changes to the root user configuration
  - name: XProtect Remediator scanning activity
    type: bool
    description: Look for XProtect scanning activity
  - name: Length 
    type: int 
    default: 10000000  
required_permissions:
  - EXECVE
precondition: SELECT OS From info() where OS = 'darwin' AND StartDate AND EndDate
sources:
  - query: |
      LET QueryTable = SELECT * FROM parse_csv(accessor="data", filename='''
            QueryName,Q
            Airdrop Transfer Outbound,subsystem == "com.apple.sharing" AND process == "AirDrop" AND processImagePath BEGINSWITH "/System/Library" AND eventMessage BEGINSWITH "Successfully issued sandbox extension for"
            Application Firewall Logging,subsystem == "com.apple.alf"
            Configuration Profile - Manual Install,subsystem == "com.apple.ManagedClient" AND process == "mdmclient" AND category == "MDMDaemon" and eventMessage CONTAINS "Installed configuration profile:" AND eventMessage CONTAINS "Source: Manual"
            Configuration Profile - Manual Removal,subsystem == "com.apple.ManagedClient" AND process == "mdmclient" AND category == "MDMDaemon" and eventMessage CONTAINS "Removed configuration profile:" AND eventMessage CONTAINS "Source: Manual"
            DNS configuration changes,subsystem == "com.apple.networkextension" and process == "nehelper" and eventMessage CONTAINS "DNS settings are enabled" OR subsystem == "com.apple.networkextension" and process == "nesessionmanager" and eventMessage contains "status changed to disconnected, last stop reason Configuration was disabled"'
            Gatekeeper File Access Rejections and User Bypasses,subsystem == "com.apple.launchservices" AND process == "CoreServicesUIAgent" AND category == "uiagent" AND (eventMessage BEGINSWITH "Saving rejection record:" OR eventMessage CONTAINS "Gatekeeper rejection record")
            Gatekeeper File Access Scan Activity,subsystem == "com.apple.syspolicy.exec" AND process == "syspolicyd" AND category == "default"
            Failed Lock Screen Unlock,processImagePath BEGINSWITH "/System/Library/CoreServices" AND process == "loginwindow" AND eventMessage CONTAINS[c] "INCORRECT"
            Failed Local Password Login,processImagePath BEGINSWITH "/System/" AND process == "SecurityAgent" AND subsystem == "com.apple.loginwindow" AND eventMessage CONTAINS "Authentication failure"
            Failed Local TouchID Login,process == "loginwindow" AND eventMessage CONTAINS[c] "APEventTouchIDNoMatch"
            Failed Local User Password Change,subsystem == "com.apple.opendirectoryd" AND process == "opendirectoryd" AND category == "auth" AND eventMessage CONTAINS "Failed to change password"
            Failed sudo access,process == "sudo" AND eventMessage CONTAINS[c] "TTY" AND eventMessage CONTAINS[c] "3 incorrect password attempts"
            Inbound screen sharing,process == "screensharingd" AND eventMessage BEGINSWITH "Authentication: "
            Kernel Extension Additions,process == "kextd" && sender == "IOKit"
            Keychain DB Unlock,process == "loginwindow" && sender == "Security"
            MDM Profile - Manual Removal,subsystem == "com.apple.ManagedClient" AND eventMessage CONTAINS "Removed configuration profile: MDM Profile" AND eventMessage CONTAINS "Source: Manual"
            Network server connection attempts inbound,process == "NetAuthSysAgent" AND subsystem == "com.apple.NetAuthAgent" AND category == "IPC" AND eventMessage BEGINSWITH "URL = "
            Permissions and Access Violations,process == "tccd"
            Root user enabled or password changed,processImagePath == "/usr/libexec/opendirectoryd" AND process == "opendirectoryd" AND subsystem == "com.apple.opendirectoryd" AND eventMessage CONTAINS "Password changed for root"
            Session Creation and Destruction,process == "securityd" && eventMessage CONTAINS "Session"  && subsystem == "com.apple.securityd"
            SSH Login Activity,process == "sshd"
            Successful Local Password Login,processImagePath BEGINSWITH "/System/Library/CoreServices" AND process == "loginwindow" AND subsystem == "com.apple.loginwindow.logging" AND eventMessage CONTAINS "[Login1 doLogin] | shortUsername"
            Successful Local TouchID Login,process == "loginwindow" AND eventMessage CONTAINS[c] "APEventTouchIDMatch"
            Successful Local User Password Change,subsystem == "com.apple.opendirectoryd" AND process == "opendirectoryd" AND category == "auth" AND eventMessage CONTAINS "Password changed for"
            XProtect Remediator scanning activity,subsystem == "com.apple.XProtectFramework.PluginAPI" && category == "XPEvent.structured"''')
      
      LET QueriesToRun <= SELECT Q FROM QueryTable WHERE if(condition=RunAllQueries, then=QueryName, else=get(field=QueryName))
                                              
      LET Raw <= SELECT * FROM foreach(row={ SELECT * FROM chain( a=QueriesToRun, b=if(condition=Predicate, then={ SELECT Predicate AS Q FROM scope()}))}, 
                                       query={ SELECT Stdout FROM execve(
                   length=Length, 
                   argv=[
                     "log", 
                     "show",
                     "--start",
                     grok(grok="%{TIMESTAMP_ISO8601:Date}", data=StartDate).Date,
                     "--end",
                     grok(grok="%{TIMESTAMP_ISO8601:Date}", data=EndDate).Date,
                     "--predicate",
                     Q,
                     "--style",
                     "json"])}, async=TRUE) WHERE NOT Stdout = "[]"
      SELECT 
        timestamp(string=get(member="timestamp")) AS EventTime,
        get(member="machTimestamp") AS _TimeSinceBoot,
        get(member="traceID") AS _TraceID,
        get(member="eventMessage") AS EventMessage,
        get(member="eventType") AS EventType,
        get(member="messageType") AS MessageType,
        get(member="category") AS Category,
        get(member="subsystem") AS Subsystem,
        get(member="processID") AS PID,
        get(member="processImagePath") AS ProcessImagePath,
        get(member="processImageUUID") AS ProcessImageUUID,
        get(member="senderImagePath") AS SenderImagePath,
        get(member="senderImageUUID") AS SenderImageUUID,
        get(member="senderProgramCounter") AS SenderProgramCounter,
        get(member="source") AS _Source,
        get(member="formatString") AS _FormatString,
        get(member="activityIdentifier") AS ActivityID,
        get(member="parentActivityIdentifier") AS ParentActivityID,
        get(member="threadID") AS _ThreadID,
        get(member="backtrace") AS _Backtrace,
        get(member="bootUUID") AS _BootUUID,
        get(member="timezoneName") AS _TimezoneName
      FROM foreach(row=Raw.Stdout, query={SELECT * FROM parse_json_array(data=_value)})