diff --git a/.gitignore b/.gitignore index c9f30cb..d815c71 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ xcuserdata/ *.xccheckout *.xcscmblueprint *.profraw +*.xcscheme ## Obj-C/Swift specific *.hmap diff --git a/README.md b/README.md index eded990..5e0669c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ -# Aftermath + ___ ______ __ __ + / | / __/ /____ _________ ___ ____ _/ /_/ /_ + / /| | / /_/ __/ _ \/ ___/ __ `__ \/ __ `/ __/ __ \ + / ___ |/ __/ /_/ __/ / / / / / / / /_/ / /_/ / / / + /_/ |_/_/ \__/\___/_/ /_/ /_/ /_/\__,_/\__/_/ /_/ ## About Aftermath is a Swift-based, open-source incident response framework. @@ -62,13 +66,16 @@ sudo aftermath [option1] [option2] ## Help Menu ``` --o or --output -> specify an output location for Aftermath collection results (defaults to /tmp) - usage: -o Users/user/Desktop ---analyze -> Analyze the results of the Aftermath results +--analyze -> analyze the results of the Aftermath results usage: --analyze ---cleanup -> Remove Aftermath Response Folders ---deep or -d -> Perform a deep scan of the file system for modified and accessed timestamped metadata +--collect-dirs -> specify locations of (space-separated) directories to dump those raw files + usage: --collect-dirs +--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. +-o or --output -> specify an output location for Aftermath collection results (defaults to /tmp) + usage: -o Users/user/Desktop +--pretty -> colorize Terminal output +--cleanup -> remove Aftermath Response Folders ``` ## Contributors diff --git a/aftermath.entitlements b/aftermath.entitlements deleted file mode 100644 index 0c67376..0000000 --- a/aftermath.entitlements +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/aftermath.xcodeproj/project.pbxproj b/aftermath.xcodeproj/project.pbxproj index 5723390..14f34a6 100644 --- a/aftermath.xcodeproj/project.pbxproj +++ b/aftermath.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ A0E1E3F6275ED2E4008D0DC6 /* NetworkModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0E1E3F5275ED2E4008D0DC6 /* NetworkModule.swift */; }; A0E1E3F8275ED35D008D0DC6 /* NetworkConnections.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0E1E3F7275ED35D008D0DC6 /* NetworkConnections.swift */; }; A0E22EF2285CD60A003A411A /* CommonDirectories.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0E22EF1285CD60A003A411A /* CommonDirectories.swift */; }; + A0FAEEFE28B94B2C00AC655F /* LogParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0FAEEFD28B94B2C00AC655F /* LogParser.swift */; }; A3046F8E27627DAC0069AA21 /* Module.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3046F8D27627DAC0069AA21 /* Module.swift */; }; A3046F902763AE5E0069AA21 /* CaseFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3046F8F2763AE5E0069AA21 /* CaseFiles.swift */; }; A3745358275730870074B65C /* LaunchItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3745357275730870074B65C /* LaunchItems.swift */; }; @@ -93,7 +94,6 @@ A08342D7284E48FC005E437A /* LogFiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogFiles.swift; sourceTree = ""; }; A0879956275AD2DC00E885BC /* SystemConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemConfig.swift; sourceTree = ""; }; A09B239B2848F6050062D592 /* Periodic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Periodic.swift; sourceTree = ""; }; - A0A1AE4B288ADD85004B2BE5 /* aftermathRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = aftermathRelease.entitlements; sourceTree = ""; }; A0C930D328A4318F0011FB87 /* Timeline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Timeline.swift; sourceTree = ""; }; A0D6D54227F76C58002BB3C8 /* Cron.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cron.swift; sourceTree = ""; }; A0D6D54627FE147D002BB3C8 /* Overrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Overrides.swift; sourceTree = ""; }; @@ -106,13 +106,13 @@ A0E1E3F5275ED2E4008D0DC6 /* NetworkModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkModule.swift; sourceTree = ""; }; A0E1E3F7275ED35D008D0DC6 /* NetworkConnections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConnections.swift; sourceTree = ""; }; A0E22EF1285CD60A003A411A /* CommonDirectories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonDirectories.swift; sourceTree = ""; }; - A0E46B8A288F55A600975EC8 /* aftermath.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = aftermath.entitlements; sourceTree = ""; }; + A0FAEEFD28B94B2C00AC655F /* LogParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogParser.swift; sourceTree = ""; }; A3046F8D27627DAC0069AA21 /* Module.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Module.swift; sourceTree = ""; }; A3046F8F2763AE5E0069AA21 /* CaseFiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaseFiles.swift; sourceTree = ""; }; A3745357275730870074B65C /* LaunchItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchItems.swift; sourceTree = ""; }; A3745359275735B40074B65C /* LoginHooks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginHooks.swift; sourceTree = ""; }; A374535C2757C1300074B65C /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = ""; }; - A3A3A3CD274754B400F8F557 /* Readme.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = Readme.md; sourceTree = ""; }; + A3A3A3CD274754B400F8F557 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; A3CD4E52274434EE00869ECB /* aftermath */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = aftermath; sourceTree = BUILT_PRODUCTS_DIR; }; A3CD4E55274434EE00869ECB /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -228,6 +228,7 @@ A006B5A02882FBA70091FAA1 /* DatabaseParser.swift */, A0C930D328A4318F0011FB87 /* Timeline.swift */, A02509F028AD93DA0030D6A7 /* Storyline.swift */, + A0FAEEFD28B94B2C00AC655F /* LogParser.swift */, ); path = analysis; sourceTree = ""; @@ -287,7 +288,7 @@ A029AB132876A01300649701 /* processes */, 70A44401275707800035F40E /* systemRecon */, 8ABB9E2927568E9000C0ADD7 /* unifiedlogs */, - A3A3A3CD274754B400F8F557 /* Readme.md */, + A3A3A3CD274754B400F8F557 /* README.md */, A3CD4E53274434EE00869ECB /* Products */, ); sourceTree = ""; @@ -303,8 +304,6 @@ A3CD4E54274434EE00869ECB /* aftermath */ = { isa = PBXGroup; children = ( - A0E46B8A288F55A600975EC8 /* aftermath.entitlements */, - A0A1AE4B288ADD85004B2BE5 /* aftermathRelease.entitlements */, A3CD4E55274434EE00869ECB /* Command.swift */, 8ABB9E302756D2B500C0ADD7 /* Aftermath.swift */, A3046F8D27627DAC0069AA21 /* Module.swift */, @@ -383,6 +382,7 @@ A3CD4E56274434EE00869ECB /* Command.swift in Sources */, A0C2E89728AAAE33008FA597 /* ProcLib.h in Sources */, A3745358275730870074B65C /* LaunchItems.swift in Sources */, + A0FAEEFE28B94B2C00AC655F /* LogParser.swift in Sources */, A05BF3BD284FF8C0009E197B /* FileSystemModule.swift in Sources */, A0E22EF2285CD60A003A411A /* CommonDirectories.swift in Sources */, A3046F902763AE5E0069AA21 /* CaseFiles.swift in Sources */, @@ -461,6 +461,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_HARDENED_RUNTIME = YES; @@ -523,6 +524,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_HARDENED_RUNTIME = YES; @@ -550,8 +552,8 @@ buildSettings = { ARCHS = "$(ARCHS_STANDARD)"; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = aftermath/aftermath.entitlements; CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = 6PV5YF2UES; ENABLE_HARDENED_RUNTIME = YES; @@ -576,8 +578,8 @@ buildSettings = { ARCHS = "$(ARCHS_STANDARD)"; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = aftermath/aftermathRelease.entitlements; CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = YES; CODE_SIGN_STYLE = Manual; DEVELOPMENT_TEAM = 6PV5YF2UES; ENABLE_HARDENED_RUNTIME = YES; diff --git a/aftermath.xcodeproj/xcshareddata/xcschemes/aftermath-analysis.xcscheme b/aftermath.xcodeproj/xcshareddata/xcschemes/aftermath-analysis.xcscheme deleted file mode 100644 index f576916..0000000 --- a/aftermath.xcodeproj/xcshareddata/xcschemes/aftermath-analysis.xcscheme +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/aftermath.xcodeproj/xcshareddata/xcschemes/aftermath-clean.xcscheme b/aftermath.xcodeproj/xcshareddata/xcschemes/aftermath-clean.xcscheme deleted file mode 100644 index d061fdf..0000000 --- a/aftermath.xcodeproj/xcshareddata/xcschemes/aftermath-clean.xcscheme +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/aftermath.xcodeproj/xcshareddata/xcschemes/aftermath.xcscheme b/aftermath.xcodeproj/xcshareddata/xcschemes/aftermath.xcscheme deleted file mode 100644 index 062be28..0000000 --- a/aftermath.xcodeproj/xcshareddata/xcschemes/aftermath.xcscheme +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/aftermath/CaseFiles.swift b/aftermath/CaseFiles.swift index b40f71f..b979b44 100644 --- a/aftermath/CaseFiles.swift +++ b/aftermath/CaseFiles.swift @@ -36,6 +36,8 @@ struct CaseFiles { static func MoveCaseDir(outputDir: String) { + print("Moving the collection directory from its temporary location. This may take some time. Please wait...") + var endURL: URL if outputDir == "default" { @@ -61,6 +63,8 @@ struct CaseFiles { let endURL = URL(fileURLWithPath: "/tmp/\(analysisCaseDir.lastPathComponent)") let zippedURL = endURL.appendingPathExtension("zip") + print("Moving the analysis directory from its temporary location. This may take some time. Please wait...") + do { try fm.zipItem(at: analysisCaseDir, to: endURL, shouldKeepParent: true, compressionMethod: .deflate) try fm.moveItem(at: endURL, to: zippedURL) diff --git a/aftermath/Command.swift b/aftermath/Command.swift index 5e5b60b..802c0cb 100644 --- a/aftermath/Command.swift +++ b/aftermath/Command.swift @@ -1,9 +1,9 @@ // - // Command.swift - // aftermath - // +// Command.swift +// aftermath +// // Copyright 2022 JAMF Software, LLC - // +// import Foundation @@ -13,13 +13,17 @@ static let deep = Options(rawValue: 1 << 0) static let output = Options(rawValue: 1 << 1) static let analyze = Options(rawValue: 1 << 2) + static let pretty = Options(rawValue: 1 << 3) + static let collectDirs = Options(rawValue: 1 << 4) + } @main class Command { - static var options: Options = [] - static var analysisDir: String? = nil - static var outputDir: String = "/tmp" + static var options: Options = [] + static var analysisDir: String? = nil + static var outputDir: String = "/tmp" + static var collectDirs: [String] = [] static func main() { setup(with: CommandLine.arguments) @@ -35,6 +39,7 @@ class Command { case "-h", "--help": Self.printHelp() case "--cleanup": Self.cleanup() case "-d", "--deep": Self.options.insert(.deep) + case "--pretty": Self.options.insert(.pretty) case "-o", "--output": if let index = args.firstIndex(of: arg) { Self.options.insert(.output) @@ -45,6 +50,15 @@ class Command { Self.options.insert(.analyze) Self.analysisDir = args[index + 1] } + case "--collect-dirs": + if let index = args.firstIndex(of: arg) { + self.options.insert(.collectDirs) + var i = 1 + while (index + i) < args.count && !args[index + i].starts(with: "-") { + self.collectDirs.append(contentsOf: [args[index + i]]) + i += 1 + } + } default: if !arg.starts(with: "-") { } else { @@ -55,6 +69,8 @@ class Command { } static func start() { + printBanner() + if Self.options.contains(.analyze) { CaseFiles.CreateAnalysisCaseDir() @@ -75,6 +91,7 @@ class Command { mainModule.log("Started analysis on Aftermath directory: \(unzippedDir)") let analysisModule = AnalysisModule(collectionDir: unzippedDir) analysisModule.run() + mainModule.log("Finished analysis module") // Move analysis directory to tmp @@ -85,8 +102,9 @@ class Command { } else { CaseFiles.CreateCaseDir() let mainModule = AftermathModule() - mainModule.log("Aftermath Started") + mainModule.log("Aftermath Collection Started") mainModule.addTextToFile(atUrl: CaseFiles.metadataFile, text: "file,birth,modified,accessed,permissions,uid,gid, downloadedFrom") + // System Recon mainModule.log("Started system recon") @@ -115,7 +133,7 @@ class Command { persistenceModule.run() mainModule.log("Finished logging persistence items") - + // FileSystem mainModule.log("Started gathering file system information...") let fileSysModule = FileSystemModule() @@ -139,15 +157,12 @@ class Command { mainModule.log("Finished running Aftermath collection") - // Copy from cache to /tmp -// let dir = else { -// mainModule.log("Output directory not provided") -// return -// } guard isDirectoryThatExists(path: Self.outputDir) else { mainModule.log("Output directory is not a valid directory that exists") return } + + // Copy from cache to /tmp CaseFiles.MoveCaseDir(outputDir: Self.outputDir) // End Aftermath @@ -187,11 +202,26 @@ class Command { } static func printHelp() { - print("-o -> specify an output location for Aftermath results") + print("-o -> specify an output location for Aftermath results (defaults to /tmp)") print(" usage: -o Users/user/Desktop") print("--analyze -> Analyze the results of the Aftermath results") print(" usage: --analyze ") - print("--cleanup -> Remove Aftermath Response Folders") + print("--collect-dirs -> specify locations of (space-separated) directories to dump those raw files") + 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("--pretty -> colorize Terminal output") + print("--cleanup -> remove Aftermath Folders in default locations") exit(1) } + + static func printBanner() { + print(#""" + ___ ______ __ __ + / | / __/ /____ _________ ___ ____ _/ /_/ /_ + / /| | / /_/ __/ _ \/ ___/ __ `__ \/ __ `/ __/ __ \ + / ___ |/ __/ /_/ __/ / / / / / / / /_/ / /_/ / / / + /_/ |_/_/ \__/\___/_/ /_/ /_/ /_/\__,_/\__/_/ /_/ + + """#) + } } diff --git a/aftermath/Module.swift b/aftermath/Module.swift index 580834a..6457222 100644 --- a/aftermath/Module.swift +++ b/aftermath/Module.swift @@ -25,6 +25,7 @@ class AftermathModule { let filemanager = FileManager.default var caseLogSelector: URL var caseDirSelector: URL + var isPretty: Bool = false init() { if Command.options.contains(.analyze) { @@ -34,6 +35,10 @@ class AftermathModule { caseLogSelector = CaseFiles.logFile caseDirSelector = CaseFiles.caseDir } + if Command.options.contains(.pretty) { + isPretty = true + } + users = getUsersOnSystem() } @@ -183,64 +188,56 @@ class AftermathModule { var lastModifiedTimestamp: String var lastAccessedTimestamp: String + if fromFile.path.contains(",") { + let sanitized = fromFile.path.replacingOccurrences(of: ",", with: " ") + metadata = "\(sanitized)," + } else { + metadata = "\(fromFile.path)," + } + + if let birth = helpers.getFileBirth(fromFile: fromFile) { + birthTimestamp = Aftermath.dateFromEpochTimestamp(timeStamp: birth) + metadata.append("\(birthTimestamp),") + } else { + metadata.append("unknwon,") + } + + if let lastModified = helpers.getFileLastModified(fromFile: fromFile) { + lastModifiedTimestamp = Aftermath.dateFromEpochTimestamp(timeStamp: lastModified) + metadata.append("\(lastModifiedTimestamp),") + } else { + metadata.append("unknown,") + } + + if let lastAccessed = helpers.getFileLastAccessed(fromFile: fromFile) { + lastAccessedTimestamp = Aftermath.dateFromEpochTimestamp(timeStamp: lastAccessed) + metadata.append("\(lastAccessedTimestamp),") + } else { + metadata.append("unknown,") + } + + if let permissions = helpers.getFilePermissions(fromFile: fromFile) { + metadata.append("\(String(permissions).dropFirst(3)),") + } else { + metadata.append("unknwon,") + } + + if let uid = helpers.getFileUid(fromFile: fromFile) { + metadata.append("\(uid),") + } else { + metadata.append("unknown,") + } + + if let gid = helpers.getFileGid(fromFile: fromFile) { + metadata.append("\(gid),") + } else { + metadata.append("unknown,") + } if let mditem = MDItemCreate(nil, fromFile.path as CFString), let mdnames = MDItemCopyAttributeNames(mditem), let mdattrs = MDItemCopyAttributes(mditem, mdnames) as? [String:Any] { - if fromFile.path.contains(",") { - let sanitized = fromFile.path.replacingOccurrences(of: ",", with: " ") - metadata = "\(sanitized)," - } else { - metadata = "\(fromFile.path)," - } - - if let birth = mdattrs[kMDItemContentCreationDate as String] { - birthTimestamp = Aftermath.standardizeMetadataTimestamp(timeStamp: String(describing: birth)) - metadata.append("\(birthTimestamp),") - } else if let birthFS = mdattrs[kMDItemFSCreationDate as String] { - birthTimestamp = Aftermath.standardizeMetadataTimestamp(timeStamp: String(describing: birthFS)) - metadata.append("\(birthTimestamp),") - } else { - metadata.append("unknown,") - } - - if let lastModified = mdattrs[kMDItemContentModificationDate as String] { - lastModifiedTimestamp = Aftermath.standardizeMetadataTimestamp(timeStamp: String(describing: lastModified)) - metadata.append("\(lastModifiedTimestamp),") - } else if let lastModifiedFS = mdattrs[kMDItemFSContentChangeDate as String] { - lastModifiedTimestamp = Aftermath.standardizeMetadataTimestamp(timeStamp: String(describing: lastModifiedFS)) - metadata.append("\(lastModifiedTimestamp),") - } else { - metadata.append("unknown,") - } - - if let lastAccessed = mdattrs[kMDItemLastUsedDate as String] { - lastAccessedTimestamp = Aftermath.standardizeMetadataTimestamp(timeStamp: String(describing: lastAccessed)) - metadata.append("\(lastAccessedTimestamp),") - } else { - metadata.append("unknown,") - } - - - if let permissions = helpers.getFilePermissions(fromFile: fromFile) { - metadata.append("\(String(permissions).dropFirst(3)),") - } else { - metadata.append("unknwon,") - } - - if let uid = mdattrs[kMDItemFSOwnerUserID as String] { - metadata.append("\(uid),") - } else { - metadata.append("unknwon,") - } - - if let gid = mdattrs[kMDItemFSOwnerGroupID as String] { - metadata.append("\(gid),") - } else { - metadata.append("unknown,") - } - // this is last in case array is longer than 1 or 2 items if let downloadedFrom = mdattrs[kMDItemWhereFroms as String] { if let downloadedArr = downloadedFrom as Any as? [String] { @@ -248,16 +245,13 @@ class AftermathModule { metadata.append("\(downloaded),") } } - } else { - metadata.append("unknown,") } + + } else { + metadata.append("unknown,") + } - - self.addTextToFile(atUrl: CaseFiles.metadataFile, text: metadata) - - } else { - print("Can't get attributes for \(fromFile.path)") - } + self.addTextToFile(atUrl: CaseFiles.metadataFile, text: metadata) } func log(_ note: String, displayOnly: Bool = false, file: String = #file) { @@ -265,9 +259,14 @@ class AftermathModule { let module = URL(fileURLWithPath: file).lastPathComponent let entry = "\(Date().ISO8601Format()) - \(module) - \(note)" - let colorized = "\(Color.magenta.rawValue)\(Date().ISO8601Format())\(Color.colorstop.rawValue) - \(Color.yellow.rawValue)\(module)\(Color.colorstop.rawValue) - \(Color.cyan.rawValue)\(note)\(Color.colorstop.rawValue)" - print(colorized) - + if isPretty { + let colorized = "\(Color.magenta.rawValue)\(Date().ISO8601Format())\(Color.colorstop.rawValue) - \(Color.yellow.rawValue)\(module)\(Color.colorstop.rawValue) - \(Color.cyan.rawValue)\(note)\(Color.colorstop.rawValue)" + print(colorized) + } else { + let plainText = "\(Date().ISO8601Format()) - \(module) - \(note)" + print(plainText) + } + if displayOnly == false { addTextToFile(atUrl: caseLogSelector, text: entry) } @@ -304,6 +303,5 @@ class AftermathModule { enum SystemUsers: String, CaseIterable { case nobody = "nobody" case daemon = "daemon" - case empty = "empty" } } diff --git a/aftermath/aftermath.entitlements b/aftermath/aftermath.entitlements deleted file mode 100644 index 0c67376..0000000 --- a/aftermath/aftermath.entitlements +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/aftermath/aftermathRelease.entitlements b/aftermath/aftermathRelease.entitlements deleted file mode 100644 index 0c67376..0000000 --- a/aftermath/aftermathRelease.entitlements +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/aftermathRelease.entitlements b/aftermathRelease.entitlements deleted file mode 100644 index 0c67376..0000000 --- a/aftermathRelease.entitlements +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/analysis/AnalysisModule.swift b/analysis/AnalysisModule.swift index 2457509..bc2ba2b 100644 --- a/analysis/AnalysisModule.swift +++ b/analysis/AnalysisModule.swift @@ -14,8 +14,9 @@ class AnalysisModule: AftermathModule, AMProto { let description = "A module for analyzing results of Aftermath" lazy var moduleDirRoot = self.createNewDirInRoot(dirName: dirName) let collectionDir: String - lazy var timelineFile = self.createNewCaseFile(dirUrl: CaseFiles.analysisCaseDir, filename: "timeline.csv") - lazy var storylineFile = self.createNewCaseFile(dirUrl: CaseFiles.analysisCaseDir, filename: "storyline.csv") + lazy var timelineFile = self.createNewCaseFile(dirUrl: CaseFiles.analysisCaseDir, filename: "temp_timeline.csv") + lazy var storylineFile = self.createNewCaseFile(dirUrl: CaseFiles.analysisCaseDir, filename: "temp_storyline.csv") + init(collectionDir: String) { @@ -26,13 +27,17 @@ class AnalysisModule: AftermathModule, AMProto { func run() { self.log("Running analysis on collected aftermath files") - + + let _ = self.copyFileToCase(fileToCopy: URL(fileURLWithPath: "\(collectionDir)/Recon/system_information.txt"), toLocation: CaseFiles.analysisCaseDir, isAnalysis: true) // ex: timestamp, tcc_update, com.jamf.aftermath, addTextToFile(atUrl: storylineFile, text: "timestamp,type,other,path") let dbParser = DatabaseParser(collectionDir: collectionDir, storylineFile: storylineFile) dbParser.run() + let logParser = LogParser(collectionDir: collectionDir, storylineFile: storylineFile) + logParser.run() + let timeline = Timeline(collectionDir: collectionDir, timelineFile: timelineFile, storylineFile: storylineFile) timeline.run() diff --git a/analysis/DatabaseParser.swift b/analysis/DatabaseParser.swift index 13315fe..a45d2d7 100644 --- a/analysis/DatabaseParser.swift +++ b/analysis/DatabaseParser.swift @@ -23,7 +23,7 @@ class DatabaseParser: AftermathModule { func parseTCC() { self.addTextToFile(atUrl: tccWriteFile, text: "name, service, auth_value, auth_reason, last_modified") - + let rawDir = "\(self.collectionDir)/Artifacts/raw/" var tccFiles = [URL]() for f in filemanager.filesInDir(path: rawDir) { @@ -33,7 +33,6 @@ class DatabaseParser: AftermathModule { } for tcc_path in tccFiles { - self.log("Querying TCC database for path \(tcc_path.path)") var db : OpaquePointer? if sqlite3_open(tcc_path.path, &db) == SQLITE_OK { @@ -166,9 +165,6 @@ class DatabaseParser: AftermathModule { } } - - self.log("Finished capturing LSQuarantine data") - } else { self.log("An error occurred when attempting to query the LSQuarantine database...") } @@ -176,11 +172,11 @@ class DatabaseParser: AftermathModule { } func run() { - self.log("Parsing collected database files...") - self.log("Parsing LSQuarantine database") + self.log("Parsing collected database files") + self.log("Parsing LSQuarantine database...") parseLSQuarantine() - self.log("Parsing TCC database") + self.log("Parsing TCC database...") parseTCC() } diff --git a/analysis/LogParser.swift b/analysis/LogParser.swift new file mode 100644 index 0000000..f8d2f09 --- /dev/null +++ b/analysis/LogParser.swift @@ -0,0 +1,165 @@ +// +// LogParser.swift +// aftermath +// +// Created by Stuart Ashenbrenner on 8/26/22. +// + +import Foundation + +class LogParser: AftermathModule { + + lazy var logsFile = self.createNewCaseFile(dirUrl: CaseFiles.analysisCaseDir, filename: "logs.csv") + let collectionDir: String + let storylineFile: URL + + init(collectionDir: String, storylineFile: URL) { + self.collectionDir = collectionDir + self.storylineFile = storylineFile + } + + func parseInstallLog() { + // install.log + + let installLog = "\(collectionDir)/Artifacts/raw/logs/system_logs/install.log" + do { + let contents = try String(contentsOf: URL(fileURLWithPath: installLog)) + let installLogContents = contents.components(separatedBy: "\n") + + for ind in 0...installLogContents.count - 1 { + + let splitLine = installLogContents[ind].components(separatedBy: " ") + + guard let date = splitLine[safe: 0] else { continue } + guard let time = splitLine[safe: 1] else { continue } + let unformattedDate = date + "T" + time // ex: 2022-03-15T16:22:55-07 + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: "en_US") + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ" + dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) + + var info = "" + + for i in 0...splitLine.count - 1 { + if i == 0 || i == 1 { continue } + info = info.appending(" " + splitLine[i]) + } + + sanatizeInfo(&info) + + guard let dateZone = dateFormatter.date(from: unformattedDate) else { continue } + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" + let formattedDate = dateFormatter.string(from: dateZone) + let text = "\(formattedDate), INSTALL, \(info)" + self.addTextToFile(atUrl: logsFile, text: text) + self.addTextToFile(atUrl: self.storylineFile, text: text) + } + } catch { + print("Unable to parse contents") + } + } + + fileprivate func sanatizeInfo(_ info: inout String) { + info = info.replacingOccurrences(of: ",", with: "") + info = info.replacingOccurrences(of: "\"", with: "") + } + + func parseSysLog() { + // system.log + + let systemLog = "\(collectionDir)/Artifacts/raw/logs/system_logs/system.log" + + do { + let contents = try String(contentsOf: URL(fileURLWithPath: systemLog)) + let systemLogConetnts = contents.components(separatedBy: "\n") + + for ind in 0...systemLogConetnts.count - 1 { + + let splitLine = systemLogConetnts[ind].components(separatedBy: " ") + + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: "en_US") + dateFormatter.dateFormat = "MMM dd yyyy HH:mm:ss" + dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) + + let currentYear = Calendar(identifier: .gregorian).dateComponents([.year], from: .now).year + guard let month = splitLine[safe: 0] else { continue } // Feb + guard let date = splitLine[safe: 1] else { continue } // 26 + guard let time = splitLine[safe: 2] else { continue } // 00:17:38 + + var info = "" + let dateArray = [0,1,2] + + for i in 0...splitLine.count - 1 { + if dateArray.contains(i) { continue } + info = info.appending(" " + splitLine[i]) + } + + sanatizeInfo(&info) + + let unformattedTimestamp = "\(month) \(date) \(currentYear!) \(time)" + + guard let formatted = dateFormatter.date(from: unformattedTimestamp) else { continue } //Ex: 2022-08-26 00:01:40 UTC + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" + let dateString = dateFormatter.string(from: formatted) + + let text = "\(dateString), SYSLOG, \(info)" + self.addTextToFile(atUrl: logsFile, text: text) + self.addTextToFile(atUrl: storylineFile, text: text) + } + } catch { + print("Unable to parse contents") + } + } + + func parseXProtectRemediatorLog() { + + let xprotectremLog = "\(collectionDir)/UnifiedLog/xprotect_remediator.txt" + + do { + let contents = try String(contentsOf: URL(fileURLWithPath: xprotectremLog)) + let remediatorLogContents = contents.components(separatedBy: "\n") + + for ind in 1...remediatorLogContents.count - 1 { + let splitLine = remediatorLogContents[ind].components(separatedBy: " ") + + guard let date = splitLine[safe: 0] else { continue } + guard let time = splitLine[safe: 1] else { continue } + let unformattedDate = date + "T" + time // ex: 2022-08-30T06:51:47.381439-0700 + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: "en_US") + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSZ" + dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) + + var info = "" + + for i in 0...splitLine.count - 1 { + if i == 0 || i == 1 { continue } + info = info.appending(" " + splitLine[i]) + } + + sanatizeInfo(&info) + + guard let dateZome = dateFormatter.date(from: unformattedDate) else { continue } + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" + let formattedDate = dateFormatter.string(from: dateZome) + let text = "\(formattedDate), XPROTECT_REMEDIATOR, \(info)" + self.addTextToFile(atUrl: logsFile, text: text) + self.addTextToFile(atUrl: self.storylineFile, text: text) + } + } catch { + print("Unable to parse contents") + } + } + + func run() { + self.log("Parsing install log...") + parseInstallLog() + + self.log("Parsing system log...") + parseSysLog() + + self.log("Parsing XProtect Remediator log...") + parseXProtectRemediatorLog() + } +} diff --git a/analysis/Storyline.swift b/analysis/Storyline.swift index bc199ab..af4d599 100644 --- a/analysis/Storyline.swift +++ b/analysis/Storyline.swift @@ -26,6 +26,8 @@ class Storyline: AftermathModule { for (title,p) in safariPaths { + if !filemanager.fileExists(atPath: p) { continue } + let csvContents = Aftermath.readCSVRows(path: p) for r in csvContents.rows { @@ -56,6 +58,8 @@ class Storyline: AftermathModule { for (title,p) in chromePaths { + if !filemanager.fileExists(atPath: p) { continue } + let csvContents = Aftermath.readCSVRows(path: p) for r in csvContents.rows { @@ -85,6 +89,8 @@ class Storyline: AftermathModule { for (title,p) in chromePaths { + if !filemanager.fileExists(atPath: p) { continue } + let csvContents = Aftermath.readCSVRows(path: p) for r in csvContents.rows { @@ -111,10 +117,9 @@ class Storyline: AftermathModule { func sortStoryline() { - self.log("Sorting the storyline...") - - let sortedStoryline = self.createNewCaseFile(dirUrl: CaseFiles.analysisCaseDir, filename: "sorted_storyline.csv") + self.log("Creating the storyline...Please wait...") + let sortedStoryline = self.createNewCaseFile(dirUrl: CaseFiles.analysisCaseDir, filename: "storyline.csv") do { let csvFile = try EnumeratedCSV(url: self.storylineFile) let sortedArr = try Aftermath.sortCSV(unsortedArr: csvFile.rows) @@ -123,16 +128,28 @@ class Storyline: AftermathModule { let line = row.joined(separator: ",") self.addTextToFile(atUrl: sortedStoryline, text: "\(line)") } - self.log("Finished sorting the storyline") + self.log("Finished creating the storyline") } catch { print(error) } } + + func removeUnsorted() { + + do { + if filemanager.fileExists(atPath: self.storylineFile.path) { + try filemanager.removeItem(at: self.storylineFile) + } + } catch { + print("Unable to remove unsorted timeline file at \(self.storylineFile.path) due to error\n\(error)") + } + } func run() { addSafariData() addFirefoxData() addChromeData() sortStoryline() + removeUnsorted() } } diff --git a/analysis/Timeline.swift b/analysis/Timeline.swift index 6372457..73bdcae 100644 --- a/analysis/Timeline.swift +++ b/analysis/Timeline.swift @@ -61,9 +61,9 @@ class Timeline: AftermathModule { func sortTimeline() { - self.log("Sorting the timeline...") + self.log("Creating a file timeline...") - let sortedTimeline = self.createNewCaseFile(dirUrl: CaseFiles.analysisCaseDir, filename: "sorted_timeline.csv") + let sortedTimeline = self.createNewCaseFile(dirUrl: CaseFiles.analysisCaseDir, filename: "file_timeline.csv") do { let csvFile = try EnumeratedCSV(url: self.timelineFile) @@ -75,15 +75,27 @@ class Timeline: AftermathModule { self.addTextToFile(atUrl: sortedTimeline, text: "\(line)") } - self.log("Finished sorting the timeline") + self.log("Finished creating the timeline") } catch { print(error) } } + func removeUnsorted() { + + do { + if filemanager.fileExists(atPath: self.timelineFile.path) { + try filemanager.removeItem(at: self.timelineFile) + } + } catch { + print("Unable to remove unsorted timeline file at \(self.timelineFile.path) due to error\n\(error)") + } + } + func run() { organizeMetadata() //timestamp, type(download,birth,access,etc), path sortTimeline() + removeUnsorted() } } diff --git a/artifacts/ArtifactsModule.swift b/artifacts/ArtifactsModule.swift index fc4f437..ac19e44 100644 --- a/artifacts/ArtifactsModule.swift +++ b/artifacts/ArtifactsModule.swift @@ -7,15 +7,6 @@ import Foundation - -// tcc -// lsquarantine -// /etc/sudoers -// /etc/host -// /etc/ssh/.* -// ~/.ssh - - class ArtifactsModule: AftermathModule, AMProto { let name = "Artifacts Module" diff --git a/filesystem/CommonDirectories.swift b/filesystem/CommonDirectories.swift index 3ef7d1e..85fd560 100644 --- a/filesystem/CommonDirectories.swift +++ b/filesystem/CommonDirectories.swift @@ -16,35 +16,40 @@ class CommonDirectories: FileSystemModule { self.writeFile = writeFile } - func dumpTmp(tmpDir: String, tmpRawDir: URL) { + func writeTmpPaths(tmpDir: String) { + self.addTextToFile(atUrl: self.writeFile, text: "\n\nContents of \(tmpDir)\n") + for file in filemanager.filesInDirRecursive(path: tmpDir) { if isAftermathDir(directory: file) { continue } - self.copyFileToCase(fileToCopy: file, toLocation: tmpRawDir) + self.addTextToFile(atUrl: self.writeFile, text: "\(file.path)") } } - func dumpTrash(trashRawDir: URL) { - + func writeTrashPaths() { + for user in getBasicUsersOnSystem() { let path = "\(user.homedir)/.Trash" + self.addTextToFile(atUrl: self.writeFile, text: "\n\nContents of \(path)\n") + for file in filemanager.filesInDirRecursive(path: path) { if isAftermathDir(directory: file) { continue } - self.copyFileToCase(fileToCopy: file, toLocation: trashRawDir) + self.addTextToFile(atUrl: self.writeFile, text: "\(file.path)") } } } - func dumpDownloads(downloadsRawDir: URL) { - + func writeDownloadsPaths() { + for user in getBasicUsersOnSystem() { let path = "\(user.homedir)/Downloads" - + self.addTextToFile(atUrl: self.writeFile, text: "\n\nContents of \(path)\n") + for file in filemanager.filesInDirRecursive(path: path) { if isAftermathDir(directory: file) { continue } if file.lastPathComponent == ".DS_Store" { continue } - self.copyFileToCase(fileToCopy: file, toLocation: downloadsRawDir) + self.addTextToFile(atUrl: self.writeFile, text: "\(file.path)") } } } @@ -62,19 +67,37 @@ class CommonDirectories: FileSystemModule { return isAftermath } - override func run() { - self.log("Capturing data from common directories...") + func collectContents(directory: String) { + + let rawDir = self.createNewDir(dir: self.rawDir, dirname: URL(fileURLWithPath: directory).lastPathComponent) + self.addTextToFile(atUrl: self.writeFile, text: "\n\nContents of \(directory)\n") - self.log("Dumping tmp directory...") - let tmpRawDir = self.createNewDir(dir: self.rawDir, dirname: "tmp_files") - dumpTmp(tmpDir: "/tmp", tmpRawDir: tmpRawDir) + for file in filemanager.filesInDirRecursive(path: directory) { + if isAftermathDir(directory: file) { continue } + if file.lastPathComponent == ".DS_Store" { continue } + self.addTextToFile(atUrl: self.writeFile, text: "\(file.path)") + self.copyFileToCase(fileToCopy: file, toLocation: rawDir) + } - self.log("Dumping the Trash...") - let trashRawDir = self.createNewDir(dir: self.rawDir, dirname: "trash") - dumpTrash(trashRawDir: trashRawDir) + } + + override func run() { + self.log("Capturing data from common directories...") + + if Command.options.contains(.collectDirs) { + for dir in Command.collectDirs { + self.log("Dumping the contents from directory \(dir)") + collectContents(directory: dir) + } + } - self.log("Dumping the Downloads directory") - let downloadsRawDir = self.createNewDir(dir: self.rawDir, dirname: "downloads") - dumpDownloads(downloadsRawDir: downloadsRawDir) + self.log("Writing the files in the tmp directory...") + writeTmpPaths(tmpDir: "/tmp") + + self.log("Writing the file names in the Trash...") + writeTrashPaths() + + self.log("Writing the file paths of Downloads directory") + writeDownloadsPaths() } } diff --git a/filesystem/FileSystemModule.swift b/filesystem/FileSystemModule.swift index 19cf6ad..5e1ce21 100644 --- a/filesystem/FileSystemModule.swift +++ b/filesystem/FileSystemModule.swift @@ -16,11 +16,6 @@ class FileSystemModule: AftermathModule, AMProto { lazy var moduleDirRoot = self.createNewDirInRoot(dirName: dirName) lazy var rawDir = self.createNewDir(dir: moduleDirRoot, dirname: "raw") -// let deepScan: Bool -// -// init(deepScan: Bool) { -// self.deepScan = deepScan -// } func run() { // run browser module diff --git a/filesystem/browsers/BrowserModule.swift b/filesystem/browsers/BrowserModule.swift index 1dc9e6f..64eb4db 100644 --- a/filesystem/browsers/BrowserModule.swift +++ b/filesystem/browsers/BrowserModule.swift @@ -14,7 +14,6 @@ class BrowserModule: AftermathModule, AMProto { var description = "A module that gathers artifacts from different web browsers" lazy var moduleDirRoot = self.createNewDirInRoot(dirName: dirName) - func run() { let firefoxDir = self.createNewDir(dir: moduleDirRoot, dirname: "Firefox") @@ -22,7 +21,7 @@ class BrowserModule: AftermathModule, AMProto { let safariDir = self.createNewDir(dir: moduleDirRoot, dirname: "Safari") let writeFile = self.createNewCaseFile(dirUrl: moduleDirRoot, filename: "browsers.txt") - self.log("Collecting browser information...") + self.log("Collecting browser information. Make sure browsers are closed to prevent file data from being locked.") // Check if Firefox is installed @@ -40,4 +39,3 @@ class BrowserModule: AftermathModule, AMProto { } } - diff --git a/filesystem/browsers/Chrome.swift b/filesystem/browsers/Chrome.swift index 228edc4..5367083 100644 --- a/filesystem/browsers/Chrome.swift +++ b/filesystem/browsers/Chrome.swift @@ -180,11 +180,24 @@ class Chrome: BrowserModule { self.addTextToFile(atUrl: self.writeFile, text: "\n----- End of Chrome Cookies -----\n") } + func captureExtensions() { + let chromeExtensionDir = self.createNewDir(dir: self.chromeDir, dirname: "extensions") + + for user in getBasicUsersOnSystem() { + let path = "\(user.homedir)/Library/Application Support/Google/Chrome/Default/Extensions" + + for file in filemanager.filesInDirRecursive(path: path) { + self.copyFileToCase(fileToCopy: file, toLocation: chromeExtensionDir) + } + } + } + override func run() { self.log("Collecting Chrome browser information...") gatherHistory() dumpDownloads() dumpPreferences() dumpCookies() + captureExtensions() } } diff --git a/filesystem/browsers/Safari.swift b/filesystem/browsers/Safari.swift index 1f7e2d8..d149416 100644 --- a/filesystem/browsers/Safari.swift +++ b/filesystem/browsers/Safari.swift @@ -100,7 +100,6 @@ class Safari: BrowserModule { let dateString = dateFormatter.string(from: dateTimestamp as Date) timestamp = dateString -// timestamp = Aftermath.standardizeMetadataTimestamp(timeStamp: value as! String) } if key as! String == "DownloadEntryURL" { url = value as! String diff --git a/helpers/CHelpers.swift b/helpers/CHelpers.swift index d82096c..46115b0 100644 --- a/helpers/CHelpers.swift +++ b/helpers/CHelpers.swift @@ -38,8 +38,31 @@ class CHelpers { return Int(status.st_gid) } } -} + + func getFileBirth(fromFile: URL) -> Double? { + return fromFile.path.withCString { cs in + var status = stat() + stat(cs, &status) + return Double(status.st_birthtimespec.tv_sec) + } + } + func getFileLastAccessed(fromFile: URL) -> Double? { + return fromFile.path.withCString { cs in + var status = stat() + stat(cs, &status) + return Double(status.st_atimespec.tv_sec) + } + } + + func getFileLastModified(fromFile: URL) -> Double? { + return fromFile.path.withCString { cs in + var status = stat() + stat(cs, &status) + return Double(status.st_mtimespec.tv_sec) + } + } +} struct FileMode: OptionSet { let rawValue: mode_t diff --git a/libs/ProcLib/ProcLib.h b/libs/ProcLib/ProcLib.h index dcd1b8c..0a8af99 100644 --- a/libs/ProcLib/ProcLib.h +++ b/libs/ProcLib/ProcLib.h @@ -1,3 +1,4 @@ +// TrueTree License: https://github.com/themittenmac/TrueTree/blob/master/license.md #ifndef ProcLib_h #define ProcLib_h diff --git a/libs/launchdXPC/launchdXPC.h b/libs/launchdXPC/launchdXPC.h index 26cad99..77a1cdd 100644 --- a/libs/launchdXPC/launchdXPC.h +++ b/libs/launchdXPC/launchdXPC.h @@ -4,6 +4,7 @@ // Created by Patrick Wardle // Ported from code by Jonathan Levin // +// TrueTree License: https://github.com/themittenmac/TrueTree/blob/master/license.md #ifndef launchdXPC_h #define launchdXPC_h diff --git a/libs/launchdXPC/launchdXPC.m b/libs/launchdXPC/launchdXPC.m index 326ef6b..edfcf13 100644 --- a/libs/launchdXPC/launchdXPC.m +++ b/libs/launchdXPC/launchdXPC.m @@ -3,6 +3,7 @@ // Created by Patrick Wardle // Ported from code by Jonathan Levin // +// TrueTree License: https://github.com/themittenmac/TrueTree/blob/master/license.md #include #import @@ -162,22 +163,17 @@ int getSubmittedPid(int pid) { processInfoBuffer = 0; } + // return the path of the process return processInfo[@"path"]; -// return processInfo; } - - - - -///////////////// -/// -/// -/// -/// - -//hit up launchd (via XPC) to get process info +/* + hit up launchd (via XPC) to get process info + this method was updated from the original version of TrueTree to + include collecting the process arguments instead of just the + proc path + */ NSMutableDictionary* getProcessArgs(unsigned long pid) { //proc info @@ -292,6 +288,7 @@ int getSubmittedPid(int pid) { processInfoBuffer = 0; } + // get the arguments return processInfo[@"arguments "]; } diff --git a/processes/Pids.swift b/processes/Pids.swift index 4278c0e..0e31a1b 100644 --- a/processes/Pids.swift +++ b/processes/Pids.swift @@ -7,6 +7,7 @@ // The following code (with minor modifications) is from TrueTree, written by Jaron Bradley. // 2020 TheMittenMac // TrueTree: https://github.com/themittenmac/TrueTree +// TrueTree License: https://github.com/themittenmac/TrueTree/blob/master/license.md import Foundation import Darwin diff --git a/processes/Tree.swift b/processes/Tree.swift index 9c9b4f4..4b7a471 100644 --- a/processes/Tree.swift +++ b/processes/Tree.swift @@ -8,6 +8,7 @@ // 2020 TheMittenMac // TrueTree: https://github.com/themittenmac/TrueTree // Inspired by https://www.journaldev.com/21383/swift-tree-binary-tree-data-structure +// TrueTree License: https://github.com/themittenmac/TrueTree/blob/master/license.md import Foundation @@ -201,7 +202,6 @@ class Tree: ProcessModule { } } - // get the arguments of the process let processArguments = getProcessArgs(UInt(pid)) var allArgs: String = "" @@ -214,7 +214,6 @@ class Tree: ProcessModule { } } - self.addTextToFile(atUrl: processFile, text: "\(node.timestamp) \(node.pid) \(node.ppid) \(node.responsiblePid) \(subNode) \(node.procPath) \(allArgs)") diff --git a/systemRecon/SystemReconModule.swift b/systemRecon/SystemReconModule.swift index 9db834a..f1c98fa 100644 --- a/systemRecon/SystemReconModule.swift +++ b/systemRecon/SystemReconModule.swift @@ -30,8 +30,13 @@ class SystemReconModule: AftermathModule, AMProto { self.log("Error has occured, MRT returned nil") return } + + guard let xprotectRemediatorVersion = XProtectRemediator(key: "CFBundleShortVersionString") else { + self.log("Error has occured, XProtect Remediator returned nil") + return + } - self.addTextToFile(atUrl: saveFile, text: "HostName: \(hostName)\nUserName: \(userName)\nFullName: \(fullName)\nSystem Version: \(systemVersion)\nXProtect Version: \(xprotectVersion)\nMRT Version: \(mrtVersion)") + self.addTextToFile(atUrl: saveFile, text: "HostName: \(hostName)\nUserName: \(userName)\nFullName: \(fullName)\nSystem Version: \(systemVersion)\nXProtect Version: \(xprotectVersion)\nXProtect Remediator Version: \(xprotectRemediatorVersion)\nMRT Version: \(mrtVersion)") self.addTextToFile(atUrl: saveFile, text: "\n----------\n") } @@ -54,10 +59,11 @@ class SystemReconModule: AftermathModule, AMProto { func installHistory(saveFile: URL) { let installPath = "/Library/Receipts/InstallHistory.plist" + self.addTextToFile(atUrl: saveFile, text: "ProcessName,Datetime,ContentType,DisplayName,DisplayVersion,PackageIdentifers") + let data = filemanager.contents(atPath: installPath) let installDict = try! PropertyListSerialization.propertyList(from: data!, options: [], format: nil) as! Array<[String: Any]> - var installHistoryArray = [String]() var date:String = "" var contentType:String = "" var displayName:String = "" @@ -66,53 +72,51 @@ class SystemReconModule: AftermathModule, AMProto { var processName:String = "" for data in installDict { - let dateFormater = DateFormatter() - dateFormater.dateFormat = "yyyy-mm-dd hh:mm:ss" + let dateFormatter = DateFormatter() + dateFormatter.locale = Locale(identifier: "en_US") + dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" + if data["processName"] != nil { processName = data["processName"]! as! String - installHistoryArray.append("ProcessName: \(processName)") } else { - installHistoryArray.append("ProcessName: ") + processName = "unknown" } if data["date"] != nil { - date = dateFormater.string(from: data["date"]! as! Date) - installHistoryArray.append("Date: \(date)") + date = dateFormatter.string(from: data["date"]! as! Date) } else { - installHistoryArray.append("Date: ") + date = "unknown" } if data["contentType"] != nil { contentType = data["contentType"]! as! String - installHistoryArray.append("ContentType: \(contentType)") } else { - installHistoryArray.append("ContentType: ") + contentType = "unknown" } if data["displayName"] != nil { displayName = data["displayName"]! as! String - installHistoryArray.append("DisplayName: \(displayName)") } else { - installHistoryArray.append("DisplayName: ") + displayName = "unknown" } if data["displayVersion"] != nil { displayVersion = data["displayVersion"]! as! String - installHistoryArray.append("DisplayVersion: \(displayVersion)") } else { - installHistoryArray.append("DisplayVersion: ") + displayVersion = "unknown" } if data["packageIdentifiers"] != nil { packageIdentifiers = data["packageIdentifiers"]! as! Array - installHistoryArray.append("PackageIdentifiers: \(packageIdentifiers.joined(separator: ", "))\n") } else { - installHistoryArray.append("PackageIdentifiers: \n") + packageIdentifiers = ["unknown"] } + self.addTextToFile(atUrl: saveFile, text: "\(processName),\(date),\(contentType),\(displayName),\(displayVersion),\(packageIdentifiers.joined(separator: ","))") + } - self.addTextToFile(atUrl: saveFile, text: installHistoryArray.joined(separator: "\n")) } func runningApps(saveFile: URL) { @@ -172,22 +176,32 @@ class SystemReconModule: AftermathModule, AMProto { return nil } } + + func XProtectRemediator(key: String) -> String? { + let xprotectRemediatorPath = URL(fileURLWithPath: "/Library/Apple/System/Library/CoreServices/XProtect.app/Contents/version.plist") + + let xprotectRemediatorDict = Aftermath.getPlistAsDict(atUrl: xprotectRemediatorPath) + + if let xprotectRemKeyValue = xprotectRemediatorDict[key] { + return String(describing:xprotectRemKeyValue) + } else { + self.log("Error has occured reading xprotect remediator plist") + return nil + } + } func securityAssessment(saveFile: URL) { - - - let dict = ["Gatekeeper Status": "spctl --status", "SIP Status": "csrutil status", - "Login History": "last", "Screen Sharing": "sudo launchctl list com.apple.screensharing", - "I/O Statistics": "iostat", - "Network Interface Parameters": "ifconfig", "Firewall Status (Enabled = 1, Disabled = 0)": "defaults read /Library/Preferences/com.apple.alf globalstate", "Filevault Status": "sudo fdesetup status", "Airdrop Status": "sudo ifconfig awdl0 | awk '/status/{print $2}'", "Remote Login": "sudo systemsetup -getremotelogin", - "Network File Shares": "nfsd status" + "Network File Shares": "nfsd status", + "I/O Statistics": "iostat", + "Login History": "last", + "Network Interface Parameters": "ifconfig" ] for (heading,command) in dict { @@ -219,7 +233,7 @@ class SystemReconModule: AftermathModule, AMProto { let runningAppsFile = self.createNewCaseFile(dirUrl: moduleDirRoot, filename: "running_apps.txt") let interfacesFile = self.createNewCaseFile(dirUrl: moduleDirRoot, filename: "interfaces.txt") let environmentVariablesFile = self.createNewCaseFile(dirUrl: moduleDirRoot, filename: "environment_variables.txt") - let installHistoryFile = self.createNewCaseFile(dirUrl: moduleDirRoot, filename: "install_history.txt") + let installHistoryFile = self.createNewCaseFile(dirUrl: moduleDirRoot, filename: "install_history.csv") let installedUsersFile = self.createNewCaseFile(dirUrl: moduleDirRoot, filename: "users.txt") systemInformation(saveFile: systemInformationFile) diff --git a/unifiedlogs/UnifiedLogModule.swift b/unifiedlogs/UnifiedLogModule.swift index c7365f3..b805254 100644 --- a/unifiedlogs/UnifiedLogModule.swift +++ b/unifiedlogs/UnifiedLogModule.swift @@ -16,14 +16,15 @@ class UnifiedLogModule: AftermathModule, AMProto { let predicates: [String: String] override init() { - //predicates eventually to be loaded from external file + self.predicates = [ "login": "process == \"logind\"", "tcc": "process == \"tccd\"", "ssh": "process == \"sshd\"", "failed_sudo": "process == \"sudo\" and eventMessage CONTAINS \"TTY\" AND eventMessage CONTAINS \"3 incorrect password attempts\"", "manual_configuration_profile_install": "subsystem == \"com.apple.ManagedClient\" AND process == \"mdmclient\" AND category == \"MDMDaemon\" and eventMessage CONTAINS \"Installed configuration profile:\" AND eventMessage CONTAINS \"Source: Manual\"", - "screensharing": "(process == \"screensharingd\" || process == \"ScreensharingAgent\")" + "screensharing": "(process == \"screensharingd\" || process == \"ScreensharingAgent\")", + "xprotect_remediator": "subsystem == \"com.apple.XProtectFramework.PluginAPI\"" ] } @@ -38,7 +39,6 @@ class UnifiedLogModule: AftermathModule, AMProto { if output.components(separatedBy: "\n").count > 2 { let logfile = self.createNewCaseFile(dirUrl: moduleDirRoot, filename: "\(filtername).txt") self.addTextToFile(atUrl: logfile, text: output) - //self.caseHandler.log(module: self.moduleName, "Done filtering for \(filtername) events") } else { continue } } }