macOS 进程滥用

Reading time: 21 minutes

tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)

支持 HackTricks

进程基本信息

进程是正在运行的可执行文件的实例,但进程并不运行代码,这些是线程。因此,进程只是运行线程的容器,提供内存、描述符、端口、权限等...

传统上,进程是在其他进程中启动的(除了 PID 1),通过调用 fork 创建当前进程的精确副本,然后子进程通常会调用 execve 来加载新的可执行文件并运行它。随后,引入了 vfork 以加快此过程而无需任何内存复制。
然后引入了 posix_spawn,将 vforkexecve 结合在一个调用中,并接受标志:

  • POSIX_SPAWN_RESETIDS: 将有效 ID 重置为真实 ID
  • POSIX_SPAWN_SETPGROUP: 设置进程组归属
  • POSUX_SPAWN_SETSIGDEF: 设置信号默认行为
  • POSIX_SPAWN_SETSIGMASK: 设置信号掩码
  • POSIX_SPAWN_SETEXEC: 在同一进程中执行(类似于 execve,但有更多选项)
  • POSIX_SPAWN_START_SUSPENDED: 启动时挂起
  • _POSIX_SPAWN_DISABLE_ASLR: 无 ASLR 启动
  • _POSIX_SPAWN_NANO_ALLOCATOR: 使用 libmalloc 的 Nano 分配器
  • _POSIX_SPAWN_ALLOW_DATA_EXEC: 允许数据段的 rwx
  • POSIX_SPAWN_CLOEXEC_DEFAULT: 默认情况下在 exec(2) 时关闭所有文件描述符
  • _POSIX_SPAWN_HIGH_BITS_ASLR: 随机化 ASLR 滑动的高位

此外,posix_spawn 允许指定一个 posix_spawnattr 数组,以控制生成进程的某些方面,以及 posix_spawn_file_actions 来修改描述符的状态。

当一个进程终止时,它会通过信号 SIGCHLD返回代码发送给父进程(如果父进程已终止,则新父进程为 PID 1)。父进程需要通过调用 wait4()waitid() 来获取此值,直到那时,子进程保持在僵尸状态,仍然被列出但不消耗资源。

PIDs

PID,进程标识符,标识一个唯一的进程。在 XNU 中,PIDs64 位,单调递增且 永不回绕(以避免滥用)。

进程组、会话和联盟

进程 可以被插入到 中,以便更容易处理它们。例如,shell 脚本中的命令将处于同一进程组中,因此可以使用 kill 等方式 一起发送信号
也可以 将进程分组到会话中。当一个进程启动一个会话(setsid(2))时,子进程被设置在该会话中,除非它们启动自己的会话。

联盟是另一种在 Darwin 中分组进程的方式。加入联盟的进程可以访问池资源,共享账本或面对 Jetsam。联盟有不同的角色:领导者、XPC 服务、扩展。

凭证与角色

每个进程都持有 凭证,以 识别其在系统中的权限。每个进程将有一个主要的 uid 和一个主要的 gid(尽管可能属于多个组)。
如果二进制文件具有 setuid/setgid 位,也可以更改用户和组 ID。
有几个函数可以 设置新的 uids/gids

系统调用 persona 提供了一组 替代凭证。采用一个角色会同时假定其 uid、gid 和组成员资格。在 源代码 中可以找到该结构:

c
struct kpersona_info { uint32_t persona_info_version;
uid_t    persona_id; /* overlaps with UID */
int      persona_type;
gid_t    persona_gid;
uint32_t persona_ngroups;
gid_t    persona_groups[NGROUPS];
uid_t    persona_gmuid;
char     persona_name[MAXLOGNAME + 1];

/* TODO: MAC policies?! */
}

线程基本信息

  1. POSIX 线程 (pthreads): macOS 支持 POSIX 线程 (pthreads),这是 C/C++ 的标准线程 API 的一部分。macOS 中 pthreads 的实现位于 /usr/lib/system/libsystem_pthread.dylib,该库来自公开可用的 libpthread 项目。此库提供创建和管理线程所需的函数。
  2. 创建线程: pthread_create() 函数用于创建新线程。在内部,此函数调用 bsdthread_create(),这是一个特定于 XNU 内核的低级系统调用(macOS 基于的内核)。此系统调用接受来自 pthread_attr(属性)的各种标志,这些标志指定线程行为,包括调度策略和堆栈大小。
  • 默认堆栈大小: 新线程的默认堆栈大小为 512 KB,足以满足典型操作,但如果需要更多或更少的空间,可以通过线程属性进行调整。
  1. 线程初始化: __pthread_init() 函数在线程设置过程中至关重要,利用 env[] 参数解析环境变量,这些变量可以包含有关堆栈位置和大小的详细信息。

macOS 中的线程终止

  1. 退出线程: 线程通常通过调用 pthread_exit() 来终止。此函数允许线程干净地退出,执行必要的清理,并允许线程将返回值发送回任何加入者。
  2. 线程清理: 调用 pthread_exit() 后,将调用 pthread_terminate() 函数,该函数处理所有相关线程结构的移除。它会释放 Mach 线程端口(Mach 是 XNU 内核中的通信子系统),并调用 bsdthread_terminate,这是一个移除与线程相关的内核级结构的系统调用。

同步机制

为了管理对共享资源的访问并避免竞争条件,macOS 提供了几种同步原语。这些在多线程环境中至关重要,以确保数据完整性和系统稳定性:

  1. 互斥锁:
  • 常规互斥锁 (签名: 0x4D555458): 标准互斥锁,内存占用为 60 字节(互斥锁 56 字节,签名 4 字节)。
  • 快速互斥锁 (签名: 0x4d55545A): 类似于常规互斥锁,但经过优化以实现更快的操作,大小也为 60 字节。
  1. 条件变量:
  • 用于等待某些条件的发生,大小为 44 字节(40 字节加 4 字节签名)。
  • 条件变量属性 (签名: 0x434e4441): 条件变量的配置属性,大小为 12 字节。
  1. 一次变量 (签名: 0x4f4e4345):
  • 确保一段初始化代码仅执行一次。其大小为 12 字节。
  1. 读写锁:
  • 允许多个读者或一个写者同时访问,促进对共享数据的高效访问。
  • 读写锁 (签名: 0x52574c4b): 大小为 196 字节。
  • 读写锁属性 (签名: 0x52574c41): 读写锁的属性,大小为 20 字节。

tip

这些对象的最后 4 字节用于检测溢出。

线程局部变量 (TLV)

线程局部变量 (TLV) 在 Mach-O 文件(macOS 中可执行文件的格式)的上下文中用于声明特定于 每个线程 的变量,以便在多线程应用程序中使用。这确保每个线程都有自己单独的变量实例,提供了一种避免冲突并维护数据完整性的方法,而无需像互斥锁那样的显式同步机制。

在 C 及相关语言中,可以使用 __thread 关键字声明线程局部变量。以下是您示例中的工作原理:

c
cCopy code__thread int tlv_var;

void main (int argc, char **argv){
tlv_var = 10;
}

这个片段将 tlv_var 定义为线程局部变量。每个运行此代码的线程将拥有自己的 tlv_var,一个线程对 tlv_var 的更改不会影响另一个线程中的 tlv_var

在 Mach-O 二进制文件中,与线程局部变量相关的数据被组织成特定的部分:

  • __DATA.__thread_vars: 此部分包含有关线程局部变量的元数据,如它们的类型和初始化状态。
  • __DATA.__thread_bss: 此部分用于未显式初始化的线程局部变量。它是为零初始化数据保留的内存的一部分。

Mach-O 还提供了一个特定的 API,称为 tlv_atexit,用于管理线程退出时的线程局部变量。此 API 允许您 注册析构函数——在线程终止时清理线程局部数据的特殊函数。

线程优先级

理解线程优先级涉及查看操作系统如何决定哪些线程运行以及何时运行。这个决定受到分配给每个线程的优先级级别的影响。在 macOS 和类 Unix 系统中,这通过 nicerenice 和服务质量 (QoS) 类等概念来处理。

Nice 和 Renice

  1. Nice:
  • 进程的 nice 值是一个影响其优先级的数字。每个进程都有一个范围从 -20(最高优先级)到 19(最低优先级)的 nice 值。进程创建时的默认 nice 值通常为 0。
  • 较低的 nice 值(接近 -20)使进程更“自私”,相对于其他具有较高 nice 值的进程,给予其更多的 CPU 时间。
  1. Renice:
  • renice 是一个用于更改已运行进程的 nice 值的命令。这可以用于动态调整进程的优先级,基于新的 nice 值增加或减少其 CPU 时间分配。
  • 例如,如果一个进程暂时需要更多的 CPU 资源,您可能会使用 renice 降低其 nice 值。

服务质量 (QoS) 类

QoS 类是处理线程优先级的更现代的方法,特别是在支持 Grand Central Dispatch (GCD) 的 macOS 等系统中。QoS 类允许开发人员根据任务的重要性或紧急性将工作 分类 为不同级别。macOS 根据这些 QoS 类自动管理线程优先级:

  1. 用户交互:
  • 此类用于当前与用户交互或需要立即结果以提供良好用户体验的任务。这些任务被赋予最高优先级,以保持界面的响应性(例如,动画或事件处理)。
  1. 用户发起:
  • 用户发起并期望立即结果的任务,例如打开文档或单击需要计算的按钮。这些任务优先级高,但低于用户交互。
  1. 实用程序:
  • 这些任务是长时间运行的,通常显示进度指示器(例如,下载文件、导入数据)。它们的优先级低于用户发起的任务,不需要立即完成。
  1. 后台:
  • 此类用于在后台运行且对用户不可见的任务。这些可以是索引、同步或备份等任务。它们的优先级最低,对系统性能的影响最小。

使用 QoS 类,开发人员不需要管理确切的优先级数字,而是专注于任务的性质,系统会相应优化 CPU 资源。

此外,还有不同的 线程调度策略,用于指定调度器将考虑的一组调度参数。这可以通过 thread_policy_[set/get] 来完成。这在竞争条件攻击中可能会很有用。

MacOS 进程滥用

MacOS 像其他操作系统一样,提供多种方法和机制供 进程交互、通信和共享数据。虽然这些技术对系统的高效运行至关重要,但也可能被威胁行为者滥用以 执行恶意活动

库注入

库注入是一种技术,攻击者 强制进程加载恶意库。一旦注入,库将在目标进程的上下文中运行,攻击者将获得与该进程相同的权限和访问权限。

macOS Library Injection

函数钩子

函数钩子涉及 拦截软件代码中的函数调用 或消息。通过钩住函数,攻击者可以 修改进程的行为、观察敏感数据,甚至控制执行流程。

macOS Function Hooking

进程间通信

进程间通信 (IPC) 指的是不同方法,通过这些方法,独立进程 共享和交换数据。虽然 IPC 对许多合法应用程序至关重要,但也可能被滥用以破坏进程隔离、泄露敏感信息或执行未经授权的操作。

macOS IPC - Inter Process Communication

Electron 应用程序注入

使用特定环境变量执行的 Electron 应用程序可能容易受到进程注入的攻击:

macOS Electron Applications Injection

Chromium 注入

可以使用标志 --load-extension--use-fake-ui-for-media-stream 执行 浏览器中的人攻击,从而窃取击键、流量、cookie,在页面中注入脚本...:

macOS Chromium Injection

脏 NIB

NIB 文件 定义用户界面 (UI) 元素 及其在应用程序中的交互。然而,它们可以 执行任意命令,而且 Gatekeeper 不会阻止 已执行的应用程序在 NIB 文件被修改 后继续执行。因此,它们可以用于使任意程序执行任意命令:

macOS Dirty NIB

Java 应用程序注入

可以滥用某些 Java 功能(如 _JAVA_OPTS 环境变量)使 Java 应用程序执行 任意代码/命令

macOS Java Applications Injection

.Net 应用程序注入

可以通过 滥用 .Net 调试功能(未受到 macOS 保护,如运行时强化)向 .Net 应用程序注入代码。

macOS .Net Applications Injection

Perl 注入

检查不同选项以使 Perl 脚本执行任意代码:

macOS Perl Applications Injection

Ruby 注入

也可以滥用 Ruby 环境变量使任意脚本执行任意代码:

macOS Ruby Applications Injection

Python 注入

如果环境变量 PYTHONINSPECT 被设置,Python 进程将在完成后进入 Python CLI。也可以使用 PYTHONSTARTUP 指定在交互会话开始时执行的 Python 脚本。
然而,请注意,当 PYTHONINSPECT 创建交互会话时,PYTHONSTARTUP 脚本不会被执行。

其他环境变量如 PYTHONPATHPYTHONHOME 也可能对执行任意代码的 Python 命令有用。

请注意,使用 pyinstaller 编译的可执行文件即使在使用嵌入式 Python 运行时也不会使用这些环境变量。

caution

总的来说,我找不到通过滥用环境变量使 Python 执行任意代码的方法。
然而,大多数人使用 Hombrew 安装 Python,这将把 Python 安装在 可写位置,供默认管理员用户使用。您可以通过以下方式劫持它:

mv /opt/homebrew/bin/python3 /opt/homebrew/bin/python3.old
cat > /opt/homebrew/bin/python3 <<EOF
#!/bin/bash
# 额外劫持代码
/opt/homebrew/bin/python3.old "$@"
EOF
chmod +x /opt/homebrew/bin/python3

即使是 root 在运行 Python 时也会执行此代码。

检测

Shield

Shield (Github) 是一个开源应用程序,可以 检测和阻止进程注入 行为:

  • 使用 环境变量: 它将监控以下任何环境变量的存在:DYLD_INSERT_LIBRARIESCFNETWORK_LIBRARY_PATHRAWCAMERA_BUNDLE_PATHELECTRON_RUN_AS_NODE
  • 使用 task_for_pid 调用: 查找一个进程何时想要获取 另一个进程的任务端口,这允许在该进程中注入代码。
  • Electron 应用程序参数: 有人可以使用 --inspect--inspect-brk--remote-debugging-port 命令行参数以调试模式启动 Electron 应用程序,从而注入代码。
  • 使用 符号链接硬链接: 通常最常见的滥用是 放置一个具有我们用户权限的链接,并 指向一个更高权限 的位置。对于硬链接和符号链接,检测非常简单。如果创建链接的进程与目标文件具有 不同的权限级别,我们会创建一个 警报。不幸的是,在符号链接的情况下,阻止是不可能的,因为我们在创建之前没有关于链接目标的信息。这是 Apple 的 EndpointSecuriy 框架的一个限制。

其他进程发出的调用

这篇博客文章 中,您可以找到如何使用函数 task_name_for_pid 获取有关其他 进程注入代码到一个进程 的信息,然后获取有关该其他进程的信息。

请注意,要调用该函数,您需要与运行该进程的 相同 uidroot(并且它返回有关进程的信息,而不是注入代码的方法)。

参考文献

tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)

支持 HackTricks