前一段时间需要开发应用去只允许在白名单中的应用开启。因此查找了相关资料。在 Mac 平台上,该操作需要在内核级别进行。因此,看懂该方法的需要了解一部分内核的知识。个人觉得可以参考这本书:《OS X and iOS Kernel Programming》,基础概念基本上就可以了。
实现程序禁止启动的核心代码是在内核方法中,有一个方法 kauth_listen_scope。如果你使用它注册了 Auth 的监听,那么每个程序启动的时候都会去你注册的方法上获取 Auth 监听,这个时候,你就可以获取到运行的程序信息并且给出是否允许运行的值了。
思路:
-
注册内核监听
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; } -
程序监听中,将授权信息传到 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; } -
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); } } }); } }; } 返回 Auth 信息
当 App Auth 禁止的时候,会提示以下信息:

disalowapp001.png
至此,实现方法成功。