Skip to content
This repository has been archived by the owner on Oct 15, 2024. It is now read-only.

feat: implement InjectGUI Helper #10

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions Helper/Extension/Extension+OSStatus.swift
Original file line number Diff line number Diff line change
@@ -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)"
}
}
}
62 changes: 62 additions & 0 deletions Helper/Helper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// 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 {
// FIX: Something went wrong here?

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
}
}
16 changes: 16 additions & 0 deletions Helper/HelperConstants.swift
Original file line number Diff line number Diff line change
@@ -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"
}
22 changes: 22 additions & 0 deletions Helper/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>dev.wibus-wee.InjectGUI.helper</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>dev.wibus-wee.InjectGUI.helper</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>SMAuthorizedClients</key>
<array>
<string>identifier "dev.wibus-wee.InjectGUI" and anchor apple generic and certificate leaf[subject.CN] = "Apple Development: [email protected] (YX89Z87JNG)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */</string>
</array>
</dict>
</plist>
15 changes: 15 additions & 0 deletions Helper/Launchd.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>dev.wibus-wee.InjectGUI.helper</string>
<key>StandardOutputPath</key>
<string>/tmp/injectgui.helper.log</string>
<key>MachServices</key>
<dict>
<key>dev.wibus-wee.InjectGUI.helper</key>
<true/>
</dict>
</dict>
</plist>
13 changes: 13 additions & 0 deletions Helper/Protocol/HelperProtocol.swift
Original file line number Diff line number Diff line change
@@ -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
}
13 changes: 13 additions & 0 deletions Helper/Protocol/RemoteApplicationProtocol.swift
Original file line number Diff line number Diff line change
@@ -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
}
59 changes: 59 additions & 0 deletions Helper/Service/ConnectionIdentityService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// 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<Any>.size) }

static func checkConnectionIsValid(connection: NSXPCConnection) throws {
let tokenData = try tokenData(in: connection)
let secCode = try secCode(from: tokenData)
try verifySecCode(secCode: secCode)
}
}
44 changes: 44 additions & 0 deletions Helper/Service/ExecutionService.swift
Original file line number Diff line number Diff line change
@@ -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: "/usr/bin/env")

// 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
}
}
14 changes: 14 additions & 0 deletions Helper/main.swift
Original file line number Diff line number Diff line change
@@ -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()

Loading
Loading