diff --git a/README.md b/README.md index cfe0811..de3ec49 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ Because `eslogger` and `tcpdump` run on additional threads and the goal is to co - Arc - Brave - Chrome + - Comet - Edge - Firefox - Safari @@ -157,6 +158,7 @@ To uninstall the aftermath binary, run the `AftermathUninstaller.pkg` from the [ - Maggie Zirnhelt - Matt Benyo - Ferdous Saljooki +- Lilly Ryan ## Thank You This project leverages the open source [TrueTree](https://github.com/themittenmac/TrueTree) project, written and [licensed](https://github.com/themittenmac/TrueTree/blob/master/license.md) by Jaron Bradley. diff --git a/aftermath.xcodeproj/project.pbxproj b/aftermath.xcodeproj/project.pbxproj index 096accd..4d84dac 100644 --- a/aftermath.xcodeproj/project.pbxproj +++ b/aftermath.xcodeproj/project.pbxproj @@ -62,6 +62,7 @@ A190FFE328B8168400B9EF9A /* AftermathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A190FFCF28B8084F00B9EF9A /* AftermathTests.swift */; }; A1E433D928B918FF00E2B510 /* Aftermath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABB9E302756D2B500C0ADD7 /* Aftermath.swift */; }; A1E433E528B9270800E2B510 /* dummyPlist.plist in Resources */ = {isa = PBXBuildFile; fileRef = A1E433E428B9270800E2B510 /* dummyPlist.plist */; }; + A202BE352EE03E6B006277CA /* Comet.swift in Sources */ = {isa = PBXBuildFile; fileRef = A202BE342EE03E6B006277CA /* Comet.swift */; }; A3046F8E27627DAC0069AA21 /* Module.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3046F8D27627DAC0069AA21 /* Module.swift */; }; A3046F902763AE5E0069AA21 /* CaseFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3046F8F2763AE5E0069AA21 /* CaseFiles.swift */; }; A31009A42B9B838100068593 /* Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = A31009A32B9B838100068593 /* Network.swift */; }; @@ -144,6 +145,7 @@ A190FFD528B80C3900B9EF9A /* MockFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFileManager.swift; sourceTree = ""; }; A190FFDB28B8151300B9EF9A /* tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A1E433E428B9270800E2B510 /* dummyPlist.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = dummyPlist.plist; sourceTree = ""; }; + A202BE342EE03E6B006277CA /* Comet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comet.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 = ""; }; A31009A32B9B838100068593 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = ""; }; @@ -300,6 +302,7 @@ A0E1E3E7275EC720008D0DC6 /* browsers */ = { isa = PBXGroup; children = ( + A202BE342EE03E6B006277CA /* Comet.swift */, A0E1E3E8275EC736008D0DC6 /* BrowserModule.swift */, A0E1E3EA275EC800008D0DC6 /* Firefox.swift */, A0E1E3EC275EC809008D0DC6 /* Chrome.swift */, @@ -572,6 +575,7 @@ A374535D2757C1300074B65C /* FileManager.swift in Sources */, A09B239C2848F6050062D592 /* Periodic.swift in Sources */, A0DA61B028625E1D00224810 /* FileWalker.swift in Sources */, + A202BE352EE03E6B006277CA /* Comet.swift in Sources */, A0E1E3ED275EC809008D0DC6 /* Chrome.swift in Sources */, A3046F8E27627DAC0069AA21 /* Module.swift in Sources */, 8ABB9E2B27568EB700C0ADD7 /* UnifiedLogModule.swift in Sources */, diff --git a/filesystem/browsers/BrowserModule.swift b/filesystem/browsers/BrowserModule.swift index 15de98b..7ac877b 100644 --- a/filesystem/browsers/BrowserModule.swift +++ b/filesystem/browsers/BrowserModule.swift @@ -23,6 +23,7 @@ class BrowserModule: AftermathModule, AMProto { let safariDir = self.createNewDir(dir: moduleDirRoot, dirname: "Safari") let arcDir = self.createNewDir(dir: moduleDirRoot, dirname: "Arc") let braveDir = self.createNewDir(dir: moduleDirRoot, dirname: "Brave") + let cometDir = self.createNewDir(dir: moduleDirRoot, dirname: "Comet") let writeFile = self.createNewCaseFile(dirUrl: moduleDirRoot, filename: "browsers.txt") self.log("Collecting browser information. Checking for open browsers. Closing any open browsers...") @@ -57,10 +58,14 @@ class BrowserModule: AftermathModule, AMProto { // Check if Brave is installed let brave = Brave(braveDir: braveDir, writeFile: writeFile) brave.run() + + // Check if Comet is installed + let comet = Comet(cometDir: cometDir, writeFile: writeFile) + comet.run() } func closeBrowsers() { - let browserData = ["/Applications/Microsoft Edge.app": "com.microsoft.edgemac", "/Applications/Firefox.app": "org.mozilla.firefox", "/Applications/Google Chrome.app": "com.google.Chrome", "/Applications/Safari.app": "com.apple.Safari", "/Applications/Arc.app": "company.thebrowser.Browser"] + let browserData = ["/Applications/Microsoft Edge.app": "com.microsoft.edgemac", "/Applications/Firefox.app": "org.mozilla.firefox", "/Applications/Google Chrome.app": "com.google.Chrome", "/Applications/Safari.app": "com.apple.Safari", "/Applications/Arc.app": "company.thebrowser.Browser", "/Applications/Comet.app": "ai.perplexity.comet"] for (key, value) in browserData { if filemanager.fileExists(atPath: key) { diff --git a/filesystem/browsers/Comet.swift b/filesystem/browsers/Comet.swift new file mode 100644 index 0000000..5897f0b --- /dev/null +++ b/filesystem/browsers/Comet.swift @@ -0,0 +1,229 @@ +// +// Comet.swift +// aftermath +// +// Copyright 2022 JAMF Software, LLC +// + +import Foundation +import SQLite3 + +class Comet: BrowserModule { + + let cometDir: URL + let writeFile: URL + + init(cometDir: URL, writeFile: URL) { + self.cometDir = cometDir + self.writeFile = writeFile + } + + func gatherHistory() { + + let historyOutput = self.createNewCaseFile(dirUrl: self.cometDir, filename: "history_output.csv") + self.addTextToFile(atUrl: historyOutput, text: "datetime,user,profile,url") + + for user in getBasicUsersOnSystem() { + for profile in getCometProfilesForUser(user: user) { + + // Get the history file for the profile + var file: URL + if filemanager.fileExists(atPath: "\(user.homedir)/Library/Application Support/Comet/\(profile)/History") { + file = URL(fileURLWithPath: "\(user.homedir)/Library/Application Support/Comet/\(profile)/History") + self.copyFileToCase(fileToCopy: file, toLocation: self.cometDir, newFileName: "history_and_downloads_\(user.username)_\(profile).db") + } else { continue } + + // Open the history file + var db: OpaquePointer? + if sqlite3_open(file.path, &db) == SQLITE_OK { + + // Query the history file + var queryStatement: OpaquePointer? = nil + let queryString = "SELECT datetime(((v.visit_time/1000000)-11644473600), 'unixepoch'), u.url FROM visits v INNER JOIN urls u ON u.id = v.url;" + + if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK { + var dateTime: String = "" + var url: String = "" + + // write the results to the historyOutput file + while sqlite3_step(queryStatement) == SQLITE_ROW { + if let col1 = sqlite3_column_text(queryStatement, 0) { + let unformattedDatetime = String(cString: col1) + dateTime = Aftermath.standardizeMetadataTimestamp(timeStamp: unformattedDatetime) + } + + let col2 = sqlite3_column_text(queryStatement, 1) + if col2 != nil { + url = String(cString: col2!) + } + + self.addTextToFile(atUrl: historyOutput, text: "\(dateTime),\(user.username),\(profile),\(url)") + } + } else { self.log("Unable to query the database. Please ensure that Comet is not running.") } + } else { self.log("Unable to open the database") } + } + } + } + + func dumpDownloads() { + self.addTextToFile(atUrl: self.writeFile, text: "----- Comet Downloads: -----\n") + + let downlaodsRaw = self.createNewCaseFile(dirUrl: self.cometDir, filename: "downloads_output.csv") + self.addTextToFile(atUrl: downlaodsRaw, text: "datetime,user,profile,url,target_path,danger_type,opened") + + for user in getBasicUsersOnSystem() { + for profile in getCometProfilesForUser(user: user) { + var file: URL + if filemanager.fileExists(atPath: "\(user.homedir)/Library/Application Support/Comet/\(profile)/History") { + file = URL(fileURLWithPath: "\(user.homedir)/Library/Application Support/Comet/\(profile)/History") + } else { continue } + + var db: OpaquePointer? + if sqlite3_open(file.path, &db) == SQLITE_OK { + var queryStatement: OpaquePointer? = nil + let queryString = "SELECT datetime(d.start_time/1000000-11644473600, 'unixepoch'), dc.url, d.target_path, d.danger_type, d.opened FROM downloads d INNER JOIN downloads_url_chains dc ON dc.id = d.id;" + + if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK { + var dateTime: String = "" + var url: String = "" + var targetPath: String = "" + var dangerType: String = "" + var opened: String = "" + + while sqlite3_step(queryStatement) == SQLITE_ROW { + if let col1 = sqlite3_column_text(queryStatement, 0) { + let unformattedDatetime = String(cString: col1) + dateTime = Aftermath.standardizeMetadataTimestamp(timeStamp: unformattedDatetime) + } + + let col2 = sqlite3_column_text(queryStatement, 1) + if let col2 = col2 { url = String(cString: col2) } + + let col3 = sqlite3_column_text(queryStatement, 2) + if let col3 = col3 { targetPath = String(cString: col3) } + + let col4 = sqlite3_column_text(queryStatement, 3) + if let col4 = col4 { dangerType = String(cString: col4) } + + let col5 = sqlite3_column_text(queryStatement, 4) + if let col5 = col5 { opened = String(cString: col5) } + + self.addTextToFile(atUrl: downlaodsRaw, text: " \(dateTime),\(user.username),\(profile),\(url),\(targetPath),\(dangerType),\(opened)") + } + } + } + } + } + + self.addTextToFile(atUrl: self.writeFile, text: "\n----- End of Comet Downloads -----\n") + } + + func dumpPreferences() { + for user in getBasicUsersOnSystem() { + for profile in getCometProfilesForUser(user: user) { + var file: URL + if filemanager.fileExists(atPath: "\(user.homedir)/Library/Application Support/Comet/\(profile)/Preferences") { + file = URL(fileURLWithPath: "\(user.homedir)/Library/Application Support/Comet/\(profile)/Preferences") + self.copyFileToCase(fileToCopy: file, toLocation: self.cometDir, newFileName: "preferences_\(user.username)_\(profile)") + } else { continue } + + do { + let data = try Data(contentsOf: file, options: .mappedIfSafe) + if let json = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as? [String: Any] { + self.addTextToFile(atUrl: writeFile, text: "\nComet Preferences -----\n\(String(describing: json))\n ----- End of Comet Preferences -----\n") + } + + } catch { self.log("Unable to capture Comet Preferenes") } + } + } + } + + func dumpCookies() { + self.addTextToFile(atUrl: self.writeFile, text: "----- Comet Cookies: -----\n") + + for user in getBasicUsersOnSystem() { + for profile in getCometProfilesForUser(user: user) { + var file: URL + if filemanager.fileExists(atPath: "\(user.homedir)/Library/Application Support/Comet/\(profile)/Cookies") { + file = URL(fileURLWithPath: "\(user.homedir)/Library/Application Support/Comet/\(profile)/Cookies") + self.copyFileToCase(fileToCopy: file, toLocation: self.cometDir, newFileName: "cookies_\(user.username)_\(profile).db") + } else { continue } + + var db: OpaquePointer? + if sqlite3_open(file.path, &db) == SQLITE_OK { + var queryStatement: OpaquePointer? = nil + let queryString = "select datetime(creation_utc/100000 -11644473600, 'unixepoch'), name, host_key, path, datetime(expires_utc/100000-11644473600, 'unixepoch') from cookies;" + + if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK { + var dateTime: String = "" + var name: String = "" + var hostKey: String = "" + var path: String = "" + var expireTime: String = "" + + while sqlite3_step(queryStatement) == SQLITE_ROW { + if let col1 = sqlite3_column_text(queryStatement, 0) { + dateTime = String(cString: col1) + } + + if let col2 = sqlite3_column_text(queryStatement, 1) { + name = String(cString: col2) + } + + if let col3 = sqlite3_column_text(queryStatement, 2) { + hostKey = String(cString: col3) + } + + if let col4 = sqlite3_column_text(queryStatement, 3) { + path = String(cString: col4) + } + + if let col5 = sqlite3_column_text(queryStatement, 4) { + expireTime = String(cString: col5) + } + + self.addTextToFile(atUrl: self.writeFile, text: "DateTime: \(dateTime)\nUser: \(user.username)\nProfile: \(profile)\nName: \(name)\nHostKey: \(hostKey)\nPath:\(path)\nExpireTime: \(expireTime)\n\n") + } + } + } + } + } + self.addTextToFile(atUrl: self.writeFile, text: "\n----- End of Comet Cookies -----\n") + } + + func captureExtensions() { + for user in getBasicUsersOnSystem() { + for profile in getCometProfilesForUser(user: user) { + let cometExtensionDir = self.createNewDir(dir: self.cometDir, dirname: "extensions_\(user.username)_\(profile)") + let path = "\(user.homedir)/Library/Application Support/Comet/\(profile)/Extensions" + + for file in filemanager.filesInDirRecursive(path: path) { + self.copyFileToCase(fileToCopy: file, toLocation: cometExtensionDir) + } + } + } + } + + func getCometProfilesForUser(user: User) -> [String] { + var profiles: [String] = [] + // Get the directory name if it contains the string "Profile" + if filemanager.fileExists(atPath: "\(user.homedir)/Library/Application Support/Comet") { + for file in filemanager.filesInDir(path: "\(user.homedir)/Library/Application Support/Comet") { + if file.lastPathComponent.starts(with: "Profile") || file.lastPathComponent == "Default" { + profiles.append(file.lastPathComponent) + } + } + } + + return profiles + } + + override func run() { + self.log("Collecting Comet browser information...") + gatherHistory() + dumpDownloads() + dumpPreferences() + dumpCookies() + captureExtensions() + } +}