前一段時(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)行的值了。
思路:
-
注冊(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; } -
程序監(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; } -
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); } } }); } }; } 返回 Auth 信息
當(dāng) App Auth 禁止的時(shí)候,會(huì)提示以下信息:

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