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)