# macOS MACF {{#include ../../../banners/hacktricks-training.md}} ## 基本信息 **MACF** 代表 **强制访问控制框架**,这是一个内置于操作系统的安全系统,用于帮助保护您的计算机。它通过设置 **关于谁或什么可以访问系统某些部分的严格规则** 来工作,例如文件、应用程序和系统资源。通过自动执行这些规则,MACF 确保只有授权用户和进程可以执行特定操作,从而降低未经授权访问或恶意活动的风险。 请注意,MACF 并不真正做出任何决策,因为它只是 **拦截** 操作,它将决策留给它调用的 **策略模块**(内核扩展),例如 `AppleMobileFileIntegrity.kext`、`Quarantine.kext`、`Sandbox.kext`、`TMSafetyNet.kext` 和 `mcxalr.kext`。 ### 流程 1. 进程执行 syscall/mach trap 2. 内核内部调用相关函数 3. 函数调用 MACF 4. MACF 检查请求在其策略中挂钩该函数的策略模块 5. MACF 调用相关策略 6. 策略指示是否允许或拒绝该操作 > [!CAUTION] > 只有 Apple 可以使用 MAC 框架 KPI。 ### 标签 MACF 使用 **标签**,然后策略会检查是否应该授予某些访问权限。标签结构声明的代码可以在 [这里](https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/security/_label.h) 找到,该代码随后在 [**这里**](https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/sys/ucred.h#L86) 的 **`struct ucred`** 中使用,位于 **`cr_label`** 部分。标签包含标志和可由 **MACF 策略分配指针** 的 **槽** 数量。例如,Sandbox 将指向容器配置文件。 ## MACF 策略 MACF 策略定义了 **在某些内核操作中应用的规则和条件**。 内核扩展可以配置 `mac_policy_conf` 结构,然后通过调用 `mac_policy_register` 注册它。从 [这里](https://opensource.apple.com/source/xnu/xnu-2050.18.24/security/mac_policy.h.auto.html): ```c #define mpc_t struct mac_policy_conf * /** @brief Mac policy configuration This structure specifies the configuration information for a MAC policy module. A policy module developer must supply a short unique policy name, a more descriptive full name, a list of label namespaces and count, a pointer to the registered enty point operations, any load time flags, and optionally, a pointer to a label slot identifier. The Framework will update the runtime flags (mpc_runtime_flags) to indicate that the module has been registered. If the label slot identifier (mpc_field_off) is NULL, the Framework will not provide label storage for the policy. Otherwise, the Framework will store the label location (slot) in this field. The mpc_list field is used by the Framework and should not be modified by policies. */ /* XXX - reorder these for better aligment on 64bit platforms */ struct mac_policy_conf { const char *mpc_name; /** policy name */ const char *mpc_fullname; /** full name */ const char **mpc_labelnames; /** managed label namespaces */ unsigned int mpc_labelname_count; /** number of managed label namespaces */ struct mac_policy_ops *mpc_ops; /** operation vector */ int mpc_loadtime_flags; /** load time flags */ int *mpc_field_off; /** label slot */ int mpc_runtime_flags; /** run time flags */ mpc_t mpc_list; /** List reference */ void *mpc_data; /** module data */ }; ``` 很容易通过检查对 `mac_policy_register` 的调用来识别配置这些策略的内核扩展。此外,通过检查扩展的反汇编,也可以找到使用的 `mac_policy_conf` 结构。 请注意,MACF 策略也可以**动态**注册和注销。 `mac_policy_conf` 的主要字段之一是 **`mpc_ops`**。该字段指定策略感兴趣的操作。请注意,它们有数百个,因此可以将所有操作置为零,然后仅选择策略感兴趣的操作。从 [here](https://opensource.apple.com/source/xnu/xnu-2050.18.24/security/mac_policy.h.auto.html): ```c struct mac_policy_ops { mpo_audit_check_postselect_t *mpo_audit_check_postselect; mpo_audit_check_preselect_t *mpo_audit_check_preselect; mpo_bpfdesc_label_associate_t *mpo_bpfdesc_label_associate; mpo_bpfdesc_label_destroy_t *mpo_bpfdesc_label_destroy; mpo_bpfdesc_label_init_t *mpo_bpfdesc_label_init; mpo_bpfdesc_check_receive_t *mpo_bpfdesc_check_receive; mpo_cred_check_label_update_execve_t *mpo_cred_check_label_update_execve; mpo_cred_check_label_update_t *mpo_cred_check_label_update; [...] ``` 几乎所有的钩子在拦截这些操作时都会被 MACF 回调。然而,**`mpo_policy_*`** 钩子是一个例外,因为 `mpo_hook_policy_init()` 是在注册时调用的回调(即在 `mac_policy_register()` 之后),而 `mpo_hook_policy_initbsd()` 是在 BSD 子系统正确初始化后进行晚期注册时调用的。 此外,任何 kext 都可以注册 **`mpo_policy_syscall`** 钩子,以暴露一个私有的 **ioctl** 风格调用 **接口**。然后,用户客户端将能够调用 `mac_syscall` (#381),并指定 **策略名称**、一个整数 **代码** 和可选的 **参数** 作为参数。\ 例如,**`Sandbox.kext`** 经常使用这个。 检查 kext 的 **`__DATA.__const*`** 可以识别在注册策略时使用的 `mac_policy_ops` 结构。可以找到它,因为它的指针在 `mpo_policy_conf` 内部的一个偏移量处,并且因为该区域内将有许多 NULL 指针。 此外,还可以通过从内存中转储结构 **`_mac_policy_list`** 来获取已配置策略的 kext 列表,该结构会随着每个注册的策略而更新。 ## MACF 初始化 MACF 很快就会初始化。它在 XNU 的 `bootstrap_thread` 中设置:在 `ipc_bootstrap` 之后调用 `mac_policy_init()`,该函数初始化 `mac_policy_list`,随后调用 `mac_policy_initmach()`。除了其他功能外,该函数将获取所有在其 Info.plist 中具有 `AppleSecurityExtension` 键的 Apple kext,如 `ALF.kext`、`AppleMobileFileIntegrity.kext`、`Quarantine.kext`、`Sandbox.kext` 和 `TMSafetyNet.kext` 并加载它们。 ## MACF 回调 在代码中常常可以找到对 MACF 的回调定义,例如:**`#if CONFIG_MAC`** 条件块。此外,在这些块内可以找到对 `mac_proc_check*` 的调用,该调用会调用 MACF 来 **检查权限** 以执行某些操作。此外,MACF 回调的格式为:**`mac___opName`**。 对象是以下之一:`bpfdesc`、`cred`、`file`、`proc`、`vnode`、`mount`、`devfs`、`ifnet`、`inpcb`、`mbuf`、`ipq`、`pipe`、`sysv[msg/msq/shm/sem]`、`posix[shm/sem]`、`socket`、`kext`。\ `opType` 通常是 check,用于允许或拒绝该操作。然而,也可以找到 `notify`,这将允许 kext 对给定操作做出反应。 您可以在 [https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/kern/kern_mman.c#L621](https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/kern/kern_mman.c#L621) 中找到一个示例:
int
mmap(proc_t p, struct mmap_args *uap, user_addr_t *retval)
{
[...]
#if CONFIG_MACF
			error = mac_file_check_mmap(vfs_context_ucred(ctx),
			    fp->fp_glob, prot, flags, file_pos + pageoff,
&maxprot);
if (error) {
(void)vnode_put(vp);
goto bad;
}
#endif /* MAC */
[...]
然后,可以在 [https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/security/mac_file.c#L174](https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/security/mac_file.c#L174) 中找到 `mac_file_check_mmap` 的代码。 ```c mac_file_check_mmap(struct ucred *cred, struct fileglob *fg, int prot, int flags, uint64_t offset, int *maxprot) { int error; int maxp; maxp = *maxprot; MAC_CHECK(file_check_mmap, cred, fg, NULL, prot, flags, offset, &maxp); if ((maxp | *maxprot) != *maxprot) { panic("file_check_mmap increased max protections"); } *maxprot = maxp; return error; } ``` 调用 `MAC_CHECK` 宏,其代码可以在 [https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/security/mac_internal.h#L261](https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/security/mac_internal.h#L261) 找到。 ```c /* * MAC_CHECK performs the designated check by walking the policy * module list and checking with each as to how it feels about the * request. Note that it returns its value via 'error' in the scope * of the caller. */ #define MAC_CHECK(check, args...) do { \ error = 0; \ MAC_POLICY_ITERATE({ \ if (mpc->mpc_ops->mpo_ ## check != NULL) { \ DTRACE_MACF3(mac__call__ ## check, void *, mpc, int, error, int, MAC_ITERATE_CHECK); \ int __step_err = mpc->mpc_ops->mpo_ ## check (args); \ DTRACE_MACF2(mac__rslt__ ## check, void *, mpc, int, __step_err); \ error = mac_error_select(__step_err, error); \ } \ }); \ } while (0) ``` 将遍历所有注册的 mac 策略,调用它们的函数并将输出存储在 error 变量中,该变量只能通过成功代码的 `mac_error_select` 进行覆盖,因此如果任何检查失败,整个检查将失败,操作将不被允许。 > [!TIP] > 然而,请记住,并非所有 MACF 调用仅用于拒绝操作。例如,`mac_priv_grant` 调用宏 [**MAC_GRANT**](https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/security/mac_internal.h#L274),如果任何策略返回 0,则将授予请求的特权: > > ```c > /* > * MAC_GRANT 执行指定的检查,通过遍历策略 > * 模块列表并与每个模块检查其对请求的看法。 > * 与 MAC_CHECK 不同,如果任何策略返回 '0',则授予, > * 否则返回 EPERM。请注意,它通过 > * 调用者的作用域中的 'error' 返回其值。 > */ > #define MAC_GRANT(check, args...) do { \ > error = EPERM; \ > MAC_POLICY_ITERATE({ \ > if (mpc->mpc_ops->mpo_ ## check != NULL) { \ > DTRACE_MACF3(mac__call__ ## check, void *, mpc, int, error, int, MAC_ITERATE_GRANT); \ > int __step_res = mpc->mpc_ops->mpo_ ## check (args); \ > if (__step_res == 0) { \ > error = 0; \ > } \ > DTRACE_MACF2(mac__rslt__ ## check, void *, mpc, int, __step_res); \ > } \ > }); \ > } while (0) > ``` ### priv_check & priv_grant 这些调用旨在检查和提供在 [**bsd/sys/priv.h**](https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/sys/priv.h) 中定义的(数十个)**特权**。\ 一些内核代码会使用进程的 KAuth 凭据调用 [**bsd/kern/kern_priv.c**](https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/kern/kern_priv.c) 中的 `priv_check_cred()`,并使用特权代码调用 `mac_priv_check` 以查看是否有任何策略 **拒绝** 授予特权,然后调用 `mac_priv_grant` 以查看是否有任何策略授予该 `privilege`。 ### proc_check_syscall_unix 此钩子允许拦截所有系统调用。在 `bsd/dev/[i386|arm]/systemcalls.c` 中,可以看到声明的函数 [`unix_syscall`](https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/dev/arm/systemcalls.c#L160C1-L167C25),其中包含此代码: ```c #if CONFIG_MACF if (__improbable(proc_syscall_filter_mask(proc) != NULL && !bitstr_test(proc_syscall_filter_mask(proc), syscode))) { error = mac_proc_check_syscall_unix(proc, syscode); if (error) { goto skip_syscall; } } #endif /* CONFIG_MACF */ ``` 将检查调用进程的 **bitmask**,以确定当前的系统调用是否应该调用 `mac_proc_check_syscall_unix`。这是因为系统调用的频率很高,因此避免每次都调用 `mac_proc_check_syscall_unix` 是很有意义的。 请注意,函数 `proc_set_syscall_filter_mask()`,用于设置进程中的 bitmask 系统调用,是由 Sandbox 调用以在沙箱进程上设置掩码的。 ## 暴露的 MACF 系统调用 可以通过在 [security/mac.h](https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/security/mac.h#L151) 中定义的一些系统调用与 MACF 进行交互: ```c /* * Extended non-POSIX.1e interfaces that offer additional services * available from the userland and kernel MAC frameworks. */ #ifdef __APPLE_API_PRIVATE __BEGIN_DECLS int __mac_execve(char *fname, char **argv, char **envv, mac_t _label); int __mac_get_fd(int _fd, mac_t _label); int __mac_get_file(const char *_path, mac_t _label); int __mac_get_link(const char *_path, mac_t _label); int __mac_get_pid(pid_t _pid, mac_t _label); int __mac_get_proc(mac_t _label); int __mac_set_fd(int _fildes, const mac_t _label); int __mac_set_file(const char *_path, mac_t _label); int __mac_set_link(const char *_path, mac_t _label); int __mac_mount(const char *type, const char *path, int flags, void *data, struct mac *label); int __mac_get_mount(const char *path, struct mac *label); int __mac_set_proc(const mac_t _label); int __mac_syscall(const char *_policyname, int _call, void *_arg); __END_DECLS #endif /*__APPLE_API_PRIVATE*/ ``` ## 参考 - [**\*OS 内部结构 第三卷**](https://newosxbook.com/home.html) {{#include ../../../banners/hacktricks-training.md}}