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

Edge and CSV parser #42

Merged
merged 11 commits into from
Dec 2, 2022
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@

![](https://github.com/jamf/aftermath/blob/main/AftermathLogo.png)


![](https://img.shields.io/badge/release-1.1.0-bright%20green) ![](https://img.shields.io/badge/macOS-12.0%2B-blue) ![](https://img.shields.io/badge/license-MIT-orange)


## About
Aftermath is a Swift-based, open-source incident response framework.

Expand Down
29 changes: 4 additions & 25 deletions aftermath.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
5E6780F22922E7E800BAF04B /* Edge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E6780F12922E7E800BAF04B /* Edge.swift */; };
70A44403275707A90035F40E /* SystemReconModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70A44402275707A90035F40E /* SystemReconModule.swift */; };
70A44405275A76990035F40E /* LSQuarantine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70A44404275A76990035F40E /* LSQuarantine.swift */; };
70CF9E3A27611C6100FD884B /* ShellHistoryAndProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70CF9E3927611C6100FD884B /* ShellHistoryAndProfiles.swift */; };
Expand All @@ -33,7 +34,6 @@
A0C2E89728AAAE33008FA597 /* ProcLib.h in Sources */ = {isa = PBXBuildFile; fileRef = A029AB2E2877F56E00649701 /* ProcLib.h */; };
A0C930CF289852E80011FB87 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = A0C930CE289852E80011FB87 /* ZIPFoundation */; };
A0C930D428A4318F0011FB87 /* Timeline.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0C930D328A4318F0011FB87 /* Timeline.swift */; };
A0C930D728A543F80011FB87 /* SwiftCSV in Frameworks */ = {isa = PBXBuildFile; productRef = A0C930D628A543F80011FB87 /* SwiftCSV */; };
A0D6D54327F76C58002BB3C8 /* Cron.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0D6D54227F76C58002BB3C8 /* Cron.swift */; };
A0D6D54727FE147D002BB3C8 /* Overrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0D6D54627FE147D002BB3C8 /* Overrides.swift */; };
A0D6D54927FE52C1002BB3C8 /* SystemExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = A0D6D54827FE52C1002BB3C8 /* SystemExtensions.swift */; };
Expand All @@ -51,7 +51,6 @@
A190FFE228B8151F00B9EF9A /* MockFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A190FFD528B80C3900B9EF9A /* MockFileManager.swift */; };
A190FFE328B8168400B9EF9A /* AftermathTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A190FFCF28B8084F00B9EF9A /* AftermathTests.swift */; };
A1E433D928B918FF00E2B510 /* Aftermath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8ABB9E302756D2B500C0ADD7 /* Aftermath.swift */; };
A1E433DB28B919A400E2B510 /* SwiftCSV in Frameworks */ = {isa = PBXBuildFile; productRef = A1E433DA28B919A400E2B510 /* SwiftCSV */; };
A1E433E528B9270800E2B510 /* dummyPlist.plist in Resources */ = {isa = PBXBuildFile; fileRef = A1E433E428B9270800E2B510 /* dummyPlist.plist */; };
A3046F8E27627DAC0069AA21 /* Module.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3046F8D27627DAC0069AA21 /* Module.swift */; };
A3046F902763AE5E0069AA21 /* CaseFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3046F8F2763AE5E0069AA21 /* CaseFiles.swift */; };
Expand All @@ -74,6 +73,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
5E6780F12922E7E800BAF04B /* Edge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Edge.swift; sourceTree = "<group>"; };
70A44402275707A90035F40E /* SystemReconModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemReconModule.swift; sourceTree = "<group>"; };
70A44404275A76990035F40E /* LSQuarantine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LSQuarantine.swift; sourceTree = "<group>"; };
70CF9E3927611C6100FD884B /* ShellHistoryAndProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShellHistoryAndProfiles.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -135,7 +135,6 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
A1E433DB28B919A400E2B510 /* SwiftCSV in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -145,7 +144,6 @@
files = (
A190FFD328B8094600B9EF9A /* XCTest.framework in Frameworks */,
A0C930CF289852E80011FB87 /* ZIPFoundation in Frameworks */,
A0C930D728A543F80011FB87 /* SwiftCSV in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -263,6 +261,7 @@
A0E1E3EA275EC800008D0DC6 /* Firefox.swift */,
A0E1E3EC275EC809008D0DC6 /* Chrome.swift */,
A0E1E3EE275EC810008D0DC6 /* Safari.swift */,
5E6780F12922E7E800BAF04B /* Edge.swift */,
);
path = browsers;
sourceTree = "<group>";
Expand Down Expand Up @@ -397,7 +396,6 @@
);
name = tests;
packageProductDependencies = (
A1E433DA28B919A400E2B510 /* SwiftCSV */,
);
productName = aftermathTests;
productReference = A190FFDB28B8151300B9EF9A /* tests.xctest */;
Expand All @@ -418,7 +416,6 @@
name = aftermath;
packageProductDependencies = (
A0C930CE289852E80011FB87 /* ZIPFoundation */,
A0C930D628A543F80011FB87 /* SwiftCSV */,
);
productName = aftermath;
productReference = A3CD4E52274434EE00869ECB /* aftermath */;
Expand Down Expand Up @@ -454,7 +451,6 @@
mainGroup = A3CD4E49274434EE00869ECB;
packageReferences = (
A0C930CD289852E80011FB87 /* XCRemoteSwiftPackageReference "ZIPFoundation" */,
A0C930D528A543F80011FB87 /* XCRemoteSwiftPackageReference "SwiftCSV" */,
);
productRefGroup = A3CD4E53274434EE00869ECB /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -500,6 +496,7 @@
A0E22EF2285CD60A003A411A /* CommonDirectories.swift in Sources */,
A3046F902763AE5E0069AA21 /* CaseFiles.swift in Sources */,
A029AB152876A02800649701 /* ProcessModule.swift in Sources */,
5E6780F22922E7E800BAF04B /* Edge.swift in Sources */,
A029AB1C28774CA400649701 /* Tree.swift in Sources */,
A007835028947E80008489EA /* LoginItems.swift in Sources */,
A0C930D428A4318F0011FB87 /* Timeline.swift in Sources */,
Expand Down Expand Up @@ -794,14 +791,6 @@
minimumVersion = 0.9.9;
};
};
A0C930D528A543F80011FB87 /* XCRemoteSwiftPackageReference "SwiftCSV" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/swiftcsv/SwiftCSV";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 0.8.0;
};
};
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
Expand All @@ -810,16 +799,6 @@
package = A0C930CD289852E80011FB87 /* XCRemoteSwiftPackageReference "ZIPFoundation" */;
productName = ZIPFoundation;
};
A0C930D628A543F80011FB87 /* SwiftCSV */ = {
isa = XCSwiftPackageProductDependency;
package = A0C930D528A543F80011FB87 /* XCRemoteSwiftPackageReference "SwiftCSV" */;
productName = SwiftCSV;
};
A1E433DA28B919A400E2B510 /* SwiftCSV */ = {
isa = XCSwiftPackageProductDependency;
package = A0C930D528A543F80011FB87 /* XCRemoteSwiftPackageReference "SwiftCSV" */;
productName = SwiftCSV;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = A3CD4E4A274434EE00869ECB /* Project object */;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
{
"pins" : [
{
"identity" : "swiftcsv",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftcsv/SwiftCSV",
"state" : {
"revision" : "048a1d3c2950b9c151ef9364b36f91baadc2c28c",
"version" : "0.8.0"
}
},
{
"identity" : "zipfoundation",
"kind" : "remoteSourceControl",
Expand Down
16 changes: 1 addition & 15 deletions aftermath/Aftermath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@


import Foundation
import SwiftCSV

class Aftermath {

Expand Down Expand Up @@ -83,20 +82,7 @@ class Aftermath {

}


static func readCSVRows(path: String) -> NamedCSV {

do {
let csvFile = try NamedCSV(url: URL(fileURLWithPath: path), delimiter: .comma, encoding: .utf8)
return csvFile

} catch {
print(error)
exit(1)
}
}


@available(macOS 12.0, *)
static func sortCSV(unsortedArr: [[String]]) throws -> [[String]] {
var arr = unsortedArr
try arr.sort { lhs, rhs in
Expand Down
26 changes: 23 additions & 3 deletions aftermath/CaseFiles.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,31 @@ import Foundation
import ZIPFoundation

struct CaseFiles {
static let caseDir = FileManager.default.temporaryDirectory.appendingPathComponent("Aftermath_\(Host.current().localizedName ?? "")_\(Date().ISO8601Format().replacingOccurrences(of: ":", with: "_"))")
static let caseDir = FileManager.default.temporaryDirectory.appendingPathComponent("Aftermath_\(serialNumber ?? Host.current().localizedName?.replacingOccurrences(of: " ", with: "_") ?? "")")
static let logFile = caseDir.appendingPathComponent("aftermath.log")
static let analysisCaseDir = FileManager.default.temporaryDirectory.appendingPathComponent("Aftermath_Analysis_\(Host.current().localizedName ?? "")_\(Date().ISO8601Format().replacingOccurrences(of: ":", with: "_"))")
static var analysisCaseDir = FileManager.default.temporaryDirectory
static let analysisLogFile = analysisCaseDir.appendingPathComponent("aftermath_analysis.log")
static let metadataFile = caseDir.appendingPathComponent("metadata.csv")
static let fm = FileManager.default
static var serialNumber: String? {
var platformExpert: io_service_t = 0
if #available(macOS 12.0, *) {
platformExpert = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching("IOPlatformExpertDevice"))
} else {
return nil
}

guard platformExpert > 0 else {
return nil
}
guard let serialNumber = (IORegistryEntryCreateCFProperty(platformExpert, kIOPlatformSerialNumberKey as CFString, kCFAllocatorDefault, 0).takeUnretainedValue() as? String)?.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) else {
return nil
}

IOObjectRelease(platformExpert)

return serialNumber
}

static func CreateCaseDir() {
do {
Expand All @@ -25,7 +44,8 @@ struct CaseFiles {
}
}

static func CreateAnalysisCaseDir() {
static func CreateAnalysisCaseDir(filename: String) {
self.analysisCaseDir = self.analysisCaseDir.appendingPathComponent("Aftermath_Analysis_\(filename)")
do {
try fm.createDirectory(at: analysisCaseDir, withIntermediateDirectories: true, attributes: nil)
print("Temporary Aftermath Analysis directory created at \(analysisCaseDir.relativePath)")
Expand Down
29 changes: 22 additions & 7 deletions aftermath/Command.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,20 @@ class Command {
static var analysisDir: String? = nil
static var outputDir: String = "/tmp"
static var collectDirs: [String] = []
static let version: String = "1.1.0"
static let version: String = "1.2.0"

static func main() {
setup(with: CommandLine.arguments)
start()
}

static func setup(with fullArgs: [String]) {

if NSUserName() != "root" {
print("Aftermath must be run as root")
print("Exiting...")
exit(1)
}

let args = [String](fullArgs.dropFirst())

Expand Down Expand Up @@ -73,15 +79,19 @@ class Command {
}
}

static func start() {
static func start() {
printBanner()

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


let mainModule = AftermathModule()
mainModule.log("Running Aftermath Version \(version)")
mainModule.log("Aftermath Analysis Started")
mainModule.log("Analysis started at \(mainModule.getCurrentTimeStandardized().replacingOccurrences(of: ":", with: "_"))")

guard let dir = Self.analysisDir else {
mainModule.log("Analysis directory not provided")
Expand All @@ -95,9 +105,13 @@ class Command {
let unzippedDir = mainModule.unzipArchive(location: dir)

mainModule.log("Started analysis on Aftermath directory: \(unzippedDir)")
let analysisModule = AnalysisModule(collectionDir: unzippedDir)
analysisModule.run()

if #available(macOS 12, *) {
let analysisModule = AnalysisModule(collectionDir: unzippedDir)
analysisModule.run()
} else {
// Fallback on earlier versions
}

mainModule.log("Finished analysis module")

guard isDirectoryThatExists(path: Self.outputDir) else {
Expand All @@ -115,6 +129,7 @@ class Command {
let mainModule = AftermathModule()
mainModule.log("Running Aftermath Version \(version)")
mainModule.log("Aftermath Collection Started")
mainModule.log("Collection started at \(mainModule.getCurrentTimeStandardized())")
mainModule.addTextToFile(atUrl: CaseFiles.metadataFile, text: "file,birth,modified,accessed,permissions,uid,gid, downloadedFrom")


Expand Down Expand Up @@ -194,7 +209,7 @@ class Command {
try FileManager.default.removeItem(at: dirToRemove)
print("Removed \(dirToRemove.relativePath)")
} catch {
print("\(Date().ISO8601Format()) - Error removing \(dirToRemove.relativePath)")
print("Error removing \(dirToRemove.relativePath)")
print(error)
}
}
Expand Down
33 changes: 24 additions & 9 deletions aftermath/Module.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class AftermathModule {
let newFile = dirUrl.appendingPathComponent(filename)
let path = newFile.relativePath
if !(FileManager.default.createFile(atPath: path, contents: nil, attributes: nil)) {
print("\(Date().ISO8601Format()) - Error creating \(path)")
print("\(getCurrentTimeStandardized()) - Error creating \(path)")
}

return newFile
Expand All @@ -131,7 +131,7 @@ class AftermathModule {

func addTextToFileFromUrl(fromFile: URL, toFile: URL) {
if (!FileManager.default.fileExists(atPath: fromFile.relativePath)) {
self.log("\(Date().ISO8601Format())- Unable to copy text from file \(fromFile.relativePath) as the file does not exist")
self.log("\(getCurrentTimeStandardized()) - Unable to copy text from file \(fromFile.relativePath) as the file does not exist")
let _ = self.createNewCaseFile(dirUrl: toFile.deletingLastPathComponent(), filename: toFile.lastPathComponent)
return
}
Expand All @@ -140,13 +140,13 @@ class AftermathModule {
let contents = try String(contentsOf: fromFile, encoding: .ascii)
self.addTextToFile(atUrl: toFile, text: "\(fromFile):\n\n\(contents)\n----------\n")
} catch {
self.log("\(Date().ISO8601Format())- Unable to writing contents of \(fromFile) to \(toFile) due to error:\n\(error) ")
self.log("\(getCurrentTimeStandardized())- Unable to writing contents of \(fromFile) to \(toFile) due to error:\n\(error) ")
}
}

func copyFileToCase(fileToCopy: URL, toLocation: URL?, newFileName: String? = nil, isAnalysis: Bool? = false) {
if (!FileManager.default.fileExists(atPath: fileToCopy.relativePath)) {
self.log("\(Date().ISO8601Format()) - Unable to copy file \(fileToCopy.relativePath) as the file does not exist")
self.log("\(getCurrentTimeStandardized()) - Unable to copy file \(fileToCopy.relativePath) as the file does not exist")
return
}

Expand All @@ -170,7 +170,7 @@ class AftermathModule {
do {
try FileManager.default.copyItem(at:fileToCopy, to:dest)
} catch {
self.log("\(Date().ISO8601Format()) - Error copying \(fileToCopy.relativePath) to \(dest)")
self.log("\(getCurrentTimeStandardized()) - Error copying \(fileToCopy.relativePath) to \(dest)")
}

}
Expand All @@ -181,7 +181,7 @@ class AftermathModule {
if fromFile.pathComponents.contains("audit") {
return
}

let helpers = CHelpers()
var metadata: String
var birthTimestamp: String
Expand All @@ -198,10 +198,13 @@ class AftermathModule {
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),")
Expand Down Expand Up @@ -257,13 +260,13 @@ class AftermathModule {
func log(_ note: String, displayOnly: Bool = false, file: String = #file) {

let module = URL(fileURLWithPath: file).lastPathComponent
let entry = "\(Date().ISO8601Format()) - \(module) - \(note)"
let entry = "\(getCurrentTimeStandardized()) - \(module) - \(note)"

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)"
let colorized = "\(Color.magenta.rawValue)\(getCurrentTimeStandardized())\(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)"
let plainText = "\(getCurrentTimeStandardized()) - \(module) - \(note)"
print(plainText)
}

Expand All @@ -272,6 +275,18 @@ class AftermathModule {
}
}

func getCurrentTimeStandardized() -> String {
let testFormatter = DateFormatter()
testFormatter.dateStyle = .full
testFormatter.timeStyle = .full
testFormatter.locale = Locale(identifier: "en_US_POSIX")
testFormatter.dateFormat = "yyyy-MM-dd'T'hh:mm:ss'Z'"

let currentDateTime = Date()

return testFormatter.string(from: currentDateTime)
}

func unzipArchive(location: String) -> String {

let zippedURL = URL(fileURLWithPath: location)
Expand Down
Loading