macOS Universal binaries & Mach-O Format
基本信息
Mac OS二进制文件通常被编译为通用二进制文件。通用二进制文件可以在同一文件中支持多个架构。
这些二进制文件遵循Mach-O结构,基本上由以下部分组成:
头部(Header)
装载命令(Load Commands)
数据(Data)
Fat Header
使用以下命令搜索包含:mdfind fat.h | grep -i mach-o | grep -E "fat.h$"
头部包含魔数字节,后跟文件包含的架构数(nfat_arch
),每个架构都将有一个fat_arch
结构。
使用以下命令检查:
或使用Mach-O View工具:
正如你可能在想的那样,通常为2个架构编译的通用二进制文件会使大小翻倍,相比于只为1个架构编译的情况。
Mach-O头部
头部包含有关文件的基本信息,例如用于识别其为Mach-O文件的魔数字节以及有关目标架构的信息。您可以在以下位置找到它:mdfind loader.h | grep -i mach-o | grep -E "loader.h$"
Mach-O 文件类型
有不同的文件类型,你可以在这里的源代码中找到它们的定义。最重要的类型包括:
MH_OBJECT
: 可重定位目标文件(编译的中间产品,还不是可执行文件)。MH_EXECUTE
: 可执行文件。MH_FVMLIB
: 固定虚拟内存库文件。MH_CORE
: 代码转储。MH_PRELOAD
: 预加载的可执行文件(在 XNU 中不再支持)。MH_DYLIB
: 动态库。MH_DYLINKER
: 动态链接器。MH_BUNDLE
: "插件文件"。使用 -bundle 在 gcc 中生成,并由NSBundle
或dlopen
显式加载。MH_DYSM
: 伴随的.dSym
文件(带有用于调试的符号的文件)。MH_KEXT_BUNDLE
: 内核扩展。
或者使用Mach-O View:
Mach-O 标志
源代码还定义了几个对加载库有用的标志:
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
: 二进制文件具有 Objective-C 部分MH_SIM_SUPPORT
: 模拟器支持MH_DYLIB_IN_CACHE
: 在共享库缓存中使用的 dylibs/frameworks。
Mach-O 加载命令
在这里指定了文件在内存中的布局,详细说明了符号表的位置,执行开始时主线程的上下文以及所需的共享库。提供了有关二进制文件加载到内存中的过程的指令给动态加载器**(dyld)**。
使用了在提到的**loader.h
中定义的load_command**结构。
有大约50种不同类型的加载命令,系统会以不同方式处理。最常见的是:LC_SEGMENT_64
、LC_LOAD_DYLINKER
、LC_MAIN
、LC_LOAD_DYLIB
和LC_CODE_SIGNATURE
。
LC_SEGMENT/LC_SEGMENT_64
基本上,这种类型的加载命令定义了在执行二进制文件时,根据数据部分中指示的偏移量,如何加载**__TEXT**(可执行代码)和**__DATA**(进程数据)段。
这些命令定义了在执行过程中映射到进程的虚拟内存空间中的段。
有不同类型的段,比如**__TEXT段,保存程序的可执行代码,以及__DATA段,包含进程使用的数据。这些段位于Mach-O文件的数据部分**中。
每个段可以进一步划分为多个 区段。加载命令结构包含了关于各自段内的这些区段的信息。
在头部首先找到段头:
段头的示例:
此头部定义了其后出现的区段头的数量:
章节标题示例:
如果您将节偏移量(0x37DC)与arch开始的偏移量相加,在本例中为0x18000
--> 0x37DC + 0x18000 = 0x1B7DC
还可以通过命令行获取头信息。
以下是由此命令加载的常见段:
__PAGEZERO
: 它指示内核映射****地址零,因此无法从中读取、写入或执行。结构中的maxprot和minprot变量设置为零,表示此页面上没有读写执行权限。此分配对于缓解空指针解引用漏洞很重要。这是因为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_UNIXTHREAD/LC_MAIN
LC_MAIN
包含entryoff属性中的入口点。在加载时,dyld只需将此值添加到(内存中的)二进制文件的基址,然后跳转到此指令以开始执行二进制文件的代码。
LC_UNIXTHREAD
包含启动主线程时寄存器必须具有的值。这已经被弃用,但**dyld
**仍在使用它。可以通过以下方式查看此设置的寄存器的值:
LC_CODE_SIGNATURE
LC_CODE_SIGNATURE
包含有关 Mach-O 文件的代码签名的信息。它只包含一个指向签名 blob 的偏移量。这通常位于文件的末尾。 但是,您可以在此博客文章和这个gists中找到关于此部分的一些信息。
LC_ENCRYPTION_INFO[_64]
LC_ENCRYPTION_INFO[_64]
支持二进制加密。但是,当然,如果攻击者设法 compromise 进程,他将能够以未加密的方式 dump 内存。
LC_LOAD_DYLINKER
LC_LOAD_DYLINKER
包含动态链接器可执行文件的路径,将共享库映射到进程地址空间。值始终设置为 /usr/lib/dyld
。重要的是要注意,在 macOS 中,dylib 映射发生在用户模式,而不是内核模式。
LC_IDENT
LC_IDENT
已过时,但当配置为在 panic 时生成 dumps 时,将创建一个 Mach-O 核心 dump,并在 LC_IDENT
命令中设置内核版本。
LC_UUID
LC_UUID
随机 UUID。它本身没有直接用途,但 XNU 会将其与进程信息的其余部分一起缓存。它可用于崩溃报告。
LC_DYLD_ENVIRONMENT
LC_DYLD_ENVIRONMENT
允许在进程执行之前指定 dyld 的环境变量。这可能非常危险,因为它可以允许在进程内部执行任意代码,因此此加载命令仅在使用 #define SUPPORT_LC_DYLD_ENVIRONMENT
构建的 dyld 中使用,并进一步限制处理仅限于形式为 DYLD_..._PATH
的变量,指定加载路径。
LC_LOAD_DYLIB
LC_LOAD_DYLIB
此加载命令描述了动态库依赖项,指示加载器(dyld)加载和链接该库。Mach-O 二进制文件所需的每个库都有一个 LC_LOAD_DYLIB
加载命令。
此加载命令是**
dylib_command
**类型的结构(其中包含一个描述实际依赖动态库的 struct dylib):
您也可以通过命令行获得此信息:
一些潜在的与恶意软件相关的库包括:
DiskArbitration:监控USB驱动器
AVFoundation:捕获音频和视频
CoreWLAN:Wifi扫描。
Mach-O二进制文件可以包含一个或多个构造函数,这些函数将在LC_MAIN指定的地址之前执行。 任何构造函数的偏移量都保存在**__DATA_CONST段的__mod_init_func**部分中。
Mach-O数据
文件的核心是数据区域,由加载命令区域中定义的几个段组成。每个段中可以包含各种数据部分,每个部分保存特定类型的代码或数据。
数据基本上是包含在加载命令LC_SEGMENTS_64加载的所有信息的部分。
这包括:
函数表:保存有关程序函数的信息。
符号表:包含二进制文件使用的外部函数的信息
它还可以包含内部函数、变量名称等等。
要检查它,您可以使用Mach-O View工具:
或者从命令行界面:
Objective-C常见部分
在__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
_swift_typeref
,_swift3_capture
,_swift3_assocty
,_swift3_types
,_swift3_proto
,_swift3_fieldmd
,_swift3_builtin
,_swift3_reflstr
Last updated