MacOS.Forensics.FSEvents

This artifact parses the FSEvents log files.

We can filter on Path, Flags or use time box on source file.

An interesting hunt may be filter for Entries of plist files modified or created on a specific date. Malware often creates plist files in /Library/LaunchAgents, Library/Preferences, /Library/LaunchDaemons, or /Library/Internet Plugins.

NOTE:

  • FSEvents do not have timestamps so we specify source file Mtime and Btime.
  • default timeout is only 600 seconds - you will need to increase for this colection to finish.

name: MacOS.Forensics.FSEvents
description: |
   This artifact parses the FSEvents log files.

   We can filter on Path, Flags or use time box on source file.

   An interesting hunt may be filter for Entries of plist files modified or
   created on a specific date. Malware often creates plist files in
   /Library/LaunchAgents, Library/Preferences, /Library/LaunchDaemons, or
   /Library/Internet Plugins.

   NOTE:

   - FSEvents do not have timestamps so we specify source file Mtime and
   Btime.
   - default timeout is only 600 seconds - you will need to increase for this
   colection to finish.

author: |
  Mike Cohen, Matt Green - @mgreen27, Yogesh Khatri (@swiftforensics), CyberCX

reference:
- https://www.osdfcon.org/presentations/2017/Ibrahim-Understanding-MacOS-File-Ststem-Events-with-FSEvents-Parser.pdf
- https://www.crowdstrike.com/blog/using-os-x-fsevents-discover-deleted-malicious-artifact/

type: CLIENT

parameters:
   - name: GlobTable
     type: csv
     default: |
       Glob
       /.fseventsd/*
       /System/Volumes/Data/.fseventsd/*
   - name: Glob
     type: string
     description: Instead of providing the globs in a table, a single glob may be given.
   - name: PathRegex
     description: Filter the path by this regexp
     default: .
     type: regex
   - name: FlagsRegex
     description: Filter by flags
     type: regex
     default: .
   - name: DateAfter
     type: timestamp
     description: "search for source files with Btime after this date. YYYY-MM-DDTmm:hh:ssZ"
   - name: DateBefore
     type: timestamp
     description: "search for source files with Mtime before this date. YYYY-MM-DDTmm:hh:ssZ"

export: |
    LET FSEventProfile = '''[
    ["FSEventsProfile", 0, [
      ["Entries", 0, "Array", {
         type: "Header",
         count: 10000,
      }],
    ]],
    ["Header", "x=>x.Info.StreamSize", [
      ["Version", 0, "Enumeration", {
         type: "unsigned int",
         choices: {
           "1145852721": "V1",
           "1145852722": "V2",
           "1145852723": "V3"
         }
      }],
      ["Info", 8, "Union", {
         selector: "x=>x.Version",
         choices: {
             "V1": "FS1",
             "V2": "FS2",
             "V3": "FS3",
         }
      }],
    ]],
    ["FS1", "x=>x.StreamSize - 8", [
      ["StreamSize", 0, uint32],
      ["Items", 4, "Array", {
          count: 10000,
          max_count: 10000,
          type: FSEventEntry1,
          sentinel: "x=>this.EndOf < x.EndOf",
      }],
    ]],
    ["FS2", "x=>x.StreamSize - 8", [
      ["StreamSize", 0, uint32],
      ["Items", 4, "Array", {
          count: 10000,
          max_count: 10000,
          type: FSEventEntry2,
          sentinel: "x=>this.EndOf < x.EndOf",
      }],
    ]],
    ["FS3", "x=>x.StreamSize - 8", [
      ["StreamSize", 0, uint32],
      ["Items", 4, "Array", {
          count: 10000,
          max_count: 2336,
          type: FSEventEntry3,
          sentinel: "x=>this.EndOf < x.EndOf",
      }],
    ]],
    ["FSEventEntry1", "x=>len(list=x.path) + 13", [
      ["path", 0, "String"],
      ["id", "x=>len(list=x.path) + 1", "uint64"],
      ["flags", "x=>len(list=x.path) + 9", "Flags", {
          type: "uint32",
          bitmap: {
            FSE_CREATE_FILE: 0,
            FSE_DELETE: 1,
            FSE_STAT_CHANGED: 2,
            FSE_RENAME: 3,
            FSE_CONTENT_MODIFIED: 4,
            FSE_EXCHANGE: 5,
            FSE_FINDER_INFO_CHANGED: 6,
            FSE_CREATE_DIR: 7,
            FSE_CHOWN: 8,
            FSE_XATTR_MODIFIED: 9,
            FSE_XATTR_REMOVED: 10,
            FSE_DOCID_CREATED: 11,
            FSE_DOCID_CHANGED: 12,
            FSE_UNMOUNT_PENDING: 13,
            FSE_CLONE: 14,
            FSE_MODE_CLONE: 16,
            FSE_TRUNCATED_PATH: 17,
            FSE_REMOTE_DIR_EVENT: 18,
            FSE_MODE_LAST_HLINK: 19,
            FSE_MODE_HLINK: 20,
            IsSymbolicLink: 22,
            IsFile: 23,
            IsDirectory: 24,
            Mount: 25,
            Unmount: 26,
            EndOfTransaction: 29
          }
      }],
      ["file_id", 0, "Value", {"value": ""}],
    ]],
    ["FSEventEntry2", "x=>len(list=x.path) + 21", [
      ["path", 0, "String"],
      ["id", "x=>len(list=x.path) + 1", "uint64"],
      ["flags", "x=>len(list=x.path) + 9", "Flags", {
          type: "uint32",
          bitmap: {
            FSE_CREATE_FILE: 0,
            FSE_DELETE: 1,
            FSE_STAT_CHANGED: 2,
            FSE_RENAME: 3,
            FSE_CONTENT_MODIFIED: 4,
            FSE_EXCHANGE: 5,
            FSE_FINDER_INFO_CHANGED: 6,
            FSE_CREATE_DIR: 7,
            FSE_CHOWN: 8,
            FSE_XATTR_MODIFIED: 9,
            FSE_XATTR_REMOVED: 10,
            FSE_DOCID_CREATED: 11,
            FSE_DOCID_CHANGED: 12,
            FSE_UNMOUNT_PENDING: 13,
            FSE_CLONE: 14,
            FSE_MODE_CLONE: 16,
            FSE_TRUNCATED_PATH: 17,
            FSE_REMOTE_DIR_EVENT: 18,
            FSE_MODE_LAST_HLINK: 19,
            FSE_MODE_HLINK: 20,
            IsSymbolicLink: 22,
            IsFile: 23,
            IsDirectory: 24,
            Mount: 25,
            Unmount: 26,
            EndOfTransaction: 29
          }
      }],
      ["file_id", "x=>len(list=x.path) + 13", "int64"],
    ]],
    ["FSEventEntry3", "x=>len(list=x.path) + 25", [
      ["path", 0, "String"],
      ["id", "x=>len(list=x.path) + 1", "uint64"],
      ["flags", "x=>len(list=x.path) + 9", "Flags", {
          type: "uint32",
          bitmap: {
            FSE_CREATE_FILE: 0,
            FSE_DELETE: 1,
            FSE_STAT_CHANGED: 2,
            FSE_RENAME: 3,
            FSE_CONTENT_MODIFIED: 4,
            FSE_EXCHANGE: 5,
            FSE_FINDER_INFO_CHANGED: 6,
            FSE_CREATE_DIR: 7,
            FSE_CHOWN: 8,
            FSE_XATTR_MODIFIED: 9,
            FSE_XATTR_REMOVED: 10,
            FSE_DOCID_CREATED: 11,
            FSE_DOCID_CHANGED: 12,
            FSE_UNMOUNT_PENDING: 13,
            FSE_CLONE: 14,
            FSE_MODE_CLONE: 16,
            FSE_TRUNCATED_PATH: 17,
            FSE_REMOTE_DIR_EVENT: 18,
            FSE_MODE_LAST_HLINK: 19,
            FSE_MODE_HLINK: 20,
            IsSymbolicLink: 22,
            IsFile: 23,
            IsDirectory: 24,
            Mount: 25,
            Unmount: 26,
            EndOfTransaction: 29
          }
      }],
      ["file_id", "x=>len(list=x.path) + 13", "int64"],
      ["unknown_id", "x=>len(list=x.path) + 21", "int32"],
    ]],
    ]'''

sources:
  - query: |
      LET files = SELECT OSPath, Mtime, Btime
        FROM glob(globs=(Glob || GlobTable.Glob))
        WHERE   if(condition=DateAfter, then= Btime > DateAfter, else= True )
            AND if(condition=DateBefore, then= Mtime < DateBefore, else= True )
            AND log(message=OSPath)

      LET x = SELECT * FROM foreach(row=files,
        query={
            SELECT
                Version,
                Info.Items as items,
                OSPath.Basename as SourceFile,
                Mtime as SourceMtime,
                Btime as SourceBtime
            FROM
                foreach(row=parse_binary(
                    filename=read_file(filename=OSPath, accessor="gzip", length=1000000),
                    accessor="data",
                    profile=FSEventProfile, struct="FSEventsProfile").Entries)
        })
        WHERE EntryPath =~ PathRegex AND EntryFlags =~ FlagsRegex

      SELECT
        items.path as EntryPath,
        items.id as EntryId,
        join(array=items.flags, sep=", ") AS EntryFlags,
        items.file_id as FileId,
        SourceFile,
        SourceMtime,
        SourceBtime,
        Version
      FROM
        flatten(query=x)