美文网首页
Mac 平台上禁止某一应用程序启动的关键代码

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

作者: l蓝色梦幻 | 来源:发表于2018-10-08 15:54 被阅读165次

    前一段时间需要开发应用去只允许在白名单中的应用开启。因此查找了相关资料。在 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

    相关文章

      网友评论

          本文标题:Mac 平台上禁止某一应用程序启动的关键代码

          本文链接:https://www.haomeiwen.com/subject/ffrjaftx.html