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