Skip to content

Commit

Permalink
Merge pull request #42 from stuartjash/stuartjash_edge
Browse files Browse the repository at this point in the history
Edge and CSV parser
  • Loading branch information
jbradley89 committed Dec 2, 2022
2 parents e99ac5a + 9c463ae commit 623aa77
Show file tree
Hide file tree
Showing 16 changed files with 555 additions and 169 deletions.
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

0 comments on commit 623aa77

Please sign in to comment.