OS internals 卷一 读书笔记 - 1-7章

199 阅读1小时+

第一章 Darwinism

10.9 Mavericks

对应iOS7,添加了很多特性

  • 电量管理:自动休眠功能+定时器合并
  • compressed memory的重引进
  • 多线程引入了线程QoS机制,朝实时操作系统更进一步
  • kernel extensions添加了code signature

10.10

  • Javascript for Automation (JXA)
  • 引入"Continuity" feature(a.k.ahandoff),允许iOS和MacOS之间的无缝操作(一个系统中start document而在另一个系统中finish)

10.12: Sierra

  • Siri首次亮相
  • os_log
  • APFS取代HFS+
  • node linking mnl_ API

10.13: High Sierra

  • 成熟的APFS
  • 支持APFS volume的EFI protocol
  • Remote XPC,使XPC消息可以通过IPv6传递(newer MacBooks and iMacs与embedded "BridgeOS"通信的协议)

10.14: Mojave

  • Dark Mode
  • kernel scheduling model(turnstiles和priority queues)和Grand Central Dispatcher
  • 用户态 memory magazine allocation(第8章)进入了内核,引入了Zone Cache(II/11)
  • iOS/Mac 融合中,iOS UIKit等重要frameworks引入了Mojave,使得iOS app编译后可以在Mac 模拟器之外运行

10.15: Catalina

  • root filesystem重分区到两个volume中,root filesystem继续readonly,user data挂载到/private/var/,这个方式类似于*OS,在第3章中可以看到这是一个更安全更有弹性的策略
  • DriverKit.framework,SystemExtensions.framework,用于支持用户态内核extension,虽然有点矛盾,但思路是将IOKit的部分功能移出内核,使第三方可以创建family device drivers(NICs, USB, HID等),同时还不用访问内核空间。用户态 extension framework, EndpointSecurity. kext, and它associated的kext 代替了the venerable KAuth (described in III/3). These, along with the already supported Hypervisor.framework (from 10.10) ,可以看出apple目标是消除第三方创建内核extension的需求

iOS 5

  • 引入iMessage,支持通过互联网发送短信
  • 引入iCloud,苹果的云存储产品
  • Siri,Siri的大脑是在云端,断网的时候Siri就完全无能为力了
  • Over- The-Air (OTA) 更新iOS

iOS 7

  • 桌面图片由三D立体转向了扁平图标

iOS 8

  • 手机尺寸开始变大
  • 苹果设备之间的continuity,即通过iCloud 允许文档和task在iDevice和Mac之间移动
  • HomeKit 进军 IoT
  • Health.app 进军健康监测领域
  • application extension,允许第三方在 自定义键盘,VPN等场景提供对应扩展

iOS 9

  • 3D touch
  • Kernel Patch Protection (KPP, or "WatchTower")

iOS 10 WhiteTail

  • raise to wake解锁

iOS 11

  • ARKit
  • feature更倾向iPad,让UI与mac渐渐融合: 包括加入dock,files app(类Finder),改进drag&drop
  • 发布3款设备 8/8+ X(OLED+FaceID(内部名pearl id))

iOS 12 - X is the new iPhone

  • UI addition: Screen Time
  • Siri was extended with "Shortcuts" (automated user programmable actions, similar to Amazon Alexa's "skills")
  • iPhone X[/r] series,Apple Watch Series 4,这些是arm64e,这个架构带来了新物理页保护机制physical page protection mechanism (PPL),同时实现了armv8.3(支持authenticated pointers, which - while entirely unnoticeable by developers, have profound implications for system security)
  • 为iOS引入了新kernel extension:CoreTrust(to perform code signing certificate validation in kernel.)

iOS 13 - Dark Mode,"iPadOS"

  • iPadOS:多窗口,mouse和pointer支持,向app暴露USB mass storage
  • 抛弃了A7-A8设备(5S/6)这些4KB页而非16KB页的设备

eOS BridgeOS

  • "Embedded OS" (or eOS) - TouchBar, MacBook Pro中Touch Bar是由独立的Armv7k处理器(T1)控制的,The MacOS host and the eOS guest communicate over a dedicated IPv6 network interface, through entitled daemons.私有DR* frameworks- DFRFoundation, DFRDisplay and DFRBrightness- provide the APIs used by Touch Bar clients. Touch Bar于its host而言是一个USB HID (Human Interface Device) keyboard, with programmable keys. The coprocessor也有自己的Secure Enclave Processor (SEP, detailed in Volume II), which allows for Touch ID
  • The iMac Pro, 本书initial版印刷后发布的, has "OS 2.0",重命名为"BridgeOS",它虽然没有Touch Bar但也有一个独立的coprocessor(ARM64 T8010-family ("A10" variant)),用以支持secure boot and Face ID on the Mac (thankst o the SEP coprocessor).MacBook Pros 2018, BridgeOS负责处理boot sequence (from the familiar boot chime, now in its /us/libexec/chimed, to EFI bootloader verification, as explained in Volume II) 及管理摄像头

i-Devices模拟器

模拟器的core是XCode CoreSimulator私有framework的一个组件,它启动com.apple. CoreSimulator.CoreSimulatorService 这个XPC服务,而这个framework又依赖另一个Xcode私有framework:Simulatorkit

Emulating iOS(iOS仿真)

iOS仿真 将*OS运行在通用boards(比如BeagleBoard),甚至运行在一个QEMU窗口中

第二章 E Pluribus Unum: The *OS Architecture

*OS经历了很长的演化,从史前的MacOS9到更近的祖先NeXTSTEP,自此之后又有更多的改动包括OC 2.0,以及向Mach和BSD层中添加了更多的新traps和proprietary system calls

Architecture

苹果official视角的*OS体系架构

苹果*OS体系架构.png

Application Layer:直接的UI比如:Aqua、Dashboard、Spotligt、Accessibility,主要的component是cocoa framework(移动端是MobileCocoa),提供AppKit的API并重新导出底层的Foundation和CoreData

Media Frameworks:包括Graphics和Audio frameworks,包括AVFoundation, CoreAudio, Corelmage and CoreAnimation, 以及它们的底层支持 - OpenAL, OpenGL and Quartz

Core Services:包括applications和processes的支持性frameworks, 对UI没有直接效果. 包括Foundation framework (NS* APIs) and its modern successor, CoreFoundation (CF* APIs), 以及frameworks.

Core OS:提供基本的OS APIs. 包括OpenDirectory, SystemConfiguration和一些其它frameworks

Kernel and Device Drivers:包括XNU和所有kernel extensions

上面这五层架构过于简单化了整个复杂的体系结构,漏掉了一些重要的组件,并模糊了各层边界,这里举几个例子:

  • Core和Application frameworks的区分很模糊,实际上, Apple自己将frameworks分为"Private" or not. 前者是undocumented, with APIs intended for Apple's内部使用,但后者是开放给第三方使用, 同时提供少量的文档
  • runtime环境 - Obiective-C and Swift被从官方视角中omitted, 但它很重要,它遍及整个体系架构
  • "Core OS" and "Kernel"并不仅是两层 (也不是其它文档中叫做"Darwin"的单层), 而是一个庞杂的多个项目代码库, 由三方libraries, Apple's自己的libraries (大部分但并不是全部是开源的), 以及内核XNU组成. XNU 自身又是由多个components组成(本章及 Volume I会更详细介绍)

更精确的图应该是这样

*OS架构图.png

这个图使用Venn图来描绘,左右边圆代表MacOS,右边的圆代表iOS,中间交叠的部分是相同的,两边是不同的,不过iOS变体自身也有些微差异(比如home App,也就是说iOS的SpringBoard .vs. WatchOS的Carousel或者TVOS的HeadBoard)),这些小差异在图中刻意隐去了

正如上图所示, Darwin仍可认为是分层的, 甚至某种程度上说不同于Apple的分层:

  • Applications 并不是体系中必备的部分(所以从上图中拿掉了), 但Apple's OSes尽管如此需要一个定义良好的structure. 这个structure,叫做application bundle, 是app二进制和其它资源的一个特定目录
  • Frameworks 也是bundles, 包含动态链接库和其它资源(包括support daemons, data files, 和其它subframeworks)
  • Runtime Environments: 一部分特定的动态库选定为runtime environments. 其中两个让人熟知的是Objective-C (libobjc.dylib)和组成Swift Runtime的无数dylibs
  • Third Party Libraries: 包括很多Apple附加进Darwin的开源项目,最著名的是SQLite3, 为 *OS frameworks和一些applications提供数据存储
  • Darwin Libraries: 是其一部分并专属于Darwin的libraries. 其中大部分是开源的, 但有些(包括尤其是libXPC和libtrace)不是
  • XNU: 是core of Darwin的kernel. 像其它现代kernels一样, XNU lives在一个separate addressspace, and runs at a privileged execution level. kernel自己就是值得用一本书介绍 (在 Volume II中的重要一部分). 本章只提供一个简单的概览

交叠区域之外的components是特定于Darwin flavor的. 最明显的一个是UI: MacOS's Aqua对应于iOS's SpringBoard, TVOS's PineBoard, WatchOS's Carousel, PodOS's SoundBoard. iOS and TvOS除此之外很相似, 但WatchOS是两者的进一步裁剪版. MacOS 有Apple长期选择不移植到*OS variants中的components - 尤其是DTrace (15章)和 Auditing (III/2). boot loader 也不同- MacOS使用EFI bootloader, 而所有*OS variants使用Apple's custom iBoot (II/2)

这一章聚焦于相同的部分而不是不同部分, 余下的其它章会发散来讨论这些不同部分. 开始讨论各层之前, 先解释一下property lists,是所有体系架构中都存在的一种文件格式

小插曲:plist

Plists是NeXTSTEP另一项遗产,而且它的XML格式已经归于Apple,现在最常见的表示格式是XML,遵守如下语法:

  • !DOCTYPE常量定义, document类型定义指向 www.apple.com/DTDs/Proper… 这个URL指向一个XML DTD
  • XML的根元素总是 . 也是唯一的有attributes的元素, 只有一个叫做version的attribute,而且其值总是1.0
  • 其它XML元素是, 但因为它不能拥有attributes, 所以其元素值作为key名, 后面跟的是实际的value, 使用下表中的任一datatype

image.png

binary plist

XML格式机器处理起来不方便,需要做很多解析并且占据很多字节,所以二进制格式很有必要,通常在*OS中比较常见

bplist的魔数是bplist00

image.png

有另两种二进制plist是bplist15和bplist16

JSON plist

Applications

Application是NeXTSTEP中"App"的概念,是特殊类型的Bundle,一个有着固定目录结构的package,包含application's执行文件, supporting files - 比如私有dylibs, frameworks, icons和其它UI Elements. MacOS热心地在下图中附了fixed structure. *OS less so, omitting Contents/ and collapses Resources/ and the executable into the aplication root directory.

image.png

Frameworks

Frameworks是Apple OS中关键的概念, 是NeXTSTEP的重要遗产. framework定义为"... a shared library packaged with associated resources, such as headers, localized strings, and documentation, installed in a standard folder hierarchy, usually in a standard location in the file system", 换句话说framework is a bundled shared library (dylib), 也就是除了代码还包含各种各样的related files (通常位于Resources/ 目录). 这些包括internationalized strings (in .Iproj subdirectories), GUI definitions (nib files) and multimedia files (such as images or audio). 有时frameworks可能也包含support executables,比如需要提供服务的daemons(often in a Support/ 目录). As of 10.7/5.0, frameworks can also contain XPC bundles (in an XPCServices subfolder).

Frameworks和普通的dylibs一样, may be versioned. version scheme很简单 -framework目录定义了一个Versions/子目录,包含一个字母编码的版本. framework的当前version符号链接为"Current". 实际上大多数folders只包含一个framework版本所以符号链接是不必要的, 但这使得扩展性和多版本side-by-side布署得以实现- 避免Windows"DLL- hell"问题. 除了少数exceptions (such as Foundation, AppKit), framework version都是 "A",在“/System/Library/Frameworks”目录执行命令 find . -name Current | xargs ls -l 可以看所有framework版本,执行find . -name Current | xargs ls -l | grep -v A$可以看不是A的例外framework

还有一种情况是framework包含sub-framework,这样的container frameworks 叫做"umbrella frameworks", 例子包括 Cocoa.framework, [Mobile]CoreServices.framework, Quartz.framework and WebKit.framework

image.png

没有严格要求framework包含除了dylib之外的东西. Frameworks like System.framework实际只是一个a dylib的符号链接 (ust/lib/libSystem.B.dylib). The Kernel framework甚至dylib都没有 - 只在Resources/目录提供kernel programming interfaces的一个列表

Framework designation

虽然官方文档把framework类型通常冠以"Application", "Media" and "Core" frameworks, 但唯一重要的其实是其是否"Private". "Private" frameworks定义为Apple内部使用, 且Apple不提供任何headers or documentation. Looking at the framework directories ni MacOS/iOS暴露数量惊人的frameworks- 且大多是Private:

image.png

MacOS和OS共享很多frameworks.有些framework在OS中是"Private"而在MacOS中是可用的,比如IOSurface.framework(provides graphic services). 有些情况下framework名differ也可能不一样 (比如MacOS's Appkit and *OS的UIKit, or MacOSs' CoreServices and *OS的MobileCoreServices). Apple通常对public frameworks文档都较充分, 会提供headers (Xcode安装了的情况下),甚至有些有编程指引, 但也有framework的文档只提供了导出API的子集,比如LaunchServices.framework. 此外Apple对所有 frameworks都是闭源的,不管是否private. 从链接器的角度看private vs. non-private是毫无意义的.任何framework都可以通过-framework开关来链接上, 同时the framework path may be specified with -F,可以手动设置为/System/Library/PrivateFrameworks. Framework paths也可以通过runtime环境变量指定, using DYLD_(FALLBACK ]LIBRARY_PATH and DYLD_[FALLBACK_]FRAMEWORK_PATH,dyld(1)文档中有写. 私有于某bundle的Frameworks可以通过"rpath"来指向(是一个相对路径specification,在第6章). Developers依赖private frameworks会被警告, 且任何这种企图都是蛮干,因为APIs可能毫无征兆就可能被改动. 进一步讲App Store禁止使用private frameworks的API(通过查看EXE的library依赖就可以验证是否存在 这种情况) Public frameworks不依赖private framework, 但只暴露其他功能一个相对小的子集. 因此众多的私有API和它们提供的强大功能,使得逆向和使用它们变得有趣而益处多多

三方库

Darwin操作系统遵循Linux's方法,集成了已有开源代码库以支持common technologies,而不是重复造轮子. 在类UN*X操作系统中这个方法相当普遍, 而且对大量的代码和通常免费的license而言,这种做法很合理。大多数三方库都是.dylib形式,都存在于/usr/lib目录。通常Apple可能进一步把library打包为framework, 这种事发生在了MacOS's Tcl, Tk和Python libraries/frameworks. 另一个framework打包的例子是VirusTotal's YARA (a library for pattern matching aimed for malware identification), Apple在later MacOS 10.11 versions中有使用, 也就是private的YARA.framework. Apple在opensource.apple.com与其它Darwin源代码一道提供了大多数第三方库的源代码

image.png 有些时候Apple也更倾向于自身的实现而不是开源实现. 最好的例子是Apple的encryption stack,它由CoreCrypto 和CommonCrypto projects组成,提供跟OpenSSL's libcrypto,同时Security.framework提供protocol support (参见libssl),以及很多其它feature.不使用OpenSSL毫无疑问使Apple幸免于很多安全bug - 但也不是万能药. 实际上代码中的一个细小错误 - 比如"goto fail" bug (or, officially, CVE-2014-1266) 实际上会导致gaping vulnerability影响了iOS 7.0.6以前的版本和MacOS10.9.2以前的版本. 另一方面,也有很多情况Darwin有与开源代码一样的bug.

System Libraries

所有user-mode binaries的症结在于一个核心库 - /usr/lib/libSystem.B.dylib. 这是Darwin's最基础的library, 提供成千上万exported functions,包括"core OS" services - 也就是由最底层的C runtime, 或者kernel functions的wrappers提供的services. 执行ls -l /usr/lib|grep libSystem 会看到 libc.dylib, libm.dylib, libpthread.dylib 和很多 standard UN* libraries的实现都是symbolic links到libSystem, for compatibility. 此外对libSystem.B.dylib的依赖对所有系统中的动态链接binaries很常见. 观察MacOS's libSystem.B.dylib, 它是一个仅50-60K的小文件, 更让人吃惊的是它是个包含了386和x86_64架构的"fat"文件. 事实是libSystem 实际并不提供它导出的任何符号. 相反,它链接了很多/usr/lib/system中的其它dylibs, 并re-exports了它们的符号当做自己的导出符号 (用Mach-O的 LC_REEXPORT_DYLIB,第6章).

Table 2-19 (next page) 此处要有 re-exported dylibs的列表

真正从libSystem中导出的是atfork handlers, 和a bunch of R8289209 symbols, 可能是参考一个radar bug中将library特定调用导出从而修复的方案. 不过最重要的符号是libSystem_initializer, 虽然没有导出但却定义在了Mach-O的__DATA.__mod_init_func section中作为库的entry point.因此libSystem由dyld加载进进程中的时候就会调用initializer,而它转而会调用被re-exported libraries的initializers. 这些有时可能会调用它们自己的依赖库, leading to the process of initialization 如Listing 2-18中所示

image.png

Kernel Interface

image.png

XNU向User space导出了多种personality,有BSD systemcall,有mach trap,还有因硬件架构不同而不同的machine dependent call和诊断call,但这些不同personality的接口都是通过相同的入口调用的,ARM上是fleh_swi

在操作系统中,在处理文件,sockets和其它共享资源时,需要在用户进程的内存空间之外进行操作,这就需要使用到内核

System calls

Darwin's libSystem.B.dylib提供了系统调用的导出函数, 方式是通过re-export libsystem_kernel.dylib中的符号.导出函数都是wrappers, 因为它们的入口点都是同一个: 不管实际上是哪个系统调用,, 所有调用都最终会调用到一个统一的接口. 每个特定的系统调用都会分配一个编号(#defined in sys/syscall.h), 这个编号是ABI强制需要通过一个machine-specific寄存器传递给kernel的. 实际的call是用一个machine-specific指令来执行, 如列表2-22所示, 演示了bsdthread_create系统调用 wrapper的汇编代码, from /us/lib/system/libsystem_kernel.dylib

image.png

将处理器从用户态(Intel's Ring 3, ARM32's USR or ARM64's EL0)切换到内核态(Intel's Ring 0, ARM32's SVC or ARM64's EL1)必须调用machine specific指令.一旦进入内核态, execution被转移到一个pre-defined entry point(内核初始化期间设置的,Volume II中有更多介绍).由于这个entry point function是所有系统调用都会复用的,所以它必须通过查看调用号"de-multiplex"出特定的系统调用,并且通过一个function pointer table将其调度到合适的handler,如上图2-21所示.

这些对用户态都是不可见的,用户态会在从内核态返回的时候恢复执行(通过另一个machine-specific指令),并且控制流会转移到下一条指令. Darwin遵循UN*X system call convention - 所有system calls都会返回一个value(Intel: E/RAX, ARM: R0/X0), 成功的时候这个值会是0或者positive. 而发生错误时,值会是negative并翻译为-1 * errno(by the jump to cerror_nocancel, in Listing 2-22), with the global variable holding the error code of the call, so that the caller may handle such cases, or display the error to the user by strerror (3) and the like.

MacOS's system calls的完整列表可以在<sys/syscall.h>中找到,虽然并不是所有的都定义在这个头文件中. 谢天谢地的是(不像Linux),系统调用的numbers在所有架构中都是一样的常量, 虽然也有极少场景*OS可能提供一个MacOs上不可用的syscall, 或者反过来(通过conditional #ifdefs).

system calls可用集合是由POSIX system calls (which are defined in unist.h and other headers), 和Apple proprietary ones组成的. POSIX calls集是常量(标准强制的), 但Apple一直在添加system calls as XNU evolves. 数量的增长非常dramatic, from 438 in 10.7 (XNU 1699) to 531 in 10.14 (XNU 4903), 虽然并不是所有entries都正在使用. system call table也并不是没有空洞项,且Apple不定期进一步deprecates了一些system calls(e.g. stack snapshot (#365) in XNU 3789), 导致空洞项进一步扩大. "Holes"会返回 -ENOSYS且通常会保持vacant to avoid因为deprecation而带来的API confusion, 虽然Apple曾经将deprecated项用作它用.

system calls一个值得注意的子集是nocancel group: 这些calls,调用号开始于大概#395, around 30 or so syscalls which are variants of the traditional I/O calls (e.g. open (2), [read/write]v and some of the blocking calls,不支持通过signal来进行的cancelation。standard system call实现会调用它们的nocancel counterparts,但要先检查thread_testcancel,以便一条标记了cancelation的线程可以避免执行这个会阻塞的system call,并返回用户态. 调用call实现会调用它们的nocancel syscall 直接跳过了这个检查.

The joker tool(本书companion website可获取)可以用来display the system call table of XNU (for both ARM and Intel architectures) as shown in Output 2-23. joker在OS发布的beta阶段尤其有用, 因为它能枚举system call table,并根据last known version of the system call table检测出变化, 显示出是否出现了新的system call "holes",或者有新的entries添加进了system call table.

image.png

使用undocumented/unexported system calls

Apple专有的system calls是XNU's undocumented functionality中最“有趣”的部分,其中大多在XNU's bsd/kern/syscalls.master中都通过显式定义为NO_SYSCALL_STUB以未不可用 (虽然有些仍wrapped by libsystem_kernel.dylib exports). 不管是否exported,任何system call都能通过syscall function来调用, in <unistd.h>. 这是一个可变参数的function, which passes the system call number in its first argument, and allows for additional arguments.

image.png

Mach traps

不像其它UN*X-based OS, Darwin systems的独特之处在于Mach traps,它们呈现了一个完全不相交的calls集合, 这些call可以调用到XNU's Mach functionality. Mach traps是与system calls使用同样的方式来调用 - 通过底层的syscall/svc指令 - but encode the Mach trap differently, as shown in Listing 2-25:

image.png

如上所示×86_64使用数字trap value (in this case, 10), 并通过将高字节置0x1标记 Mach trap ( BSD- style syscalls是0x2, as shown in Listing 2-22). 而ARM and i386选择了不同的ABI,将trap number编码为negative (i.e. -10). 不像system calls,trap不需要设置errno - 因为Mach traps成功时return 0 (KERN SUCCESS), 或者失败时返回positive error code(<mach/kern_return.h>中)。

与system calls不同的是, 没有public头文件列举了Mach traps. 可以在XNUs' kernel 源代码中找到这份列表, with osfmk/kern/syscall_sw.c defining the mach_trap_table, and osfmk/mach/mach_traps.h定义函数prototypes。同样地joker很方便展示XNU的system cal table

image.png 与system calls不同, XNU硬编码限制Mach traps最大值为 MACHTRAPTABLECOUNT (=128),虽然多2-26可以看出在使用的大约只有一半,其它的都是kern_invalid (调用后会返回KERN_INVALID)。Mach trap table从XNU早期就大部分都未变化过,虽然Apple加了一些traps以支持guarded ports, vouchers and activities*. 不像system calls, Mach traps会经历"munging",或者一个function的预处理 (MACHTRAP macro的第4个参数), 这个function会对齐input参数。Mach traps这个munging处理在卷II中会详述.

相比起来system calls最少还有部分文档, 而Mach traps是完全没文档。 Apple似乎极力避免提到Mach traps, 就像它们根本不存在一样。但就像你后面会看到的一样, Darwin不能缺少Mach traps - 就算只是Mach messaging, IPC的核心, 依赖 #31 and #32 - mach_msg_trap and mach_msg_trap_overwrite

Machine Dependent(machdep) calls

除了BSD-style syscalls和Mach traps,XNU还支持第三种calls,也就是"machine dependent"。顾名思义它们因架构变化而变化,甚至32位和64位系统也不同。甚至调用方法也会变化 - i386用 int $0x82而更现代的用syscall:

image.png 不同架构下,calls本身和它们的调用号都不保证一样,如Table 2-28所示:

image.png x86_64's hypervisor traps只在Hypervisor.framework的hv APIs中使用,本书卷II会介绍

Diagnostic Calls(MacOS)

MacOS XNU支持第4种calls,称为diagnostic calls,明显只支持Intel架构(i.e. MacOS)。虽然XNU's osfmk/386/Diagnostics.h defines around 26 codes, only a few are used, and fewer still are available outside of DEVELOPMENT or DEBUG kernels。

diagnostics call的ABI的syscall class是4(i.e. 0×4000000),但实际的diagnostics call号是通过rdi寄存器单独传递的。diagnostics syscalls没有已知的stub,但鼓励读者创建一个(e.g. for dgRuptStat) using inline assembly, as an experiment

The comm page

有很多常用kernel services,它们封装进一个system call或者trap会很麻烦或者很重expensive。通常是快速获取信息的call,而为这种call切进切出一次内核态会严重地低效 因此XNU为这种情况提供了comm page,它是一个kernel-owned memory page,也由kernel映射进了每个用户态进程的address space。不像其它kernel memory(映射到高地址空间,且将用户态对其的访问限制在MMU层级), the comm page映射在用户态的固定地址(0x7FFFFFE00000 for MacOS, OxFFFFFF80001C00O iOS 9、OxFFFFFFF0001FC000 iOS 10+)且允许用户态访问。

虽然允许用户态访问,但comm page在XNU's own sources之外完全没文档化,Apple也会周期性地更新它的格式. 当前的版本(as of Darwin 17), 13,从XNU-2422 (Darwin 13)开始使用。Rather than a C-structure,一组硬编码的地址#defined为_COMM_PAGE*常量 在 XNU's /osfmk/386/cu_capabilities.h。在XNU-4570,comm page definitions for *OS devices也开始可用(in /osfmk/arm/commpage/)

launchctl(1)命令(available in *OS on the recovery dmg)可以用来显示comm page的内容(以及其它信息)通过hostinfo subcommand。这个功能是由Apple添加 beginning with launchctI(1) "Version 2.0" in Darwin 13 (source version 975 and onwards). 而到"Version 5.0" (Darwin 17 betas) Apple决定移除这个功能。幸运的是作者的开源clone of the utility, launjct1(j)(本书companion website), 仍支持显示comm page contents, on MacOS 13 and the *OS variants:

image.png

类似libplatform's _platform_bzero和platform_memchr()的功能参照了comm page's cpu_capabilities和cpu_family values以判断使用哪个优化了的variant (Base, Haswell or IvyBridge)。另一个comm page用户是gettimeofday(2) call - 它并非system call- 它读取timeval 值从comm page,通过反汇编libsystem_kernel就可以看出。另一个comm page用户是libsystem_platform, 它建立"preemption free zone" (PFZ) associated with the comm page

XNU(at a glance)

Mach & BSD

XNU在用户态提供了两种完全不同的"personalities" - 一个是传统的UN*X-style system calls,另一个是Mach traps。这种personality紊乱可以追溯到XNU's历史:

Mach最初是一个微内核,这个微内核只提供有限的functionality: 任务和线程管理,以及消息机制。但这个核心功能对于一个操作系统来说完全不够,因为还有虚拟内存管理、硬件访问(设备驱动)、文件系统等等。

其设计之beauty在于其顶层可以是任何接口,而且实际上出现过基于Mach的Linux实现(MkLinux),甚至是 Windows(相当失败)。最初选择的top layer是FreeBSD - 之所以选择它是因为它是当时唯一可行且完全开源的类POSIX系统 (要知道1980's晚期,Linux尚在襁褓)。

逐渐地, XNU演化为了它现在的样子,演化过程中经历了很多妥协,最著名的是抛弃了"true"微内核设计,而转向单内核的方向(i.e.所有组件运行在同一地址空间)。这极大地提升了性能,但也牺牲了微内核的特性,比如鲁棒性和安全。但Mach messaging仍是最高效的IPC机制且大量使用

libkern

XNU包含一个近乎自包含的运行时环境叫做libkern,与大多内核是C与汇编的混合不同,libern是一个C++运行时环境。libern提供一组对象,这些对象映射了用户态的CoreFoundation对象。因此OSObject类似于CFObject, 同样地还有OSString, OSDictionary和其它对象

就其自身而言,libern是个有趣的想法,但它真正的威力在于作为XNU最令人印象深刻的feature IOKit的基石所发挥的作用。

IOKit

NeXTSTEP一个最前卫的feature(且至今仍然如此)是它面向kerner driver的自包含,面向对象的运行环境。这个叫做"Driverkit"的东西最终演化成了XNU's IOKit,这个kernel最重要的组件之一

Special kernel driver interface

对于用户态,IOKit的重要性在于它提供了device drivers一个完全隔离的通信渠道。虽然XNU支持传统的字、块、网络设备(through /dev nodes or interfaces)的UN* model,但IOKit支持自身的mode。首先,driver通过IORegistry查找,它提供一个所有已安装drivers的catalog。一旦located,driver properties maybe gueried or set。driver可能额外提供一个IOUserClient,通过这个driver可能额外提供一个IOUserClient可以调用驱动functions (=methods)。用户态IOKit.framework(只在MacOS是public)通过使用标准的IO* APIs隐藏了这个功能的细节。

image.png

Object-oriented drivers

IOKit是libkern的主要客户,利益到C++的继承和重载,新的device driver可以自由选择最近的最合适的IOKit家族中的driver类继承,并选择在派生类中新增特性或者重载默认实现

Kernel API abstraction

IOKit也抽象了kernel大部分API,大多只是一层简单的包装(比如IOPanic over panic(),或者IOLog over o s_log_with_args),但这个解耦API实现的做法最少在理论上支持其移植到其它内核。理论上,驱动开发只需要使用IOKit APIs, 允许其(重新编译后)运行在其它兼容的IOKit环境。但实际上大多驱动也调用了Mach和BSD APIs, 因此违反了这层抽象.此外XNU之外再没有其它IOKit实现,唯一粗略相似的环境是windows的NDIS(Network Driver Interface Specification),它为网络驱动提供了与内核无关的抽象。

Platform Expert

每个内核都需要某种Hardware Abstraction Layer (HAL): 用于适配底层硬件的代码,抽象CPU和总线的共同特征以便内核可以在一种无知的状态下进行高效管理。XNU's主要HAL是Platform Expert (expert),并且它提供了重要的服务: • Serial device (and console) support • Installing interrupt handlers • Access to boot arguments • Access to the Device Tree • Boot-loader (in MacOS: EFI) support 作为内核内核的最底层之一,pexpert在内部使用,且不能通过用户态访问。Apple提供了386/ implementation的代码(which also covers ×86_64/),但将ARM架构的代码闭源直到XNU-4570

The ml_* APIs

任何抽象都有局限,并且任何内核都可能在某种底层硬件独特性陷入困境。XNU中,这种情况是由一组ml_* functions解决的,它们并不能称为一层"layer",实际更多是一种"hacks"。这些实现很多是在XNU's osfmk/1386/machine_routines.c(for MacOS),与Platform Expert一起, Apple直到XNU-4570才让ARM 实现开源。

Kernel Extensions

所有现代操作系统允许加载代码进内核,功能上等同于动态库,而不同之外在于它们可链接进内核空间,而不是用户空间。这些代码用途多样,包括设备驱动,安全(MAC/KAuth) extensions, filesystem drivers,或者 generic extensions。很多情况下,这些代码不仅在内核中受欢迎,也十分重要因为它提供硬件组件的重要接口,而不需要内核知道怎么发挥这些功能,甚至不需要内核load。

Windows称呼这些代码块为"drivers",并对它们使用后缀.sys。Linux称呼它们为"modules",并使用后缀.ko。Darwin称呼它们为"kernel extensions",或者简单地称为"kexts",也作为它们的后缀。与其它OS类似, Darwin's kexts是二进制的Mach-O格式。但与其它OS不同的是,Darwin二进制只是一个更大bundle的组件, 也必须包括一个Info.plist, 也可能包含额外resources。Info.plist定义extension's依赖以及(对于IOkit enabled extensions而言)"personalities"-定义了他们的driver functions(卷II中讨论)。

image.png MacOS's kexts通常在/System/Library/Extensions中,虽然并没有强制要求。打开目录有超过300个extensions,并不是所有的都有被使用。The system通常构建一个优化版本的kernel,通常会预链接所有常规kexts成一个kernelcache, 可以在/System/Library/Caches/com.apple. kext.caches/Startup/kernelcache中找到。在现代MacOS versions中这是一个指向/System/Library/PrelinkedKernels/prelinkedkernel的symlink。使用kernelcache可以显著提升loading速度,因为它减轻了映射每个kext并动态链接它们依赖的符号的负担。

The *OS variants也利用prelinked kernelcache (目录位置不同 - /System/Library/Caches/com.apple.kernelcaches)。整个kernelcache由Apple数字签名并进一步通过APTicket (卷II中会介绍)进行了保护。因此除了极大加速boot process,这也确保了kernel space所有代码的完整性。使用prelinked kernelcache又进一步使Apple从XNU中完全略去了kext的linking代码,减少了XNU的大小并使making extension几乎不可能(并不是完全不可能)。The *OS kernels有kext loading能力,但只针对那些先预链接进kernelcache的kext。

MacOS一直允许直接加载kext,但更推荐的方法是用专用的kernel extension daemon- /us/libexec/kext。随着SIP的出现,这已经成为唯一加载kexts的方式了,因为要强制使用一个专门的entitlement。The kextd进一步验证(as of 10.9)kexts是数字签名过的,并且(as of 10.12)会拒绝加载未签名的kexts。10.13添加了一个额外的护栏,通过要求用户通过GateKeeper来允许三方kexts - 这个决定很明智因为kernel extensions已经成为高级恶意软件的rootkit component。 The kextstat(8)命令可能用来显示加载的kernel extensions。The joker tool可以用来诊断MacOS 和*OS kernelcaches,并且还可以显示和导出预链接的extensions。

Darwin Technologies, at a glance

如章名所指,Apple's OSes是如此多code bases的组合,所以提供了非常多的technologies - 有些是借用的有些是新的。This work目标是详尽这些技术,但你可以在这个section中快速形成一个这些技术的高层的视角

image.png

第三章 Promenade:A Tour of Filesystems & Directories

任何OS都必须需要要为app提供file service,自己也要使用很多文件。 Darwin based OSes使用专用的file systems (HFS+和新标准化的APFS),使用了一个复杂的目录结构,混合了经典UNIX和NeXTSTEP。

先通过概览分区和filesystems,这一章然后会转向Disk Images (.dmg),.dmg是一直都在MacOS中使用。然后聚焦于经典UNIX filesystem model的extensions - 即Access Control Lists (ACLs)和Extended Attributes (attrs)。后者提供了System Integrity Protection (SIP)、transparent compression和legacy resource forks的基石。

然后才能tour无数的filesystem directories, 对比MacOS和*OS variants。有如此多的files和 directories,数据使用多个tables来呈现和总结 - 读者可以简略概览。未来探索file systems会碰到这些directories, 这些tables可以作为查询那些神秘名字的快速索引。

Partitioning

MacOS和*OS variants都使用GUID Partition Table(GPT)来将可用的存储空间进行分区。GPT是分区的事实标准,可选的替代方案MBR在64位领域已经过时了。GPT也是EFI specification的一部分,EFI是Apple followed since its migration from PPC to Intel。

MacOS传统上所有文件使用单个partition (the root),但- as of MacOS 15 - 所有Darwin开始采用同一个方案,也就是使用两个不同的分区:root filesystem包含除/var目录之外的所有文件,/var mounted 一个单独的data分区。In MacOS,还有一个额外的recovery分区。On *OS,第三分区(on data-enabled devices)用来存储baseband data。*OS variants不要求单独的recovery分区,因为the process是directed by a host, over iTunes。

This is shown in the output of the mount (1) command from an iOS device:

image.png 将system和data分为两个不同分区很有意义,因为它有效地解耦了两个分区,允许其中一个的修改不会影响另一个

  • system分区可以不触及用户数据就更新: 轻松升级 *OS version而不惧用户数据损失。但为以防万一,更新时仍会备份用户数据,只是为了防止更新半路失败弄坏了分区表
  • data分区可以不影响system分区即被擦除: i-Device快速恢复出厂设置,确保格式化后system仍是bootable和out-of-box的状态
  • system分区可以只读: 除了system更新,实际没有需要修改system分区中的任何文件。实际上唯一非系统更新场景改动到system files的情况是恶意软件尝试寻找persistence vector。从iOS 7开始, Apple强制必须在kernel中mount(MNT_RDONLY),并也进一步保护root filesystem's块device
  • 数据分区可以加密:system分区没什么好保密的东西值得加密,它在所有设备中都是一样的。因此/var是使用MNT_CPROTECT flag ("protect", in Output 3-1)挂载的,enabling data protection(第3卷11章)

File Systems

分区以raw形式存在是很少见的,除了分区相当小或者只包含同一种类型的少量数据的情况外。绝大部分partitions 都很大并包含了很多项data,也就是为什么通常使用文件系统格式化分区。Apple支持两个层级的filesystems:

  • kernel level,通过一个kernel extension (kext)支持,通常in lowercase且(通常但并不总是)后缀是"fs"。这些extensions是非常特别的类型且插入kernel's Virtual Filesystem Switch (VFS) layer - 一个BSD层的组件(为用户态clients提供统一接口)(VFS在卷II/7中介绍)。Clients因此能对特定的文件系统特性无感,并调用标准POSIX APIs和像permissions、access control lists和extended attributes(where supported)这些特性。filesystem kexts很容易通过它们的bundle id来辨认,而且只要是Apple提供的总是会冠以com.apple.filesystems.*的前缀

image.png

  • Low level文件系统操作,比如mounting,formatting和repairing(通过fsck)filesystems是为每个.fs bundle的文件系统通过tools执行的, 都在/[System/]Library/Filesystems中。这些bundles通常在其Resources/ 目录中包含tools,其中同时还包含国际化支持。标准tools是mount_fstype和 fsck_fstype(both called from mount (8) and fsck(8))。另一个常用工具是fstype.util,其提供特殊的low-level operations。可以格式化的Filesystems也包括newfs_fstype

image.png

Native File Systems

每个OS似乎都钟爱自己地方性的filesystems。Darwin-based systems也不例外

  • HFS+/HFSX: 都是Hierarchical File System的变体,长期都是选中的文件系统。在mount(8)的输出中都标记为"hfs",但它们的不同在于是否大小写敏感: MacOS's HFS+是一个大小写不敏感filesystem,这意味着 使用cat /etc/hosts和cat /ETC /HOSTS,甚至 CAT /EtC/HoSts之间没有区别: 虽然文件名是其创建时指定的确切字母,但文件系统归一化了(read:小写)字符,以便不同大小写的变体都落到同一种形式。HFS (in the *OS variants)并不会如此,所以要注意大小写。HFS+文件系统格式和structures在卷II中有详述,但此时需要注意的是HFS+的魔数是'H+'(而HFSX是'HX') exactly 1024 bytes into the partition。HFS+/HFSX都用同一个GPT GUID - 48465300-0000-11AA-AA11-00306543ECAC

另一个不同是mount options,Output 3-4可以看出HFS[+ /X] filesystems在mount的时候可以带或者不带journaling。journal通过记录所有写transaction从而提供filesystem可靠性,以在写失败时可以回滚。MacOS filesystem得益于此,因为root(且当前only major)filesystem is mounted read/write。*OS's root partition,默认mounted read-only,所以使用journal基本没有意义(即便越狱remount为read-write后仍是如此)。*OS的Data分区(/private/var)mounted带journal,以及其它options,包括nosuid(to reject u+s marked SetUID binaries), nodev(to reject device node creation using mknod(1)), noatime (to not record file access time automatically, extending the lifetime of the flash) and protect (per-file encryption with NSDataProtectionClass,卷III/11).

image.png

  • APFS:Apple已经宣布APFS(Apple [Proprietary] File System), 作为MacOS 10.12的重要更新, 并正式使用它替换HFS+/HFSX:*OS iOS 10.3,MacOS 10.13. APFS提供了急需且创新的features,包括full 64-bit mode, volume management, snapshots, multiple levels of encryption, and more. APFS internals are discussed in 卷I。不同于HFS+,APFS没有"magic"数,但APFS分区在GPT下使用的GUID是7C3457EF-0000-11AA-AA11- 00306543ECAC

Disk Images(.DMG)

MacOS很早就开始使用disk images,MacOS X之前, .DMG文件就支持了将软件包通过单个文件发布,方便下载。 双击一个.dmg即可将其"attaches"进Mac,将其mounting到/Volumes/DMG下,并让其自包含的filesystem 可访问并与本机上其它文件无异(甚至通常挂载为只读类型)。Finder.app自动弹出一个目录窗口,软件提供者提供一个简单的拖拽UI用以完成app安装或者启动(See Figure and discussion in Figure 5-2)。

Disk images一直沿用到了现在,虽然Apple很可能希望它们消逝,但它们仍被被software供应商用来在App store之外分发软件。不幸的是DMGs也成为一个恶意软件的common vector,有时候也会很少使用的DMG加密以避免detection。虽然disk images某种程度上像不能自动触发其payload的Irish virus且需要用户的介入,从MacOS 12开始Gatekeeper将从网络上下载的DMG applications的启动隔离并将它们"translocates"一个randomized path(卷III/6中有讨论)

Mounting

DMGs通过Finder.app打开时会自动挂载,它们也可以通过命令行打开:open(1)(使用LaunchServices.framework类似Finder)是其中之一,但有另两个命令hdiutil(1) and hdik(1)也可以操作DMGs。挂载DMGs需要创建即时的块设备(to back the mount),且因此不可避免地需要kernel - 特别是IOHDIXController.kext的参与。如Figure 3-6*所示:

image.png

Format

Disk images后缀是.dmg,但可以是多种格式。可以是raw filesystem images(因此也能通过dd (1)创建),也可以是压缩过的。压缩格式有多种,压缩文件总会错误地被file(1)识别为VAX COFF exectuables (或者根本不会),因为它们并没有一个可识别的头。相反512字节的trailer魔数"koly",包含DMG的元数据。koly块格式已经完全被逆向了,如Listing 3-7所示:

image.png 若存在koly trailer,其指向一个XML property list只包含一个key - resource-fork,包含一个提供disk image布局的字典。property list包含两个key- plst和blkx,包含一个对应disk image分区为元素的数组。 举例来说,如果MacOS's InstallESD.dmg (system installation image的一部分)。它的property list是SimPLISTic格式,如下所示:

image.png 上面的Data元素是base64的,是由魔数"mish"标识的二进制结构,这里提供了一个block table,structure 如Listing 3-9所示:

image.png 最终block table中的分区的每个block sequence都编码进自己的structure中,包含着其在image中的位置信息和跨越的sector信息。block格式如Listing 3-10所示:

image.png 这个block编码的方法允许每个block分别压缩以获取最佳压缩率,也允许blocks独立访问,因此减轻了对整个image进行映射的需要(因为整个image可能有数GigaBytes)。Disk Images更让人印象深刻的(也有潜在危险的)是XNU 通过一系列kernel extension提供了对image formats的in-kernel支持。IOHDIXControler kext (a.k.a com.apple.driver.DiskImages)作为主要的内核extension,它会根据检测到的image type从其Plugins/目录加载相应的helpers。加载后可以通过kextstat查看,如Table 3-11所示:

image.png 除了以上,DiskImages.framework也支持其它格式,包括ULFO (LZFSE压缩images,MacOS 10.11), UDCO (ADC压缩), UDSB (Sparse Bundle)等 - 完整列表可以在hdiutil(1)的man page中找到,hdiutil也可以用来在支持的格式中自由转换(用convert -format ..)。

DMG复杂的格式也有缺点,且DiskImages组件已经成为很多目前脆弱点的源头,攻击点是DMG fileformat的蓄意malformation。而disk images支持内核态(IOHDIXController.kext)又进一步加剧了这个问题,内核支持可能潜在地导致kernel compromise。Apple过去修复了很多与DiskImages.framework相关的问题,目前可能仍有很多问题。

Disk images可以进一步加密(使用hdiutil -encryption),这种情况下密码需要通过UI指定或者通过hdiutil --stdinpass。Images也可能需要代码签名- with either embedded(Darwin 15x)或者detached code signatures。嵌入式的签名添加到文件末尾,位于koly trailer之前(通常位于information property list 和header之间)。对于Security.framework 57337 (into Darwin 15),代码签名格式已经扩展到reserve slot 6- as RepSpecificslot。因此DMGs的签名会在打开前校验,除非在DiskImages.framework/Resources/defaults.plist中skip-verify key设置为true。其可以通过 defaults(1)设置,且property list包含其它keys和用途的简短解释。Additional property lists, in ..DiskImages.framework/Resources/agent-defaults控制其它framework tools (hid, etc)的设置。

Disk images in the *OS variants

iOS,TOS和WatchOS中Disk images也是支持的,以支持Xcode's DeveloperDiskImagedmg。这是一种包含所有 XCode使用工具的filesystem image。The image(在Xcode.app's Developer/Platforms/OS.platform/DeviceSupport/Version(Build)中)是与其detached signature一起通过一个到/us/libexec/mobile_storage_proxy的USB lockdown连接上传到i-Device 的。这个proxy会将image传递给MobileStorageMounter,如果签名匹配其会将image挂载到/Developer。

一旦挂载完成,image中的子目录就会root filesystem中的子目录一样可用。尤其是/Developer/Library/LaunchDaemons中的daemons的加载是通过SMSubmitJob calls to launchd,且/Developer/Lockdown/ServiceAgents are processed by to lockdownd,在MobileStorageMounter向其发送了com.apple.mobile.developer_image_mounted的Darwin notification后。 Listing 3-12展示了iOS 11.0 Developer DiskImage带注释的内容,TOS和WatchOS的DDI大部分相同

image.png

Disk Arbitration(MacOS)

Filesystems需要挂载到一个目录挂载点以供访问,filesystem挂载到MacOS需要应对两个挑战:

  • 挂载和卸载都是有可能引发危险后果的特权操作,另一方面来讲用户需要被允许挂载和弹出特定类型的filesystem,比如disk images和可移除的media
  • 普通用户可能连挂载操作是什么都不知道,更不要说意识到命令行。挂载和卸载应当越透明越好,需要在检测到media(e.g. 磁盘挂载attachment)的时候自动挂载,不需要的时候自动卸载

MacOS使用一个daemon-/us/libexec/diskarbitrationd来应对这两个问题。用户可以通过com.apple.DiskArbitration.diskarbitrationd Mach service来访问,其消息集合在iskArbitration.framework APIs中进行了封装。通过这些APIs用户可以请求daemon进行挂载和卸载,也可以获取filesystem status和notifications。用户包括Finder.app, TMHelperAgent(以便Time Machine感知到new volumes), hdieject (负责ejecting volumes)和hdiutil utility。 作为一个公用framework,DiskArbitration的头文件和Apple开发者指引文档都是全的。实际上它是为数不多的开源framework之一。检测这个framework会发现头文件中导出的API只是其功能全集的一小部分,如Table 3-14所示:

image.png 磁盘仲裁daemon支持将其详细操作日志记录到了/var/log/diskarbitrationd.log,这通过指定-d这个命令行参数即可。一个简单的方法是直接修改LaunchDaemon property list,能达成此目的的命令序列如下所示:

image.png 一旦打开之后,日志可以log记录daemon与其众多用户之间发生的事情,包括交换的消息和请求者id。其实也可以不通过/us/libexec/diskarbitrationd而直接与com.apple.driver.DiskImages这个内核ext通信。MacOs中可以通过hdik (8)实现,(*OS 中并不存在这个daemon)。Darwin最近的版本中,IOHDIXController(kernel ext提供的对象) 开始强制要求com.apple.private.diskimages.kext.user-client-access这个entitlement,hdik、hdiutil和diskimages-helper都已经entitled,也包括/us/bin/nbdst(NetBoot Deferred Shadow Tool,可以使系统通过网络主机硬盘启动)。OS中, 唯一的另一个entitled的二进制是MobileStorageMounter,因为它需要entitlement以在/Developer上挂载DeveloperDiskImage.dmg。hdik 不属于OS的根filesystem,但可以在iOS recovery/update ramdisk中找到

ACLs&Attributes

Darwin是一个基于UN*X的系统,那么毫无意外地其原生filesystems完全支持permission model。而这个model已经过时了(user/group/other for chown (1) and r/w/x for chmod (1)),且不再能满足现代文件管理的需要。尤其是只支持一个用户遴选为owner,对其来说permissions几乎没有(因为它们总能被chmod(1)ed)。类似地,只有一个group可以被定义(且groups不能轻易嵌套)而其它人的permissions集是一样的。 Enter:Access Control Lists ACLs通过允许更细粒度的文件权限扩展了传统model,通过定义特殊权限(称为Access Control Entries, or ACEs)。ACEs可以为任意数量的用户和groups定义,ACEs也能定义细粒度的操作,比如delete、append和read/write attributes。ACLs can be displayed using the -e switch of 1s (1), and can be manipulated using chmod [-+=]a, as well documented in the man page, and shown in Output 3-16:

image.png

#: chmod +a "admin allow write" index.m3u8
#: ls -le index.m3u8
-rw-r--r--@ 1 root  staff  111  7 13 19:40 index.m3u8
 0: group:admin allow write
#: chmod -a "admin allow write" index.m3u8
#: ls -le index.m3u8
-rw-r--r--@ 1 root  staff  111  7 13 19:40 index.m3u8

File flags

Darwin支持BSD文件flags的概念,这些是布尔属性,可以通过[f]chflags(2) system calls (分别是#35和#34)来设置。flags定义在<sys/stat.h> 中,如Table 3-17所示。UF_前缀代表user(owner)可设置的flags, 而SF_要求超级用户(root)权限:

image.png chflags(1)命令提供了命令行接口来设置或者移除flags(当前面冠以"no")。Flag到名字的转换是通过fflagstostr/strtofflags(3)来完成的。ls(1)的O-开关会显示任何flags,且-flags可以与find(1)一起使用。Apple一直使用sunink来保护系统目录被修改并使用hidden来从Finder.app中隐藏UNIX(lowercase)目录。最近Apple添加了restricted,它甚至可以防止来自superuser的任何修改,datavault用来在SIP开启时防止读取。为了更好地学习flags,看MacOS system volume的根目录,如下Output 3-18所示:

image.png

Extended Attributes

as with permissions,文件attributes也需要更新,也就是extended attributes。相比于硬编码进文件系统逻辑,extended attributes(通常称为xattrs)are named,且可以包含任何想要的值 - 让它们像文件标签一样方便使用且很灵活。xattrs可以通过ls -@开关查看,且通过xattr(1)命令操作,man page中有详述如Output 3-19所示:

image.png 属性命名没有严格的要求,虽然像Output 3-19所示,Apple使用逆DNS标记,Table 3-20展示了一些Apple使用的属性:

image.png 上述两个阴影的xattr - com.apple.decmpfs和com.apple.cprotect不可见且用xattr也看不到,这是因为它们是内核Virtual Filesystem Switch (VFS)层使用的,以提供透明的文件压缩和per-file加密(卷III/11)。 Apple也为attributes添加了非标准扩展: xattr_name_with_flags(3)(来自libcopyfile.dylib)定义了额外的XATTR_FLAG_*常量。这允许在与copyfile(3) API共同使用时为attributes设置copy behavior。这些flag编码为字母,并通过'#'分隔符跟随在attribute name之后(e.g. com.apple.lastuseddate#PS)

image.png 在文件带有xattrs的场景,需要文档化或者转移到不支持xattrs的文件系统中(e.g. FAT),Apple使用点下划线文件(._)来存在xattrs。在带有attr标签的文件zip(1)ed或者tar(1)ed时可以轻易看出来

Transparent file compression

在Table 3-20中看出, com.apple.decmpfs实现了透明文件系统压缩。这个特性在MacOS 10.6中引入,且Apple几乎所有文件分区的文件中都有使用。压缩了的文件会打上UF_COMPRESSED标签,且它们的inode对外挂的size会是0。文件内容相反会在这个attr中,通过ls -l得到的size会是假的未压缩的大小。任何对这个文件的访问都会触发内存中的解压(使用某个com.apple.AppleFSCompression.* kernel ext)。Output 3-22所示:

image.png

File forks (MacOS)

Directories

Darwin目录结构是标准UNIX目录结构和顶层叠加的NeXTSTEP的混合。这两种类型分容易区分-NeXTSTEP目录以大写字母开头,而UNIX目录总是小写的

UNIX directories

[/System]/Library

/Users (MacOS)

/Volumes (MacOS)

/Volumes目录用在MacOS中作为附属硬盘和disk images的默认挂载点。当一块任意物理硬盘连接上或者disk image (.DMG)attach上的时候,diskarbitrationd daemon会进行一个自动挂载的操作。

/AppleInternal

File Descriptors in Darwin

UN*X系统中的谚语"everything is a file"。更准确地说"everything can be opened as a file descriptor"。文件描述符是一大类对象类型,但实际的文件类型(vnodes)只有一种。包括读者熟悉的sockets,以及System V倡导者使用过的共享内存或者semaphores。Darwin支持所有这些以及更多,支持的完整描述符类型列表是(outside the obvious nodes):

  • Sockets:无需介绍,是最常用的非文件类型描述符,通过socket() system call (#97)创建或者socketpair(#135)。Darwin非标准地址族(PF_NDRV和PF_SYSTEM)在16章重点介绍

  • POSIX Shared Memory:通过shm_open(2) syscall,(#266)创建然后像正常文件描述符一样mmap(2) 。close(2)d后会被销毁,通过调用shm_unlink(2) (#267)。几乎很少用,只在两个场合CFNetwork's defaultStorageSession (for external cookie storage in a C++ class called "Crumbery"), 和Darwin 19's runningboardd

  • POSIX Semaphores:由sem_open() syscall(#268)创建,由sem_close() (#269)或sem_unlink( )(#270)结束。其生命周期内,sempost()(#273)发送通知, 且它们也能被sem_[try]wait()ed (#271, 272)。 POSIX semaphores在Darwin中几乎不怎么用,因为Mach semaphores和其它同步primitives提供了更快和更高效的机制

  • KQueues:第8章会讨论的内核事件通知机制,通过kgueue()(#362)或者其guarded变种 (guarded _kqueue np, #443), 以及 kevent[641 (#363/#369)创建

  • Pipes: 由pipe()(#42)和mkfifol_extended]()(#132/291) syscalls创建的 先进先出(FIFO)的IPC机制

  • FSEvents: aret h e cloned file descriptors of /dev/fsevents, an Apple extension allowing filesystemmonitoring, similar to (but better than) Linux's n o t i f y . FSEvents are described in Chapter 4

  • NetworkExtensionControlPolicies: Added in Darwin 16, Network extension control policies - NECs- permit clients to define high level policies and apply them toI P based sessions. These descriptors are created with the undocumented necp open( ) system call (#501). or necp_session_open () (#522). NECs are described ni Chapter 16.

  • Nexuses &Channels: are entirely undocumented descriptor types. So undocumented, that most references to them arenowhere tob e foundinthe open sources of XNU, likely due to a conditional #i f block. Both types were added silently by Apple in Darwin 16, along with numerous system calls (503 through 514). A nexus provides a fast link between the kernel's networking subsystems, and a channel provides atransport. Al these types can be viewed in use through procexp (j), particularly when used with the fi l e s command.

所有这些类型都可以通过procexp (j)查看, 尤其是在与files命令一起使用时

Guarded Descriptors

Apple通过扩展POSIX model得出了guarded descriptors,Darwin 13起可用。Guarded descriptors类似于普通的描述符但其一旦通过guarded_open_np(#441)的non-POSIX extension打开就只能通过guarded_close_np (#442)关闭。change_fdguard_np system call (#444)用于保护描述符上的flags, 包括移除整个保护

File Ports

File ports是Apple的重要添加,旨在连接Mach ports和BSD文件描述符。将文件描述符封装为Mach port会生成一个file port,与其它Mach port rights一样,可以通过Mach消息在Mach tasks之间转移。File ports 提供了通过XPC转移文件描述符的基础能力,而未文档化的pc_type_fd本质上是一个XPC包装的fileport File ports可以直接通过两个专有且未文档化的system calls: fileport_makeport (#430)可以从一个指定的文件描述符创建一个fileport, fileport_makefd (#431)是其逆操作,从fileport创建文件描述符

第四章 Experience Points: UX and System Services

UE layer,虽然Apple更喜欢称其为user facing services,是apple操作系统最与众不同的feature之一。每次WWDC及产品发布会都会强调其先进性,简洁性和高效率

"User Experience"范畴内有很多services,它们松散连接并影响用户与OS交互的方法。这一章会研究其中一些,但会从用户实际不会直接体验到的机制FSEvents开始。虽然其并不是直接的UI,但它影响系统中很多其它组件,这些组件用其来获取file system activity通知。

然后介绍FSEvents's最主要的客户SpotLight,就是那个最熟悉的放大镜,它是本地及远程所有app和用户内容的门户。然后是QuickLook,一个简单但功能强大的接口,它可以用来不在app中打开就可以预览内容。然后会介绍两个system-wide aspects - information and configuration - 虽然不会直接影响UX但会间接影响其它app的行为。

接下来会讨论DUET,一个新而文档完善的Apple's OS feature(Darwin 14左右引入),其不断地收集用户与OS交互的数据,以期对行为模型变得更主动也更有预测性。DUET在其收集信息的数量和特异性上是非常卓越的,且其首次文档化可以帮助第三方使用这个数据宝库。

随后会讨论Macos's打印子系统,解释其与经典UNIX打印机制的整合。最后会介绍系统语音交互机制,像iOS's 语音控制与无所不在的Siri都被逆向并会详述

FSEvents

任何OS的核心功能都包括响应事件,包括本地文件系统的变化,添加和删除等。这些事件可能要求自动响应,比如indexing, registration,或者通知特定app。BSD kevent (2)机制(第8章)可以通过EVFILT_VNODE监控vnode(=file和directory)事件。这类似于Linux's inotify功能。而kevents限制在了单个vnodes层级,而MacOS 10.5 (和iOS 5.0)引入的FSEvents机制提供了更多文件系统层级范围的通知。这个机制Apple广泛地应用在监控并响应文件系统的变化,最值得注意的是launchd的com.apple.sevents.matchingLaunchEvents key,第13章 FSevents向用户态暴露了一个字设备- /dev/fsevents。使用它很简单,只需要open(2)并使用FSEVENTSCLONE ioct1(2) "cloning"它并指定一个fsevents clone args structure,其中可以用来指定监控事件的列表。其会返回一个复制的文件描述符,其可以进一步由ioctl(2)接收处理(比如FSEVENTS_WANT_COMPACT_EVENTS或者.. WANT_EXTENDED_INFO),以读取通知流,如Figure 4-1所示:

image.png /dev/fsevents node是全局可读的,但FSEVENTS_CLONE是一个可写ioctl(2)(指定为_IOW)。监控app因此需要root运行。虽然fsevents.h头文件向用户态暴露了众多常量和fsevents定义typedefs,Appler已经将它们移除恢复到10.7。这些常量通过XNU's bsd/sys/fsevents.h仍可用,但要使用必须手动导出。你可以在一个文件系统监控器FileMon中找到一个完整的实现(本书网站上可用),这个工具下面的实验中也会讨论

此处略去了实验详情

The FSEvents.framework

Apple将/dev/fsevents接口视为私有,进一步将其限制为root可用。公用的FSEvents.framework([Mobile]CoreServices的子framework),自10.5可用,提供了三方可用的API。这个framework头文件中详细文档化了,FSEvents.h,并通过FSEventStream对象抽象了其底层实现,其可以轻易添加进进程的CFRunLoop。这也让其很容易作为CFRunLoopSource为app监控文件系统事件。

fseventsd

相比于直接操作字设备FSEvents.framework发送Mach消息给专用的daemon,fseventsd,其是作为一个LaunchDaemon从framework's Support/目录(MacOS)或/usr/libexec (elsewhere)启动的。fseventsd daemon声明了com.apple.FSEvents这个Mach port,通过这个端口提供MIG subsystem 66000服务。这个子系统包含7个消息,均是内部使用的。满足指定观察path的事件会发送给感兴趣的parties,通过Mach message #67000。daemon也可以用于dump其内部状态(通过syslog或者更新的os_log(3)),当其收到信号SIGUSR1之后

image.png

Spotlight

Darwin中的文件和目录数量惊人,但相比于不断变多的用户文件仍略显苍白。需要有一个功能追踪所有文件的位置,这个功能位于Spotlight。 Spotlight在MacOS 10.4(Tiger)引入,并成为大多用户的第二nature,通过其放大镜图标和⌘-空格快捷方式。这两个操作都会调起搜索窗口并奇迹般地搜索到匹配的文件和目录。Apple在3.0将Spotlight带进了iOS,其可以直接从SpringBoard使用,只需要在主屏幕右划,或者(iOS 7)在屏幕中间往下划。iOS 9开始Apple部分开放了CoreSpotlight.framework,以开启application data索引和UI-driven的搜索。

在内部Spotlight APIs是由一组关系错综复杂依赖关系的frameworks提供的,如Figure 4-5所示:

image.png

  • MetaData:CoreServices.framework的子framework,提供Spotlight数据背后的model - MDItem*, MDQuery*, MDStore* and MDPlist* APIs。它们使用户可以使用丰富的语言查询元数据存储。其在OS中不存在。这个framework也包含ma族的daemons,以及(MacOS 11) corespotlightd (后面会介绍)

  • CoreSpotlight:公共但大部分未文档的,其在大多MetaData之上提供了OC的CS和MD class抽象。其也持有一个schema.plist,包含了所有KMD* 可搜索属性值

  • Spotlight:提供特殊查询(e.g. WebHistory, Calculator, Dictionary等)的Objective-C SP*Query类

  • SpotlightServices: 提供mapCategoriesToBundleIds.plist和无数.mdplist(专属格式,非plist)文件 形式的数据model支持

  • SpotlightIndex:私有framework,导出SI* APIs,其抽象了通过Mach消息的索引查询

  • SpotlightReceiver 只提供一个Objective-C类, CReceiverConnection。由suggestd接收Spotlight索引更新

  • SpotlightServerkit只在MacOS存在,且支持Metadata.framework's mds

  • SpotlightDaemon 导出startAllAgents and startIndexAgent,它们实例化CSSearchAgent, CIndexAgent和MDAgent

iOS also offers an additional private framework, SpotlightUl, which handles the UI effects (search field andblurred view) when invoking Spotlight. This is used by SpringBoard: swiping down displays the SPUINavigationBar and SPUISearchViewController

MacOS Spotlight daemons

MacOS spotlight daemons是从MetaData.framework's Support/目录启动的,作为LaunchDaemons(也就是system启动)或者LaunchAgents (也就是user session启动)启动。Figure 4-7显示了许多daemons*和它们的关系:

image.png

mds - The metadata server
otherdaemons
The mdworker daemons
corespotlightd
iOS: searchd
searchd's XPC

QuickLook

MacOS 10.5,QuickLook功能不可思议的生成式实时icon能力,可以生成对应文件的全景展示。按空格键即可以打开一个只读的简单有效的预览UI,也支持标准操作(Mail, message, Airdrop等)

image.png

Programmatic API

QuickLookUIService

ThumbnailsAgent

System Information

Applications(尤其是system services)经常需要查询硬件信息或者OS系统本身的信息。这些分别有对应的APIs和system calls来获取, (卷II)IORegistry提供了这些信息里面的大部分。然而无数的参数使追踪它们变得很困难

MacOS的经典解决方案是Gestalt API,这个简单的调用集允许app查询一个32-bit值的key,并从Gestalt获取值, 而不需要知道这些值是怎样以及从哪里获取的。不幸的是, Gestalt在10.9被禁用了,在这里遗留下了一个功能真空

Darwin: sysctl

MacOS: AppleSystemInfo.framework

MacOS: System Profiler

*OS: MobileGestalt

Feature Flags (Darwin 19)

Darwin 19引入了Feature Flags,作为在运行时决策OS feature和设置的新选项

System Configuration

OS需要维护系统和网络配置。而Windows有Registry, UNX使用/etc及子目录中无数的不同文件。Darwin通过SystemConfiguration.framework和其丰富的SC APIs 提供标准API来读取和操作系统配置,通过它,感兴趣的用户(通常是daemons但也可以是app)可以查询和修改配置。这个framework不只是公共的,而且是完全开源的(configd项目的一部分),其详细文件在Apple Developer's System Configuration Programming Guidelines(4],虽然从设计上来说可以配置系统的多方面,但SystemConfiguration基本只处理网络和连接设置。

/us/libexec/configd

/usr/sbin/scutil

Configuration notifications

Duet

Duet*子系统是一组私有frameworks和几个daemons,它们一起gather详细的系统统计数据并执行多个相应操作。 它是Darwin 14引入的新子系统,但其对系统健康、维护和预测性的行为非常重要。Table 4-35显示了Duet子系统直接关联的多个

image.png

/us/libexec/coreduetd

Duet的客户-daemons和私有frameworks- 报告app和system events给专用的daemon - /us/libexec/coreduetd

The Knowledge Base

Events and Monitors

Activity Scheduler

开发者熟悉的Apple's NSBackgroundActivityScheduler APIs,其支持deferred background tasks。 Apple文档中的描述是"the system flexibility to determine the most efficient time to execute based on energy usage, thermal conditions, and CPU use"。在其背后其实是另一个公共API - XPC activities(14章讲解)在支持。XPC activities背后的"brains"就是Duet Activity Scheduler, /us/libexec/dasd (DuetHeuristic-BM, up to Darwin 17)

The Activity Database

Duet activities信息在数据库中维护 - DuetActivitySchedulerClassC.db-位于/var/db/DuetActivityScheduler(MacOS)或者/var/mobile/Library/DuetActivityScheduler (iOS)。这是一个SQLite3数据库,但其内容在内存中且重启后不连续,所以查数据库文件没什么必要

iOS: The DUET Expert

iOS有另一个daemon, /us/libexec/duetexpertd。它用于app预测,其通过Spotlight展示为"Siri App Suggestions",其它suggestions served by /us/libexec/suggest (through the CoreSuggestions trio of frameworks)

Printing

Siri & Voice Control

MacOS,很久以前就是第一个支持text-to-speech (TTS)能力的。所以引入speech to text-和voice control只是时间问题。

Precursor: Voice Control

Siri之前 (useropts out of Siri的场景),按住i-Device的home按钮会唤起Voice Control界面。虽然比Siri场景受限,但它有个显著优势就是可以脱离互联网连接工作

Siri

Siri最初是作为app于2010年引入iOS的,Apple很快意识到其潜能并将其收购。随后将其大张旗鼓地作为新特性引入了iPhone 4S。从此之后其重要性不断增加,最近iOS 10开始 - 其逐渐开放给第三方开发者

Architecture
Plugins
Shortcuts (iOS 12)

Graphical User Interface

Touch, keyboard和voice control都需要提供用户接口,即图形界面。Darwin多变体的图形架构都很像,但也有些不同,即对"home" application的选择,以及其支撑角色,如Table xx-gui所示

image.png

MacOS: WindowServer

MacOS系统(除非用单用户模式启动)会自动进入loginwindow.app,需要用户输入密码。一旦提供密码loginwindow.app便将控制权转交给home application -著名的Finder.app (from /System/Library/CoreServices) 真正的UI management实际是由WindowServer进行的。然而查看其二进制可以看出它只是一个one liner,它启动CoreGraphics.framework's CGXServer或者(MacOS 13), SkyLight framework的SIXServer*。这俩frameworks都很大,且包含多个MIG subsystems,如Table 4-54所示:

image.png

Windows

MacOS's最引人注目的特性是其先进的Aqua UI,光滑的窗口和效果。windowserver实现了这一切,它维持windows的逻辑呈现,提供其客户可以使用的API,其主要客户是LaunchServices.framework,LaunchServices.framework使用这些API可以获取UI状态

UI Events

待补充

*OS: backboardd

Revisiting touch

虽然voice交互快速占领人机交互,但Touch 仍是交互的主要方式。在MacBooks,touchpad是是首要的接口。*OS 中没有鼠标,但机制仍相同,且可认为是离散的鼠标点,在用户触摸i-Device时出现。虽然touch events最终会成为IOHID事件 (如Figure 4-56), Apple's这块有趣的实现值得专门讨论

MultitouchSupport.framework

第五章 Automatic for the People: Application Services

App并非存在于真空中,它们也不能重新发明轮子。因此它们使用底层OS提供的API进行“创作”。最底层是内核的system calls (XNU还有Mach Traps), 但这些都太底层了。Frameworks和libraries基于system calls 提供了处理抽象。Darwin的真正强大在于它的frameworks,如第2章讨论的,有数百个frameworks,太多了很难完全详述

因此这一章选择app需要的frameworks和functions的子集。先讲app最基本的服务-安装。安装体验是最简单且高效的 - 但其后是LaunchServices.framework和其daemon为app和其数据类型注册提供的复杂逻辑。

然后讨论CFPreferences.framework的preferences APIs,其提供了标准化的方式在key/value中存储app默认配置。假定读者对这些API不陌生(来自公共且文档化了的framework),讨论的焦点会是其实现- cfprefsd

然后焦点转向Notifications- 一个关键的app间同步机制。大多数通知是apps内部的,但有些用户通知是交互式的,也会进行讨论。

一个鲜为人知的事件通知机制 - libXSEvent和MacOS's emond - 提供了一种可以script通知的方法。最终会介绍MacOS的Open Scripting Architecture,以及其底层的Apple Events

Application Installation

每个app都需要安装服务——app的进程需要将其文件部署进本地文件系统,将自己与系统整合起来。整合过程中需要将其Info.plist中的UTIs和URLs注册。大多情况下系统会透明地完成这些,用户不需要关注这些

MacOS App只需要将appName.app的完整目录放进/Applications即可完成安装,通常只通过拖拽就可以完成。/Applications目录权限允许admin group(the interactive user是其中的成员)写入目录,但不能删除。MacOS 11, Apple自己的内置applications在SIP下标记为了restricted,舍不得篡改甚至删除不可能,如Output 5-1所示:

image.png app bundle拖拽放下之后,系统将其复制到新位置。一旦新文件创建一个FSEvents通知会生成。这会唤醒Finder daemon,然后它会解析app的metadata(也就是Info.plist),并自动任何type注册(本章后面会讨论)。 app要做的就是分发给用户安装,通常是打包成一个文件。MacOS开发者仍有选择,即是使用App store或者legacy方法

MacOS Legacy Methods

最常见的legacy方法是通过disk image安装。DMG文件兴起于世界告别软盘转向下载的年代- DMGs通常只是一个文件系统的拷贝,如果不是DMG文件形式它可能会出现在某种可移除的媒介上。DMGs利用了这一点,通过直观的拖拽交互,在双击触发DMG挂载的时候自动弹出交互界面

Package installation GUI
Installing packages

App Store applications

iOS 2.0开始Apple引入"App Store",彻底改变的软件工业。它的成功如此深远便快速扩展到了MacOS(late 10.6 versions),并成为推荐的下载和部署方式。

除了可视化和方便的软件分发的优势, Apple也提供了两个激励的feature给开发者:

  • Code Signatures:所有下载的app都是签名过的,使用可以回溯到Apple的证书链。这确保了app的权威性并进一步使Apple嵌入指定的entitlements,其提供了细粒度的权限粒度以及强制沙箱

  • CodeEncryption: App store中*OS app二进制自动使用苹果强大的称为FairPlay的加密机制进行了加密。这个加密(a vestige of Apple's attempt at iTunes DRM)可以让app二进制不能解密,除非在那个i-Device上且掌握了关联的Apple ID

iOS和MacOS的App Stores是Apple的重要收入来源, app售卖和内购都有权收费。*OS开发者除App Store外别无选择,因为i-Devices上除此外无法安装三方软件。MacOS开发者仍有选择可以分发.pkg或者 .dmg(像VLC和Skype),但Apple仍时刻在寻找方法弃用或者彻底移除这种方式

App Store Receipts

MacOS App Store下载的app在其bundle中有一个额外的目录称为_MASReceipt。包含一个文件receipt。这个receipt是由CommerceKit.framework's ReceiptInstaller维护的,其是在每次成功下载之后按需启动(通过com.apple.storereceiptinstaller)。这是正常的/Library/Receipts/InstallHistory.plist中的包receipts之外的一个补充 虽然无法被file(1)识别,但receipt其实是DER编码的 PCS#7 data, capturing下载时间并使用App Store certificate签名 (苹果证书可以通过openssl asnlparse -inform der展示)。Apple部分地在Receipt Validation Programming Guide[4]中文档化了receipt格式。其也提供了服务器在buy.itunes.apple.com 以通过HTTPS验证receipts。虽然文档不全,但dumping这个receipt并使用server可以逆向这个格式的所有字段

image.png

Installing applications (*OS)

*OS variants中的app安装完全不一样,因为/Applications目录是留给system app的,且不能修改(因为系统分区是挂载为只读的)。三方app (作为ipa zip archives下载的)是安装在data分区的,是可写的。

从App Store下载后, iOS app是ipa包,其现实中实际是一个.zip包,包中结构固定: app文件zip到Payload/ 目录,其中会有一个appname.app目录。app安装到i-Device后,ipa解压到/var/containers/Bundle/Application,然后Payload目录重命名为一个随机生成的128-bit Universally-Unique-IDentifier (UUID)。如Output 5-8所示:

image.png

lockdown support
App receipts

Application Uninstallation

Application data

Transparent Application Lifecycle (MacOS)

The LaunchServices Database

app安装和卸载包含了前述众多的工作。有些app可能需要特别的设置,或者声明一个与URL scheme或file type的关联。system因此维护了一个已安装app的registry - 称为LaunchServices数据库。编程访问这个数据库的方法是由同名framework提供的,其是由[Mobile]CoreServices reexport出来的。LaunchServices.framework 是公用库,但其headers可惜是不完整的,只声明了其丰富功能集的一个子集 MacOS和*OS LaunchServices数据库都位于一个临时目录,且在常规app可触及范围之外。lsd(8) daemon允许通过XPC访问这个数据库(稍后会讨论)。使用procexp(j)检视/usr/libexec/lsd可以推测出它的位置,因为它是mmap(2)进daemon的地址空间的:

image.png

lsd
Entitlements
MacOS: launchservicesd

Preferences

所有app需要维护它们多样的配置,包括默认的和用户指定的,且有两种方式实现。Linux中,每个app有自己的:搞清楚配置的存储位置和格式是每个app自己的责任 - 要么在/etc中,要么在用户的根目录下或者其它地方。Windows使用分散的方法,提供了使用标准API的Windows Registry作为存储库

Darwin也选择中心化的方法,但是介于上述两种之间:一方面提供标准化的API,另一方面避免registry因选择per-app文件而把事情搞砸。文件存储在如下几个位置:

  • Library./Preferences:包含系统层面的apps偏好设置,通常是Apple's自己内置的app, as individual property lists (XML/binary)
  • MacOS, $HOME/Library /Preferences: 包含每个app特定的偏好设置,允许每个用户进一步定制配置
  • iOS, /var/mobile/Containers/Data/Application/UUID/Library/Preferences)):包含每个app的偏好设置,这个model存在是因为iOS只有一个用户,且所有三方app都是containerized

/usr/sbin/cfprefsd

Notifications

System通知在任何OS中都是重要组件,支持悠关方监听与发送事件,以及携带关联数据。Apple OS使用很多通知,而且通知机制不止一套,而是有多套机制。CoreFoundation.framework抽象这些机制成一套统一的API - CFNotificationCenter.h:

image.png 本地通知中心,于其进程来说是本地的(或者说内部的),且是实现在内存中的 - 因此不在我们的关注的范围。Darwin 和Distributed centers是system-wide 且因此是由external daemons实现的

Darwin Notifications (libsystem_notify)

Darwin的通知子系统是一个开源实现,归属于Libnotify项目,完整文档在notify (3) manual page中。这套机制依赖于一个专用的daemon- /us/sin/notifyd- 其从用户调用notify_post(3)时获取消息,指定一个任意的字符串,通常是反DNS标记法。收到消息时,daemon会将其传递给监听者,监听者需要先调一个notify_register_* functions,指定要监听的字符串name作为参数

/usr/sbin/notifyd

The Distributed Notification Center (/us/sbin/distnoted)

distnoted注册了com.apple.distributed_notifications@[0/1]v3 XPC services (在com.apple.distnoted.xpc.daemon.plist LaunchDaemon)。其作为一个特殊的uid运行,_distnote(241) 在所有Darwin versions中。MacOS中distnoted还扮演agent,其注册XPC服务com. apple.distributed_notifications@Uv3 (在com.apple.distnoted.xpc.agent.plist LaunchAgent)。由于agents运行在login sessions中,所以通常对于登录用户和_spotlight用户来说有多个实例。

与依赖于MIG的Darwin Notification Center不同,distnoted是一个XPC service。XPC's payloads是字典,其在userinfo中可以传递额外的plist形式的通知数据。Listing 5-27展示了注册和通知消息的基本格式,由XPoCe捕获:

image.png

*OS: Notification Proxying

*OS没有agents,但有/usr/libexec/notification_proxy- 其由lockdownd用来中继notifications给一个USB连接的host, as well as allow the host to post notifications。notification_proxy支持两个不同的配置,每个配置包含不同的LaunchDaemon property list和XPC服务名。secure配置 (com.apple.mobile.notificationproxy)正常启动daemon,而insecure的配置(com. apple.mobile. insecure_notification_proxy)将其使用-i参数启动。insecure center不能发送通知,但仍能监听通知 两种配置由/usr/libexec/lockdownd的裁决启动,其中包含了ServiceAgent的定义在__TEXT.__services section,连同host上按需启动的其它服务。 As with the other services, a useful host-side client exists in the libimobiledevice project's idevicenotificationproxyl8], Mac hosts normally start the secure proxy

两种配置中,notification_proxy都监听两个centers:Darwin Notification Center(使用notify_register_dispatch)和Distributed Notification Center(使用CFNotificationCenterAddObserver)。proxy监听host使用observeNotification命令发出的通知,当监听的通知触发时,通知会被使用RelayNotification命令中继返回。这个过程也可以反过来,使用PostNotification。host也可以请求proxy关闭 Figure 5-28展示了通过私有的MobileDevice.framework启动notification_proxy的事件序列。开源的libimobiledevice模拟了client的功能,尤其是在MacOS平台外

image.png

Apple Push Notifications (APN)

Apple支持的最新的通知机制是Push Notifications(熟知为APNs)。本地通知中心是进程级别的,Distributed notification center是系统级别的,而APN是面向互联网因此是世界级别的

本地设备app可以注册"Topics"... 客户端client app使用ApplePushService.framework和本地apsd联络Apple服务器,生成一个独一无二的设备token。这是一个类似于UDID的不透明ID,但它是间或重新生成的以保护用户隐私。app将其发回自己的服务器,称为provider

provider会使用其token连接APN服务器, or aclient side certificate,生成RFC4627 (JSON) 格式的消息,消息中可以包含"Alert"(String), "Badge" (integer)和一个可选的"Sound" (String)等。provider发的每个消息都必须包含一个token,也就是先前APNS服务器生成而由客户端获取的那个token。Push通知原本使用的是专用的二进制协议TCP 2195 (feedback用2196)与gateway.push.apple.com交互,但后来迁移到了基于SSL的HTTP/2(api.push.apple.com)

不管用哪个协议,Apple的服务器将消息中继发回token标记的那个本地设备。再一次,本地提供服务的是apsd(i.e.它既处理对外注册也处理接收通知消息)。本地apsd将通知中继给本地app manager。后者显示一个用户通知并根据UIBackgroundModes key选择是否自动启动匹配的app。daemon通过TCP端口5223维持一个与苹果服务器的连接,通过网络监测工具可以看出来

image.png 看起来Apple自己的daemons在本地通知上走了捷径,因为它们可以与apsd直接通信。调用APSConnection时,用一个namedDelegatePort: selector,并用其bundle id加上后缀.aps作为 named port

也可以通过其偏好配置/Library/Preferences/com.apple.apsd.plist 找到很多apsd配置。然而需要注意的是这个plist是root拥有的所以需要超级用户权限。push通知自身是维护在一个SOLite3数据库的, aps.db,在 /Library/Application Support/ApplePushService (MacOS) or /var/mobile/Library/ApplePushService (*OS)。数据库中包括两个表 - 收到的消息和外发的消息,且只作为在消息传递得到保证之前临时的消息存储

User Notifications

MacOS和*OS也提供User Notifications的机制。不同于先前讨论的用于进程同步的通知,用户通知意味着吸引用户的注意,因此必须拥有一个可注意到的GUI

CFUserNotification

MacOS和*OS提供CFUserNotification API,其弹出一个简单的带标题和正文的消息框。如下所示Listing 5-31:

image.png

*OS: The Bulletin Board

MacOS: NSUserNotificationCenter

/sbin/emond (MacOS)

Apple Events (MacOS)

MacOS最强大的功能之一是Apple Events,其提供了无与伦比的自动化能力,有些类似于Windows' DCOM。这个功能自MacOS (classic) System 7 (1991!)就存在,而且是AppleScript自然语言脚本的基础。这两个feature的开发都可以在Cook et al.[10]中找到,更官方的文档可以在Apple Developer中找到

AppleScript

Apple Events的主要使用场景是AppleScript环境。Apple提供了Open Scripting Architecture,理论上任何脚本语言都可以插入。支持两种语言- Javascript和AppleScript- 后者因其便捷性更受偏爱,且其几乎就是自然语言的语法(有些类似于Siri,甚至可以通过text interface)。AppleScript解释器环境拥有很多内置原语(比如基础数学、UI等),但它依赖于Apple Events传递脚本命令给其它app 数个scripting clients可用。编辑脚本可以使用Utilities/Script\ Editor.app。Automator提供了直观的工作流 GUI (Figure 5-39)。通过命令行, the osascript(1)可以用来运行脚本(from stdin or a file), or one liners (using - e)*。也可以使用osacompile(1)预编译的脚本

Scripting definitions

Sending AppleEvents

第六章 Ex Machina: The Binary Format

这部分内容已经广为人知,所以先略过了,感兴趣可以搜各类相关介绍文章来学习

第七章 In the Darkness, Bind Them: dyld internals

设计良好的文件格式只是加载和执行谜题的一部分。还需要一个动态链接器以解析load-time和run-time依赖,并将必要的组件组合起来,将其在内存中排列起来并绑定(或者链接)起来。

使用了独特的二进制格式,Darwin系统也使用独特的动态链接器/usr/lib/dyld来完成目标。虽然它是开源的,但实际上在Apple(以及本书同名网站)之外没有这个链接器内部的公开的文档。这一章将这个链接器放置到聚光灯下,并开始解释它的基本流程,然后聚焦在它是怎么完成链接和符号绑定的

这一章也介绍苹果最有用的frameworks之一 - CoreSymbolication。虽然是私有的,但苹果自己的工具都使用它解析Mach-O符号- 不管是导出的和未导出的 - 做逆向工程非常有用。焦点会给到dyld的内置功能interposing,其对于动态分析是无价的

然后很大篇幅介绍shared library cache - 所有frameworks的预链接blob,MacOS和*OS都在重度使用- 对后者而言它是唯一真实持有的系统framework和library的拷贝。这个cache曾经变化的格式也会讨论到,以及怎样使用jtool(j)解析它。新版本dyld- "dyldv3"-引入后,也带来了新且重要的概念链接器闭包。本章最后会介绍一组技术 - 编程操作Mach-O以及DYLD状态的inspection和tracing

Program Startup

虽然LC_UNIXTHREAD或者LC_MAIN commands指定了程序的入口点,但它们都没有指定程序真正的入口点。所有程序(除了静态链接的) 入口点都是动态链接器(其路径在LC_LOAD_DYLINKER中指定) -i.e. 也就是dyld。这个入口点称为__dyld_start,且编码在特定于架构的汇编代码中(dyldStartup.s中),所以是依赖于架构的

紧随其后的是漫长的进程 - 如Figure 7-2所示 (next page),让人叹为观止的是这些都是在程序入口点之前发生的。程序开始前dyld需要设置自身的内部状态,获取执行文件的依赖库,将其映射进内存并链接所有启动需要的未解析的符号(也就是"non-lazy" bindings)。只有在这个冗长的映射和绑定处理完成后,控制流才能转移给程序指定的入口点。幸运的是dyld自身并没有依赖,否则会进入先有鸡还是先有蛋的怪圈。它是静态链接到自己的system call wrappers和libSystem excerpts的

image.png

一旦启动,程序的初始状态是如下定义的 在dyldStartup.s中

image.png

The apple[] argument vector

dyld::_main ()

dyld::main()函数可以认为是dyld实际的入口点。它的第一个任务是configureProcessRestrictions(), 会影响多个DYLD*环境变量的行为(本章稍后会讨论)。Restrictions在不同Darwin variants中的处理有一点不一样: MacOS's restrictions(在全局上下文的sProcessIsRestricted中设置)是强加在满足如下条件之一的进程的:

  • setuid or setgid
  • 有一个__RESTRICT段 (example: /usr/libexec/amfid)
  • Possess any entitlement in their code signature, which sets the Cs RESTRICT code signing flag- but only when SIP is enabled

Linking

最简单的程序也并不是自包含的。简单的API调用比如printf(3)或者write(2)或者更复杂的framework调用,或多或少会使用到外部定义并实现的函数,这些都必须与编译的代码链接到一起以实现预期的功能。 有两种链接模式:

  • 静态链接: 要求编译期间所有外部代码存在,在最终阶段静态链接器从library archives (.ar)获取相关函数并将它们合成进最终的目标文件。此时目标文件就是自包含的,这时候就不需要运行时依赖了
  • 动态链接: 将外部链接延迟到程序加载或者运行阶段,并要求相关函数存在于预定义的库中。依赖库需要存在于预定义或配置的位置, 以便外部函数首次调用之前,程序控制流可以转向动态链接器并将函数缝合进程序中 静态链接的特点是执行文件相对大,且无法享受到代码库共享内存的好处 动态链接的二进制需要嵌入指令以让动态链接器可以顺利执行。最常见的方式是在外部函数地址的地方留"stubs"。 "stubs"将控制流指向动态链接器,同时指供足够的信息供链接到目标函数。这其中最少需要用到4个Mach-O sections:

image.png

DYLD opcodes

绑定使用dyld中一个特定的有限状态机。它使用几种"opcodes",它们位于LC_DYLD_ INFO load command中,与Output 6-26一样,如下所示: image.png 每个操作码定义在字节的高4位,后4位是立即数,如果4位不够,则需要使用使用ULEB128(unsigned little endian base 128)的DWARF编码 image.png

Advantages of the dyld opcode scheme

这种table encoding scheme的方式,相比于其它形式比如使用压缩的优势在哪里呢。这种选择合理性在于:

  1. 使用opcodes允许表中的数据排列相对紧致,不需要额外的压缩。压缩对最小化磁盘中二进制的大小是很好的,反正这个列表只能在内存中扩展。压缩并不能很好满足对列表行随机访问的要求 - 而随机访问是基于索引的scheme尤其擅长的
  2. 列表编码在nonlazy操作码流场景更高效,延续上述实验Output 7-19 shows that of iTunes:

image.png 3. 操作码模式可扩展,因为新操作码只需要添加即可。而且现实中真实出现的A12设备中出现了添加新操作码的需求,因为这些架构支持指针认证和在加载期间绑定所有符号。加载期间绑定影响程序的启动时长(Apple dyld工程师的课题),为了加快它需要在Darwin 18's dyld-625添加一个新操作码,这个举动重新定义了整个绑定过程(see "arm64e and dvld-625 changes",本章稍后会介绍)

Rebasing Opcodes

arm64e (A12 and later) and dyld-625 changes

Exports

导出是DYLD_INFO数据中唯一不需要操作码的部分。相反地,使用了一个叫做trie的结构,Tries (发音为"trees")在<mach-o/loader.h>中有详细介绍:

image.png 导出符号可以通过dyldinfo(1)或者jtool(j)使用-export开关查看。执行文件通常只导出__mh_execute_header,但也可能导出它们的插件需要的符号(最佳范例是/usr/libexec/UserEventAgent) Dylibs通常导出所有全局符号(uppercase in jtool -S 或者 nm)

CoreSymbolication.framework

标准API dlsym(3)和dladdr(3)在Darwin中也是支持的,但dyld拒绝解析非全局的符号(uppercase letter in jtool -S或者nm(1)的输出)。但即使是本地符号都可以(除非已经被编辑过了)手动从符号表中按<mach-o/nlist.>格式筛选出来。开源的liblorgnette[3] project可以从Mach-O image (local or remote)获取全部符号,作者自己的machlib也可以 CoreSymbolication.framework framework也可以实现这个功能,不只对自己的task,别人的也可以。使用这个私有库好过选择使用nlist,因为它对苹果极力进行的弱化符号编辑后仍然有妙用(jtool(j)在i-Device上就是利用这一点)。虽然它是私有的但在苹果的dtrace项目中仍有使用,dtrace几乎可以推导出完整的逆向的头文件 on the MountainStorm GitHub[4]。 这个framework有它自己的私有daemon,coresymbolicationd, which consults a cache of packed symbol files,位于/System/Library/Caches/com.apple.coresymbolicationd/data (MacOS), /var/root/Library/Caches/com.apple.coresymbolication/{kern.osversion} (*OS)。这个 framework通过XPC一个简单的接口访问这个daemon,接口包括通过CSCppDaemonConnection类的方法提供的9个命令,如Table 7-24所示:

image.png 这个framework在ReportCrash中重度使用,15章会介绍。另一个CoreSymbolication客户的好例子是Xcode's symbols(1)。这个功能强大的工具可以文件和进程中符号 - 或者内核的(指定了 -1及特定的PID)。这个工具(是SamplingTools project的一部分)不幸地是闭源的,但其功能可以通过使用CS* APIs复刻出来。Listing 7-25 (next page)展示了一个骨架例子,其可以扩展出枚举和解析image中符号的功能,它的代码相对少而紧凑,但其中主要是因为使用了推断和缺失的头定义 如果将其放入main()通过lldb或者XPoCe启动这个程序,会看到symbolication中用到的主要命令是read_mmap_archives,这个命令要求调用者提交一个entry —— 也就是一个序列化的有8个值的MMapArchiveFileSystem::ArchiveEntry数组,其中第一个值是Mach-O的UUID complement(也就是取其LC_UUID值并翻转以便所有16个字节的和都是FF)。对其的回复使用XPC共享内存将符号archive映射进调用者有内存空间

image.png

Interposing

dyld最有用的功能之一是函数插入。Interposing指的是将指定函数替换为不同的实现,可以用来追踪,hook或者拦截它们的调用。这是一个极为有用的能力,为软件测试人员、逆向工程师和黑客所喜爱。 通过一个DYLD_INTERPOSE宏,dyld让interposing极为简单,include/mach-o/dyld-interposing.h中的定义如下:

image.png

Dynamic Interposing

interposing如此有用,其传统上限制在load-time使用,通常DYLD_INSERT_LIBRARIES来注入一个库。而interposing的真正威力在于run-time,也就是进程已经运行后。为了达到这个目的,dyld-353和后面的版本支持了dynamic interposing,支持简单的函数调用就能进行interpose 所有调用者需要做的就是提供一组函数指针和其replacement。API如Listing 7-28所示:

image.png

dyldv3仍未实现dynamic interposing(重写了一些核心代码,dyld-5xx)

The Shared Library Cache

Shared cache inspection APIs

Shared cache format

Mapping the Shared Cache

Branch Pools

Acceleration Info

Overriding the SLC

Cache Extraction

Darwin 17: dyldv3

虽然dyld总是用源代码版本区分,但Apple希望区分几个主要版本。从NeXTSTEP 3.3到MacOS 10.4 ('1.0"), 到MacOs 10.6 ("2.0"),从10.6到10.13 ("2.x"),从10.13开始 ("3.0")。这是全人为划分,只是为了强调dyld与Darwin一起的演化。Apple在unusually detailed WWDC2017 session[71]中引入了"dyldv3"特性

DYLD Closures

Darwin 17引入的新概念是DYLD closures。DYLD闭包是一组提前递归解析过的所有dylib依赖,通常在app安装或者系统升级时完成。DYLD将闭包定义为执行文件依赖的递归集 - 从其LC_LOAD_*_DYLIB commands和任何@paths的集合, 包括解析出的所有依赖。与其每次启动时解析, DYLDv3将解析的结果集写入磁盘,而只需要在每次启动时检验一下

The (short lived) closured

Darwin 17 (dyld 509)早期版本引入了新daemon - /usr/libexec/closured (closure daemon)。closured daemon是为了进程外的闭包解析服务,以显著加快app load time。这个daemon声明了com.apple.dyld.closured 端口,也关联了一个host special port (#27),基于这个端口其提供MIG subsystem 6000,包含两个消息,定义在dyld3/closured/closuredProtocol.defs:

image.png

Programmatic manipulation of Mach-O objects

很多security产品和plugin frameworks需要在Mach-O对象加载进进程空间之前和之后遍历和检视Mach-O对象的API。Apple提供了两个库,从libSystem.B.dylib中双重export了一遍

libmacho.dylib

libdyld.dylib

libdscsym.dylib

machlib

Remote inspection of dyld state

不能在同一个进程空间运行的Security工具和其它工具经常需要检视dyld的状态和加载的images。dyld为这些场景提供了支持,通过导出dyld_all_image_infos。这个structure由Apple在dyld迭代过程中持续地在更新,也解释了为什么它从版本1 (in 10.4 and 10.5, dyld-95)到了版本16 (in 10.13 (dyld-509) and later)

image.png

Debugging and tracing dyld operations

动态链接是重要的操作,且链接错误会可能会导致无法追溯的bug。dyld通过使用环境变量提供了详细的tracing能力,在dyld(1) man page中有详述

Environment variables

image.png

Kdebug Codes

Apple也扩展了Mach task APIs以支持dyld tracing,如Table 7-46所示。这些API补充甚至增强了task_info API,但目前(as of Darwin 17)并不是所有的都已经被实现了,其它的也只是kdebug codes

image.png

image.png 从509版本开始(Darwin 17's "dyldv3"),dyld使用Kdebug facilty注册其timestamps和loaded images。DBG_DYLD (0x1F) code set就是为这个目的分配的,codes如Table 7-48所示:

image.png Darwin 18的dyld (version 625)在此基础上又添加了更多codes,都在/usr/share/misc/trace.codes中有指明