深入分析macOS签名验证权限com.apple.private.security.clear-library-validation
2021-02-23 12:00:00 Author: www.4hou.com(查看原文) 阅读量:179 收藏

0x00 概述

在macOS 10.15.2版本上,Apple引入了com.apple.private.security.clear-library-validation权限(entitlement),该权限正在逐渐取代以前在系统二进制文件上使用的com.apple.security.cs.disable-library-validation权限。尽管二者的影响大致相同,但它们的工作原理却存在差异。尽管使用com.apple.security.cs.disable-library-validation和com.apple.private.security.clear-library-validation会自动禁用库验证,但应用程序必须通过csops系统调用将其禁用。

0x01 简介

在Big Sur版本发布后,我注意到许多系统二进制文件都具有新的权限,其中的com.apple.private.security.clear-library-validation是我此前没有接触过的。这些应用程序之前使用的是com.apple.security.cs.disable-library-validation,看来它们似乎已经被一个新的权限替换。由于二者的名字比较相似,并且经过测试也证实了这些二进制文件仍然可以加载非Apple开发人员签名的第三方插件。这意味着,这些权限具有相同的影响。但是,二者的内部工作原理是不同的。

0x02 csops系统调用

在遇到下面列出的新csops操作代码后,我开始深入研究这一新的权限,可以在xnu-7195.50.7.100.1/bsd/sys/codesign.h中找到该代码。

#define CS_OPS_CLEAR_LV     15  /* clear the library validation flag */

csops是一个系统调用,可以用于对进程执行各类与代码签名相关的操作。我们可以查询进程的状态,在运行时设置各种标志,查询其代码签名blob等等。这是我以前没有发现过的新功能,因此我开始对其进行分析。

根据这个常量的说明,我们可以使用这个操作代码来清除进程的库验证标志。这意味着,如果我们可以在某个进程上运行它,则在调用成功的情况下,可以将第三方库加载到该进程中。

这个常量仅在xnu-7195.50.7.100.1/bsd/kern/kern_proc.c源文件中引用,该文件中包含csops_internal函数的源代码。这是在进行系统调用时将会运行的函数。下面是与CS_OPS_CLEAR_LV操作相关的部分源代码。

static int
csops_internal(pid_t pid, int ops, user_addr_t uaddr, user_size_t usersize, user_addr_t uaudittoken)
{
(...)
       if (pid == 0) {
              pid = proc_selfpid();
       }
       if (pid == proc_selfpid()) {
              forself = 1;
       }
 
 
       switch (ops) {
       case CS_OPS_STATUS:
       case CS_OPS_CDHASH:
       case CS_OPS_PIDOFFSET:
       case CS_OPS_ENTITLEMENTS_BLOB:
       case CS_OPS_IDENTITY:
       case CS_OPS_BLOB:
       case CS_OPS_TEAMID:
       case CS_OPS_CLEAR_LV:
              break;          /* not restricted to root */
       default:
              if (forself == 0 && kauth_cred_issuser(kauth_cred_get()) != TRUE) {
                     return EPERM;
              }
              break;
       }
 
       pt = proc_find(pid);
       if (pt == PROC_NULL) {
              return ESRCH;
       }
(...)
#if CONFIG_MACF
       switch (ops) {
       case CS_OPS_MARKINVALID:
       case CS_OPS_MARKHARD:
       case CS_OPS_MARKKILL:
       case CS_OPS_MARKRESTRICT:
       case CS_OPS_SET_STATUS:
       case CS_OPS_CLEARINSTALLER:
       case CS_OPS_CLEARPLATFORM:
       case CS_OPS_CLEAR_LV:
              if ((error = mac_proc_check_set_cs_info(current_proc(), pt, ops))) {
                     goto out;
              }
              break;
       default:
              if ((error = mac_proc_check_get_cs_info(current_proc(), pt, ops))) {
                     goto out;
              }
       }
#endif
       switch (ops) {
(...)
       case CS_OPS_CLEAR_LV: {
              /*
               * This option is used to remove library validation from
               * a running process. This is used in plugin architectures
               * when a program needs to load untrusted libraries. This
               * allows the process to maintain library validation as
               * long as possible, then drop it only when required.
               * Once a process has loaded the untrusted library,
               * relying on library validation in the future will
               * not be effective. An alternative is to re-exec
               * your application without library validation, or
               * fork an untrusted child.
               */
#if !defined(XNU_TARGET_OS_OSX)
              // We only support dropping library validation on macOS
              error = ENOTSUP;
#else
              /*
               * if we have the flag set, and the caller wants
               * to remove it, and they're entitled to, then
               * we remove it from the csflags
               *
               * NOTE: We are fine to poke into the task because
               * we get a ref to pt when we do the proc_find
               * at the beginning of this function.
               *
               * We also only allow altering ourselves.
               */
              if (forself == 1 && IOTaskHasEntitlement(pt->task, CLEAR_LV_ENTITLEMENT)) {
                     proc_lock(pt);
                     pt->p_csflags &= (~(CS_REQUIRE_LV | CS_FORCED_LV));
                     proc_unlock(pt);
                     error = 0;
              } else {
                     error = EPERM;
              }
(...)
}

我们将一步一步进行介绍。其中,有三个地方会对其进行检查。我们首先来看第一个swtich条件语句。

switch (ops) {
       case CS_OPS_STATUS:
       case CS_OPS_CDHASH:
       case CS_OPS_PIDOFFSET:
       case CS_OPS_ENTITLEMENTS_BLOB:
       case CS_OPS_IDENTITY:
       case CS_OPS_BLOB:
       case CS_OPS_TEAMID:
       case CS_OPS_CLEAR_LV:
              break;          /* not restricted to root */
       default:
              if (forself == 0 && kauth_cred_issuser(kauth_cred_get()) != TRUE) {
                     return EPERM;
              }
              break;
       }

在这里,系统将允许非root执行switch条件中列出的操作,其中的一项是我们关注的重点。这表明,即使我们没有以root用户身份运行,也可以使用CS_OPS_CLEAR_LV操作调用csops。

接下来,我们来分析另一个switch条件。

#if CONFIG_MACF
       switch (ops) {
       case CS_OPS_MARKINVALID:
       case CS_OPS_MARKHARD:
       case CS_OPS_MARKKILL:
       case CS_OPS_MARKRESTRICT:
       case CS_OPS_SET_STATUS:
       case CS_OPS_CLEARINSTALLER:
       case CS_OPS_CLEARPLATFORM:
       case CS_OPS_CLEAR_LV:
              if ((error = mac_proc_check_set_cs_info(current_proc(), pt, ops))) {
                     goto out;
              }
              break;
       default:
              if ((error = mac_proc_check_get_cs_info(current_proc(), pt, ops))) {
                     goto out;
              }
       }
#endif

在这里,我们使用mac_proc_check_get_cs_info函数进行了MACF策略调用。如果成功,MACF策略调用将返回0,这就是对条件的检查。在xnu-7195.50.7.100.1/security/mac_process.c内部实现了mac_proc_check_get_cs_info函数。我们跟踪这个函数。

int
mac_proc_check_set_cs_info(proc_t curp, proc_t target, unsigned int op)
{
       kauth_cred_t cred;
       int error = 0;
 
#if SECURITY_MAC_CHECK_ENFORCE
       /* 21167099 - only check if we allow write */
       if (!mac_proc_enforce) {
              return 0;
       }
#endif
       if (!mac_proc_check_enforce(curp)) {
              return 0;
       }
 
       cred = kauth_cred_proc_ref(curp);
       MAC_CHECK(proc_check_set_cs_info, cred, target, op);
       kauth_cred_unref(&cred);
 
       return error;
}

该函数最终将使用MAC_CHECK宏进行MACF调用,我在针对CVE-2020-9771补丁的逆向分析过程中已经讨论过。它将遍历MACF策略挂钩,该挂钩对proc_check_set_cs_info进行检查。目前,它仅仅被沙箱挂钩,如下所示。

void _hook_proc_check_set_cs_info(int arg0, int arg1) {
    ___bzero(&var_1A0, 0x188);
    *(int32_t *)(&var_1A0 + 0xa8) = 0x4;
    *(&var_1A0 + 0xb8) = arg1;
    _cred_sb_evaluate(arg0, 0x65, &var_1A0, rcx, r8, r9);
    return;
}

在这里,将使用操作码0x65对_cred_sb_evaluate进行内部调用,我在上一篇文章中也对此进行了讨论。

回到我们的csops调用中,不论调用过程允许或不允许这个操作,都会运行MACF策略检查。

假设允许这个操作,那么我们继续,最后到达实际执行该操作的位置。

       case CS_OPS_CLEAR_LV: {
              /*
               * This option is used to remove library validation from
               * a running process. This is used in plugin architectures
               * when a program needs to load untrusted libraries. This
               * allows the process to maintain library validation as
               * long as possible, then drop it only when required.
               * Once a process has loaded the untrusted library,
               * relying on library validation in the future will
               * not be effective. An alternative is to re-exec
               * your application without library validation, or
               * fork an untrusted child.
               */
#if !defined(XNU_TARGET_OS_OSX)
              // We only support dropping library validation on macOS
              error = ENOTSUP;
#else
              /*
               * if we have the flag set, and the caller wants
               * to remove it, and they're entitled to, then
               * we remove it from the csflags
               *
               * NOTE: We are fine to poke into the task because
               * we get a ref to pt when we do the proc_find
               * at the beginning of this function.
               *
               * We also only allow altering ourselves.
               */
              if (forself == 1 && IOTaskHasEntitlement(pt->task, CLEAR_LV_ENTITLEMENT)) {
                     proc_lock(pt);
                     pt->p_csflags &= (~(CS_REQUIRE_LV | CS_FORCED_LV));
                     proc_unlock(pt);
                     error = 0;
              } else {
                     error = EPERM;
              }

在这里,Apple添加了非常详细的注释,对所有内容都进行了说明。如果满足要求,它将清除目标进程的库验证标志(pt->p_csflags &= (~(CS_REQUIRE_LV | CS_FORCED_LV));)。

条件非常严格。该操作只能由进程自身(forself == 1)以及具有由常量CLEAR_LV_ENTITLEMENT定义的权限的进程调用。这是在xnu-7195.50.7.100.1/bsd/sys/codesign.h中进行的定义。

#define CLEAR_LV_ENTITLEMENT "com.apple.private.security.clear-library-validation"

在循环结束后,我们就得到了以前曾见到过的权限。

综上所述,我们可以确定,拥有com.apple.private.security.clear-library-validation权限的进程可以使用CLEAR_LV_ENTITLEMENT调用csops系统调用,以清除自身的库验证代码签名标志。即使该进程未以root用户身份运行,这个方法也是有效的。

0x03 SSH

为了确认这个发现,我将具有这个权限的SSH加载到Hopper中,并验证它是否使用csops禁用了库验证。

int sub_10016d41f(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5) {
(...)
loc_10016d67b:
    rax = getpid();
    rax = csops(rax, 0xf, 0x0, 0x0);
    if (rax == 0x0) goto loc_10016d6d6;
 
loc_10016d694:
    rdx = 0x0;
    rcx = 0x0;
    rbx = 0x0;
    sub_10014e556("csops(CS_OPS_CLEAR_LV) failed: %d", rax, rdx, rcx, r8, r9, stack[-136]);
(...)

的确,我们可以找到使用操作码0xf调用csops的函数,也就是CS_OPS_CLEAR_LV操作。如果该调用失败,我们还能得到一个详细的错误消息。

0x04 历史

尽管我仅仅在Big Sur版本中发现了这个变化,但是这个功能是较早之前引入的。在xnu-6153.51.1中引入了用于清除库验证标志的详细csops操作,该操作已经在macOS 10.15.2中使用。但是使用该权限的二进制文件仅在macOS 10.15.4版本以后出现,只涉及四个应用程序——su、screen、login和passwd。

从Big Sur(macOS 11.0)开始,有20个二进制文件开始具有这个新的权限,因此Apple逐渐将其迁移到这个新方法上。

0x05 新权限的优势

我们非常好奇,与之前的方法相比,这种方法有哪些优点。在这里我仅仅是推测,但确实发现了一些优势所在。使用新的方法,在加载应用程序时会强行执行库验证,这意味着攻击者无法对这类二进制文件进行dylib劫持或代理攻击。这种攻击方式是非常常见的,特别是针对第三方应用程序。

我们仍然可以将代码注入到这类应用程序中,但只能通过插件的方式,而不能通过其他方式。尽管插件的攻击方式并不比标准dylib方式难,但这种改进已经向更好的设计迈出了一步。遗憾的是,它只适用于Apple自己的文件,如果我们尝试在自己的二进制文件中使用它,则会出现错误。

mac_vnode_check_signature: /tmp/launch: code signature validation failed fatally: When validating /tmp/launch:
  Code has restricted entitlements, but the validation of its code signature failed.
Unsatisfied Entitlements:

0x06 总结

我们看到Apple在macOS 10.15.2中引入了新的权限com.apple.private.security.clear-library-validation,它允许进程使用csops系统调用清除自身的库验证标志。Apple正在将应用程序从旧的com.apple.security.cs.disable-library-validation权限缓慢迁移到新的权限,从而在设计层面上增加安全性。

本文翻译自:​https://theevilbit.github.io/posts/com.apple.private.security.clear-library-validation/如若转载,请注明原文地址:


文章来源: https://www.4hou.com/posts/D68B
如有侵权请联系:admin#unsafe.sh