走过千山万水(苹果开发文档),踏遍天涯海角(苹果开发论坛)~
众里寻他千百度,蓦然回首,那人却在,灯火阑珊处~
历史的车轮
内核扩展的弊端
内核扩展能力是大部分系统扩展底层系统能力的手段,对于macOS系统而言,内核扩展主要分为两类:
- 针对I/O Kit的内核扩展,主要用于硬件驱动程序开发;
- 针对通用内核扩展,比如网络扩展及文件系统监控等;
虽然内核扩展提供了增强系统能力的强大支撑,但其运行在内核态,能够访问内核空间,这就容易造成恶意的内核扩展程序渗透到系统获取到整个系统最高权限,导致安全隐患!并且对于内核扩展开发而言,考虑到性能开发语言也受限于C或者阉割版的C++语言,为啥是阉割版的c++,因为对于c++中的高级特性,如异常、多继承、模板、运行时类型信息等,都不可用!只能使用残缺的面向对象!
不仅如此!调试内核扩展也是一个难题,还需要搭建虚拟机甚至还需要开启内核调试模式,难受的是:错误的编码极易导致整个系统崩溃!就陷入崩溃~重启的恶性循环!
即使开发完成对于后续的维护bug排查也没有有效的手段来处理,通过控制台查看系统内核日志的方式简单粗暴~但效率低!
......
苹果的决心
内核扩展的种种弊端苹果开发工程师也看在眼里,“痛改前非”与内核扩展下刀子!
WWDC2019上苹果就宣布决定启用内核扩展,在macOS 10.15开始启动系统扩展!不过给开发者一个过渡期,macOS 10.15未强制执行。不过从macOS 10.15.4开始,使用已弃用的KPI会触发一条通知,通知用户该软件包括已弃用的API,并要求用户与开发人员联系以寻求替代方案。并且明确macOS 10.15将是最后一个支持完全支持内核扩展的版本,后续使用禁用的KPI(Kernel Programming Interface,内核编程接口)将不再被加载,具体禁用的KPI可参见Deprecated Kernel Extensions and System Extension Alternatives。
网络过滤的内核扩展几乎全军覆没,这也代表与网络相关的如构建VPN隧道的能力被废弃!
说了这么多苹果弃用内核扩展的决心,那系统扩展是啥呢?具体概念后面说,不过它的好处可以来谈谈。最主要的是运行在用户态,这样就可以摒弃了内核扩展的导致系统崩溃、安全漏洞的隐患,且不再受开发语言的限制,一些高级语言高级特性都可以拿来做利刃,比如Objective-C、Swift、C++17等,调试修复bug起来相对也easy了~
未来的展望
使用运行在用户态的系统扩展程序扩展系统能力,这是大势所趋。其实Windows阵营已经提供了WDF(Windows Driver Foundation)驱动开发框架来支持用户态驱动(UMDF,User-Mode Driver Framework)开发,不过这只是一个框架该框架也可以用于内核态驱动程序开发,Windows未明确禁用内核态驱动开发(Windows开发的别拍~)。但受益于苹果封闭的开发生态及对安全意识重视,已经提前迈出了一大步,逐步使用系统扩展替换内核扩展(不过对于macOS10.15以前的系统不受影响)。
系统扩展
概念及框架
"系统扩展"说白了就是运行在用户态的内核扩展来扩展系统的功能,支持macOS10.15+,官方的定义如下:
System extensions on macOS Catalina allow software like network extensions and endpoint security solutions to extend the functionality of macOS without requiring kernel-level access.
其整个的框架如下:
系统扩展将整个的之前的内核扩展部分整体封装交由系统来管理,只需要通过SystemExtensions.framework框架来管理系统扩展的配置及管理请求,最终的执行交由系统的守护进程,如sysextd(负责安装/批准/卸载系统扩展)、kextd(内核扩展服务守护进程,用于处理内核或者用户进程加载内核扩展的请求)、endpoontersecurityd(端点安全守护进程,用于初始化、启动并监视端点安全扩展)、nesessionmanager(网络内核扩展会话守护进程,用于运行及管理网络扩展会话,比如启动/停止/配置网络扩展)、launchd(最终的系统扩展都是由该进程启动)。
系统扩展包括:
-
网络扩展
其实网络扩展早就已经出现在macOS 10.10,不过系统扩展增强了其功能,比如内容过滤器、透明代理、DNS代理等,且macOS10.15之前的网络扩展只能通过Mac App Store来发布,不过现有的系统网络扩展可以通过
Developer ID来发布在Mac App Store之外。 -
端点安全
替换
KAUTH接口来拦截和监视与安全相关的事件的内核扩展。 -
设备驱动扩展
使用DriverKit替代的是IOKit的驱动内核扩展。
网络扩展
网络扩展其实已经出现在iOS9中,只是将iOS中的一些特性移植到了macOS上且支持Mac App Store外发布,下面主要阐述系统扩展新增的特性。
内容过滤器
使用场景如“个人防火墙app”、家长控制APP或者只记录网络访问行为的APP。
主要框架如下图所示:
NEFilterManager通过设定其属性providerConfiguration来配置过滤规则,比如过滤传输层流量还是网络层流量,通过调用saveTcPreference来配置到系统,这样就启动了系统扩展过滤。
系统扩展运行着NEFilterDataPorvider子类,并可以重写startFilterWithCompletionHandler/StopFilterWithCompletionHandler/HandleNewFlow方法,其中系统开启过滤时会调用startFilterWithCompletionHandler方法。默认情况下,系统会把每一个TCP/UDP数据流都转移至内容过滤器,不过可以使用NEFilterSetting来详细配置具体的流量过滤规则,具体的过滤规则可以使用NEFilterRule来配置过滤规则对象,然后系统就会调用startFilterWithCompletionHandler:来启动过滤配置。
当有新的数据流创建并匹配上面的过滤规则,就会调用HandleNewFlow函数。该函数接受NEFilterFlow对象来裁决具体如何处理数据流:允许或者拒绝。
透明代理
云安全APP,把APP中指定网站的流量转移到云服务器,云服务会对这些流量进行一些额外的安全性检查,比如额外的用户认证或授权。
系统扩展支持NEAppProxyProvider子类来代理传输层的流量,并且需要通过NENetworkRule指定代理规则,才可以将流量转移到NEAppProxyProvider类中。这样TCP/UDP数据流打开后会根据上面设置的匹配规则,来转移数据流至NEAppProxyProvider类中,具体的数据流处理由NEAppProxyProvider类来决定
App代理的方式与通过路由IP至虚拟网卡UTUN0来修改路由规则不同,不需要配置虚拟IP至虚拟网卡UTNU0,部署网络代理服务器相比VPN更容易,且扩展性更好.
App代理是个人VPN的一种形式,另一种就是基于数据包隧道提供的形式。
DNS代理
DNS劫持会被恶意网站利用指向自己的网站,而DNS代理就可以进行一些安全性检查。
主要框架如下图所示:
DNS代理允许你的应用拦截设备上生成的所有DNS流量,你可以使用此功能提供DNS流量加密等服务,通常是将DNS流量重定向到你自己的服务器。你通常在托管设备的上下文中执行此操作。控制应用通过NEDNSproxyManager来创建DNS代理配置并在系统中配置,来运行系统扩展。系统扩展创建NEDNSProxyProvider子类来处理所有DNS查询流量。
需要主控制应用来通过
NEDNSProxyManager来创建并注册DNS代理配置至系统;
DNS代理可以实现如下功能:
- 将DNS查询请求转发至互联网的DNS服务器;
- DNS代理使用HTTPS或者TLS协议;
- 实现自定义DNS代理协议;
对于使用HTTPS或者TLS协议或者自定义协议,只支持macOS11.0+;
启动流程
启动DNS代理配置流程如下:
-
配置
NEDNSProxyManager类示例并初始化其属性成员,包括enabled、providerProtocol(其中包含了providerBundleIdentifier,若存在多个系统扩展则需要指定)、localizedDescription; -
通过
loadFromPreferencesWithCompletionHandler来加载配置至系统,并通过saveToPreferencesWithCompletionHandler持久化至系统文件供下次直接启用; -
配置
NEDNSProxyManager中的使能配置属性enabled为YES,并通过loadFromPreferencesWithCompletionHandler来更新属性的修改至系统;
启用加密的DNS
通过加密传输DNS请求,避免被拦截监控。wwdc2020苹果开始原生支持加密的DNS,有两个支持的协议:TLS的DNS成为DOT,及HTTPS的DNS称为DoH。这两者都使用了TLS加密DNS信息,不过DoH还使用了HTTP协议来提高性能。
系统范围的DNS设置如何工作?
可以使用网络扩展进行配置,其使用NEDNSSettingsManage或者包含DNSSettins的MDM配置文件,这两者都允许你指定相同的内容。
- 首先通过共享的
NEDNSSettingsManage.share()实例调用loadFromPreference来加载现存的配置; - 使用
NEDNSOverHTTPSSetting来配置DNS服务器,即配置dnsSettings属性; - 调用
saveToPreferences来配置应用到系统;
VPN
VPN主要是安全地远程访问办公内网进行办公,主要框架如下:
使用路由规则来定向流量,并建立隧道来发送数据包;通过includeAllNetworks选项可以将所有流量都能通过VPN,对于访问本地资源可以使用ExcludeLocalNetworks选项来允许访问本地网络资源。
数据包隧道的形式主要通过NEPacketTunnelNetworkSettings,该类具体是配置虚拟网卡的过滤的IP地址。因为是在网络层过滤,因此只能配置IP地址而不能配置端口。而App代理是作用于传输层因此可以指定过滤的端口,但对于过滤所有的DNS查询存在限制。若需要过滤DNS查询,MacOSX10.15+支持的DNS代理就可以过滤所有DNS查询数据包,且不用关心具体的本地的DNS服务器地址,因其自动判定过滤所有的DNS服务器的DNS查询数据包。
端点安全
端点安全框架是
catalina中引入的框架,用于替代KauthKPI,用于替换内核扩展框架。
系统扩展安装后,受到系统完整性保护的防护,防止扩展遭到意外或恶意篡改。为用户守护进程提供了高级别保护,保护级别类似系统守护进程,甚至能阻止root用户来卸载launchd作业。该框架是用C语言编写,可有效利用内存提升性能,也可以被其他语言调用,比如swift、OC及Rust。
整体架构如下图:
使用了消息队列保证了消息穿行处理不丢失,并且端点安全系统会缓存消息避免消息过多。
每条消息主要包含三类信息,如下:
- 消息元数据,包含事件类型、消息生成时间等信息;
- 进程信息,比如可执行文件的信息、例如完整文件状态信息,还有代码签名、进程及用户ID等信息;
- 事件数据,例如SIGNAL事件就包含收到信号的 进程相关信息和信号编号 EXEC事件包含被执行文件 和可执行参数等信息
运行时要求:
- 恰当的ES权限,这样才可以申请配置文件
- 应用程序也需要额外的权限才能安装这个扩展
- 系统扩展需要用户同意才能完成安装
- 需要完全获取磁盘访问权限
- 若部署在托管设备上,需要MDM负责辅助分发
开发指南
开发准备
首先要理解“主控制应用”概念,系统扩展类似iOS中的App扩展,而主控制应用就是包含系统扩展的应用,由其来启动/停止/配置系统扩展。
需要开发系统扩展需要具备开发者账号,且开发者账号为拥有者,因为只有这样才能申请app id及赋予其相应的权利。具体权利需要开启System Extension权利,且规定了系统扩展的bundle id需要以主控制应用的bundle id为前缀,如主控制器应用为com.apple.demo,则系统扩展须为com.apple.demo.xxx。对于需要赋予应用其他能力的,则根据当前应用需要来开启相应的权利,如App Group,来允许系统扩展之间的通信,这个后面阐述。
还有就是需要macOS10.15+,因为只有macOS10.15+系统才支持SystemExtension.framework框架。
系统扩展发布被加载需要经过系统校验,因此需要具备如下条件:
- 系统扩展需要被安装在捆绑的应用
/Content/文件夹下的/Library/SystemExtensions文件夹下; - 捆绑的应用需要安装在
/Applications文件夹下; - 系统扩展需要被签名(且需要和捆绑的应用同一个
TeamId); - 代码签名的
entitlement权利需要匹配开发者团队申请被授予的权利; - 请求的
bundle ID需要匹配系统扩展的bundle ID; - 系统扩展的
bundle ID不能被其他系统扩展使用;
若需要在Mac App Store之外发布,则需要:
-
拥有
Developer ID签发权利; -
创建开发描述文件
provision profile并且需要其类型指定为Developer ID,开启System Extension的权利并下载; -
创建系统扩展工程(xcode已经提供了模板),需要手动签名管理,指定上面申请的
bundle id,并添加上面的provision profile描述文件; -
xcode工程中
Capability中添加System Extension,对于开发网络扩展的则需要开启Network Extension,网络扩展中的权利则不需要指定,由工程中的.entitlements文件来指定,比如开发App或者DNS代理,指定如下:
具体的开发系统网络扩展.entitlements权利描述中指定的项如下:
自定义网络扩展权利特征,具体是配置entitlement权利项中的com.apple.developer.networking.networkextension数组包含值,如下:
-
dns-proxy,用于DNS代理 -
app-proxy-provider,用于代理TCP/UDP连接 -
content-filter-provider,内容过滤器来允许/阻止网络连接 -
packet-tunnel-provider使用自定义隧道协议来将IP数据包发送到远程服务器
-
dns-proxy-systemextension使用
Developer ID profile描述文件来实现DNS代理 -
app-proxy-provider-systemextensionDeveloper ID profile描述文件来实现代理TCP/UDP连接 -
content-filter-provider-systemextensionDeveloper ID profile描述文件来实现内容过滤 -
packet-tunnel-provider-systemextensionDeveloper ID profile描述文件来实现数据包隧道 -
dns-settings用于创建和管理系统范围的DNS配置,可以在主控制应用或者系统扩展,不过需要赋予其网络扩展权利;
-
app-push-provider当无法访问更广泛的Internet时,用于提供类似于Apple Push Notification Service功能的API。
其中,若需要发布到Mac App Store外则需要使用xxx-systemextension为后缀的值。
调试指南
系统扩展开发阶段可以通过如下手段禁用系统的检查避免无法安装,具体如下:
- 通过开启系统扩展开发模式,来禁用系统扩展安装目录要求,即
systemextensionctl develper on; - 禁用
SIP,来避免系统代码签名及公证检查; - 使用
sudo lldb中的process attach --pid xxx(pid)命令来挂载系统扩展调试; arm64e架构下开发Deriver extensions(dexts)驱动扩展说明后续补充
挂载调试也可以使用xcode中的挂载调试,这样可以方便跟踪代码,如下图:
除此之外,因为系统扩展功能都是由系统来控制,因此跟踪系统的日志是必要的。这时“控制台”就派上用场了,可以来过滤系统扩展的日志,方便排查系统的报错。
系统也提供了查看及管理系统扩展的命令行工具systemextensionsctl,具体使用如下:
systemextensionsctl developer [on|off]
systemextensionsctl list [category]
systemextensionsctl reset - reset all System Extensions state
systemextensionsctl uninstall <teamId> <bundleId>; can also accept '-' for teamID
系统扩展启动
透明代理系统扩展启动时序主要如下图所示(仅供参考):
系统扩展通信
App Group
对于iOS应用的App扩展通过App Group可建立两个同一teamid下的进程间通信,比如使用NSUserDefault、NSFileManager、钥匙串、域套接字、数据库同步等。但对于macOS中,由于主控制应用与系统扩展属于不同用户,主控制应用属于当前用户进程,而系统扩展属于root用户,导致App Group访问的路径不同,进而导致一些App Group通信方式不能使用,如上面提到的通信方式。不过苹果工程师推荐使用XPC,下面将简单阐述XPC。
DistributedNotificationCenter
MacOSX提供了一种简单的进程间通信方式就是CFNotificationCenter,其中用于不同进程间通信的为“分发通知中心”,可以用来传输CFString字符串及CFDictionaryRef字典对象,并且上层还封装了NSDistributedNotificationCenter来使用OC对象来通信。
注意:通知中心依赖于
runloop,并且runloop模式为common模式,比如NSDefaultRunLoopMode;macOS10.13+会将分布式通知分发至主线程来处理。若系统分布式通知过多,存在导致本地通知中心队列已满时被丢弃的可能;
NSDistributedNotificationCenter分布式通知对象能发送NSString字符串及NSDictionary字典的消息,但对于沙箱应用只能发送NSString字符串消息,如下:
即使开启App Group在沙箱环境内也不能发送包含字典对象的分布式通知消息!即使禁用沙箱也无法使用,待研究
XPC
这个是苹果推荐的方式,优点就不在此表述,具体使用可见谈谈Mac进程间通信--XPC,通过XPC可建立主控制应用与系统扩展的双向通信。
具体需要系统扩展工程Info.plist中配置NEMachServiceName字段来用于XPC服务,具体字段如下:
若不需要使用XPC,则可以不设置该字段;
NEMachServiceName字段名称必须以``..`为前缀,后面跟上自定义的名称!
对于端点安全则需要配置NSEndpointSecurityMachServiceName字段,该字段用于端点安全扩展与主控制应用进行XPC通信,如果不设定,则默认为<teamID>.<bundleID>.xpc,但是不推荐如此。具体见EndpointSecurity
需要在系统扩展中使用NEMachServiceName来创建侦听器,主控制器应用来发起连接。若关系倒置的话,就会出现设备多用户切换下,系统扩展就无法感知需要连接哪个用户,具体见苹果工程师回复System Extension & App communication
若在沙箱环境中,还需要指定如下权利:
IPC
通过苹果提供的IPC通信来进行主控制应用与系统扩展的通信。主控制应用通过sendProviderMessage:returnError:responseHandler:方法发送消息并响应,系统扩展使用handleAppMessage:completionHandler:处理消息并响应,具体使用可参见相关的API。
验证及发布
验证阶段需要开启SIP,关闭系统扩展开发模式,即systemextensionsctl developer off,并且安装的应用需要安装到/Applications目录下。
启动系统扩展,比如透明代理,系统会弹出让用户“允许”加载系统扩展,如下图:
友情感谢
System Extensions and DriverKit
Filtering Network Traffic--Demo
Deprecated Kernel Extensions and System Extension Alternatives
Roles for the Apple Developer Program
*AMFI*: checking file integrity on your Mac
Mac system extensions for threat detection: Part 1
Mac system extensions for threat detection: Part 2
Mac system extensions for threat detection: Part 3
SimpleTunnel: Customized Networking Using the NetworkExtension Framework--Apple demo
手把手 NetworkExtension(二):分析官方 Demo 源码之 NEPacketTunnelProvider 使用部分
手把手NetworkExtension: 1. 创建L2TP/IPSec VPN连接
Debugging and Testing System Extensions
Installing System Extensions and Drivers
App extension实战 - Personal VPN 连接并捕获packet
Monitoring System Events with Endpoint Security--Demo
network extension, app groups, unix domain socket
Cannot access shared keychain from NE System Extension