From 49aab5a66f34125dd4ba12ba36112c7c673c3d27 Mon Sep 17 00:00:00 2001 From: Stuart Ashenbrenner <72467868+stuartjash@users.noreply.github.com> Date: Sat, 2 Dec 2023 15:11:10 -0800 Subject: [PATCH] v2.2.0 (#19) * dump the btm file to capture other persistence items * capture fish config file * bump to v2.2.0 * memory usage dump * formatting * added unified logging to ignore/disable list * unified log addition * bump to v2.2.0 * collection of diagnosticsreports and crashreporter files * fix username --- README.md | 4 +- aftermath.xcodeproj/project.pbxproj | 20 ++++++ aftermath/Command.swift | 11 ++- artifacts/LogFiles.swift | 36 ++++++++++ artifacts/ShellHistoryAndProfiles.swift | 4 +- filesystem/browsers/Safari.swift | 5 +- memory/MemoryModule.swift | 22 ++++++ memory/Stat.swift | 92 +++++++++++++++++++++++++ persistence/BTM.swift | 21 ++++++ persistence/PersistenceModule.swift | 10 +++ unifiedlogs/UnifiedLogModule.swift | 38 +++++----- 11 files changed, 236 insertions(+), 27 deletions(-) create mode 100644 memory/MemoryModule.swift create mode 100644 memory/Stat.swift create mode 100644 persistence/BTM.swift diff --git a/README.md b/README.md index 762e949..e34d265 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![](https://github.com/jamf/aftermath/blob/main/AftermathLogo.png) -![](https://img.shields.io/badge/release-2.1.1-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-2.2.0-bright%20green) ![](https://img.shields.io/badge/macOS-12.0%2B-blue) ![](https://img.shields.io/badge/license-MIT-orange) ## About @@ -85,7 +85,7 @@ To uninstall the aftermath binary, run the `AftermathUninstaller.pkg` from the [ --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. --disable -> disable a set of aftermath features that may collect personal user data - Available features to disable: browsers -> collecting browser information | browser-killswitch -> force-closes browers | -> databases -> tcc & lsquarantine databases | filesystem -> walking the filesystem for timestamps | proc-info -> collecting process information via TrueTree and eslogger | all -> all aforementioned options + Available features to disable: browsers -> collecting browser information | browser-killswitch -> force-closes browers | -> databases -> tcc & lsquarantine databases | filesystem -> walking the filesystem for timestamps | proc-info -> collecting process information via TrueTree and eslogger | slack -> slack data | ul -> unified logging modules | all -> all aforementioned options usage: --disable browsers browser-killswitch databases filesystem proc-info slack --disable all --es-logs -> specify which Endpoint Security events (space-separated) to collect (defaults are: create exec mmap). To disable, see --disable es-logs diff --git a/aftermath.xcodeproj/project.pbxproj b/aftermath.xcodeproj/project.pbxproj index 9fad263..f8040fa 100644 --- a/aftermath.xcodeproj/project.pbxproj +++ b/aftermath.xcodeproj/project.pbxproj @@ -16,6 +16,9 @@ 5E93B0AE2941608D009D2AB5 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E93B0AD2941608D009D2AB5 /* Data.swift */; }; 5E93B0B0294160B6009D2AB5 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E93B0AF294160B6009D2AB5 /* String.swift */; }; 5EA438FF2A7010FF00F3E2B9 /* XProtectBehavioralService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EA438FE2A7010FF00F3E2B9 /* XProtectBehavioralService.swift */; }; + 5ECE5DC12ADF2B4A00939BB0 /* BTM.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ECE5DC02ADF2B4A00939BB0 /* BTM.swift */; }; + 5ECE5DC42AE0406700939BB0 /* MemoryModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ECE5DC32AE0406700939BB0 /* MemoryModule.swift */; }; + 5ECE5DC62AE040B700939BB0 /* Stat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ECE5DC52AE040B700939BB0 /* Stat.swift */; }; 5EFDDCD72AC6661A00EEF193 /* Brave.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EFDDCD62AC6661A00EEF193 /* Brave.swift */; }; 70A44403275707A90035F40E /* SystemReconModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70A44402275707A90035F40E /* SystemReconModule.swift */; }; 70A44405275A76990035F40E /* LSQuarantine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70A44404275A76990035F40E /* LSQuarantine.swift */; }; @@ -91,6 +94,9 @@ 5E93B0AD2941608D009D2AB5 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; }; 5E93B0AF294160B6009D2AB5 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = ""; }; 5EA438FE2A7010FF00F3E2B9 /* XProtectBehavioralService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XProtectBehavioralService.swift; sourceTree = ""; }; + 5ECE5DC02ADF2B4A00939BB0 /* BTM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BTM.swift; sourceTree = ""; }; + 5ECE5DC32AE0406700939BB0 /* MemoryModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryModule.swift; sourceTree = ""; }; + 5ECE5DC52AE040B700939BB0 /* Stat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stat.swift; sourceTree = ""; }; 5EFDDCD62AC6661A00EEF193 /* Brave.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Brave.swift; sourceTree = ""; }; 70A44402275707A90035F40E /* SystemReconModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemReconModule.swift; sourceTree = ""; }; 70A44404275A76990035F40E /* LSQuarantine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LSQuarantine.swift; sourceTree = ""; }; @@ -177,6 +183,15 @@ path = endpointSecurity; sourceTree = ""; }; + 5ECE5DC22AE0405500939BB0 /* memory */ = { + isa = PBXGroup; + children = ( + 5ECE5DC32AE0406700939BB0 /* MemoryModule.swift */, + 5ECE5DC52AE040B700939BB0 /* Stat.swift */, + ); + path = memory; + sourceTree = ""; + }; 70A44401275707800035F40E /* systemRecon */ = { isa = PBXGroup; children = ( @@ -266,6 +281,7 @@ A09B239B2848F6050062D592 /* Periodic.swift */, A007834D28947D71008489EA /* Emond.swift */, A007834F28947E80008489EA /* LoginItems.swift */, + 5ECE5DC02ADF2B4A00939BB0 /* BTM.swift */, ); path = persistence; sourceTree = ""; @@ -383,6 +399,7 @@ A374535B2757C1110074B65C /* extensions */, A0E1E3F9275ED4B7008D0DC6 /* filesystem */, A02509F228ADB1930030D6A7 /* helpers */, + 5ECE5DC22AE0405500939BB0 /* memory */, A0E1E3F4275ED2D6008D0DC6 /* network */, A0776CAC27482FF2007D18D8 /* persistence */, A029AB132876A01300649701 /* processes */, @@ -538,6 +555,7 @@ A029AB1C28774CA400649701 /* Tree.swift in Sources */, A007835028947E80008489EA /* LoginItems.swift in Sources */, A0C930D428A4318F0011FB87 /* Timeline.swift in Sources */, + 5ECE5DC12ADF2B4A00939BB0 /* BTM.swift in Sources */, A374535A275735B40074B65C /* LoginHooks.swift in Sources */, 70CF9E3A27611C6100FD884B /* ShellHistoryAndProfiles.swift in Sources */, A0E1E3EB275EC800008D0DC6 /* Firefox.swift in Sources */, @@ -546,6 +564,7 @@ 5E93B0B0294160B6009D2AB5 /* String.swift in Sources */, A0E1E3E9275EC736008D0DC6 /* BrowserModule.swift in Sources */, A02509F428ADB1A80030D6A7 /* CHelpers.swift in Sources */, + 5ECE5DC62AE040B700939BB0 /* Stat.swift in Sources */, 70A44403275707A90035F40E /* SystemReconModule.swift in Sources */, A029AB2B2877F52D00649701 /* launchdXPC.m in Sources */, A0E1E3EF275EC810008D0DC6 /* Safari.swift in Sources */, @@ -574,6 +593,7 @@ A0D6D54927FE52C1002BB3C8 /* SystemExtensions.swift in Sources */, A08342D6284A8247005E437A /* AnalysisModule.swift in Sources */, A029AB192876A29600649701 /* Pids.swift in Sources */, + 5ECE5DC42AE0406700939BB0 /* MemoryModule.swift in Sources */, A08342D8284E48FC005E437A /* LogFiles.swift in Sources */, A0D6D54327F76C58002BB3C8 /* Cron.swift in Sources */, A02509F128AD93DA0030D6A7 /* Storyline.swift in Sources */, diff --git a/aftermath/Command.swift b/aftermath/Command.swift index 862d4d8..c4e21e8 100644 --- a/aftermath/Command.swift +++ b/aftermath/Command.swift @@ -28,8 +28,8 @@ class Command { static var collectDirs: [String] = [] static var unifiedLogsFile: String? = nil static var esLogs: [String] = ["create", "exec", "mmap"] - static let version: String = "2.1.0" - static var disableFeatures: [String:Bool] = ["all": false, "browsers": false, "browser-killswitch": false, "databases": false, "filesystem": false, "proc-info": false, "slack": false] + static let version: String = "2.2.0" + static var disableFeatures: [String:Bool] = ["all": false, "browsers": false, "browser-killswitch": false, "databases": false, "filesystem": false, "proc-info": false, "slack": false, "ul": false] static func main() { setup(with: CommandLine.arguments) @@ -209,6 +209,11 @@ class Command { // FileSystem let fileSysModule = FileSystemModule() fileSysModule.run() + + + // Memory + let memoryModule = MemoryModule() + memoryModule.run() // Artifacts @@ -264,7 +269,7 @@ class Command { print(" usage: --collect-dirs /Users//Downloads /tmp") print("--deep -> performs deep scan and captures metadata from Users entire directory (WARNING: this may be time-consuming)") print("--disable -> disable a set of aftermath features that may collect personal user data") - print(" usage: --disable browsers browser-killswitch databases filesystem proc-info slack") + print(" usage: --disable browsers browser-killswitch databases filesystem proc-info slack ul") print(" --disable all") print("--es-logs -> specify which Endpoint Security events (space-separated) to collect (defaults are: create exec mmap)") print(" usage: --es-logs exec open rename") diff --git a/artifacts/LogFiles.swift b/artifacts/LogFiles.swift index 84a7b3b..445775e 100644 --- a/artifacts/LogFiles.swift +++ b/artifacts/LogFiles.swift @@ -61,8 +61,44 @@ class LogFiles: ArtifactsModule { } } + func collectDiagnosticsReports() { + let diagReportsDir = self.createNewDir(dir: self.logFilesDir, dirname: "diagnostics_reports") + + let files = filemanager.filesInDirRecursive(path: "/Library/Logs/DiagnosticReports") + for file in files { + let filePath = URL(fileURLWithPath: file.relativePath) + if (filemanager.fileExists(atPath: filePath.path)) { + self.copyFileToCase(fileToCopy: filePath, toLocation: diagReportsDir) + } + } + + for user in getBasicUsersOnSystem() { + let files = filemanager.filesInDirRecursive(path: "\(user.homedir)/Library/Logs/DiagnosticReports") + for file in files { + let filePath = URL(fileURLWithPath: file.relativePath) + if (filemanager.fileExists(atPath: filePath.path)) { + self.copyFileToCase(fileToCopy: filePath, toLocation: diagReportsDir, newFileName: "\(user.username)_\(filePath.lastPathComponent)") + } + } + } + } + + func collectCrashReports() { + let crashReportsDir = self.createNewDir(dir: self.logFilesDir, dirname: "crash_reporter") + + let files = filemanager.filesInDirRecursive(path: "/Library/Logs/CrashReporter") + for file in files { + let filePath = URL(fileURLWithPath: file.relativePath) + if (filemanager.fileExists(atPath: filePath.path)) { + self.copyFileToCase(fileToCopy: filePath, toLocation: crashReportsDir) + } + } + } + override func run() { captureLogFiles() captureUserLogs() + collectDiagnosticsReports() + collectCrashReports() } } diff --git a/artifacts/ShellHistoryAndProfiles.swift b/artifacts/ShellHistoryAndProfiles.swift index d230bb0..43a70e5 100644 --- a/artifacts/ShellHistoryAndProfiles.swift +++ b/artifacts/ShellHistoryAndProfiles.swift @@ -21,7 +21,7 @@ class BashProfiles: ArtifactsModule { let userFiles = [ ".bash_history", ".bash_profile", ".bashrc", ".bash_logout", ".zsh_history", ".zshenv", ".zprofile", ".zshrc", ".zlogin", ".zlogout", - ".sh_history" + ".sh_history", ".config/fish/config.fish" ] let globalFiles = ["/etc/profile", "/etc/zshenv", "/etc/zprofile", "/etc/zshrc", "/etc/zlogin", "/etc/zlogout"] @@ -31,7 +31,7 @@ class BashProfiles: ArtifactsModule { for filename in userFiles { let path = URL(fileURLWithPath: "\(user.homedir)/\(filename)") if (filemanager.fileExists(atPath: path.path)) { - let newFileName = "\(user.username)_\(filename)" + let newFileName = "\(user.username)_\(filename.replacingOccurrences(of: "/", with: ""))" self.copyFileToCase(fileToCopy: path, toLocation: self.profilesDir, newFileName: newFileName) } diff --git a/filesystem/browsers/Safari.swift b/filesystem/browsers/Safari.swift index 4ec836b..e9f03c3 100644 --- a/filesystem/browsers/Safari.swift +++ b/filesystem/browsers/Safari.swift @@ -116,8 +116,8 @@ class Safari: BrowserModule { self.addTextToFile(atUrl: safariDownloads, text: "timestamp,url") for user in getBasicUsersOnSystem() { - let downloadsPlist = URL(fileURLWithPath: "\(user.homedir)/Library/Safari/Downloads.plist") - + let downloadsPlist = URL(fileURLWithPath: "\(user.homedir)/Library/Safari/Downloads.plist") + if filemanager.fileExists(atPath: downloadsPlist.path) { let plistDict = Aftermath.getPlistAsDict(atUrl: downloadsPlist) @@ -144,7 +144,6 @@ class Safari: BrowserModule { } } self.addTextToFile(atUrl: safariDownloads, text: "\(timestamp),\(url)") - } } } diff --git a/memory/MemoryModule.swift b/memory/MemoryModule.swift new file mode 100644 index 0000000..b943a50 --- /dev/null +++ b/memory/MemoryModule.swift @@ -0,0 +1,22 @@ +// +// MemoryModule.swift +// aftermath +// +// Created by Stuart Ashenbrenner on 10/18/23. +// + +import Foundation + +class MemoryModule: AftermathModule { + let name = "Memory Module" + let dirName = "Memory" + let description = "A module for collecting memory data" + lazy var moduleDirRoot = self.createNewDirInRoot(dirName: dirName) + + func run() { + self.log("Collecting available memory information") + + let stat = Stat() + stat.run() + } +} diff --git a/memory/Stat.swift b/memory/Stat.swift new file mode 100644 index 0000000..14a2e5e --- /dev/null +++ b/memory/Stat.swift @@ -0,0 +1,92 @@ +// +// Stat.swift +// aftermath +// +// Created by Stuart Ashenbrenner on 10/18/23. +// + +import Foundation + +class Stat: MemoryModule { + + private func scannerModule(inputString: String, stringToFind: String) -> Double? { + let scanner = Scanner(string: inputString) + + if scanner.scanUpTo(stringToFind, into: nil), + scanner.scanString(stringToFind, into: nil) { + var result: Double = 0.0 + + if scanner.scanDouble(&result) { + return result + } + } + return nil + } + + override func run() { + + let writeFile = self.createNewCaseFile(dirUrl: moduleDirRoot, filename: "memory_usage.txt") + + // trim extra characters from output + var trimSet = CharacterSet.whitespacesAndNewlines + trimSet.insert(charactersIn: "\"") + + // create vm_stat shell + let command = "vm_stat" + let vmstatOutput = Aftermath.shell(command) + + // convert bytes to GiB + let byteConverter = 0.00000000093132257 + + // pagesize + var pagesizeOutput = Aftermath.shell("pagesize") + pagesizeOutput = pagesizeOutput.trimmingCharacters(in: trimSet) + let pagesizeDouble = Double(pagesizeOutput) + + // parse vm_stat output + var componentDict = [String:Double]() + let vmLines = vmstatOutput.split(separator: "\n") + for l in vmLines { + let components = l.split(separator: ":") + + if components.count == 2 { + let key = components[0].trimmingCharacters(in: trimSet) + let value = components[1].trimmingCharacters(in: trimSet) + let valueDouble = Double(value) + let updatedValue = valueDouble ?? 0 * pagesizeDouble! + componentDict[key] = updatedValue + } + } + + // app memory + let appMemory = (componentDict["Anonymous pages"] ?? 0.0) - (componentDict["Pages purgeable"] ?? 0.0) + let wired = Double(componentDict["Pages wired down"]!) + let active = Double(componentDict["Pages active"]!) + let inactive = Double(componentDict["Pages inactive"]!) + let spec = Double(componentDict["Pages speculative"]!) + let throttled = Double(componentDict["Pages throttled"]!) + let freeMemory = Double(componentDict["Pages free"]!) + let purgeable = Double(componentDict["Pages purgeable"]!) + let compressed = Double(componentDict["Pages occupied by compressor"]!) + let tradTotal = ((wired + active + inactive + spec + throttled + freeMemory + compressed) * pagesizeDouble!) * byteConverter + let fileBacked = Double(componentDict["File-backed pages"]!) + let physicalTotal = ((appMemory + wired + compressed + fileBacked + purgeable + freeMemory) * pagesizeDouble!) * byteConverter + + // swap usage + let vmSwapUsageOutput = Aftermath.shell("sysctl vm.swapusage") + + if let vmSwapUsage = scannerModule(inputString: vmSwapUsageOutput, stringToFind: "used = ") { + self.addTextToFile(atUrl: writeFile, text: "Swap used: \(vmSwapUsage * 0.0009765625)\n") + } + + // memory pressure + let memoryPressureOutput = Aftermath.shell("memory_pressure") + if let memoryPressure = scannerModule(inputString: memoryPressureOutput, stringToFind: "percentage: ") { + self.addTextToFile(atUrl: writeFile, text: "Memory Pressure: \(100 - memoryPressure)%") + } + + // write out + self.addTextToFile(atUrl: writeFile, text: "\nTraditional Memory:\nWired Memory: \(wired)\nActive Memory: \(active)\nInactive Memory: \(inactive)\nPages Speculative: \(spec)\nPages Throttled: \(throttled)\nPurgeable: \(purgeable)\nCompressed: \(compressed)\nFree Memory: \(freeMemory)\nTotal: \(String(format: "%.2f", tradTotal))GiB\n") + self.addTextToFile(atUrl: writeFile, text: "\nActivity Monitor Memory:\nApp Memory: \(appMemory)\nWired: \(wired)\nCompressed: \(compressed)\nMemory Used: \(appMemory + wired + compressed)\nCached files: \(fileBacked + purgeable)\nTotal Physical: \(String(format: "%.2f", physicalTotal))GiB\n") + } +} diff --git a/persistence/BTM.swift b/persistence/BTM.swift new file mode 100644 index 0000000..e067e4d --- /dev/null +++ b/persistence/BTM.swift @@ -0,0 +1,21 @@ +// +// BTM.swift +// aftermath +// +// Created by Stuart Ashenbrenner on 10/17/23. +// + +import Foundation + +class BTM: PersistenceModule { + + override func run() { + self.log("Dumping btm file") + + let command = "sfltool dumpbtm" + let output = Aftermath.shell(command) + + let btmDumpFile = self.createNewCaseFile(dirUrl: moduleDirRoot, filename: "btm.txt") + self.addTextToFile(atUrl: btmDumpFile, text: output) + } +} diff --git a/persistence/PersistenceModule.swift b/persistence/PersistenceModule.swift index 7fe38a5..ee230cc 100644 --- a/persistence/PersistenceModule.swift +++ b/persistence/PersistenceModule.swift @@ -28,24 +28,34 @@ class PersistenceModule: AftermathModule, AMProto { let hooks = LoginHooks(saveToRawDir: persistenceRawDir) hooks.run() + // capture all cron tabs let cron = Cron(saveToRawDir: persistenceRawDir) cron.run() + // collect overrides file let overrides = Overrides(saveToRawDir: persistenceRawDir) overrides.run() + // write out all system extensions let systemExtensions = SystemExtensions(saveToRawDir: persistenceRawDir) systemExtensions.run() + // collect any periodic scripts let periodicScripts = Periodic(saveToRawDir: persistenceRawDir) periodicScripts.run() + // on older OSs, collect emond let emond = Emond(saveToRawDir: persistenceRawDir) emond.run() + // gather all Login Items let loginItems = LoginItems(saveToRawDir: persistenceRawDir) loginItems.run() + // dump the BTM file + let btmParser = BTM() + btmParser.run() + self.log("Finished gathering persistence mechanisms") } diff --git a/unifiedlogs/UnifiedLogModule.swift b/unifiedlogs/UnifiedLogModule.swift index ce20a71..2ee199d 100644 --- a/unifiedlogs/UnifiedLogModule.swift +++ b/unifiedlogs/UnifiedLogModule.swift @@ -67,25 +67,29 @@ class UnifiedLogModule: AftermathModule, AMProto { } func run() { - self.log("Starting logging unified logs") - self.log("Filtering Unified Log. Hang Tight!") - - // run the external input file of predicates - if let externalLogFile = self.logFile { - if !filemanager.fileExists(atPath: externalLogFile) { - self.log("No external predicate file found at \(externalLogFile)") - } else { - let externalParsedPredicates = parsePredicateFile(path: externalLogFile) - print(externalParsedPredicates) - filterPredicates(predicates: externalParsedPredicates) + if Command.disableFeatures["ul"] == false { + self.log("Starting logging unified logs") + self.log("Filtering Unified Log. Hang Tight!") + + // run the external input file of predicates + if let externalLogFile = self.logFile { + if !filemanager.fileExists(atPath: externalLogFile) { + self.log("No external predicate file found at \(externalLogFile)") + } else { + let externalParsedPredicates = parsePredicateFile(path: externalLogFile) + print(externalParsedPredicates) + filterPredicates(predicates: externalParsedPredicates) + } } + + // run default predicates + filterPredicates(predicates: self.defaultPredicates) + self.log("Unified Log filtering complete.") + + self.log("Finished logging unified logs") + } else { + self.log("Skipping unified logging") } - - // run default predicates - filterPredicates(predicates: self.defaultPredicates) - self.log("Unified Log filtering complete.") - - self.log("Finished logging unified logs") } }