Mac 平臺(tái)上禁止某一應(yīng)用程序啟動(dòng)的關(guān)鍵代碼

前一段時(shí)間需要開發(fā)應(yīng)用去只允許在白名單中的應(yīng)用開啟。因此查找了相關(guān)資料。在 Mac 平臺(tái)上,該操作需要在內(nèi)核級(jí)別進(jìn)行。因此,看懂該方法的需要了解一部分內(nèi)核的知識(shí)。個(gè)人覺得可以參考這本書:《OS X and iOS Kernel Programming》,基礎(chǔ)概念基本上就可以了。

實(shí)現(xiàn)程序禁止啟動(dòng)的核心代碼是在內(nèi)核方法中,有一個(gè)方法 kauth_listen_scope。如果你使用它注冊(cè)了 Auth 的監(jiān)聽,那么每個(gè)程序啟動(dòng)的時(shí)候都會(huì)去你注冊(cè)的方法上獲取 Auth 監(jiān)聽,這個(gè)時(shí)候,你就可以獲取到運(yùn)行的程序信息并且給出是否允許運(yùn)行的值了。

思路:

  1. 注冊(cè)內(nèi)核監(jiān)聽

    kern_return_t manager::start_listener() {
        vnode_listener_ = kauth_listen_scope(KAUTH_SCOPE_VNODE, vnode_scope_callback, reinterpret_cast<void *>(this));
        if (!vnode_listener_) {
            LOGD("have no listener");
            return kIOReturnInternalError;
        }
        enable_block = true;
    
        return kIOReturnSuccess;
    }
    
  2. 程序監(jiān)聽中,將授權(quán)信息傳到 App 上去

    block_action_t manager::get_from_daemon(block_message_t *message, block_vnode_id_t identifier) {
        auto return_action = ACTION_UNSET;
    
        do {
            add_to_cache(identifier, ACTION_REQUEST_BINARY);
        
            if (!post_to_decision_queue(message)) {
                remove_from_cache(identifier);
                return_action = ACTION_ERROR;
                break;
            }
        
            auto cache_check_count = 0;
            do {
                msleep((void *)message->vnode_id.unsafe_simple_id(), NULL, 0, "", &ts_);
                return_action = get_from_cache(identifier);
            } while (client_connected() &&
                 ((return_action == ACTION_REQUEST_BINARY && ++cache_check_count < kRequestCacheChecks)
                  || (return_action == ACTION_RESPOND_ACK)));
        } while (!RESPONSE_VALID(return_action) && client_connected());
    
        if (!RESPONSE_VALID(return_action)) {
            remove_from_cache(identifier);
            return_action = ACTION_ERROR;
        }
    
    exit:
        return return_action;
    }
    
    block_action_t manager::fetch_decision(const kauth_cred_t cred, const vnode_t vp, const block_vnode_id_t vnode_id) {
        // 從用戶空間中獲取許可記錄
        char path[MAXPATHLEN];
        int name_len = MAXPATHLEN;
        path[MAXPATHLEN - 1] = 0;
    
        if (vn_getpath(vp, path, &name_len) == ENOSPC) {
            return ACTION_RESPOND_TOOLONG;
        }
    
        auto message = new_message(cred);
        strlcpy(message->path, path, sizeof(message->path));
        message->action = ACTION_REQUEST_BINARY;
        message->vnode_id = vnode_id;
        proc_name(message->ppid, message->pname, sizeof(message->pname));
        auto return_action = get_from_daemon(message, vnode_id);
        delete message;
        return return_action;
    }
    
    int manager::vnode_callback(const kauth_cred_t cred, const vfs_context_t ctx, const vnode_t vp, int *errno) {
        // 獲取 vnode 的 id
        auto vnode_id = get_vnodeid_for_vnode(ctx, vp);
        if (vnode_id.fsid == 0 && vnode_id.fileid == 0) return KAUTH_RESULT_DEFER;
    
        // 讀取運(yùn)行許可
        auto returnedAction = fetch_decision(cred, vp, vnode_id);
    
        switch (returnedAction) {
            case ACTION_RESPOND_ALLOW:
                return KAUTH_RESULT_ALLOW;
            case ACTION_RESPOND_DENY:
                *errno = EPERM;
                return KAUTH_RESULT_DENY;
            case ACTION_RESPOND_TOOLONG:
                *errno = ENAMETOOLONG;
                return KAUTH_RESULT_DENY;
            default:
                return KAUTH_RESULT_DEFER;
        }
    }
    
    extern "C" int vnode_scope_callback(kauth_cred_t credential,
                                    void *idata,
                                    kauth_action_t action,
                                    uintptr_t arg0,
                                    uintptr_t arg1,
                                    uintptr_t arg2,
                                    uintptr_t arg3) {
        auto sdm = OSDynamicCast(manager, reinterpret_cast<OSObject *>(idata));
    
        if (unlikely(sdm == nullptr)) {
            return KAUTH_RESULT_DEFER;
        }
    
        vnode_t vp = reinterpret_cast<vnode_t>(arg1);
        //    vnode_t dvp = reinterpret_cast<vnode_t>(arg2);
    
        // We only care about regular files.
        if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
    
        if ((action & KAUTH_VNODE_EXECUTE) && !(action & KAUTH_VNODE_ACCESS)) {
            sdm->increment_listener_invocations();
            int result = sdm->vnode_callback(credential, reinterpret_cast<vfs_context_t>(arg0), vp,  reinterpret_cast<int *>(arg3));
            sdm->decrement_listener_invocations();
            return result;
        }
    
        return KAUTH_RESULT_DEFER;
    }
    
    
  3. App 判斷程序是否可以運(yùn)行,發(fā)回值到內(nèi)核中

    - (void)listenForDecisionRequests:(void (^)(block_message_t))callback {
        while (!self.kextManager.connectionEstablished) return;
    
        // 生成與 Kext 通信的共享內(nèi)存/通知隊(duì)列
        mach_port_t receivePort = IODataQueueAllocateNotificationPort();
        if (receivePort == MACH_PORT_NULL) {
            return;
        }
    
        kern_return_t kr = IOConnectSetNotificationPort(self.kextManager.connection, QUEUETYPE_DECISION, receivePort, 0);
        if (kr != kIOReturnSuccess) {
            mach_port_destroy(mach_task_self(), receivePort);
            return;
        }
    
        mach_vm_address_t address = 0;
        mach_vm_size_t size = 0;
        kr = IOConnectMapMemory(self.kextManager.connection, QUEUETYPE_DECISION, mach_task_self(), &address, &size, kIOMapAnywhere);
        if (kr != kIOReturnSuccess) {
            mach_port_destroy(mach_task_self(), receivePort);
            return;
    }
    
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
        // 2.通過異步函數(shù)將將任務(wù)加入隊(duì)列
        dispatch_async(queue, ^{
        IODataQueueMemory *queueMemory = (IODataQueueMemory *)address;
        
            do {
                while (IODataQueueDataAvailable(queueMemory)) {
                    block_message_t vdata;
                    uint32_t dataSize = sizeof(vdata);
                    if (IODataQueueDequeue(queueMemory, &vdata, &dataSize) == kIOReturnSuccess) {
                        callback(vdata);
                    } else {
                        exit(2);
                    }
                }
            } while (IODataQueueWaitForAvailableData(queueMemory, receivePort) == kIOReturnSuccess);
        
            IOConnectUnmapMemory(self.kextManager.connection, QUEUETYPE_DECISION, mach_task_self(), address);
            mach_port_destroy(mach_task_self(), receivePort);
        });
    }
    
    - (BOOL)postToKernelAction:(block_action_t)action forVnodeID:(block_vnode_id_t)vnodeId {
        enum DispatchSelectors selector = kCallMethodCount;
        switch (action) {
            case ACTION_RESPOND_ALLOW:
                selector = kCallAppBlockAllowBinary;
                break;
            case ACTION_RESPOND_DENY:
                selector = kCallAppBlockDenyBinary;
                break;
            case ACTION_RESPOND_ACK:
                selector = kCallAppBlockAcknowledgeBinary;
                break;
            default:
                return NO;
        }
        return IOConnectCallStructMethod(self.kextManager.connection, selector, &vnodeId, sizeof(vnodeId), 0, 0) == kIOReturnSuccess;
    }
    
    - (DecisionRequestCallBack)callback {
        dispatch_queue_t exec_queue = dispatch_queue_create("execution_queue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_set_target_queue(exec_queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0));
    
        return ^(block_message_t message) {
            @autoreleasepool {
                dispatch_async(exec_queue, ^{
                    switch (message.action) {
                        case ACTION_REQUEST_SHUTDOWN: {
                            exit(0);
                        }
                        case ACTION_REQUEST_BINARY: {
                            block_action_t action = [self checkPath:[AppMessage instanceWithMessage:message]] ? ACTION_RESPOND_ALLOW : ACTION_RESPOND_DENY;
                            [self postToKernelAction:action forVnodeID:message.vnode_id];
                            break;
                        }
                        default: {
                            exit(1);
                    }
                }
            });
        }
    };
    }
    
    
  4. 返回 Auth 信息

當(dāng) App Auth 禁止的時(shí)候,會(huì)提示以下信息:

disalowapp001.png

至此,實(shí)現(xiàn)方法成功。

參考

santa

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容