【译文】iOS和macOS沙盒技术分析

 众所周知,macOS的沙盒一直是一个神秘的东西,我喜欢利用各种工具并从Jonathan Levin的《*OS Internals》等参考书再或者苹果官方自己都不太清楚的文档中收集的知识来分析它。苹果的安全机制并不是最好的,这不是什么新鲜事。沙盒技术有很长的历史,macOS的用户被沙盒保护已经有很长一段时间了,随着时间的推移,沙盒变得越来越强大。苹果一直在尽其所能加强其操作系统中的沙盒和其他许多安全机制,下面就让我们一起深入了解一下macOS沙盒的神奇之处。

0x01 背景

苹果首次使用沙盒技术是在其OS X 10.5(Leopard)中,它被称为“SeatBelt”(安全带)。正如这个词语的意思一样,它就像在汽车旅行中为了安全而系上安全带一样,强制开发人员在应用程序上使用沙盒技术,以限制其对系统的访问。正如你想象的那样,没有多少开发者会这样做,而且由于“安全带”的最初概念是自愿限制,所以苹果也做不了什么。结合 MandatoryAccessControl (MAC)Framework,沙盒的想法肯定不错,但离成功还很远。MACF框架是苹果设备的整个安全模型构建的基础。

在OS X 10.7中,苹果公司吸取了OS X 10.5的教训,沙盒现在已经不再任由开发人员在应用程序上是否使用,默认情况下是强制执行的。即使是今天在macOS Mojave上,苹果仍然强制使用沙盒,基于应用程序拥有的权限( com.apple.security.app-sandbox)。如果应用程序具有此权限,它将被放置在沙盒中,而不是考虑开发人员的意愿。也就是说,在新系统中开发者的意见是没有意义的,因为上传到Appstore的程序是由苹果公司签名的,在签名过程中,苹果公司在程序上授予沙盒权限,从而迫使所有Appstore中的程序沙盒化。

需要注意的是,与iOS的沙盒相比,macOS更容易操作。在iOS上,第三方程序均不可能逃脱沙盒,除非你使用沙盒逃逸技术,而大多数这种情况下是由内核漏洞或沙盒漏洞导致的(越狱)。所有第三方程序,不管它是从哪里安装的,都放在 /var/mobile/containers/var/containers两目录中。从ios8开始,这些目录发生了很大的变化,创建了新的文件夹,移动了程序资源,静态数据和运行时数据分离,所以在旧的iOS上,你可以找到安装在 /var/mobile/Applications甚至 /var/mobile/ containers/bundl /中的程序。任何在 /var/中的东西都要被沙盒化,因为你不能直接在其他地方安装你的程序,除非你越狱了。在macOS上,只有Appstore中的程序是沙盒的。如果你从开发人员网站直接下载DMG镜像中的程序,那么它很可能不受沙盒限制。

0x02 工作原理

沙盒的唯一目的是限制程序访问系统的各种资源,比如系统调用、文件或任何东西,这是为了恶意程序肆意破坏系统。在iOS上,我可以骗你安装一个恶意的程序,但这个做法是是毫无意义的,除非我有内核或沙箱逃脱的漏洞(越狱),否则程序不会对你的设备造成很大的伤害(比如:删除你的手机里的一些重要文件)。iOS沙盒和其他保护措施会一起防止未经授权的访问,所以程序只能访问它自己的容器内的资源,并不能造成很大的破坏。同样的道理也适用于macOS应用商店的应用程序,但不适用于DMG格式的程序,因为DMG格式可能没有沙盒。

沙盒实际上是一项非常好的技术,这也就是为什么它一直沿用到今天的原因。假如你在Windows上打开了一个从非法来源上下载的恶意程序,而该程序若想删除 System32目录或其他重要文件,这是完全可以实现的。因为Windows上没有沙盒,需要使用到管理员权限的资源的地方,只需要欺骗用户点击允许管理员权限运行即可。

苹果官方说过:沙盒是一种在内核层面强制实施的访问控制技术(在内核层面,用户或任何受到损害的程序通常都无法控制)。沙盒可以确保它拦截沙盒程序执行的所有操作,并禁止访问程序没有访问权限的资源。

在macOS上,沙箱本身不是单个文件或单个进程,它被分割成多个组件,比如位于 /usr/libexec/sandboxd目录中的 userland daemon,这是 com.apple.security.sandboxkext (Kernel Extension),还有依赖于 AppContainer.FrameworkAppSandbox私有框架。正如你所见,多个组件一起工作来实现本文所诉的程序沙箱。

在终端中运行 kextstat | grepsand命令,可以看到macOS上的kext处于活动状态。

沙箱是多个MACF策略模块之一。AMFI (Apple Mobile File Integrity)的协同设计是另一个模块。

0x03 测试:根据授权决定macOS上的应用程序是否沙盒化

正如之前所提到的,该应用被沙盒化的一个明显迹象是应用程序二进制文件中是否需要 com.apple.security.app-sandbox权限。我们可以使用很多工具检查macOS上的权限,利用Jonathan Levin的 jtool这个工具,运行命令 ./jtool--ent /Applications/AppName.在终端app中,我们可以看到程序所拥有的全部权限。以iHex为例,Appstore中的只需要OpenBoardView权限。DMG格式如下:

在终端中运行该命令会得到以下iHex结果:
1.png

需要注意的是,权限是存在的,并且密钥被设置为 true,此程序将被沙盒化。现在,正如你所见,这些权利是以类似于XML的格式列出的,它们实际上位于  .PLIST or Property List 文件中,而属性列表文件只不过是美化的XML。PLISTs可以采用二进制格式,可以使用命令 plutil -convert xml1 -o将其转换为可读的格式。

使用 Jtool可以替换程序的权限,但之后需要对程序进行伪造签名。总之,这是一种解除macOS应用程序沙盒的方法。这在iOS上并不容易做到,因为沙盒是基于应用程序的安装位置,而不是基于安装权限。

现在让我们来看看OpenBoardView,这是一款未从App Store下载的应用程序。
2.png

如你所见,程序没有任何权限。它不会被沙盒化,这意味着它可以比任何应用程序商店应用程序访问更多的源代码。

com.apple.security.app-sandbox 的权限并不是iHEX开发人员自己添加的,它是由苹果官方在App Store审核的过程中自动添加的。

另一种检查程序是否被沙盒化的方法是运行 asctl sandbox check --pid XYZ命令,其中XYZ是程序的 PID(Process ID)。可以从macOS上的 Activity Monitor程序获得正在运行的进程的 PID。下面是 asctl命令的输出。
3.png

0x04 执行流程

进入沙盒容器中,也就是放置在 $HOME/Library/Containers/上的文件夹。此文件夹是为任何沙盒程序创建的,而不管实际二进制文件安装在何处。文件夹遵循简单的结构,但最重要的是,它包含一个 Container.Plist文件,其中包含有关其容器(由其 CFBundleIdentifier标识)、 SandboxProfileDataSandboxProfileDataValidationInfoVersion的应用程序的信息。

找到iHEX 的  Container ,将目录切到上面提到的路径,然后运行 ls -lF com.hewbo.hexeditorcom.hewbo.hexeditor是iHex的 CFBundleIndentifier(在.app文件夹中可以找到 Info.Plist)。

4.png

可以看到app的容器包含一个 Data文件夹和前面提到的 Container.Plist文件。数据文件夹非常有趣,如果将目录切到它,可以看到它模拟了用户的主目录。当然,所有这些都是严格控制的符号链接,该控制由沙盒容器强制执行。 Container.plist包含 SandboxProfileDataValidationRedirectablePathsKey,它指定哪些符号链接被批准。

5.png

0x05 沙盒化

在内部启动应用程序时,内核将调用 mac_execve函数,可以在 XNU源代码中看到。 __mac_execve几乎会加载二进制文件,但它也会检查 MAC label,看看是否应该强制执行沙箱。

当进程启动时,在其生命周期中很早就会加载 libSystem.B。因为所有的 APIs都依赖于它。在执行过程中的某个时刻, libSystem.B.initializer将落入 _libsecinit_setup_secinitd_client,后者将落入 xpc_copy_attribulements_for_pid以从程序二进制文件中获取权限,然后它将权限以及应用程序是否应该通过 XPC消息被 sandboxed发送到位于 /usr/libexec/secinitd中的 secinitd守护进程。此消息传输发生在 xpc_pipe_route级别,相同的函数将处理从 secinitd守护进程接收的消息,该守护进程将解析从进程接收的 XPC消息。

secinitd 守护进程将承认这样一个事实:如果存在权限,沙盒应该被强制执行,那么它将调用 AppSandbox.Framework来创建沙盒配置文件。创建概要文件之后, secinitd将返回一条 XPC message,其中包含 CONTAINER_ID_KEY、CONTAINER_ROOT_PATH_KEY、SANDBOX_PROFILE_DATA_KEY和其他数据。该信息将由 _libsecinit_setup_app_sandbox解析,然后该 sandbox落入 __sandbox_ms中,从而创建程序的沙盒并在运行时将其包含。

流程如下:

6.png

0x06 实验:跟踪运行时创建的程序沙盒

使用 LLDB可以调试一个沙盒程序,并查看到底发生了什么,包括从进程传递到 secinitd守护进程的 XPC消息。即将深入了解 TerminalLLDB,下面的清单可能很难理解。为了更容易理解发生了什么,最好尝试遵循重要的逻辑,比如传递的消息和回溯,以查看执行的函数调用。
起初,打开终端并调用 lldb。如果没有安装 LLDB,请安装 Xcode,因为它附带了您需要的所有调试工具。首先在 xpc_pipe_routine__sandbox_ms处下断点。

然后在 libxpc.dylib中停在 xpc_pipe_.routine。做一个 backtrace来看看发生了什么,可以通过 bt命令来实现这一点。

很明显这个不是我们所需要的,这是 libxpc.dylib_xpc_uncork_domain函数。我们需要 xpc_pipe_create,按c继续并再次回溯。

找到所需的 xpc_pipe_create函数。可以使用 p (char *) xpc_copy_description($rsi)查看通过 XPC管道发送的消息,这调试非常有用。使用 RSI寄存器作为消息的第二个参数(第一个参数是管道)。

这也不是所需要的。这只是一个握手信息,继续。

包含程序的权限以及它是否是沙盒的候选项的宝贵信息。正如所见, SECINITD_REGISTRATION_MESSAGE_IS_SANDBOX_CANDIDATE_KEY设置为 bool true,并且确实拥有 com.apple.security.app-sandbox权限。

可以看到了进程发送给 secinitd的内容,看是否正在创建沙盒。使用设置的第二个断点,即 __sandbox_ms上的断点,继续(c)直到找到它。

接下来,调用 libsystem_secinit_libsecinit_setup_app_sandbox。这意味着沙盒已经创建好了,将在开始的时候把程序放入沙盒中。接下来的几个 continue命令将最终落入 libsystem_sandbox.dylibsandbox_check_common中。最后进入 LaunchServices,然后通过 AppKit ' -[NSApplication init]启动应用程序。

至此,程序沙盒化完成!

原文:https://geosn0w.github.io/A-Long-Evening-With-macOS's-Sandbox/

译文源:https://xz.aliyun.com/t/3981 


发表评论

(必填)

(必填)

(以便回访)