macOS Universal binaries & Mach-O Format
Last updated
Last updated
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Mac OS 二进制文件通常被编译为 universal binaries。一个 universal binary 可以 在同一个文件中支持多种架构。
这些二进制文件遵循 Mach-O 结构,基本上由以下部分组成:
头部
加载命令
数据
使用以下命令搜索文件: mdfind fat.h | grep -i mach-o | grep -E "fat.h$"
头部包含 magic 字节,后面是文件 包含的 架构 的 数量 (nfat_arch
),每个架构将有一个 fat_arch
结构。
使用以下命令检查:
或者使用 Mach-O View 工具:
正如你所想,通常为 2 种架构编译的 universal binary 大小是 为 1 种架构编译的 两倍。
头部包含有关文件的基本信息,例如用于识别它为 Mach-O 文件的 magic 字节和有关目标架构的信息。你可以在以下位置找到它: mdfind loader.h | grep -i mach-o | grep -E "loader.h$"
有不同的文件类型,可以在源代码中找到定义,例如这里。最重要的类型有:
MH_OBJECT
: 可重定位目标文件(编译的中间产品,尚未成为可执行文件)。
MH_EXECUTE
: 可执行文件。
MH_FVMLIB
: 固定虚拟机库文件。
MH_CORE
: 代码转储
MH_PRELOAD
: 预加载的可执行文件(在 XNU 中不再支持)
MH_DYLIB
: 动态库
MH_DYLINKER
: 动态链接器
MH_BUNDLE
: “插件文件”。使用 gcc 中的 -bundle 生成,并由 NSBundle
或 dlopen
显式加载。
MH_DYSM
: 伴随的 .dSym
文件(用于调试的符号文件)。
MH_KEXT_BUNDLE
: 内核扩展。
Or using Mach-O View:
源代码还定义了几个用于加载库的标志:
MH_NOUNDEFS
: 没有未定义的引用(完全链接)
MH_DYLDLINK
: Dyld 链接
MH_PREBOUND
: 动态引用预绑定。
MH_SPLIT_SEGS
: 文件分割只读和读写段。
MH_WEAK_DEFINES
: 二进制文件具有弱定义符号
MH_BINDS_TO_WEAK
: 二进制文件使用弱符号
MH_ALLOW_STACK_EXECUTION
: 使堆栈可执行
MH_NO_REEXPORTED_DYLIBS
: 库没有 LC_REEXPORT 命令
MH_PIE
: 位置无关可执行文件
MH_HAS_TLV_DESCRIPTORS
: 有一个包含线程局部变量的部分
MH_NO_HEAP_EXECUTION
: 堆/数据页面不执行
MH_HAS_OBJC
: 二进制文件具有 oBject-C 部分
MH_SIM_SUPPORT
: 模拟器支持
MH_DYLIB_IN_CACHE
: 用于共享库缓存中的 dylibs/frameworks。
文件在内存中的布局在这里指定,详细说明了 符号表的位置、执行开始时主线程的上下文以及所需的 共享库。向动态加载器 (dyld) 提供了有关二进制文件加载到内存中的过程的指令。
它使用 load_command 结构,定义在提到的 loader.h
中:
There are about 50 different types of load commands that the system handles differently. The most common ones are: LC_SEGMENT_64
, LC_LOAD_DYLINKER
, LC_MAIN
, LC_LOAD_DYLIB
, and LC_CODE_SIGNATURE
.
基本上,这种类型的加载命令定义了如何加载 __TEXT(可执行代码)和 __DATA(进程数据)段,根据数据部分中指示的偏移量在二进制文件执行时。
这些命令定义了段,在进程执行时被映射到虚拟内存空间中。
有不同类型的段,例如**__TEXT段,保存程序的可执行代码,以及__DATA段,包含进程使用的数据。这些段位于Mach-O文件的数据部分**中。
每个段可以进一步划分为多个节。加载命令结构包含关于这些节在各自段中的信息。
在头部,首先找到段头:
Example of segment header:
This header defines the number of sections whose headers appear after it:
示例 节标题:
如果你 添加 节偏移 (0x37DC) + 架构开始的偏移,在这种情况下 0x18000
--> 0x37DC + 0x18000 = 0x1B7DC
也可以通过 命令行 获取 头信息:
常见的由此命令加载的段:
__PAGEZERO
: 它指示内核映射 地址零,以便无法读取、写入或执行。结构中的maxprot和minprot变量设置为零,以指示此页面上没有读写执行权限。
此分配对于缓解NULL指针解引用漏洞非常重要。这是因为XNU强制执行一个硬页面零,确保内存的第一页(仅第一页)不可访问(在i386中除外)。一个二进制文件可以通过制作一个小的__PAGEZERO(使用-pagezero_size
)来满足这些要求,以覆盖前4k,并使其余的32位内存在用户模式和内核模式下均可访问。
__TEXT
: 包含可执行 代码,具有读取和执行权限(不可写)。此段的常见部分:
__text
: 编译的二进制代码
__const
: 常量数据(只读)
__[c/u/os_log]string
: C、Unicode或os日志字符串常量
__stubs
和__stubs_helper
: 在动态库加载过程中涉及
__unwind_info
: 堆栈展开数据。
请注意,所有这些内容都是签名的,但也标记为可执行(为不一定需要此权限的部分(如专用字符串部分)创建更多的利用选项)。
__DATA
: 包含可读和可写的数据(不可执行)。
__got:
全局偏移表
__nl_symbol_ptr
: 非惰性(加载时绑定)符号指针
__la_symbol_ptr
: 惰性(使用时绑定)符号指针
__const
: 应该是只读数据(实际上不是)
__cfstring
: CoreFoundation字符串
__data
: 全局变量(已初始化)
__bss
: 静态变量(未初始化)
__objc_*
(__objc_classlist, __objc_protolist等): Objective-C运行时使用的信息
__DATA_CONST
: __DATA.__const不保证是常量(写权限),其他指针和GOT也不是。此部分使用mprotect
使__const
、一些初始化器和GOT表(解析后)只读。
__LINKEDIT
: 包含链接器(dyld)所需的信息,如符号、字符串和重定位表条目。它是一个通用容器,包含不在__TEXT
或__DATA
中的内容,其内容在其他加载命令中描述。
dyld信息:重定位、非惰性/惰性/弱绑定操作码和导出信息
函数开始:函数起始地址表
代码中的数据:__text中的数据岛
符号表:二进制中的符号
间接符号表:指针/存根符号
字符串表
代码签名
__OBJC
: 包含Objective-C运行时使用的信息。尽管这些信息也可能在__DATA段中找到,在各种__objc_*部分内。
__RESTRICT
: 一个没有内容的段,只有一个名为**__restrict
**(也为空)的单一部分,确保在运行二进制文件时,它将忽略DYLD环境变量。
正如在代码中所看到的,段也支持标志(尽管它们并不常用):
SG_HIGHVM
: 仅核心(未使用)
SG_FVMLIB
: 未使用
SG_NORELOC
: 段没有重定位
SG_PROTECTED_VERSION_1
: 加密。例如,Finder用于加密文本__TEXT
段。
LC_UNIXTHREAD/LC_MAIN
LC_MAIN
包含entryoff属性中的入口点。在加载时,dyld简单地将此值添加到(内存中的)二进制文件基址,然后跳转到此指令以开始执行二进制代码。
**LC_UNIXTHREAD
包含启动主线程时寄存器必须具有的值。这已经被弃用,但dyld
**仍然使用它。可以通过以下方式查看此设置的寄存器值:
LC_CODE_SIGNATURE
包含有关 Mach-O 文件的代码签名 的信息。它仅包含一个 偏移量,指向 签名 blob。这通常位于文件的最末尾。 然而,您可以在 这篇博客文章 和这个 gists 中找到有关此部分的一些信息。
LC_ENCRYPTION_INFO[_64]
支持二进制加密。然而,当然,如果攻击者设法破坏了该进程,他将能够以未加密的方式转储内存。
LC_LOAD_DYLINKER
包含 动态链接器可执行文件的路径,该文件将共享库映射到进程地址空间。值始终设置为 /usr/lib/dyld
。重要的是要注意,在 macOS 中,dylib 映射发生在 用户模式,而不是内核模式。
LC_IDENT
过时,但当配置为在崩溃时生成转储时,会创建一个 Mach-O 核心转储,并在 LC_IDENT
命令中设置内核版本。
LC_UUID
随机 UUID。它对任何直接的事情都很有用,但 XNU 会将其与其他进程信息一起缓存。它可以在崩溃报告中使用。
LC_DYLD_ENVIRONMENT
允许在进程执行之前向 dyld 指示环境变量。这可能非常危险,因为它可能允许在进程内部执行任意代码,因此此加载命令仅在使用 #define SUPPORT_LC_DYLD_ENVIRONMENT
构建的 dyld 中使用,并进一步限制处理仅限于形式为 DYLD_..._PATH
的变量,指定加载路径。
LC_LOAD_DYLIB
此加载命令描述了一个 动态 库 依赖关系,指示 加载器 (dyld) 加载和链接该库。每个 Mach-O 二进制文件所需的库都有一个 LC_LOAD_DYLIB
加载命令。
此加载命令是 dylib_command
类型的结构(其中包含一个描述实际依赖动态库的 struct dylib):
您也可以通过命令行获取此信息:
一些潜在的与恶意软件相关的库包括:
DiskArbitration: 监控 USB 驱动器
AVFoundation: 捕获音频和视频
CoreWLAN: Wifi 扫描。
Mach-O 二进制文件可以包含一个或 多个 构造函数,这些构造函数将在 LC_MAIN 指定的地址 之前 被 执行。 任何构造函数的偏移量保存在 __mod_init_func 部分的 __DATA_CONST 段中。
文件的核心是数据区域,由加载命令区域定义的几个段组成。每个段中可以容纳多种数据部分,每个部分 包含特定类型的代码或数据。
数据基本上是包含所有由加载命令 LC_SEGMENTS_64 加载的 信息 的部分。
这包括:
函数表: 包含有关程序函数的信息。
符号表: 包含有关二进制文件使用的外部函数的信息
它还可以包含内部函数、变量名称等。
要检查它,您可以使用 Mach-O View 工具:
或者从命令行:
在 __TEXT
段 (r-x):
__objc_classname
: 类名 (字符串)
__objc_methname
: 方法名 (字符串)
__objc_methtype
: 方法类型 (字符串)
在 __DATA
段 (rw-):
__objc_classlist
: 所有 Objective-C 类的指针
__objc_nlclslist
: 非懒加载 Objective-C 类的指针
__objc_catlist
: 类别的指针
__objc_nlcatlist
: 非懒加载类别的指针
__objc_protolist
: 协议列表
__objc_const
: 常量数据
__objc_imageinfo
, __objc_selrefs
, objc__protorefs
...
_swift_typeref
, _swift3_capture
, _swift3_assocty
, _swift3_types, _swift3_proto
, _swift3_fieldmd
, _swift3_builtin
, _swift3_reflstr
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)