Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
4 changes: 4 additions & 0 deletions aftermath.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -144,6 +145,7 @@
A190FFD528B80C3900B9EF9A /* MockFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFileManager.swift; sourceTree = "<group>"; };
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 = "<group>"; };
A202BE342EE03E6B006277CA /* Comet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comet.swift; sourceTree = "<group>"; };
A3046F8D27627DAC0069AA21 /* Module.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Module.swift; sourceTree = "<group>"; };
A3046F8F2763AE5E0069AA21 /* CaseFiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaseFiles.swift; sourceTree = "<group>"; };
A31009A32B9B838100068593 /* Network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Network.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -300,6 +302,7 @@
A0E1E3E7275EC720008D0DC6 /* browsers */ = {
isa = PBXGroup;
children = (
A202BE342EE03E6B006277CA /* Comet.swift */,
A0E1E3E8275EC736008D0DC6 /* BrowserModule.swift */,
A0E1E3EA275EC800008D0DC6 /* Firefox.swift */,
A0E1E3EC275EC809008D0DC6 /* Chrome.swift */,
Expand Down Expand Up @@ -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 */,
Expand Down
7 changes: 6 additions & 1 deletion filesystem/browsers/BrowserModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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...")
Expand Down Expand Up @@ -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) {
Expand Down
229 changes: 229 additions & 0 deletions filesystem/browsers/Comet.swift
Original file line number Diff line number Diff line change
@@ -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()
}
}