Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.4.0 update #7

Merged
merged 14 commits into from
Jun 29, 2023
4 changes: 4 additions & 0 deletions aftermath.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
5E494473293AC914007FFBDD /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E494472293AC914007FFBDD /* URL.swift */; };
5E494475293D50FE007FFBDD /* ConfigurationProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E494474293D50FE007FFBDD /* ConfigurationProfiles.swift */; };
5E4BC90029D75A8E0004DAA6 /* Arc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E4BC8FF29D75A8E0004DAA6 /* Arc.swift */; };
5E6780F22922E7E800BAF04B /* Edge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E6780F12922E7E800BAF04B /* Edge.swift */; };
5E93B0AE2941608D009D2AB5 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E93B0AD2941608D009D2AB5 /* Data.swift */; };
5E93B0B0294160B6009D2AB5 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E93B0AF294160B6009D2AB5 /* String.swift */; };
Expand Down Expand Up @@ -79,6 +80,7 @@
/* Begin PBXFileReference section */
5E494472293AC914007FFBDD /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = "<group>"; };
5E494474293D50FE007FFBDD /* ConfigurationProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationProfiles.swift; sourceTree = "<group>"; };
5E4BC8FF29D75A8E0004DAA6 /* Arc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Arc.swift; sourceTree = "<group>"; };
5E6780F12922E7E800BAF04B /* Edge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Edge.swift; sourceTree = "<group>"; };
5E93B0AD2941608D009D2AB5 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = "<group>"; };
5E93B0AF294160B6009D2AB5 /* String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -271,6 +273,7 @@
A0E1E3EC275EC809008D0DC6 /* Chrome.swift */,
A0E1E3EE275EC810008D0DC6 /* Safari.swift */,
5E6780F12922E7E800BAF04B /* Edge.swift */,
5E4BC8FF29D75A8E0004DAA6 /* Arc.swift */,
);
path = browsers;
sourceTree = "<group>";
Expand Down Expand Up @@ -502,6 +505,7 @@
files = (
A3CD4E56274434EE00869ECB /* Command.swift in Sources */,
5E494475293D50FE007FFBDD /* ConfigurationProfiles.swift in Sources */,
5E4BC90029D75A8E0004DAA6 /* Arc.swift in Sources */,
A0C2E89728AAAE33008FA597 /* ProcLib.h in Sources */,
A3745358275730870074B65C /* LaunchItems.swift in Sources */,
A0FAEEFE28B94B2C00AC655F /* LogParser.swift in Sources */,
Expand Down
26 changes: 16 additions & 10 deletions aftermath/Command.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class Command {
args.forEach { arg in
switch arg {
case "-h", "--help": Self.printHelp()
case "--cleanup": Self.cleanup()
case "--cleanup": Self.cleanup(defaultRun: false)
case "-d", "--deep": Self.options.insert(.deep)
case "--pretty": Self.options.insert(.pretty)
case "-o", "--output":
Expand Down Expand Up @@ -87,12 +87,13 @@ class Command {
}

static func start() {
printBanner()

if Self.options.contains(.analyze) {

printBanner()
cleanup(defaultRun: true)
if Self.options.contains(.analyze) {
if let name = self.analysisDir?.split(separator: "_").last?.split(separator: ".").first {
CaseFiles.CreateAnalysisCaseDir(filename: String(describing: name))
}
}


let mainModule = AftermathModule()
Expand Down Expand Up @@ -195,25 +196,30 @@ class Command {
}
}

static func cleanup() {
// remove any aftermath directories from tmp and /var/folders/zz
let potentialPaths = ["/tmp", "/var/folders/zz"]
static func cleanup(defaultRun: Bool) {
// remove any aftermath directories from /var/folders/zz and clean up /tmp if running this as a standalone command
var potentialPaths = ["/var/folders/zz"]
if !defaultRun { potentialPaths.append("/tmp") }
else {
print("Cleaning up temporary directories prior to starting...")
}

for p in potentialPaths {
let enumerator = FileManager.default.enumerator(atPath: p)
while let element = enumerator?.nextObject() as? String {
if element.contains("Aftermath_") {
let dirToRemove = URL(fileURLWithPath: "\(p)/\(element)")
do {
try FileManager.default.removeItem(at: dirToRemove)
print("Removed \(dirToRemove.relativePath)")
if !defaultRun {print("Removed \(dirToRemove.relativePath)") }
} catch {
print("Error removing \(dirToRemove.relativePath)")
print(error)
}
}
}
}
exit(1)
if !defaultRun { exit(1) }
}

static func printHelp() {
Expand Down
26 changes: 26 additions & 0 deletions analysis/Storyline.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,31 @@ class Storyline: AftermathModule {
}
}

func addArcData() {
let arcPaths = ["history":"\(collectionDir)/Browser/Arc/history_output.csv","downloads":"\(collectionDir)/Browser/Arc/downloads_output.csv"]

for (title,p) in arcPaths {

if !filemanager.fileExists(atPath: p) { continue }

var data = ""

do {
data = try String(contentsOfFile: p)
} catch {
print(error)
}

var rows = data.components(separatedBy: "\n")
rows.removeFirst()
for row in rows {
if row == "" { continue }
let columns = row.components(separatedBy: ",")
self.addTextToFile(atUrl: self.storylineFile, text: "\(columns[0]),arc_\(title),\(columns[3]))")
}
}
}

func sortStoryline() {

self.log("Creating the storyline...Please wait...")
Expand Down Expand Up @@ -197,6 +222,7 @@ class Storyline: AftermathModule {
addFirefoxData()
addChromeData()
addEdgeData()
addArcData()
sortStoryline()
removeUnsorted()
}
Expand Down
2 changes: 1 addition & 1 deletion artifacts/ConfigurationProfiles.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class ConfigurationProfiles: ArtifactsModule {

let outputFile = self.createNewCaseFile(dirUrl: moduleDirRoot, filename: "config_profiles.txt")

let command = "sudo profiles list -all"
let command = "sudo profiles -C -o stdout-xml"
let output = Aftermath.shell("\(command)")

self.addTextToFile(atUrl: outputFile, text: output)
Expand Down
231 changes: 231 additions & 0 deletions filesystem/browsers/Arc.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
//
// Arc.swift
// aftermath
//
// Copyright 2022 JAMF Software, LLC
//

import Foundation
import SQLite3
import AppKit

class Arc: BrowserModule {

let arcDir: URL
let writeFile: URL

init(arcDir: URL, writeFile: URL) {
self.arcDir = arcDir
self.writeFile = writeFile
}

func gatherHistory() {

let historyOutput = self.createNewCaseFile(dirUrl: self.arcDir, filename: "history_output.csv")
self.addTextToFile(atUrl: historyOutput, text: "datetime,user,profile,url")

for user in getBasicUsersOnSystem() {
for profile in getArcProfilesForUser(user: user) {

// Get the history file for the profile
var file: URL
if filemanager.fileExists(atPath: "\(user.homedir)/Library/Application Support/Arc/User Data/\(profile)/History") {
file = URL(fileURLWithPath: "\(user.homedir)/Library/Application Support/Arc/User Data/\(profile)/History")
self.copyFileToCase(fileToCopy: file, toLocation: self.arcDir, 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 Arc is not running.") }
} else { self.log("Unable to open the database") }
}
}
}

func dumpDownloads() {
self.addTextToFile(atUrl: self.writeFile, text: "----- Arc Downloads: -----\n")

let downlaodsRaw = self.createNewCaseFile(dirUrl: self.arcDir, 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 getArcProfilesForUser(user: user) {
var file: URL
if filemanager.fileExists(atPath: "\(user.homedir)/Library/Application Support/Arc/User Data/\(profile)/History") {
file = URL(fileURLWithPath: "\(user.homedir)/Library/Application Support/Arc/User Data/\(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 Arc Downloads -----\n")
}

func dumpPreferences() {
for user in getBasicUsersOnSystem() {
for profile in getArcProfilesForUser(user: user) {
var file: URL
if filemanager.fileExists(atPath: "\(user.homedir)/Library/Application Support/Arc/User Data/\(profile)/Preferences") {
file = URL(fileURLWithPath: "\(user.homedir)/Library/Application Support/Arc/User Data/\(profile)/Preferences")
self.copyFileToCase(fileToCopy: file, toLocation: self.arcDir, newFileName: "preferenes_\(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: "\nArc Preferences -----\n\(String(describing: json))\n ----- End of Arc Preferences -----\n")
}

} catch { self.log("Unable to capture Arc Preferenes") }
}
}
}

func dumpCookies() {
self.addTextToFile(atUrl: self.writeFile, text: "----- Arc Cookies: -----\n")

for user in getBasicUsersOnSystem() {
for profile in getArcProfilesForUser(user: user) {
var file: URL
if filemanager.fileExists(atPath: "\(user.homedir)/Library/Application Support/Arc/User Data/\(profile)/Cookies") {
file = URL(fileURLWithPath: "\(user.homedir)/Library/Application Support/Arc/User Data/\(profile)/Cookies")
self.copyFileToCase(fileToCopy: file, toLocation: self.arcDir, 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 Arc Cookies -----\n")
}

func captureExtensions() {
for user in getBasicUsersOnSystem() {
for profile in getArcProfilesForUser(user: user) {
let arcExtensionsDir = self.createNewDir(dir: self.arcDir, dirname: "extensions_\(user.username)_\(profile)")
let path = "\(user.homedir)/Library/Application Support/Arc/User Data/\(profile)/Extensions"

for file in filemanager.filesInDirRecursive(path: path) {
self.copyFileToCase(fileToCopy: file, toLocation: arcExtensionsDir)
}
}

}
}

func getArcProfilesForUser(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/Arc/User Data") {
for file in filemanager.filesInDir(path: "\(user.homedir)/Library/Application Support/Arc/User Data") {
if file.lastPathComponent.starts(with: "Profile") || file.lastPathComponent == "Default" {
profiles.append(file.lastPathComponent)
}
}
}

return profiles
}

override func run() {
self.log("Collecting Arc browser information...")
gatherHistory()
dumpDownloads()
dumpPreferences()
dumpCookies()
captureExtensions()
}
}
Loading