Skip to content

Commit

Permalink
Merge pull request #45 from stuartjash/stuartjash_updates
Browse files Browse the repository at this point in the history
external UL file, xattr, config profiles, hash info
  • Loading branch information
mattbenyo authored Mar 9, 2023
2 parents 4575249 + a81bb70 commit f5f1f33
Show file tree
Hide file tree
Showing 14 changed files with 296 additions and 45 deletions.
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
![](https://github.com/jamf/aftermath/blob/main/AftermathLogo.png)


![](https://img.shields.io/badge/release-1.1.0-bright%20green) ![](https://img.shields.io/badge/macOS-12.0%2B-blue) ![](https://img.shields.io/badge/license-MIT-orange)
![](https://img.shields.io/badge/release-1.2.0-bright%20green) ![](https://img.shields.io/badge/macOS-12.0%2B-blue) ![](https://img.shields.io/badge/license-MIT-orange)


## About
Expand All @@ -26,7 +26,7 @@ cd <path_to_aftermath_directory>
```
Build using Xcode
```bash
xcodebuild
xcodebuild -scheme "aftermath"
```
`cd` into the Release folder
```bash
Expand Down Expand Up @@ -56,6 +56,13 @@ sudo ./aftermath -o /Users/user/Desktop --deep
sudo ./aftermath --analyze <path_to_collection_zip>
```

### External Unified Log Predicates
As of v1.2.0, users have the ability to pass Aftermath a text file of unified log predicates using the `--logs` or `-l` arguments. The file being passed to Aftermath is required to be a text file and each predicate needs to be newline-separated. In addition, each line item will be a dictionary object. The key in the dictionary will whatever the user desires to call this predicate. For example, if you want to see all login events, we will create a predicate and title it `login_events`.
```
login_events: processImagePath contains "loginwindow" and eventMessage contains "com.apple.sessionDidLogin
tcc: process == "tccd"
```

## Releases
There is an Aftermath.pkg available under [Releases](https://github.com/jamf/aftermath/releases). This pkg is signed and notarized. It will install the aftermath binary at `/usr/local/bin/`. This would be the ideal way to deploy via MDM. Since this is installed in `bin`, you can then run aftermath like
```bash
Expand All @@ -74,6 +81,8 @@ To uninstall the aftermath binary, run the `AftermathUninstaller.pkg` from the [
usage: --collect-dirs <path_to_dir> <path_to_another_dir>
--deep or -d -> perform a deep scan of the file system for modified and accessed timestamped metadata
WARNING: This will be a time-intensive, memory-consuming scan.
--logs -> specify an external text file with unified log predicates (as dictionary objects) to parse
usage: --logs /Users/<USER>/Desktop/myPredicates.txt
-o or --output -> specify an output location for Aftermath collection results (defaults to /tmp)
usage: -o Users/user/Desktop
--pretty -> colorize Terminal output
Expand Down
31 changes: 24 additions & 7 deletions aftermath.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
objects = {

/* Begin PBXBuildFile section */
5E494473293AC914007FFBDD /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E494472293AC914007FFBDD /* URL.swift */; };
5E494475293D50FE007FFBDD /* ConfigurationProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E494474293D50FE007FFBDD /* ConfigurationProfiles.swift */; };
5E6780F22922E7E800BAF04B /* Edge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E6780F12922E7E800BAF04B /* Edge.swift */; };
5E93B0AE2941608D009D2AB5 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E93B0AD2941608D009D2AB5 /* Data.swift */; };
5E93B0B0294160B6009D2AB5 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E93B0AF294160B6009D2AB5 /* String.swift */; };
70A44403275707A90035F40E /* SystemReconModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70A44402275707A90035F40E /* SystemReconModule.swift */; };
70A44405275A76990035F40E /* LSQuarantine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70A44404275A76990035F40E /* LSQuarantine.swift */; };
70CF9E3A27611C6100FD884B /* ShellHistoryAndProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70CF9E3927611C6100FD884B /* ShellHistoryAndProfiles.swift */; };
Expand Down Expand Up @@ -73,7 +77,11 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
5E494472293AC914007FFBDD /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
5E494474293D50FE007FFBDD /* ConfigurationProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationProfiles.swift; sourceTree = "<group>"; };
5E6780F12922E7E800BAF04B /* Edge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Edge.swift; sourceTree = "<group>"; };
5E93B0AD2941608D009D2AB5 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = "<group>"; };
5E93B0AF294160B6009D2AB5 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
70A44402275707A90035F40E /* SystemReconModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemReconModule.swift; sourceTree = "<group>"; };
70A44404275A76990035F40E /* LSQuarantine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LSQuarantine.swift; sourceTree = "<group>"; };
70CF9E3927611C6100FD884B /* ShellHistoryAndProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellHistoryAndProfiles.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -221,6 +229,7 @@
70A44404275A76990035F40E /* LSQuarantine.swift */,
70CF9E3927611C6100FD884B /* ShellHistoryAndProfiles.swift */,
A08342D7284E48FC005E437A /* LogFiles.swift */,
5E494474293D50FE007FFBDD /* ConfigurationProfiles.swift */,
);
path = artifacts;
sourceTree = "<group>";
Expand Down Expand Up @@ -333,6 +342,9 @@
isa = PBXGroup;
children = (
A374535C2757C1300074B65C /* FileManager.swift */,
5E494472293AC914007FFBDD /* URL.swift */,
5E93B0AD2941608D009D2AB5 /* Data.swift */,
5E93B0AF294160B6009D2AB5 /* String.swift */,
);
path = extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -489,6 +501,7 @@
buildActionMask = 2147483647;
files = (
A3CD4E56274434EE00869ECB /* Command.swift in Sources */,
5E494475293D50FE007FFBDD /* ConfigurationProfiles.swift in Sources */,
A0C2E89728AAAE33008FA597 /* ProcLib.h in Sources */,
A3745358275730870074B65C /* LaunchItems.swift in Sources */,
A0FAEEFE28B94B2C00AC655F /* LogParser.swift in Sources */,
Expand All @@ -505,6 +518,7 @@
A0E1E3EB275EC800008D0DC6 /* Firefox.swift in Sources */,
8ABB9E312756D2B500C0ADD7 /* Aftermath.swift in Sources */,
A0E1E3F8275ED35D008D0DC6 /* NetworkConnections.swift in Sources */,
5E93B0B0294160B6009D2AB5 /* String.swift in Sources */,
A0E1E3E9275EC736008D0DC6 /* BrowserModule.swift in Sources */,
A02509F428ADB1A80030D6A7 /* CHelpers.swift in Sources */,
70A44403275707A90035F40E /* SystemReconModule.swift in Sources */,
Expand All @@ -518,9 +532,11 @@
A0E1E3ED275EC809008D0DC6 /* Chrome.swift in Sources */,
A3046F8E27627DAC0069AA21 /* Module.swift in Sources */,
8ABB9E2B27568EB700C0ADD7 /* UnifiedLogModule.swift in Sources */,
5E93B0AE2941608D009D2AB5 /* Data.swift in Sources */,
A0879957275AD2DC00E885BC /* SystemConfig.swift in Sources */,
A0FD80F628C7F82400E91584 /* ProcessParser.swift in Sources */,
A05BF3BF284FF8CF009E197B /* Slack.swift in Sources */,
5E494473293AC914007FFBDD /* URL.swift in Sources */,
A007834E28947D71008489EA /* Emond.swift in Sources */,
A076742F2755798F00ED7066 /* ArtifactsModule.swift in Sources */,
A0759135275985170006766F /* TCC.swift in Sources */,
Expand Down Expand Up @@ -699,12 +715,12 @@
buildSettings = {
ARCHS = "$(ARCHS_STANDARD)";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Developer ID Application";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES;
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = 6PV5YF2UES;
"DEVELOPMENT_TEAM[sdk=macosx*]" = "";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = 6PV5YF2UES;
ENABLE_HARDENED_RUNTIME = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand All @@ -714,6 +730,7 @@
MACH_O_TYPE = mh_execute;
NEW_SETTING = "";
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.jamf.aftermath;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_INCLUDE_PATHS = "$(SRCROOT) $(SRCROOT)/libs/ProcLib $(SRCROOT)/libs/launchdXPC";
Expand All @@ -728,11 +745,11 @@
ARCHS = "$(ARCHS_STANDARD)";
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Developer ID Application";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES;
CODE_SIGN_STYLE = Manual;
DEVELOPMENT_TEAM = 6PV5YF2UES;
"DEVELOPMENT_TEAM[sdk=macosx*]" = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = 6PV5YF2UES;
ENABLE_HARDENED_RUNTIME = YES;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
Expand All @@ -742,7 +759,7 @@
MACH_O_TYPE = mh_execute;
NEW_SETTING = "";
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.crashsecurity.aftermath;
PRODUCT_BUNDLE_IDENTIFIER = com.jamf.aftermath;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_INCLUDE_PATHS = "$(SRCROOT) $(SRCROOT)/libs/ProcLib $(SRCROOT)/libs/launchdXPC";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/weichsel/ZIPFoundation",
"state" : {
"revision" : "7254c74b49cec2cb81520523ba993c671f71b066",
"revision" : "1b662e2e7a091710ad8a963263939984e2ebf527",
"version" : "0.9.14"
}
}
Expand Down
44 changes: 27 additions & 17 deletions aftermath/CaseFiles.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ struct CaseFiles {
} else {
return nil
}

guard platformExpert > 0 else {
return nil
}
guard let serialNumber = (IORegistryEntryCreateCFProperty(platformExpert, kIOPlatformSerialNumberKey as CFString, kCFAllocatorDefault, 0).takeUnretainedValue() as? String)?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) else {
return nil
}

IOObjectRelease(platformExpert)

return serialNumber
}

Expand All @@ -56,38 +56,48 @@ struct CaseFiles {

static func MoveTemporaryCaseDir(outputLocation: String, isAnalysis: Bool) {
print("Checking for existence of output location")

let fm = FileManager.default
let isDir = fm.isDirectoryThatExists(path: outputLocation)
guard isDir || fm.fileExists(atPath: outputLocation) else {
print("Output path is not a valid file or directory that exists")
return
}

print("Moving the aftermath directory from its temporary location. This may take some time. Please wait...")

// Determine if we should look in /tmp or in the Aftermath case directory within /tmp
let localCaseDir = isAnalysis ? analysisCaseDir : caseDir

let endPath: String
if isDir {
endPath = "\(outputLocation)/\(localCaseDir.lastPathComponent)"
} else {
// Ensure that we end up with the correct (.zip) path extension
endPath = fm.deletingPathExtension(path: outputLocation)
}

// The zipped case directory should end up in the specified output location
let endURL = URL(fileURLWithPath: endPath)
let zippedURL = endURL.appendingPathExtension("zip")

do {
try fm.zipItem(at: localCaseDir, to: endURL, shouldKeepParent: true, compressionMethod: .deflate)
try fm.moveItem(at: endURL, to: zippedURL)
print("Aftermath archive moved to \(zippedURL.path)")

} catch {
print("Unable to create archive. Error: \(error)")
let endURL = URL(fileURLWithPath: endPath)
let zippedURL = endURL.appendingPathExtension("zip")

if FileManager.default.fileExists(atPath: zippedURL.relativePath) {
do {
try FileManager.default.removeItem(at: zippedURL)
} catch {
print("Unable to save archive. Error: \(error)")
}
}

do {
try fm.zipItem(at: localCaseDir, to: endURL, shouldKeepParent: true, compressionMethod: .deflate)
try fm.moveItem(at: endURL, to: zippedURL)
print("Aftermath archive moved to \(zippedURL.path)")

} catch {
print("Unable to create archive. Error: \(error)")
}
}
}
}
21 changes: 16 additions & 5 deletions aftermath/Command.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
static let analyze = Options(rawValue: 1 << 2)
static let pretty = Options(rawValue: 1 << 3)
static let collectDirs = Options(rawValue: 1 << 4)
static let unifiedLogs = Options(rawValue: 1 << 5)

}

Expand All @@ -24,7 +25,8 @@ class Command {
static var analysisDir: String? = nil
static var outputLocation: String = "/tmp"
static var collectDirs: [String] = []
static let version: String = "1.2.0"
static var unifiedLogsFile: String? = nil
static let version: String = "1.2.1"

static func main() {
setup(with: CommandLine.arguments)
Expand Down Expand Up @@ -66,6 +68,11 @@ class Command {
i += 1
}
}
case "-l", "--logs":
if let index = args.firstIndex(of: arg) {
Self.options.insert(.unifiedLogs)
Self.unifiedLogsFile = args[index + 1]
}
case "-v", "--version":
print(version)
exit(1)
Expand Down Expand Up @@ -108,8 +115,11 @@ class Command {
if #available(macOS 12, *) {
let analysisModule = AnalysisModule(collectionDir: unzippedDir)
analysisModule.run()

mainModule.log("Finished analysis module")
} else {
// Fallback on earlier versions
mainModule.log("Aftermath requires macOS 12 or later in order to analyze collection data.")
print("Aftermath requires macOS 12 or later in order to analyze collection data.")
}

mainModule.log("Finished analysis module")
Expand All @@ -125,7 +135,7 @@ class Command {
mainModule.log("Running Aftermath Version \(version)")
mainModule.log("Aftermath Collection Started")
mainModule.log("Collection started at \(mainModule.getCurrentTimeStandardized())")
mainModule.addTextToFile(atUrl: CaseFiles.metadataFile, text: "file,birth,modified,accessed,permissions,uid,gid, downloadedFrom")
mainModule.addTextToFile(atUrl: CaseFiles.metadataFile, text: "file,birth,modified,accessed,permissions,uid,gid,xattr,downloadedFrom")


// System Recon
Expand Down Expand Up @@ -169,10 +179,9 @@ class Command {
artifactModule.run()
mainModule.log("Finished gathering artifacts")


// Logs
mainModule.log("Started logging unified logs")
let unifiedLogModule = UnifiedLogModule()
let unifiedLogModule = UnifiedLogModule(logFile: unifiedLogsFile)
unifiedLogModule.run()
mainModule.log("Finished logging unified logs")

Expand Down Expand Up @@ -216,6 +225,8 @@ class Command {
print("--collect-dirs -> specify locations of (space-separated) directories to dump those raw files")
print(" usage: --collect-dirs /Users/<USER>/Downloads /tmp")
print("--deep -> performs deep scan and captures metadata from Users entire directory (WARNING: this may be time-consuming)")
print("--logs -> specify an external text file with unified log predicates to parse")
print(" usage: --logs /Users/<USER>/Desktop/myPredicates.txt")
print("--pretty -> colorize Terminal output")
print("--cleanup -> remove Aftermath Folders in default locations")
exit(1)
Expand Down
17 changes: 14 additions & 3 deletions aftermath/Module.swift
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ class AftermathModule {
var birthTimestamp: String
var lastModifiedTimestamp: String
var lastAccessedTimestamp: String
var xattr: String = ""

if fromFile.path.contains(",") {
let sanitized = fromFile.path.replacingOccurrences(of: ",", with: " ")
Expand All @@ -203,8 +204,6 @@ class AftermathModule {
metadata.append("unknwon,")
}



if let lastModified = helpers.getFileLastModified(fromFile: fromFile) {
lastModifiedTimestamp = Aftermath.dateFromEpochTimestamp(timeStamp: lastModified)
metadata.append("\(lastModifiedTimestamp),")
Expand Down Expand Up @@ -237,6 +236,18 @@ class AftermathModule {
metadata.append("unknown,")
}

do {
let xattrs = try fromFile.listExtendedAttributes()
if xattrs.isEmpty {
xattr.append("none,")
} else { xattrs.forEach { xattr.append("\($0) ") } }

metadata.append("\(xattr),")
} catch {
xattr.append("unknown,")
self.log("Unable to capture extended attributes for \(fromFile.path) due to error: \(error)")
}

if let mditem = MDItemCreate(nil, fromFile.path as CFString),
let mdnames = MDItemCopyAttributeNames(mditem),
let mdattrs = MDItemCopyAttributes(mditem, mdnames) as? [String:Any] {
Expand All @@ -245,7 +256,7 @@ class AftermathModule {
if let downloadedFrom = mdattrs[kMDItemWhereFroms as String] {
if let downloadedArr = downloadedFrom as Any as? [String] {
for downloaded in downloadedArr {
metadata.append("\(downloaded),")
metadata.append("\(downloaded) ")
}
}
}
Expand Down
8 changes: 5 additions & 3 deletions analysis/Timeline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,17 +114,18 @@ class Timeline: AftermathModule {

for row in rows {
let columns = row.components(separatedBy: ",")
if columns.count == 8 {
if columns.count >= 8 {
let filePath = columns[0]
let birth = columns[1]
let modified = columns[2]
let accessed = columns[3]
let permissions = columns[4]
let uid = columns[5]
let gid = columns[6]
let downloadedFrom = columns[7]
let xattr = columns[7]
let downloadedFrom = columns[8]

let singleEntry = Metadata(file: filePath, birth: birth, modified: modified, accessed: accessed, permissions: permissions, uid: uid, gid: gid, downloadedFrom: downloadedFrom)
let singleEntry = Metadata(file: filePath, birth: birth, modified: modified, accessed: accessed, permissions: permissions, uid: uid, gid: gid, xattr: xattr, downloadedFrom: downloadedFrom)
metadata.append(singleEntry)
}
}
Expand Down Expand Up @@ -173,6 +174,7 @@ struct Metadata {
var permissions: String
var uid: String
var gid: String
var xattr: String
var downloadedFrom: String
}

Expand Down
Loading

0 comments on commit f5f1f33

Please sign in to comment.