Mac 平台上禁止某一应用程序启动的关键代码

前一段时间需要开发应用去只允许在白名单中的应用开启。因此查找了相关资料。在 Mac 平台上,该操作需要在内核级别进行。因此,看懂该方法的需要了解一部分内核的知识。个人觉得可以参考这本书:《OS X and iOS Kernel Programming》,基础概念基本上就可以了。

实现程序禁止启动的核心代码是在内核方法中,有一个方法 kauth_listen_scope。如果你使用它注册了 Auth 的监听,那么每个程序启动的时候都会去你注册的方法上获取 Auth 监听,这个时候,你就可以获取到运行的程序信息并且给出是否允许运行的值了。

思路:

  1. 注册内核监听

    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. 程序监听中,将授权信息传到 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;
    
        // 读取运行许可
        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 判断程序是否可以运行,发回值到内核中

    - (void)listenForDecisionRequests:(void (^)(block_message_t))callback {
        while (!self.kextManager.connectionEstablished) return;
    
        // 生成与 Kext 通信的共享内存/通知队列
        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.通过异步函数将将任务加入队列
        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 信息

当 App Auth 禁止的时候,会提示以下信息:

disalowapp001.png

至此,实现方法成功。

参考

santa

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容