From fbd6475e0a1e5550d08c5ad33b2f37497e66142e Mon Sep 17 00:00:00 2001
From: wibus-wee <1596355173@qq.com>
Date: Tue, 6 Aug 2024 17:58:53 +0800
Subject: [PATCH 1/2] feat: implement InjectGUI Helper
---
Helper/Extension/Extension+OSStatus.swift | 39 ++
Helper/Helper.swift | 60 +++
Helper/HelperConstants.swift | 16 +
Helper/Info.plist | 22 +
Helper/Launchd.plist | 15 +
Helper/Protocol/HelperProtocol.swift | 13 +
.../Protocol/RemoteApplicationProtocol.swift | 13 +
.../Service/ConnectionIdentityService.swift | 60 +++
Helper/Service/ExecutionService.swift | 44 ++
Helper/main.swift | 14 +
InjectGUI.xcodeproj/project.pbxproj | 218 +++++++++
.../xcshareddata/xcschemes/Helper.xcscheme | 79 +++
.../xcshareddata/xcschemes/InjectGUI.xcscheme | 92 ++++
InjectGUI/Backend/Execution.swift | 25 +
InjectGUI/Backend/HelperRemoteProvider.swift | 170 +++++++
InjectGUI/Extension/InjectError.swift | 29 ++
InjectGUI/Info.plist | 23 +
InjectGUI/InjectGUI.entitlements | 2 -
InjectGUI/View/ContentView.swift | 16 +
SMJobBlessUtil.py | 460 ++++++++++++++++++
uninstall-helper.sh | 12 +
21 files changed, 1420 insertions(+), 2 deletions(-)
create mode 100644 Helper/Extension/Extension+OSStatus.swift
create mode 100644 Helper/Helper.swift
create mode 100644 Helper/HelperConstants.swift
create mode 100644 Helper/Info.plist
create mode 100644 Helper/Launchd.plist
create mode 100644 Helper/Protocol/HelperProtocol.swift
create mode 100644 Helper/Protocol/RemoteApplicationProtocol.swift
create mode 100644 Helper/Service/ConnectionIdentityService.swift
create mode 100644 Helper/Service/ExecutionService.swift
create mode 100644 Helper/main.swift
create mode 100644 InjectGUI.xcodeproj/xcshareddata/xcschemes/Helper.xcscheme
create mode 100644 InjectGUI.xcodeproj/xcshareddata/xcschemes/InjectGUI.xcscheme
create mode 100644 InjectGUI/Backend/Execution.swift
create mode 100644 InjectGUI/Backend/HelperRemoteProvider.swift
create mode 100644 InjectGUI/Extension/InjectError.swift
create mode 100755 SMJobBlessUtil.py
create mode 100644 uninstall-helper.sh
diff --git a/Helper/Extension/Extension+OSStatus.swift b/Helper/Extension/Extension+OSStatus.swift
new file mode 100644
index 0000000..b3669bb
--- /dev/null
+++ b/Helper/Extension/Extension+OSStatus.swift
@@ -0,0 +1,39 @@
+//
+// Extension+OSStatus.swift
+// InjectGUI
+//
+// Created by wibus on 2024/8/6.
+//
+
+import Foundation
+
+// MARK: - Check error
+
+extension OSStatus {
+
+ /// If the status is not a success, get the error out of it and throw it.
+ func checkError(_ functionName: String) throws {
+ if self == errSecSuccess { return }
+ throw SecurityError(status: self, functionName: functionName)
+ }
+}
+
+// MARK: - SecError
+
+extension OSStatus {
+
+ /// An error that might be thrown by the /// [Security Framework](https://developer.apple.com/documentation/security/1542001-security_framework_result_codes)
+ struct SecurityError: Error {
+
+ // MARK: Properties
+
+ let localizedDescription: String
+
+ // MARK: Init
+
+ init(status: OSStatus, functionName: String) {
+ let statusMessage = SecCopyErrorMessageString(status, nil) as String? ?? "Unknown sec error"
+ localizedDescription = "[\(functionName)] \(statusMessage)"
+ }
+ }
+}
diff --git a/Helper/Helper.swift b/Helper/Helper.swift
new file mode 100644
index 0000000..0f5a68b
--- /dev/null
+++ b/Helper/Helper.swift
@@ -0,0 +1,60 @@
+//
+// Helper.swift
+// InjectGUI
+//
+// Created by wibus on 2024/8/6.
+//
+
+import Foundation
+
+// MARK: - Helper
+
+final class Helper: NSObject {
+ let listener: NSXPCListener
+
+ override init() {
+ listener = NSXPCListener(machServiceName: HelperConstants.domain)
+ super.init()
+ listener.delegate = self
+ }
+}
+
+
+extension Helper: HelperProtocol {
+
+ func executeScript(at path: String) async throws -> String {
+ NSLog("Executing script at \(path)")
+ do {
+ return try await ExecutionService.executeScript(at: path)
+ } catch {
+ NSLog("Error: \(error.localizedDescription)")
+ throw error
+ }
+ }
+}
+
+extension Helper {
+ func run() {
+ listener.resume() // Start the XPC service
+ RunLoop.current.run() // Run the XPC service
+ }
+}
+
+extension Helper: NSXPCListenerDelegate {
+
+ func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
+ do {
+ try ConnectionIdentityService.checkConnectionIsValid(connection: newConnection)
+ } catch {
+ NSLog("🛑 Connection \(newConnection) has not been validated. \(error.localizedDescription)")
+ return false
+ }
+
+ newConnection.exportedInterface = NSXPCInterface(with: HelperProtocol.self)
+ newConnection.remoteObjectInterface = NSXPCInterface(with: RemoteApplicationProtocol.self)
+ newConnection.exportedObject = self
+
+ newConnection.resume()
+ return true
+ }
+}
diff --git a/Helper/HelperConstants.swift b/Helper/HelperConstants.swift
new file mode 100644
index 0000000..d67c78b
--- /dev/null
+++ b/Helper/HelperConstants.swift
@@ -0,0 +1,16 @@
+//
+// HelperConstants.swift
+// InjectGUI
+//
+// Created by wibus on 2024/8/6.
+//
+
+import Foundation
+
+enum HelperConstants {
+ static let helpersFolder = "/Library/PrivilegedHelperTools/"
+ static let domain = "dev.wibus-wee.InjectGUI.helper"
+ static let helperPath = helpersFolder + domain
+ static let bundleID = "dev.wibus-wee.InjectGUI"
+ static let subject = "YX89Z87JNG"
+}
diff --git a/Helper/Info.plist b/Helper/Info.plist
new file mode 100644
index 0000000..0fecefd
--- /dev/null
+++ b/Helper/Info.plist
@@ -0,0 +1,22 @@
+
+
+
+
+ CFBundleExecutable
+
+ CFBundleIdentifier
+ dev.wibus-wee.InjectGUI.helper
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ dev.wibus-wee.InjectGUI.helper
+ CFBundleShortVersionString
+ 1.0.0
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ SMAuthorizedClients
+
+ identifier "dev.wibus-wee.InjectGUI" and anchor apple generic and certificate leaf[subject.CN] = "Apple Development: 1596355173@qq.com (YX89Z87JNG)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */
+
+
+
diff --git a/Helper/Launchd.plist b/Helper/Launchd.plist
new file mode 100644
index 0000000..21f7f9d
--- /dev/null
+++ b/Helper/Launchd.plist
@@ -0,0 +1,15 @@
+
+
+
+
+ Label
+ dev.wibus-wee.InjectGUI.helper
+ StandardOutputPath
+ /tmp/injectgui.helper.log
+ MachServices
+
+ dev.wibus-wee.InjectGUI.helper
+
+
+
+
diff --git a/Helper/Protocol/HelperProtocol.swift b/Helper/Protocol/HelperProtocol.swift
new file mode 100644
index 0000000..483f635
--- /dev/null
+++ b/Helper/Protocol/HelperProtocol.swift
@@ -0,0 +1,13 @@
+//
+// HelperProtocol.swift
+// InjectGUI
+//
+// Created by wibus on 2024/8/6.
+//
+
+import Foundation
+
+@objc(HelperProtocol)
+public protocol HelperProtocol {
+ @objc func executeScript(at path: String) async throws -> String
+}
diff --git a/Helper/Protocol/RemoteApplicationProtocol.swift b/Helper/Protocol/RemoteApplicationProtocol.swift
new file mode 100644
index 0000000..dbd6943
--- /dev/null
+++ b/Helper/Protocol/RemoteApplicationProtocol.swift
@@ -0,0 +1,13 @@
+//
+// RemoteApplicationProtocol.swift
+// InjectGUI
+//
+// Created by wibus on 2024/8/6.
+//
+
+import Foundation
+
+@objc(MainApplicationProtocol)
+public protocol RemoteApplicationProtocol {
+ // empty protocol but required for the XPC connection
+}
diff --git a/Helper/Service/ConnectionIdentityService.swift b/Helper/Service/ConnectionIdentityService.swift
new file mode 100644
index 0000000..131a5c8
--- /dev/null
+++ b/Helper/Service/ConnectionIdentityService.swift
@@ -0,0 +1,60 @@
+//
+// ConnectionIdentityService.swift
+// InjectGUI
+//
+// Created by wibus on 2024/8/6.
+//
+
+import Foundation
+
+enum ConnectionIdentityService {
+ private static func secCode(from token: Data) throws -> SecCode {
+ let attributesDict = [kSecGuestAttributeAudit: token]
+
+ var secCode: SecCode?
+ try SecCodeCopyGuestWithAttributes(nil, attributesDict as CFDictionary, [], &secCode)
+ .checkError("SecCodeCopyGuestWithAttributes")
+
+ guard let secCode else {
+ throw InjectError.helperConnection("Unable to get secCode from token using 'SecCodeCopyGuestWithAttributes'")
+ }
+
+ return secCode
+ }
+
+ static private let requirementString =
+ #"anchor apple generic and identifier "\#(HelperConstants.bundleID)" and certificate leaf[subject.OU] = "\#(HelperConstants.subject)""# as CFString
+
+ private static func verifySecCode(secCode: SecCode) throws {
+ var secRequirements: SecRequirement?
+
+ try SecRequirementCreateWithString(requirementString, [], &secRequirements)
+ .checkError("SecRequirementCreateWithString")
+ try SecCodeCheckValidity(secCode, [], secRequirements)
+ .checkError("SecCodeCheckValidity")
+ }
+
+ private static func tokenData(in connection: NSXPCConnection) throws -> Data {
+ let property = "auditToken"
+
+ guard connection.responds(to: NSSelectorFromString(property)) else {
+ throw InjectError.helperConnection("'NSXPCConnection' has no member '\(property)'")
+ }
+ guard let auditToken = connection.value(forKey: property) else {
+ throw InjectError.helperConnection("'\(property)' from connection is 'nil'")
+ }
+ guard let auditTokenValue = auditToken as? NSValue else {
+ throw InjectError.helperConnection("Unable to get 'NSValue' from '\(property)' in 'NSXPCConnection'")
+ }
+ guard var auditTokenOpaque = auditTokenValue.value(of: audit_token_t.self) else {
+ throw InjectError.helperConnection("'\(property)' 'NSValue' is not of type 'audit_token_t'")
+ }
+
+ return Data(bytes: &auditTokenOpaque, count: MemoryLayout.size) }
+
+ static func checkConnectionIsValid(connection: NSXPCConnection) throws {
+ let tokenData = try tokenData(in: connection)
+ let secCode = try secCode(from: tokenData)
+ try verifySecCode(secCode: secCode)
+ }
+}
diff --git a/Helper/Service/ExecutionService.swift b/Helper/Service/ExecutionService.swift
new file mode 100644
index 0000000..a0d6cc6
--- /dev/null
+++ b/Helper/Service/ExecutionService.swift
@@ -0,0 +1,44 @@
+//
+// ExecutionService.swift
+// InjectGUI
+//
+// Created by wibus on 2024/8/6.
+//
+
+
+import Foundation
+
+// MARK: - ExecutionService
+
+/// Execute a script.
+enum ExecutionService {
+
+ // MARK: Constants
+
+ static let programURL = URL(fileURLWithPath: "/bin/zsh")
+
+ // MARK: Execute
+
+ /// Execute the script at the provided URL.
+ static func executeScript(at path: String) async throws -> String {
+ let process = Process()
+ process.executableURL = programURL
+ process.arguments = [path]
+
+ let outputPipe = Pipe()
+ process.standardOutput = outputPipe
+ process.standardError = outputPipe
+ try process.run()
+
+ return try await Task {
+ let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
+
+ guard let output = String(data: outputData, encoding: .utf8) else {
+ throw InjectError.unknown
+ }
+
+ return output
+ }
+ .value
+ }
+}
diff --git a/Helper/main.swift b/Helper/main.swift
new file mode 100644
index 0000000..422bb40
--- /dev/null
+++ b/Helper/main.swift
@@ -0,0 +1,14 @@
+//
+// main.swift
+// Helper
+//
+// Created by wibus on 2024/8/6.
+//
+
+import Foundation
+
+NSLog("InjectGUI Helper started")
+print("InjectGUI Helper started")
+let helper = Helper()
+helper.run()
+
\ No newline at end of file
diff --git a/InjectGUI.xcodeproj/project.pbxproj b/InjectGUI.xcodeproj/project.pbxproj
index 7d94326..c3663ec 100644
--- a/InjectGUI.xcodeproj/project.pbxproj
+++ b/InjectGUI.xcodeproj/project.pbxproj
@@ -10,6 +10,26 @@
380D777B2C61EA56005F3150 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 380D777A2C61EA56005F3150 /* Sparkle */; };
380D777D2C61EAB9005F3150 /* CheckForUpdatesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D777C2C61EAB9005F3150 /* CheckForUpdatesView.swift */; };
380D77842C620464005F3150 /* config.json in Resources */ = {isa = PBXBuildFile; fileRef = 380D77832C620464005F3150 /* config.json */; };
+ 380D778C2C620B54005F3150 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D778B2C620B54005F3150 /* main.swift */; };
+ 380D77912C620BF7005F3150 /* HelperProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77902C620BF7005F3150 /* HelperProtocol.swift */; };
+ 380D77922C620BF7005F3150 /* HelperProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77902C620BF7005F3150 /* HelperProtocol.swift */; };
+ 380D77942C620C4D005F3150 /* RemoteApplicationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77932C620C4D005F3150 /* RemoteApplicationProtocol.swift */; };
+ 380D77952C620C4D005F3150 /* RemoteApplicationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77932C620C4D005F3150 /* RemoteApplicationProtocol.swift */; };
+ 380D77972C620C63005F3150 /* HelperConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77962C620C63005F3150 /* HelperConstants.swift */; };
+ 380D77982C620C63005F3150 /* HelperConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77962C620C63005F3150 /* HelperConstants.swift */; };
+ 380D779A2C620CC5005F3150 /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77992C620CC5005F3150 /* Helper.swift */; };
+ 380D779B2C620CC5005F3150 /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77992C620CC5005F3150 /* Helper.swift */; };
+ 380D779D2C620D6C005F3150 /* ConnectionIdentityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D779C2C620D6C005F3150 /* ConnectionIdentityService.swift */; };
+ 380D779E2C620D6C005F3150 /* ConnectionIdentityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D779C2C620D6C005F3150 /* ConnectionIdentityService.swift */; };
+ 380D77A02C620D9C005F3150 /* Extension+OSStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D779F2C620D9C005F3150 /* Extension+OSStatus.swift */; };
+ 380D77A12C620D9C005F3150 /* Extension+OSStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D779F2C620D9C005F3150 /* Extension+OSStatus.swift */; };
+ 380D77A32C620F6B005F3150 /* HelperRemoteProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77A22C620F6B005F3150 /* HelperRemoteProvider.swift */; };
+ 380D77A62C620FF9005F3150 /* InjectError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77A52C620FF9005F3150 /* InjectError.swift */; };
+ 380D77A72C620FF9005F3150 /* InjectError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77A52C620FF9005F3150 /* InjectError.swift */; };
+ 380D77A92C621181005F3150 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 380D77A82C621181005F3150 /* Info.plist */; };
+ 380D77AA2C621237005F3150 /* dev.wibus-wee.InjectGUI.helper in CopyFiles */ = {isa = PBXBuildFile; fileRef = 380D77892C620B54005F3150 /* dev.wibus-wee.InjectGUI.helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
+ 380D77AC2C6212E0005F3150 /* Launchd.plist in Resources */ = {isa = PBXBuildFile; fileRef = 380D77AB2C6212E0005F3150 /* Launchd.plist */; };
+ 380D77AE2C621479005F3150 /* Execution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77AD2C621479005F3150 /* Execution.swift */; };
381027E02C5F784F00348460 /* Extension+Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 381027DF2C5F784F00348460 /* Extension+Scene.swift */; };
381027E22C5F7C4800348460 /* Extension+Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = 381027E12C5F7C4800348460 /* Extension+Font.swift */; };
381027E42C5F7E1100348460 /* Extension+URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 381027E32C5F7E1100348460 /* Extension+URL.swift */; };
@@ -27,6 +47,8 @@
38877A372C4A7294009F5910 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38877A362C4A7294009F5910 /* Configuration.swift */; };
38877A3A2C4A730F009F5910 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38877A392C4A730F009F5910 /* Constants.swift */; };
38877A3D2C4A9EB7009F5910 /* SetupApplicationSupportDirectory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38877A3C2C4A9EB7009F5910 /* SetupApplicationSupportDirectory.swift */; };
+ 38A5747A2C621E7600209B96 /* ExecutionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A574792C621E7600209B96 /* ExecutionService.swift */; };
+ 38A5747B2C621E7600209B96 /* ExecutionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A574792C621E7600209B96 /* ExecutionService.swift */; };
38AD95EA2C58E70E0032E79F /* Injector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AD95E92C58E70E0032E79F /* Injector.swift */; };
38AD95EE2C58F59C0032E79F /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AD95ED2C58F59C0032E79F /* StatusView.swift */; };
38BC1F532C4B587A00C3B60E /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38BC1F522C4B587900C3B60E /* SidebarView.swift */; };
@@ -38,10 +60,36 @@
38E944F72C5A85E200B252A3 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E944F62C5A85E200B252A3 /* Cache.swift */; };
/* End PBXBuildFile section */
+/* Begin PBXCopyFilesBuildPhase section */
+ 380D77872C620B54005F3150 /* CopyFiles */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 12;
+ dstPath = InjectGUI.app/Contents/Library/LaunchServices;
+ dstSubfolderSpec = 16;
+ files = (
+ 380D77AA2C621237005F3150 /* dev.wibus-wee.InjectGUI.helper in CopyFiles */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
/* Begin PBXFileReference section */
380D777C2C61EAB9005F3150 /* CheckForUpdatesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckForUpdatesView.swift; sourceTree = ""; };
380D777E2C61ED32005F3150 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
380D77832C620464005F3150 /* config.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = config.json; sourceTree = ""; };
+ 380D77892C620B54005F3150 /* dev.wibus-wee.InjectGUI.helper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "dev.wibus-wee.InjectGUI.helper"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 380D778B2C620B54005F3150 /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
+ 380D77902C620BF7005F3150 /* HelperProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperProtocol.swift; sourceTree = ""; };
+ 380D77932C620C4D005F3150 /* RemoteApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteApplicationProtocol.swift; sourceTree = ""; };
+ 380D77962C620C63005F3150 /* HelperConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperConstants.swift; sourceTree = ""; };
+ 380D77992C620CC5005F3150 /* Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = ""; };
+ 380D779C2C620D6C005F3150 /* ConnectionIdentityService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionIdentityService.swift; sourceTree = ""; };
+ 380D779F2C620D9C005F3150 /* Extension+OSStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+OSStatus.swift"; sourceTree = ""; };
+ 380D77A22C620F6B005F3150 /* HelperRemoteProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelperRemoteProvider.swift; sourceTree = ""; };
+ 380D77A52C620FF9005F3150 /* InjectError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InjectError.swift; sourceTree = ""; };
+ 380D77A82C621181005F3150 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
+ 380D77AB2C6212E0005F3150 /* Launchd.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Launchd.plist; sourceTree = ""; };
+ 380D77AD2C621479005F3150 /* Execution.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Execution.swift; sourceTree = ""; };
381027DF2C5F784F00348460 /* Extension+Scene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+Scene.swift"; sourceTree = ""; };
381027E12C5F7C4800348460 /* Extension+Font.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+Font.swift"; sourceTree = ""; };
381027E32C5F7E1100348460 /* Extension+URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+URL.swift"; sourceTree = ""; };
@@ -62,6 +110,8 @@
38877A362C4A7294009F5910 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; };
38877A392C4A730F009F5910 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
38877A3C2C4A9EB7009F5910 /* SetupApplicationSupportDirectory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetupApplicationSupportDirectory.swift; sourceTree = ""; };
+ 38A574792C621E7600209B96 /* ExecutionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExecutionService.swift; sourceTree = ""; };
+ 38A5747F2C62284300209B96 /* uninstall-helper.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "uninstall-helper.sh"; sourceTree = ""; };
38AD95E92C58E70E0032E79F /* Injector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Injector.swift; sourceTree = ""; };
38AD95ED2C58F59C0032E79F /* StatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusView.swift; sourceTree = ""; };
38BC1F522C4B587900C3B60E /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = ""; };
@@ -73,6 +123,13 @@
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
+ 380D77862C620B54005F3150 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
38877A162C4A6F83009F5910 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -85,11 +142,28 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 380D778A2C620B54005F3150 /* Helper */ = {
+ isa = PBXGroup;
+ children = (
+ 380D77A82C621181005F3150 /* Info.plist */,
+ 380D77AB2C6212E0005F3150 /* Launchd.plist */,
+ 380D778B2C620B54005F3150 /* main.swift */,
+ 38A5747E2C62276000209B96 /* Service */,
+ 38A5747D2C62273200209B96 /* Protocol */,
+ 38A5747C2C62272600209B96 /* Extension */,
+ 380D77992C620CC5005F3150 /* Helper.swift */,
+ 380D77962C620C63005F3150 /* HelperConstants.swift */,
+ );
+ path = Helper;
+ sourceTree = "";
+ };
38877A102C4A6F83009F5910 = {
isa = PBXGroup;
children = (
+ 38A5747F2C62284300209B96 /* uninstall-helper.sh */,
381027E92C5F816500348460 /* Localizable.strings */,
38877A1B2C4A6F83009F5910 /* InjectGUI */,
+ 380D778A2C620B54005F3150 /* Helper */,
38877A1A2C4A6F83009F5910 /* Products */,
);
sourceTree = "";
@@ -98,6 +172,7 @@
isa = PBXGroup;
children = (
38877A192C4A6F83009F5910 /* InjectGUI.app */,
+ 380D77892C620B54005F3150 /* dev.wibus-wee.InjectGUI.helper */,
);
name = Products;
sourceTree = "";
@@ -130,12 +205,14 @@
38877A2B2C4A6FCB009F5910 /* Backend */ = {
isa = PBXGroup;
children = (
+ 380D77A22C620F6B005F3150 /* HelperRemoteProvider.swift */,
38877A2D2C4A6FFA009F5910 /* InjectConfiguration.swift */,
38877A2F2C4A70DB009F5910 /* SoftwareManager.swift */,
38877A362C4A7294009F5910 /* Configuration.swift */,
38AD95E92C58E70E0032E79F /* Injector.swift */,
38E944F12C5A761B00B252A3 /* Executor.swift */,
38E944F62C5A85E200B252A3 /* Cache.swift */,
+ 380D77AD2C621479005F3150 /* Execution.swift */,
);
path = Backend;
sourceTree = "";
@@ -164,6 +241,7 @@
381027DF2C5F784F00348460 /* Extension+Scene.swift */,
381027E12C5F7C4800348460 /* Extension+Font.swift */,
381027E32C5F7E1100348460 /* Extension+URL.swift */,
+ 380D77A52C620FF9005F3150 /* InjectError.swift */,
);
path = Extension;
sourceTree = "";
@@ -185,9 +263,52 @@
path = Util;
sourceTree = "";
};
+ 38A5747C2C62272600209B96 /* Extension */ = {
+ isa = PBXGroup;
+ children = (
+ 380D779F2C620D9C005F3150 /* Extension+OSStatus.swift */,
+ );
+ path = Extension;
+ sourceTree = "";
+ };
+ 38A5747D2C62273200209B96 /* Protocol */ = {
+ isa = PBXGroup;
+ children = (
+ 380D77902C620BF7005F3150 /* HelperProtocol.swift */,
+ 380D77932C620C4D005F3150 /* RemoteApplicationProtocol.swift */,
+ );
+ path = Protocol;
+ sourceTree = "";
+ };
+ 38A5747E2C62276000209B96 /* Service */ = {
+ isa = PBXGroup;
+ children = (
+ 380D779C2C620D6C005F3150 /* ConnectionIdentityService.swift */,
+ 38A574792C621E7600209B96 /* ExecutionService.swift */,
+ );
+ path = Service;
+ sourceTree = "";
+ };
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
+ 380D77882C620B54005F3150 /* dev.wibus-wee.InjectGUI.helper */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 380D778D2C620B54005F3150 /* Build configuration list for PBXNativeTarget "dev.wibus-wee.InjectGUI.helper" */;
+ buildPhases = (
+ 380D77852C620B54005F3150 /* Sources */,
+ 380D77862C620B54005F3150 /* Frameworks */,
+ 380D77872C620B54005F3150 /* CopyFiles */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = "dev.wibus-wee.InjectGUI.helper";
+ productName = Helper;
+ productReference = 380D77892C620B54005F3150 /* dev.wibus-wee.InjectGUI.helper */;
+ productType = "com.apple.product-type.tool";
+ };
38877A182C4A6F83009F5910 /* InjectGUI */ = {
isa = PBXNativeTarget;
buildConfigurationList = 38877A282C4A6F87009F5910 /* Build configuration list for PBXNativeTarget "InjectGUI" */;
@@ -219,6 +340,9 @@
LastSwiftUpdateCheck = 1410;
LastUpgradeCheck = 1410;
TargetAttributes = {
+ 380D77882C620B54005F3150 = {
+ CreatedOnToolsVersion = 14.1;
+ };
38877A182C4A6F83009F5910 = {
CreatedOnToolsVersion = 14.1;
};
@@ -243,6 +367,7 @@
projectRoot = "";
targets = (
38877A182C4A6F83009F5910 /* InjectGUI */,
+ 380D77882C620B54005F3150 /* dev.wibus-wee.InjectGUI.helper */,
);
};
/* End PBXProject section */
@@ -252,9 +377,11 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 380D77AC2C6212E0005F3150 /* Launchd.plist in Resources */,
38877A252C4A6F87009F5910 /* Preview Assets.xcassets in Resources */,
380D77842C620464005F3150 /* config.json in Resources */,
381027E72C5F816500348460 /* Localizable.strings in Resources */,
+ 380D77A92C621181005F3150 /* Info.plist in Resources */,
38877A212C4A6F87009F5910 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -262,27 +389,53 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
+ 380D77852C620B54005F3150 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 380D778C2C620B54005F3150 /* main.swift in Sources */,
+ 380D77A12C620D9C005F3150 /* Extension+OSStatus.swift in Sources */,
+ 380D779B2C620CC5005F3150 /* Helper.swift in Sources */,
+ 380D77A72C620FF9005F3150 /* InjectError.swift in Sources */,
+ 380D77982C620C63005F3150 /* HelperConstants.swift in Sources */,
+ 38A5747B2C621E7600209B96 /* ExecutionService.swift in Sources */,
+ 380D779E2C620D6C005F3150 /* ConnectionIdentityService.swift in Sources */,
+ 380D77922C620BF7005F3150 /* HelperProtocol.swift in Sources */,
+ 380D77952C620C4D005F3150 /* RemoteApplicationProtocol.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
38877A152C4A6F83009F5910 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
38877A1F2C4A6F83009F5910 /* ContentView.swift in Sources */,
+ 380D779D2C620D6C005F3150 /* ConnectionIdentityService.swift in Sources */,
+ 380D77A32C620F6B005F3150 /* HelperRemoteProvider.swift in Sources */,
38BC1F5A2C4B98A300C3B60E /* AppDetailView.swift in Sources */,
38877A1D2C4A6F83009F5910 /* InjectGUIApp.swift in Sources */,
38877A3D2C4A9EB7009F5910 /* SetupApplicationSupportDirectory.swift in Sources */,
38E944F72C5A85E200B252A3 /* Cache.swift in Sources */,
387CEA7B2C5DEF0600E3A5AC /* Extension+String.swift in Sources */,
38877A372C4A7294009F5910 /* Configuration.swift in Sources */,
+ 380D77942C620C4D005F3150 /* RemoteApplicationProtocol.swift in Sources */,
38AD95EA2C58E70E0032E79F /* Injector.swift in Sources */,
+ 380D77A02C620D9C005F3150 /* Extension+OSStatus.swift in Sources */,
+ 380D77A62C620FF9005F3150 /* InjectError.swift in Sources */,
38877A302C4A70DB009F5910 /* SoftwareManager.swift in Sources */,
38877A332C4A7222009F5910 /* PublishedStorage.swift in Sources */,
381027E02C5F784F00348460 /* Extension+Scene.swift in Sources */,
38E944F22C5A761B00B252A3 /* Executor.swift in Sources */,
+ 380D77972C620C63005F3150 /* HelperConstants.swift in Sources */,
+ 380D779A2C620CC5005F3150 /* Helper.swift in Sources */,
38BC1F552C4B622500C3B60E /* WelcomeView.swift in Sources */,
38877A2E2C4A6FFA009F5910 /* InjectConfiguration.swift in Sources */,
38BC1F532C4B587A00C3B60E /* SidebarView.swift in Sources */,
+ 380D77AE2C621479005F3150 /* Execution.swift in Sources */,
38877A352C4A7254009F5910 /* ViewKit.swift in Sources */,
38877A3A2C4A730F009F5910 /* Constants.swift in Sources */,
+ 380D77912C620BF7005F3150 /* HelperProtocol.swift in Sources */,
+ 38A5747A2C621E7600209B96 /* ExecutionService.swift in Sources */,
381027E42C5F7E1100348460 /* Extension+URL.swift in Sources */,
38BC1F5C2C4BB02200C3B60E /* SettingsView.swift in Sources */,
381027E22C5F7C4800348460 /* Extension+Font.swift in Sources */,
@@ -308,6 +461,52 @@
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
+ 380D778E2C620B54005F3150 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = 947UYU6LTQ;
+ MACOSX_DEPLOYMENT_TARGET = 13.0;
+ OTHER_LDFLAGS = (
+ "-sectcreate",
+ __TEXT,
+ __info_plist,
+ "\"$(SRCROOT)/Helper/Info.plist\"",
+ "-sectcreate",
+ __TEXT,
+ __launchd_plist,
+ "\"$(SRCROOT)/Helper/Launchd.plist\"",
+ );
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = macosx;
+ SWIFT_VERSION = 5.0;
+ };
+ name = Debug;
+ };
+ 380D778F2C620B54005F3150 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = 947UYU6LTQ;
+ MACOSX_DEPLOYMENT_TARGET = 13.0;
+ OTHER_LDFLAGS = (
+ "-sectcreate",
+ __TEXT,
+ __info_plist,
+ "\"$(SRCROOT)/Helper/Info.plist\"",
+ "-sectcreate",
+ __TEXT,
+ __launchd_plist,
+ "\"$(SRCROOT)/Helper/Launchd.plist\"",
+ );
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SDKROOT = macosx;
+ SWIFT_VERSION = 5.0;
+ };
+ name = Release;
+ };
38877A262C4A6F87009F5910 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -425,9 +624,13 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = InjectGUI/InjectGUI.entitlements;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_ASSET_PATHS = "\"InjectGUI/Preview Content\"";
+ DEVELOPMENT_TEAM = 947UYU6LTQ;
+ ENABLE_HARDENED_RUNTIME = NO;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = InjectGUI/Info.plist;
@@ -449,6 +652,7 @@
MARKETING_VERSION = 1.1;
PRODUCT_BUNDLE_IDENTIFIER = "dev.wibus-wee.InjectGUI";
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = auto;
SUPPORTED_PLATFORMS = macosx;
SUPPORTS_MACCATALYST = NO;
@@ -464,9 +668,13 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = InjectGUI/InjectGUI.entitlements;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 4;
DEVELOPMENT_ASSET_PATHS = "\"InjectGUI/Preview Content\"";
+ DEVELOPMENT_TEAM = 947UYU6LTQ;
+ ENABLE_HARDENED_RUNTIME = NO;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = InjectGUI/Info.plist;
@@ -488,6 +696,7 @@
MARKETING_VERSION = 1.1;
PRODUCT_BUNDLE_IDENTIFIER = "dev.wibus-wee.InjectGUI";
PRODUCT_NAME = "$(TARGET_NAME)";
+ PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = auto;
SUPPORTED_PLATFORMS = macosx;
SUPPORTS_MACCATALYST = NO;
@@ -500,6 +709,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
+ 380D778D2C620B54005F3150 /* Build configuration list for PBXNativeTarget "dev.wibus-wee.InjectGUI.helper" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 380D778E2C620B54005F3150 /* Debug */,
+ 380D778F2C620B54005F3150 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
38877A142C4A6F83009F5910 /* Build configuration list for PBXProject "InjectGUI" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/InjectGUI.xcodeproj/xcshareddata/xcschemes/Helper.xcscheme b/InjectGUI.xcodeproj/xcshareddata/xcschemes/Helper.xcscheme
new file mode 100644
index 0000000..fe08171
--- /dev/null
+++ b/InjectGUI.xcodeproj/xcshareddata/xcschemes/Helper.xcscheme
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/InjectGUI.xcodeproj/xcshareddata/xcschemes/InjectGUI.xcscheme b/InjectGUI.xcodeproj/xcshareddata/xcschemes/InjectGUI.xcscheme
new file mode 100644
index 0000000..2b4e6fd
--- /dev/null
+++ b/InjectGUI.xcodeproj/xcshareddata/xcschemes/InjectGUI.xcscheme
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/InjectGUI/Backend/Execution.swift b/InjectGUI/Backend/Execution.swift
new file mode 100644
index 0000000..faea7c4
--- /dev/null
+++ b/InjectGUI/Backend/Execution.swift
@@ -0,0 +1,25 @@
+//
+// ExecutionService.swift
+// InjectGUI
+//
+// Created by wibus on 2024/8/6.
+//
+
+import Foundation
+
+// MARK: - Execution
+
+/// Execute a script.
+enum Execution {
+
+ // MARK: Constants
+
+ static let programURL = URL(fileURLWithPath: "/usr/bin/env")
+
+ // MARK: Execute
+
+ /// Execute the script at the provided URL.
+ static func executeScript(at path: String) async throws -> String {
+ try await HelperRemoteProvider.remote().executeScript(at: path)
+ }
+}
diff --git a/InjectGUI/Backend/HelperRemoteProvider.swift b/InjectGUI/Backend/HelperRemoteProvider.swift
new file mode 100644
index 0000000..d616dfd
--- /dev/null
+++ b/InjectGUI/Backend/HelperRemoteProvider.swift
@@ -0,0 +1,170 @@
+//
+// HelperRemoteProvider.swift
+// InjectGUI
+//
+// Created by wibus on 2024/8/6.
+//
+
+
+import Foundation
+import ServiceManagement
+
+// MARK: - HelperRemoteProvider
+
+/// Provide a `HelperProtocol` object to request the helper.
+enum HelperRemoteProvider {
+
+ // MARK: Computed
+
+ private static var isHelperInstalled: Bool { FileManager.default.fileExists(atPath: HelperConstants.helperPath) }
+}
+
+// MARK: - Remote
+
+extension HelperRemoteProvider {
+
+ static func remote() async throws -> some HelperProtocol {
+ let connection = try connection()
+
+ return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in let continuationResume = ContinuationResume()
+ let helper = connection.remoteObjectProxyWithErrorHandler { error in
+ // an error arrived
+ guard continuationResume.shouldResume() else { return }
+ // 1st error to arrive, it will be the one thrown
+ continuation.resume(throwing: error)
+ }
+
+ if let unwrappedHelper = helper as? HelperProtocol {
+ guard continuationResume.shouldResume() else {
+ // an error occurred even though the helper was retrieved
+ return
+ }
+ continuation.resume(returning: unwrappedHelper)
+ } else {
+ if continuationResume.shouldResume() {
+ // 1st error to arrive, it will be the one thrown
+ let error = InjectError.helperConnection("Unable to get a valid 'HelperProtocol' object for an unknown reason")
+ continuation.resume(throwing: error)
+ }
+ }
+ }
+ }
+}
+
+// MARK: - Install helper
+
+extension HelperRemoteProvider {
+
+ /// Install the Helper in the privileged helper tools folder and load the daemon
+ private static func installHelper() throws {
+
+ // try to get a valid empty authorization
+ var authRef: AuthorizationRef?
+ try AuthorizationCreate(nil, nil, [.preAuthorize], &authRef).checkError("AuthorizationCreate")
+ defer {
+ if let authRef {
+ AuthorizationFree(authRef, [])
+ }
+ }
+
+ // create an AuthorizationItem to specify we want to bless a privileged Helper
+ let authStatus = kSMRightBlessPrivilegedHelper.withCString { authorizationString in
+ var authItem = AuthorizationItem(name: authorizationString, valueLength: 0, value: nil, flags: 0)
+
+ return withUnsafeMutablePointer(to: &authItem) { pointer in
+ var authRights = AuthorizationRights(count: 1, items: pointer)
+ let flags: AuthorizationFlags = [.interactionAllowed, .extendRights, .preAuthorize]
+ return AuthorizationCreate(&authRights, nil, flags, &authRef)
+ }
+ }
+
+ guard authStatus == errAuthorizationSuccess else {
+ throw InjectError.helperInstallation("Unable to get a valid loading authorization reference to load Helper daemon")
+ }
+
+ var blessErrorPointer: Unmanaged?
+ let wasBlessed = SMJobBless(kSMDomainSystemLaunchd, HelperConstants.domain as CFString, authRef, &blessErrorPointer)
+
+ guard !wasBlessed else { return }
+ // throw error since authorization was not blessed
+// let blessError: Error == if let blessErrorPointer {
+// blessErrorPointer.takeRetainedValue() as Error
+// } else {
+// InjectError.unknown
+// }
+ let blessError = blessErrorPointer?.takeRetainedValue() as Error? ?? InjectError.unknown
+
+ throw InjectError.helperInstallation("Error while installing the Helper: \(blessError.localizedDescription)")
+ }
+}
+
+// MARK: - Connection
+
+extension HelperRemoteProvider {
+
+ static private func connection() throws -> NSXPCConnection {
+ if !isHelperInstalled {
+ try installHelper()
+ }
+ return createConnection()
+ }
+
+ private static func createConnection() -> NSXPCConnection {
+ let connection = NSXPCConnection(machServiceName: HelperConstants.domain, options: .privileged)
+ connection.remoteObjectInterface = NSXPCInterface(with: HelperProtocol.self)
+ connection.exportedInterface = NSXPCInterface(with: RemoteApplicationProtocol.self)
+ connection.exportedObject = self
+
+ connection.invalidationHandler = {
+ if isHelperInstalled {
+ print("Unable to connect to Helper although it is installed")
+ } else {
+ print("Helper is not installed")
+ }
+ }
+
+ connection.resume()
+
+ return connection
+ }
+}
+
+// MARK: - ContinuationResume
+
+extension HelperRemoteProvider {
+
+ /// Helper class to safely access a boolean when using a continuation to get the remote.
+ private final class ContinuationResume: @unchecked Sendable {
+
+ // MARK: Properties
+
+ private let unfairLockPointer: UnsafeMutablePointer
+ private var alreadyResumed = false
+
+ // MARK: Computed
+
+ /// `true` if the continuation should resume.
+ func shouldResume() -> Bool {
+ os_unfair_lock_lock(unfairLockPointer)
+ defer { os_unfair_lock_unlock(unfairLockPointer) }
+
+ if alreadyResumed {
+ return false
+ } else {
+ alreadyResumed = true
+ return true
+ }
+ }
+
+ // MARK: Init
+
+ init() {
+ unfairLockPointer = UnsafeMutablePointer.allocate(capacity: 1)
+ unfairLockPointer.initialize(to: os_unfair_lock())
+ }
+
+ deinit {
+ unfairLockPointer.deallocate()
+ }
+ }
+}
diff --git a/InjectGUI/Extension/InjectError.swift b/InjectGUI/Extension/InjectError.swift
new file mode 100644
index 0000000..6caca2d
--- /dev/null
+++ b/InjectGUI/Extension/InjectError.swift
@@ -0,0 +1,29 @@
+//
+// InjectError.swift
+// InjectGUI
+//
+// Created by wibus on 2024/8/6.
+//
+
+
+import Foundation
+
+enum InjectError {
+
+ case helperInstallation(String)
+ case helperConnection(String)
+ case unknown
+}
+
+// MARK: - LocalizedError
+
+extension InjectError: LocalizedError {
+
+ var errorDescription: String? {
+ switch self {
+ case .helperInstallation(let description): return "Helper installation error. \(description)"
+ case .helperConnection(let description): return "Helper connection error. \(description)"
+ case .unknown: return "Unknown error"
+ }
+ }
+}
diff --git a/InjectGUI/Info.plist b/InjectGUI/Info.plist
index be39681..a4df340 100644
--- a/InjectGUI/Info.plist
+++ b/InjectGUI/Info.plist
@@ -2,6 +2,29 @@
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ $(PRODUCT_BUNDLE_PACKAGE_TYPE)
+ CFBundleShortVersionString
+ $(MARKETING_VERSION)
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ NSHumanReadableCopyright
+
+ SMPrivilegedExecutables
+
+ dev.wibus-wee.InjectGUI.helper
+ identifier "dev.wibus-wee.InjectGUI.helper" and anchor apple generic and certificate leaf[subject.CN] = "Apple Development: 1596355173@qq.com (YX89Z87JNG)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */
+
SUFeedURL
https://github.com/wibus-wee/InjectGUI/releases/download/latest/appcast.xml
SUPublicEDKey
diff --git a/InjectGUI/InjectGUI.entitlements b/InjectGUI/InjectGUI.entitlements
index 921571e..e00d841 100644
--- a/InjectGUI/InjectGUI.entitlements
+++ b/InjectGUI/InjectGUI.entitlements
@@ -8,7 +8,5 @@
com.apple.security.network.client
- com.apple.security.automation.apple-events
-
diff --git a/InjectGUI/View/ContentView.swift b/InjectGUI/View/ContentView.swift
index 1e27964..4f1e441 100644
--- a/InjectGUI/View/ContentView.swift
+++ b/InjectGUI/View/ContentView.swift
@@ -31,6 +31,22 @@ struct ContentView: View {
Label("Toggle Sidebar", systemImage: "sidebar.leading")
}
}
+
+ ToolbarItem {
+ Button {
+ Task {
+ do {
+ print("Executing script")
+ let result = try await Execution.executeScript(at: "~/Desktop/test.sh")
+ print(result)
+ } catch {
+ print(error.localizedDescription)
+ }
+ }
+ } label: {
+ Label("Test", systemImage: "hammer")
+ }
+ }
ToolbarItem {
diff --git a/SMJobBlessUtil.py b/SMJobBlessUtil.py
new file mode 100755
index 0000000..349750f
--- /dev/null
+++ b/SMJobBlessUtil.py
@@ -0,0 +1,460 @@
+#! /usr/bin/python3
+#
+# File: SMJobBlessUtil.py
+#
+# Contains: Tool for checking and correcting apps that use SMJobBless.
+#
+# Written by: DTS
+#
+# Copyright: Copyright (c) 2012 Apple Inc. All Rights Reserved.
+#
+# Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
+# ("Apple") in consideration of your agreement to the following
+# terms, and your use, installation, modification or
+# redistribution of this Apple software constitutes acceptance of
+# these terms. If you do not agree with these terms, please do
+# not use, install, modify or redistribute this Apple software.
+#
+# In consideration of your agreement to abide by the following
+# terms, and subject to these terms, Apple grants you a personal,
+# non-exclusive license, under Apple's copyrights in this
+# original Apple software (the "Apple Software"), to use,
+# reproduce, modify and redistribute the Apple Software, with or
+# without modifications, in source and/or binary forms; provided
+# that if you redistribute the Apple Software in its entirety and
+# without modifications, you must retain this notice and the
+# following text and disclaimers in all such redistributions of
+# the Apple Software. Neither the name, trademarks, service marks
+# or logos of Apple Inc. may be used to endorse or promote
+# products derived from the Apple Software without specific prior
+# written permission from Apple. Except as expressly stated in
+# this notice, no other rights or licenses, express or implied,
+# are granted by Apple herein, including but not limited to any
+# patent rights that may be infringed by your derivative works or
+# by other works in which the Apple Software may be incorporated.
+#
+# The Apple Software is provided by Apple on an "AS IS" basis.
+# APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
+# WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING
+# THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
+# COMBINATION WITH YOUR PRODUCTS.
+#
+# IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT,
+# INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY
+# OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
+# OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY
+# OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
+# OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+
+import sys
+import os
+import getopt
+import subprocess
+import plistlib
+import operator
+import platform
+
+class UsageException (Exception):
+ """
+ Raised when the progam detects a usage issue; the top-level code catches this
+ and prints a usage message.
+ """
+ pass
+
+class CheckException (Exception):
+ """
+ Raised when the "check" subcommand detects a problem; the top-level code catches
+ this and prints a nice error message.
+ """
+ def __init__(self, message, path=None):
+ self.message = message
+ self.path = path
+
+def checkCodeSignature(programPath, programType):
+ """Checks the code signature of the referenced program."""
+
+ # Use the codesign tool to check the signature. The second "-v" is required to enable
+ # verbose mode, which causes codesign to do more checking. By default it does the minimum
+ # amount of checking ("Is the program properly signed?"). If you enabled verbose mode it
+ # does other sanity checks, which we definitely want. The specific thing I'd like to
+ # detect is "Does the code satisfy its own designated requirement?" and I need to enable
+ # verbose mode to get that.
+
+ args = [
+ # "false",
+ "codesign",
+ "-v",
+ "-v",
+ programPath
+ ]
+ try:
+ subprocess.check_call(args, stderr=open("/dev/null"))
+ except subprocess.CalledProcessError as e:
+ raise CheckException("%s code signature invalid" % programType, programPath)
+
+def readDesignatedRequirement(programPath, programType):
+ """Returns the designated requirement of the program as a string."""
+ args = [
+ # "false",
+ "codesign",
+ "-d",
+ "-r",
+ "-",
+ programPath
+ ]
+ try:
+ req = subprocess.check_output(args, stderr=open("/dev/null"), encoding="utf-8")
+ except subprocess.CalledProcessError as e:
+ raise CheckException("%s designated requirement unreadable" % programType, programPath)
+
+ reqLines = req.splitlines()
+ if len(reqLines) != 1 or not req.startswith("designated => "):
+ raise CheckException("%s designated requirement malformed" % programType, programPath)
+ return reqLines[0][len("designated => "):]
+
+def readInfoPlistFromPath(infoPath):
+ """Reads an "Info.plist" file from the specified path."""
+ try:
+ with open(infoPath, 'rb') as fp:
+ info = plistlib.load(fp)
+ except:
+ raise CheckException("'Info.plist' not readable", infoPath)
+ if not isinstance(info, dict):
+ raise CheckException("'Info.plist' root must be a dictionary", infoPath)
+ return info
+
+def readPlistFromToolSection(toolPath, segmentName, sectionName):
+ """Reads a dictionary property list from the specified section within the specified executable."""
+
+ # Run otool -s to get a hex dump of the section.
+
+ args = [
+ # "false",
+ "otool",
+ "-V",
+ "-arch",
+ platform.machine(),
+ "-s",
+ segmentName,
+ sectionName,
+ toolPath
+ ]
+ try:
+ plistDump = subprocess.check_output(args, encoding="utf-8")
+ except subprocess.CalledProcessError as e:
+ raise CheckException("tool %s / %s section unreadable" % (segmentName, sectionName), toolPath)
+
+ # Convert that dump to an property list.
+
+ plistLines = plistDump.strip().splitlines(keepends=True)
+
+ if len(plistLines) < 3:
+ raise CheckException("tool %s / %s section dump malformed (1)" % (segmentName, sectionName), toolPath)
+
+ header = plistLines[1].strip()
+
+ if not header.endswith("(%s,%s) section" % (segmentName, sectionName)):
+ raise CheckException("tool %s / %s section dump malformed (2)" % (segmentName, sectionName), toolPath)
+
+ del plistLines[0:2]
+
+ try:
+
+ if header.startswith('Contents of'):
+ data = []
+ for line in plistLines:
+ # line looks like this:
+ #
+ # '100000000 3c 3f 78 6d 6c 20 76 65 72 73 69 6f 6e 3d 22 31 |= 2
+ del columns[0]
+ for hexStr in columns:
+ data.append(int(hexStr, 16))
+ data = bytes(data)
+ else:
+ data = bytes("".join(plistLines), encoding="utf-8")
+
+ plist = plistlib.loads(data)
+ except:
+ raise CheckException("tool %s / %s section dump malformed (3)" % (segmentName, sectionName), toolPath)
+
+ # Check the root of the property list.
+
+ if not isinstance(plist, dict):
+ raise CheckException("tool %s / %s property list root must be a dictionary" % (segmentName, sectionName), toolPath)
+
+ return plist
+
+def checkStep1(appPath):
+ """Checks that the app and the tool are both correctly code signed."""
+
+ if not os.path.isdir(appPath):
+ raise CheckException("app not found", appPath)
+
+ # Check the app's code signature.
+
+ checkCodeSignature(appPath, "app")
+
+ # Check the tool directory.
+
+ toolDirPath = os.path.join(appPath, "Contents", "Library", "LaunchServices")
+ if not os.path.isdir(toolDirPath):
+ raise CheckException("tool directory not found", toolDirPath)
+
+ # Check each tool's code signature.
+
+ toolPathList = []
+ for toolName in os.listdir(toolDirPath):
+ if toolName != ".DS_Store":
+ toolPath = os.path.join(toolDirPath, toolName)
+ if not os.path.isfile(toolPath):
+ raise CheckException("tool directory contains a directory", toolPath)
+ checkCodeSignature(toolPath, "tool")
+ toolPathList.append(toolPath)
+
+ # Check that we have at least one tool.
+
+ if len(toolPathList) == 0:
+ raise CheckException("no tools found", toolDirPath)
+
+ return toolPathList
+
+def checkStep2(appPath, toolPathList):
+ """Checks the SMPrivilegedExecutables entry in the app's "Info.plist"."""
+
+ # Create a map from the tool name (not path) to its designated requirement.
+
+ toolNameToReqMap = dict()
+ for toolPath in toolPathList:
+ req = readDesignatedRequirement(toolPath, "tool")
+ toolNameToReqMap[os.path.basename(toolPath)] = req
+
+ # Read the Info.plist for the app and extract the SMPrivilegedExecutables value.
+
+ infoPath = os.path.join(appPath, "Contents", "Info.plist")
+ info = readInfoPlistFromPath(infoPath)
+ if "SMPrivilegedExecutables" not in info:
+ raise CheckException("'SMPrivilegedExecutables' not found", infoPath)
+ infoToolDict = info["SMPrivilegedExecutables"]
+ if not isinstance(infoToolDict, dict):
+ raise CheckException("'SMPrivilegedExecutables' must be a dictionary", infoPath)
+
+ # Check that the list of tools matches the list of SMPrivilegedExecutables entries.
+
+ if sorted(infoToolDict.keys()) != sorted(toolNameToReqMap.keys()):
+ raise CheckException("'SMPrivilegedExecutables' and tools in 'Contents/Library/LaunchServices' don't match")
+
+ # Check that all the requirements match.
+
+ # This is an interesting policy choice. Technically the tool just needs to match
+ # the requirement listed in SMPrivilegedExecutables, and we can check that by
+ # putting the requirement into tmp.req and then running
+ #
+ # $ codesign -v -R tmp.req /path/to/tool
+ #
+ # However, for a Developer ID signed tool we really want to have the SMPrivilegedExecutables
+ # entry contain the tool's designated requirement because Xcode has built a
+ # more complex DR that does lots of useful and important checks. So, as a matter
+ # of policy we require that the value in SMPrivilegedExecutables match the tool's DR.
+
+ for toolName in infoToolDict:
+ if infoToolDict[toolName] != toolNameToReqMap[toolName]:
+ raise CheckException("tool designated requirement (%s) doesn't match entry in 'SMPrivilegedExecutables' (%s)" % (toolNameToReqMap[toolName], infoToolDict[toolName]))
+
+def checkStep3(appPath, toolPathList):
+ """Checks the "Info.plist" embedded in each helper tool."""
+
+ # First get the app's designated requirement.
+
+ appReq = readDesignatedRequirement(appPath, "app")
+
+ # Then check that the tool's SMAuthorizedClients value matches it.
+
+ for toolPath in toolPathList:
+ info = readPlistFromToolSection(toolPath, "__TEXT", "__info_plist")
+
+ if "CFBundleInfoDictionaryVersion" not in info or info["CFBundleInfoDictionaryVersion"] != "6.0":
+ raise CheckException("'CFBundleInfoDictionaryVersion' in tool __TEXT / __info_plist section must be '6.0'", toolPath)
+
+ if "CFBundleIdentifier" not in info or info["CFBundleIdentifier"] != os.path.basename(toolPath):
+ raise CheckException("'CFBundleIdentifier' in tool __TEXT / __info_plist section must match tool name", toolPath)
+
+ if "SMAuthorizedClients" not in info:
+ raise CheckException("'SMAuthorizedClients' in tool __TEXT / __info_plist section not found", toolPath)
+ infoClientList = info["SMAuthorizedClients"]
+ if not isinstance(infoClientList, list):
+ raise CheckException("'SMAuthorizedClients' in tool __TEXT / __info_plist section must be an array", toolPath)
+ if len(infoClientList) != 1:
+ raise CheckException("'SMAuthorizedClients' in tool __TEXT / __info_plist section must have one entry", toolPath)
+
+ # Again, as a matter of policy we require that the SMAuthorizedClients entry must
+ # match exactly the designated requirement of the app.
+
+ if infoClientList[0] != appReq:
+ raise CheckException("app designated requirement (%s) doesn't match entry in 'SMAuthorizedClients' (%s)" % (appReq, infoClientList[0]), toolPath)
+
+def checkStep4(appPath, toolPathList):
+ """Checks the "launchd.plist" embedded in each helper tool."""
+
+ for toolPath in toolPathList:
+ launchd = readPlistFromToolSection(toolPath, "__TEXT", "__launchd_plist")
+
+ if "Label" not in launchd or launchd["Label"] != os.path.basename(toolPath):
+ raise CheckException("'Label' in tool __TEXT / __launchd_plist section must match tool name", toolPath)
+
+ # We don't need to check that the label matches the bundle identifier because
+ # we know it matches the tool name and step 4 checks that the tool name matches
+ # the bundle identifier.
+
+def checkStep5(appPath):
+ """There's nothing to do here; we effectively checked for this is steps 1 and 2."""
+ pass
+
+def check(appPath):
+ """Checks the SMJobBless setup of the specified app."""
+
+ # Each of the following steps matches a bullet point in the SMJobBless header doc.
+
+ toolPathList = checkStep1(appPath)
+
+ checkStep2(appPath, toolPathList)
+
+ checkStep3(appPath, toolPathList)
+
+ checkStep4(appPath, toolPathList)
+
+ checkStep5(appPath)
+
+def setreq(appPath, appInfoPlistPath, toolInfoPlistPaths):
+ """
+ Reads information from the built app and uses it to set the SMJobBless setup
+ in the specified app and tool Info.plist source files.
+ """
+
+ if not os.path.isdir(appPath):
+ raise CheckException("app not found", appPath)
+
+ if not os.path.isfile(appInfoPlistPath):
+ raise CheckException("app 'Info.plist' not found", appInfoPlistPath)
+ for toolInfoPlistPath in toolInfoPlistPaths:
+ if not os.path.isfile(toolInfoPlistPath):
+ raise CheckException("app 'Info.plist' not found", toolInfoPlistPath)
+
+ # Get the designated requirement for the app and each of the tools.
+
+ appReq = readDesignatedRequirement(appPath, "app")
+
+ toolDirPath = os.path.join(appPath, "Contents", "Library", "LaunchServices")
+ if not os.path.isdir(toolDirPath):
+ raise CheckException("tool directory not found", toolDirPath)
+
+ toolNameToReqMap = {}
+ for toolName in os.listdir(toolDirPath):
+ req = readDesignatedRequirement(os.path.join(toolDirPath, toolName), "tool")
+ toolNameToReqMap[toolName] = req
+
+ if len(toolNameToReqMap) > len(toolInfoPlistPaths):
+ raise CheckException("tool directory has more tools (%d) than you've supplied tool 'Info.plist' paths (%d)" % (len(toolNameToReqMap), len(toolInfoPlistPaths)), toolDirPath)
+ if len(toolNameToReqMap) < len(toolInfoPlistPaths):
+ raise CheckException("tool directory has fewer tools (%d) than you've supplied tool 'Info.plist' paths (%d)" % (len(toolNameToReqMap), len(toolInfoPlistPaths)), toolDirPath)
+
+ # Build the new value for SMPrivilegedExecutables.
+
+ appToolDict = {}
+ toolInfoPlistPathToToolInfoMap = {}
+ for toolInfoPlistPath in toolInfoPlistPaths:
+ toolInfo = readInfoPlistFromPath(toolInfoPlistPath)
+ toolInfoPlistPathToToolInfoMap[toolInfoPlistPath] = toolInfo
+ if "CFBundleIdentifier" not in toolInfo:
+ raise CheckException("'CFBundleIdentifier' not found", toolInfoPlistPath)
+ bundleID = toolInfo["CFBundleIdentifier"]
+ if not isinstance(bundleID, str):
+ raise CheckException("'CFBundleIdentifier' must be a string", toolInfoPlistPath)
+ appToolDict[bundleID] = toolNameToReqMap[bundleID]
+
+ # Set the SMPrivilegedExecutables value in the app "Info.plist".
+
+ appInfo = readInfoPlistFromPath(appInfoPlistPath)
+ needsUpdate = "SMPrivilegedExecutables" not in appInfo
+ if not needsUpdate:
+ oldAppToolDict = appInfo["SMPrivilegedExecutables"]
+ if not isinstance(oldAppToolDict, dict):
+ raise CheckException("'SMPrivilegedExecutables' must be a dictionary", appInfoPlistPath)
+ appToolDictSorted = sorted(appToolDict.items(), key=operator.itemgetter(0))
+ oldAppToolDictSorted = sorted(oldAppToolDict.items(), key=operator.itemgetter(0))
+ needsUpdate = (appToolDictSorted != oldAppToolDictSorted)
+
+ if needsUpdate:
+ appInfo["SMPrivilegedExecutables"] = appToolDict
+ with open(appInfoPlistPath, 'wb') as fp:
+ plistlib.dump(appInfo, fp)
+ print ("%s: updated" % appInfoPlistPath, file = sys.stdout)
+
+ # Set the SMAuthorizedClients value in each tool's "Info.plist".
+
+ toolAppListSorted = [ appReq ] # only one element, so obviously sorted (-:
+ for toolInfoPlistPath in toolInfoPlistPaths:
+ toolInfo = toolInfoPlistPathToToolInfoMap[toolInfoPlistPath]
+
+ needsUpdate = "SMAuthorizedClients" not in toolInfo
+ if not needsUpdate:
+ oldToolAppList = toolInfo["SMAuthorizedClients"]
+ if not isinstance(oldToolAppList, list):
+ raise CheckException("'SMAuthorizedClients' must be an array", toolInfoPlistPath)
+ oldToolAppListSorted = sorted(oldToolAppList)
+ needsUpdate = (toolAppListSorted != oldToolAppListSorted)
+
+ if needsUpdate:
+ toolInfo["SMAuthorizedClients"] = toolAppListSorted
+ with open(toolInfoPlistPath, 'wb') as f:
+ plistlib.dump(toolInfo, f)
+ print("%s: updated" % toolInfoPlistPath, file = sys.stdout)
+
+def main():
+ options, appArgs = getopt.getopt(sys.argv[1:], "d")
+
+ debug = False
+ for opt, val in options:
+ if opt == "-d":
+ debug = True
+ else:
+ raise UsageException()
+
+ if len(appArgs) == 0:
+ raise UsageException()
+ command = appArgs[0]
+ if command == "check":
+ if len(appArgs) != 2:
+ raise UsageException()
+ check(appArgs[1])
+ elif command == "setreq":
+ if len(appArgs) < 4:
+ raise UsageException()
+ setreq(appArgs[1], appArgs[2], appArgs[3:])
+ else:
+ raise UsageException()
+
+if __name__ == "__main__":
+ try:
+ main()
+ except CheckException as e:
+ if e.path is None:
+ print("%s: %s" % (os.path.basename(sys.argv[0]), e.message), file = sys.stderr)
+ else:
+ path = e.path
+ if path.endswith("/"):
+ path = path[:-1]
+ print("%s: %s" % (path, e.message), file = sys.stderr)
+ sys.exit(1)
+ except UsageException as e:
+ print("usage: %s check /path/to/app" % os.path.basename(sys.argv[0]), file = sys.stderr)
+ print(" %s setreq /path/to/app /path/to/app/Info.plist /path/to/tool/Info.plist..." % os.path.basename(sys.argv[0]), file = sys.stderr)
+ sys.exit(1)
\ No newline at end of file
diff --git a/uninstall-helper.sh b/uninstall-helper.sh
new file mode 100644
index 0000000..670e8cb
--- /dev/null
+++ b/uninstall-helper.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+# uninstall-helper.sh
+# InjectGUI
+#
+# Created by wibus on 2024/8/6.
+#
+
+sudo /bin/launchctl unload /Library/LaunchDaemons/dev.wibus-wee.InjectGUI.helper.plist
+sudo /usr/bin/killall -u root -9 dev.wibus-wee.InjectGUI.helper
+sudo /bin/rm /Library/LaunchDaemons/dev.wibus-wee.InjectGUI.helper.plist
+sudo /bin/rm /Library/PrivilegedHelperTools/dev.wibus-wee.InjectGUI.helper
From cf6fe74a382879ce3da8c6c2065f90a2b65330a0 Mon Sep 17 00:00:00 2001
From: wibus-wee <1596355173@qq.com>
Date: Wed, 7 Aug 2024 11:10:53 +0800
Subject: [PATCH 2/2] nothing
---
Helper/Helper.swift | 2 ++
.../Service/ConnectionIdentityService.swift | 1 -
Helper/Service/ExecutionService.swift | 2 +-
InjectGUI.xcodeproj/project.pbxproj | 24 +++++++------------
InjectGUI/Backend/Execution.swift | 2 +-
InjectGUI/View/ContentView.swift | 2 +-
6 files changed, 13 insertions(+), 20 deletions(-)
diff --git a/Helper/Helper.swift b/Helper/Helper.swift
index 0f5a68b..8f1a2db 100644
--- a/Helper/Helper.swift
+++ b/Helper/Helper.swift
@@ -43,6 +43,8 @@ extension Helper {
extension Helper: NSXPCListenerDelegate {
func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
+// FIX: Something went wrong here?
+
do {
try ConnectionIdentityService.checkConnectionIsValid(connection: newConnection)
} catch {
diff --git a/Helper/Service/ConnectionIdentityService.swift b/Helper/Service/ConnectionIdentityService.swift
index 131a5c8..fd5db41 100644
--- a/Helper/Service/ConnectionIdentityService.swift
+++ b/Helper/Service/ConnectionIdentityService.swift
@@ -27,7 +27,6 @@ enum ConnectionIdentityService {
private static func verifySecCode(secCode: SecCode) throws {
var secRequirements: SecRequirement?
-
try SecRequirementCreateWithString(requirementString, [], &secRequirements)
.checkError("SecRequirementCreateWithString")
try SecCodeCheckValidity(secCode, [], secRequirements)
diff --git a/Helper/Service/ExecutionService.swift b/Helper/Service/ExecutionService.swift
index a0d6cc6..ac7ba2d 100644
--- a/Helper/Service/ExecutionService.swift
+++ b/Helper/Service/ExecutionService.swift
@@ -15,7 +15,7 @@ enum ExecutionService {
// MARK: Constants
- static let programURL = URL(fileURLWithPath: "/bin/zsh")
+ static let programURL = URL(fileURLWithPath: "/usr/bin/env")
// MARK: Execute
diff --git a/InjectGUI.xcodeproj/project.pbxproj b/InjectGUI.xcodeproj/project.pbxproj
index c3663ec..125b7eb 100644
--- a/InjectGUI.xcodeproj/project.pbxproj
+++ b/InjectGUI.xcodeproj/project.pbxproj
@@ -15,20 +15,13 @@
380D77922C620BF7005F3150 /* HelperProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77902C620BF7005F3150 /* HelperProtocol.swift */; };
380D77942C620C4D005F3150 /* RemoteApplicationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77932C620C4D005F3150 /* RemoteApplicationProtocol.swift */; };
380D77952C620C4D005F3150 /* RemoteApplicationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77932C620C4D005F3150 /* RemoteApplicationProtocol.swift */; };
- 380D77972C620C63005F3150 /* HelperConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77962C620C63005F3150 /* HelperConstants.swift */; };
380D77982C620C63005F3150 /* HelperConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77962C620C63005F3150 /* HelperConstants.swift */; };
- 380D779A2C620CC5005F3150 /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77992C620CC5005F3150 /* Helper.swift */; };
380D779B2C620CC5005F3150 /* Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77992C620CC5005F3150 /* Helper.swift */; };
- 380D779D2C620D6C005F3150 /* ConnectionIdentityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D779C2C620D6C005F3150 /* ConnectionIdentityService.swift */; };
380D779E2C620D6C005F3150 /* ConnectionIdentityService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D779C2C620D6C005F3150 /* ConnectionIdentityService.swift */; };
- 380D77A02C620D9C005F3150 /* Extension+OSStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D779F2C620D9C005F3150 /* Extension+OSStatus.swift */; };
380D77A12C620D9C005F3150 /* Extension+OSStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D779F2C620D9C005F3150 /* Extension+OSStatus.swift */; };
- 380D77A32C620F6B005F3150 /* HelperRemoteProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77A22C620F6B005F3150 /* HelperRemoteProvider.swift */; };
380D77A62C620FF9005F3150 /* InjectError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77A52C620FF9005F3150 /* InjectError.swift */; };
380D77A72C620FF9005F3150 /* InjectError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77A52C620FF9005F3150 /* InjectError.swift */; };
- 380D77A92C621181005F3150 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 380D77A82C621181005F3150 /* Info.plist */; };
380D77AA2C621237005F3150 /* dev.wibus-wee.InjectGUI.helper in CopyFiles */ = {isa = PBXBuildFile; fileRef = 380D77892C620B54005F3150 /* dev.wibus-wee.InjectGUI.helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
- 380D77AC2C6212E0005F3150 /* Launchd.plist in Resources */ = {isa = PBXBuildFile; fileRef = 380D77AB2C6212E0005F3150 /* Launchd.plist */; };
380D77AE2C621479005F3150 /* Execution.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77AD2C621479005F3150 /* Execution.swift */; };
381027E02C5F784F00348460 /* Extension+Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 381027DF2C5F784F00348460 /* Extension+Scene.swift */; };
381027E22C5F7C4800348460 /* Extension+Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = 381027E12C5F7C4800348460 /* Extension+Font.swift */; };
@@ -47,7 +40,6 @@
38877A372C4A7294009F5910 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38877A362C4A7294009F5910 /* Configuration.swift */; };
38877A3A2C4A730F009F5910 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38877A392C4A730F009F5910 /* Constants.swift */; };
38877A3D2C4A9EB7009F5910 /* SetupApplicationSupportDirectory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38877A3C2C4A9EB7009F5910 /* SetupApplicationSupportDirectory.swift */; };
- 38A5747A2C621E7600209B96 /* ExecutionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A574792C621E7600209B96 /* ExecutionService.swift */; };
38A5747B2C621E7600209B96 /* ExecutionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38A574792C621E7600209B96 /* ExecutionService.swift */; };
38AD95EA2C58E70E0032E79F /* Injector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AD95E92C58E70E0032E79F /* Injector.swift */; };
38AD95EE2C58F59C0032E79F /* StatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38AD95ED2C58F59C0032E79F /* StatusView.swift */; };
@@ -55,6 +47,9 @@
38BC1F552C4B622500C3B60E /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38BC1F542C4B622500C3B60E /* WelcomeView.swift */; };
38BC1F5A2C4B98A300C3B60E /* AppDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38BC1F592C4B98A300C3B60E /* AppDetailView.swift */; };
38BC1F5C2C4BB02200C3B60E /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38BC1F5B2C4BB02200C3B60E /* SettingsView.swift */; };
+ 38E090152C631C42007D1CFD /* HelperRemoteProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77A22C620F6B005F3150 /* HelperRemoteProvider.swift */; };
+ 38E090162C631C54007D1CFD /* HelperConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D77962C620C63005F3150 /* HelperConstants.swift */; };
+ 38E090172C631C5E007D1CFD /* Extension+OSStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 380D779F2C620D9C005F3150 /* Extension+OSStatus.swift */; };
38E944F22C5A761B00B252A3 /* Executor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E944F12C5A761B00B252A3 /* Executor.swift */; };
38E944F52C5A7F6A00B252A3 /* Cache in Frameworks */ = {isa = PBXBuildFile; productRef = 38E944F42C5A7F6A00B252A3 /* Cache */; };
38E944F72C5A85E200B252A3 /* Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38E944F62C5A85E200B252A3 /* Cache.swift */; };
@@ -377,11 +372,9 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 380D77AC2C6212E0005F3150 /* Launchd.plist in Resources */,
38877A252C4A6F87009F5910 /* Preview Assets.xcassets in Resources */,
380D77842C620464005F3150 /* config.json in Resources */,
381027E72C5F816500348460 /* Localizable.strings in Resources */,
- 380D77A92C621181005F3150 /* Info.plist in Resources */,
38877A212C4A6F87009F5910 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -410,24 +403,22 @@
buildActionMask = 2147483647;
files = (
38877A1F2C4A6F83009F5910 /* ContentView.swift in Sources */,
- 380D779D2C620D6C005F3150 /* ConnectionIdentityService.swift in Sources */,
- 380D77A32C620F6B005F3150 /* HelperRemoteProvider.swift in Sources */,
38BC1F5A2C4B98A300C3B60E /* AppDetailView.swift in Sources */,
38877A1D2C4A6F83009F5910 /* InjectGUIApp.swift in Sources */,
38877A3D2C4A9EB7009F5910 /* SetupApplicationSupportDirectory.swift in Sources */,
38E944F72C5A85E200B252A3 /* Cache.swift in Sources */,
+ 38E090162C631C54007D1CFD /* HelperConstants.swift in Sources */,
387CEA7B2C5DEF0600E3A5AC /* Extension+String.swift in Sources */,
38877A372C4A7294009F5910 /* Configuration.swift in Sources */,
380D77942C620C4D005F3150 /* RemoteApplicationProtocol.swift in Sources */,
38AD95EA2C58E70E0032E79F /* Injector.swift in Sources */,
- 380D77A02C620D9C005F3150 /* Extension+OSStatus.swift in Sources */,
380D77A62C620FF9005F3150 /* InjectError.swift in Sources */,
+ 38E090172C631C5E007D1CFD /* Extension+OSStatus.swift in Sources */,
38877A302C4A70DB009F5910 /* SoftwareManager.swift in Sources */,
38877A332C4A7222009F5910 /* PublishedStorage.swift in Sources */,
+ 38E090152C631C42007D1CFD /* HelperRemoteProvider.swift in Sources */,
381027E02C5F784F00348460 /* Extension+Scene.swift in Sources */,
38E944F22C5A761B00B252A3 /* Executor.swift in Sources */,
- 380D77972C620C63005F3150 /* HelperConstants.swift in Sources */,
- 380D779A2C620CC5005F3150 /* Helper.swift in Sources */,
38BC1F552C4B622500C3B60E /* WelcomeView.swift in Sources */,
38877A2E2C4A6FFA009F5910 /* InjectConfiguration.swift in Sources */,
38BC1F532C4B587A00C3B60E /* SidebarView.swift in Sources */,
@@ -435,7 +426,6 @@
38877A352C4A7254009F5910 /* ViewKit.swift in Sources */,
38877A3A2C4A730F009F5910 /* Constants.swift in Sources */,
380D77912C620BF7005F3150 /* HelperProtocol.swift in Sources */,
- 38A5747A2C621E7600209B96 /* ExecutionService.swift in Sources */,
381027E42C5F7E1100348460 /* Extension+URL.swift in Sources */,
38BC1F5C2C4BB02200C3B60E /* SettingsView.swift in Sources */,
381027E22C5F7C4800348460 /* Extension+Font.swift in Sources */,
@@ -467,6 +457,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 947UYU6LTQ;
+ INFOPLIST_FILE = "$(SRCROOT)/Helper/Info.plist";
MACOSX_DEPLOYMENT_TARGET = 13.0;
OTHER_LDFLAGS = (
"-sectcreate",
@@ -490,6 +481,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 947UYU6LTQ;
+ INFOPLIST_FILE = "$(SRCROOT)/Helper/Info.plist";
MACOSX_DEPLOYMENT_TARGET = 13.0;
OTHER_LDFLAGS = (
"-sectcreate",
diff --git a/InjectGUI/Backend/Execution.swift b/InjectGUI/Backend/Execution.swift
index faea7c4..3456ebd 100644
--- a/InjectGUI/Backend/Execution.swift
+++ b/InjectGUI/Backend/Execution.swift
@@ -20,6 +20,6 @@ enum Execution {
/// Execute the script at the provided URL.
static func executeScript(at path: String) async throws -> String {
- try await HelperRemoteProvider.remote().executeScript(at: path)
+ return try await HelperRemoteProvider.remote().executeScript(at: path)
}
}
diff --git a/InjectGUI/View/ContentView.swift b/InjectGUI/View/ContentView.swift
index 4f1e441..2d431d2 100644
--- a/InjectGUI/View/ContentView.swift
+++ b/InjectGUI/View/ContentView.swift
@@ -37,7 +37,7 @@ struct ContentView: View {
Task {
do {
print("Executing script")
- let result = try await Execution.executeScript(at: "~/Desktop/test.sh")
+ let result = try await Execution.executeScript(at: "/Users/wibus/Desktop/test.sh")
print(result)
} catch {
print(error.localizedDescription)