MacOS系统扩展SystemExtensions深入实践
前言
从iOS转入Mac安全类产品的开发已经有一年半时间了,我也从一开始负责网络和UI模块慢慢追加负责内核模块的开发和维护,总体看,Mac开发的各种功能的实现思路和方法,要比iOS广阔很多,能接触到的计算机知识体系也更加完整,收获很多。
言归正传,今年苹果发布了M1芯片,在M1芯片的机器上又对内核开发做了很多限制,移除大量内核符号,导致我们软件无法采用原来的内核方案运作,被迫切换到系统扩展上来。由于领导的信任,对整个程序架构的调整和全新技术方案的落地就由我来主导了,经过几个月封闭式调研开发,目前基本已经能支持大部分原来内核扩展的功能了。本文就是我对系统扩展开发的记录和总结,现在,来聊一聊系统扩展的能力。
系统扩展是什么
在说系统扩展之前,先了解一下内核扩展。桌面程序的开发,一般分为应用层开发和内核开发(驱动开发),相比之下,iOS和安卓等移动端就只有应用层开发。关于内核的概念可以自行学习这不多说。
我们知道,操作系统一般是指运行在硬件之上,为各类应用层软件系统硬件管理方案的软件系统,而内核扩展作为操作系统的一个扩展功能,和操作系统运行在同个层级。这就意味着,如果内核扩展出现BUG导致Crash,也就意味着操作系统会崩溃死机。而一般的应用层软件,出问题顶多就是软件自己Crash。
所以开发内核扩展就必须小心翼翼,而且因为内核扩展是运行在内核层,可供使用的API也很少,还得用C语言来写,开发效率极低,调试效率极低。为了解决内核扩展这一系列问题,苹果在Mac 10.15开始推出系统扩展,用来取代内核扩展,并开始在后续新系统裁剪内核扩展符号,引导开发者切换到系统扩展。
当然要求开发者使用系统扩展取代内核扩展的原因,还有一点是系统封闭性的增强,因为内核扩展我们能做的功能也非常多,比如Hook内核中的系统调用等,而系统扩展只是运行在应用层的一个普通程序,对系统的威胁性就降低很多了。
能实现什么功能
说了一大堆内核扩展和系统扩展的背景知识,这里大家应该有清晰的理解,那系统扩展到底能支持什么?
目前看,系统扩展能支持的功能,基本能和内核扩展KPI对齐,细节上还是比内核扩展提供的KPI要弱一些。另外由于无法像内核扩展一样Hack操作系统的关键流程,Hook系统调用表,系统扩展总体能带来的功能要少一个数量级。
目前苹果为系统扩展提供两个功能模块:Network Extensions(下文简称NE扩展),Endpoints Security。此外还为驱动软件比如USB接口提供一个类似的应用层扩展模块:DriverKit。
Network Extension
网络扩展这个功能在iOS8和Mac10.10的时候就已经有了。在Mac10.15之前,网络扩展必须通过商店下载的App才能支持,而且只有一个Provider
类,在Mac10.15之后,网络扩展除了以原来的模式存在,还允许以系统扩展的模式运行。
系统扩展模式下的网络扩展,除了具有Provider类,还具备main函数,它被以完整程序的形式运行起来。main函数代码如下:
|
官网对系统扩展之网络扩展的文档还停留在好几年前的模式,官网网络扩展文档,这份文档里面的大部分描述都和内核KPI无关,这是一个坑,导致我调研网络扩展功能的时候走了很多弯路。旧的网络扩展API,网上有非常多的基于iOS的描述,这部分和Mac没有什么区别,这里就不多说了。
简单讲,网络扩展主要提供两套不同层级的API。一个是Packet Tunnel, 对应的类是NEPacketTunnelProvider
,提供IP层协议的流量,基于IP Packet实现的网络扩展,可以读取到整个电脑的IP报文,具体到编程,就是从苹果提供的NEPacketTunnelFlow
里面读取到NSData数组和协议编号,其中协议编号主要是用来表示协议是IPv4还是IPv6,方法原型如下:
|
通过这套API,就可以读取到整个电脑的所有网络流量了,市面上有部分VPN协议就是基于Packet实现的,比如著名的WireGuard。读取到流量之后,一般就是通过自己定义的VPN协议,把数据转发到后台服务器,后台服务器解析协议之后,提取Packet内容,写入UTUN等虚拟网卡,就能把流量从后台服务器发出去了,再读取网卡,把回包弄回到客户端,这样就实现了一套完整的VPN功能。
苹果官方提供了网络扩展Demo,不过太久没维护只能支持到Swift3.0,我推荐一个仓库,已经支持到Swift5.0了:SimpleTunnel_Swift5.0
第二套API,基于App层面的App Proxy Provider,对应的类有NEAppProxyProvider
,以及子类NETransparentProxyProvider
。 这套API主要是处理TCP/UDP流量,也就是基于IP层协议的两个传输层协议,苹果也提供了类似的Flow类已经读取数据的方法,读取出来的数据就是TCP或者UDP协议传输的数据NSData,不包括三次握手,不包括协议头部等数据。相比IP层协议,这方便开发者直接对内容进行处理。下面重点说一下这两个Provider类的区别。
NEAppProxyProvider
早在iOS 9就已经出现了,它的限制比较多,需要使用MDN进行配置,也就是描述文件,而且无法代理系统APP,想要代理什么APP的流量也需要逐个配置,另外如果流量来了,然后扩展拒绝代理的话,被代理的程序会直接得到网络中断的错误。
如果了解内核Socket KPI的人应该能看明白,这些功能是无法取代内核Socket相关的KPI。苹果自己当然也明白,所以在推出系统扩展的时候,为了让系统扩展能取代内核Socket鉴权那套KPI,苹果在MacOS11系统发布的时候,也推出了子类NETransparentProxyProvider
。
NETransparentProxyProvider
类主要是提供了类似内核Socket KPI的功能,除了拥有AppProxy全部功能外,还拥有了更加强大的功能,不需要MDN配置,可以直接通过几行代码创建是VPN配置;允许设置子网和端口来代理所有OutBound流量,也就是往外发的流量,而且也代表了它能处理系统App的流量;允许选择性地跳过代理,跳过代理时,系统会让流量走原来的流程,被代理的程序无感知,这就意味着它拥有类似内核KPI的鉴权功能,即跳过流量则流量走原路,接管流量然后转发流量则实现了VPN功能,接管流量但是不转发流量则实现了拒绝网络请求的功能。
相关API如下:
|
上面两个可被重写的方法,一个用来处理UDP流,另一个可以处理TCP和UDP,具体用法自行参考文档。
相比内核KPI,NETransparentProxyProvider
类少了处理InBound的功能,以及Inject Socket data,以及其他Socket KPI大大小小的Socket操作的功能,这里就不多说了。
Endpoint Security
Endpoint Security系统扩展(以下简称ES),是苹果全新推出的用来取代内核KAUTH的系统扩展,功能可对齐KAUTH,可能还更为强大。
ES相比网络扩展要简单很多,支持监听事件,鉴权事件,这也就意味着可以通过系统扩展来限制用户启动某些程序,访问某些文件,或者执行某些权限。具体支持的事件可以看下面:
|
可以看到能支持的事件还是挺丰富的,甚至连MPROTECT这种高端系统调用都有。
针对AUTH事件,鉴权形式可分成两种,一种是Allow/Deny模式,另一种是Flag模式。应该都很好理解,后者就是允许把事件的访问权限修改成只读操作,或者读写都拒绝。具体代码如下:
|
需要注意的是,如果全部放行,填入0xffffffff。其他相关实例可以参考苹果官方Demo:
准备工作
NE系统扩展,无需任何准备工作,照平常开发App的流程即可。不过需要注意一个问题,直接从Xcode里面勾选的NE Entitlement是错误的,需要手动添加-systemextension,举个例子,如果想开发NETransparentProxyProvider
,使用Xcode勾选App proxy的时候,生成的Entitlement是app-proxy-provider,需要手动修改成app-proxy-provider-systemextension,而且由于Xcode13.1还不支持这个手动改过的Entitlements,无法支持自动管理证书和Provisioning Profile,需要手动下载具备app-proxy-provider-systemextension权限的Profile文件。具体操作如下:
登录苹果开发账号管理界面(Certificates, Identifiers & Profiles),选择Developer ID Application类型的证书,在Identifiers里勾选NetworkExtension,无需勾选System Extension,因为这个是主App才需要的安装权限。这样下载下来的Profile文件中的NE Entitlements就是以systemextension结尾了。
ES系统扩展则比较麻烦,首先需要申请Entitlements:com.apple.developer.endpoint-security.client
和其他的Entitlements不同,ES的权限需要额外向苹果申请,申请入口可以从上面DEMO页面里找到。
在申请权限还未被审批之前,可以通过关闭SIP的方式运行没有签名的ES扩展,另外还可以使用systemextensionsctl工具,设置DEBUG模式,方便调试ES扩展。至于NE扩展,调试方式和普通程序无太大区别。
常见问题
如何安装
安装系统扩展,需要先将系统扩展放入主App中,可以先创建主App,然后在同一个工程里面创建系统扩并选择主App的Target,或者手动选择系统扩展包,在Build Phases配置里加上Embed System Extensions步骤,把系统扩展放到System Extensions目录,最终编译之后,系统扩展的App会被放在.app/Contents/Library/SystemExtensions目录下。并且已包名.systemextension
命名。
需要注意的是,主App必须具备com.apple.developer.system-extension.install的Entitlement,才能安装ES扩展。如果要安装NE扩展,还需要具备对应的NE Entitlement,例如app-proxy-provider-systemextension,否则在添加VPN配置的时候会报错。
除此之外,还需要确保主App和扩展有相同的Team编号。
如何管理
借助苹果提供的systemextensionsctl工具,可以查询扩展的状态,卸载扩展。
如何公证
系统扩展需要经过公证,才能安装到普通电脑上。具体公证方法参考苹果官方文档:
如何调试
正常调试即可,参考官方文档:
主App安装系统扩展时报错排查
主App安装ES系统扩展报错较少,基本上配置好Entitlements就可以了。
NE则有较多要求。首先需要保证Entitlements中的App-Groups和主App保持一致。其次确保NE的Info.plist文件中MachServiceName必须是主App的Groups的子集或者保持一致。
|
如何与系统扩展通讯
苹果官方推荐使用XPC技术,系统扩展也自带了全局XPC Mach服务,只需要在plist文件中设置好服务名字即可:
|
随后其他程序只要把系统扩展当作全局XPC使用即可:
|
其他错误
做好上面说的,基本就能正常运行跑起来了,如果还有问题,目前相关资料很少,只能从苹果论坛得到信息了。地址:SystemExtensions论坛 其中活跃用户:eskimo,是苹果系统扩展相关开发工程师,他的回答基本就是标准答案了。