内核扩展模块
类似用户态的共享库或动态链接库,内核扩展模块是内核使用的模块,可以根据需要动态插入或移除,而且这个需求通常来自用户态。OS X 和 iOS 中的XNU都利用了模块技术来加载各种设备的驱动程序,因此在一个完全自包含的子系统中增强内核的功能。
扩展内核的功能
在用户态有动态链接库(Windows)和共享目标(UNIX)机制,因此在内核态也有内核模块的机制:在XNU中,内核模块称为内核扩展,简称为kext。和XUN核心一样,kext也是XNU的基础构建块。事实上,通过模块插入的内核代码往往比内核核心本身的代码还要多。内核扩展运行在内核态,因此能够完全访问内核空间。开发者可以使用内核导出的所有函数,设置可以使用内核定义为私有的函数。内核对全局变量和数据结构都可以访问甚至修改,因此内核扩展成为各种内核级开发的首选方案。在内核态还可以执行性能剖析、系统调用挂钩以及其他功能。在iOS中,用户态和内核态都是固化的,企图阻止任何形式的修改。因此尽管各种i-设备上也广泛使用了内核扩展模块提供的功能,但是苹果在为每一类设备构建iOS时,都将这些内核扩展固化到kernelcache中了(不过这些内核扩展确实是在运行时从kernelcache中动态加载的)。
模块化结构的安全设计
- 代码签名:代码签名是首选的方法,如今已被大多数系统采纳为标准。Windows是一个典型实例,只允许加载具有合法数字签名的驱动程序。在控制权转交给模块入口点之前,内核会验证代码签名,代码签名保存在附加的证书中。证书必须通过私钥签名,内核已知公钥,内核也可以通过一个信任链获得这样一个密钥。苹果在iOS 中到处都使用了代码签名,不过只对自己的代码进行了签名。
- 预链接:预链接(pre-link)是苹果在OS X 和 iOS 中使用的方法。引导加载器不按照先加载内核,再以一定顺序加载kext的方式进行加载,而是加载一个kernelcache文件。这个文件包含了内核,并且预链接了经过选择的扩展。结果和通过内核动态加载的这些扩展是一样的。预链接的两点优势:
- 加载的熟读快得多,因为动态加载的过程需要在运行时解析内核和模块中的符号,而预链接只需要进行一次这种解析操作,要加载的内核已经完成了模块的加载工作,所有的链接地址都已经完成了解析
- kernelcache可以添加签名,设置还可以加密(iOS就加密了)。一旦加载了kernelcache,就可以禁止所有kext加载。这样可以阻断代码进入iOS内核的合法通道
内核扩展(kext)
内核扩展没有链到kernelcache时是单独存在的,可以在/System/Library/Extensions 目录中找到这些kext。这里大部分kext都是设备驱动程序。内核扩展可以互相依赖。每一个kext都有一个加载索引字段(Index)和一个“引用”字段(Refs)。后者表示这个kext依赖的kext个数,而前者表示这个kext所在列表中的索引,依赖其他kext的kext会使用到这个列表。每一条kext记录后面尖括号中的值表示当前kext依赖的kext索引。
kext结构
kext实际上是bundle,因此遵循通用的bundle布局:一个kext目录只包含一个子目录Contents/,Contents/子目录中的文件如下表:
文件/子目录 | 内容 |
---|---|
CodeDiretory | kext的代码目录文件 |
CodeRequirements | kext的代码需求设置 |
CodeResources | 代码资源XML文件,包含kext中文件的散列和规则 |
CoderSignature | kext的代码签名:通常包含苹果的数字证书 |
Info.plist | bundle清单属性列表 |
MacOS | 这个目录下包含了实际的kext为禁止代码,二进制代码文件的类型为BUNDLE(Mach-O类型8)或KEXTABUNDLE(因故64位,Mach-O类型11) |
_CodeSignature | 包含Code文件的目录,这些文件实际上是Code文件的符号连接 |
version.plist | 表示kext版本信息是属性列表 |
kext安全需求
由于kext包含了要加载到内核内存中的代码,所以必须有额外的安全措施保障不能随意加载任何代码,因此也不会不小心加载潜在的恶意代码。因此对kext有以下要求:
- kext 的所有者uid必须为root,gid必须为wheel。
- kext 中目录的权限最多755,即rwxrwxr-x
- kext 中任何文件的权限最多644(rw-r--r--)
内核扩展的相关操作
kext相关的命令如下:
命令 | 用户 |
---|---|
kextd | 在用户空间动态加载kext |
kextfind | 通过各种各样的属性和标准查询kext。模拟kextd的操作,因为这条命令查找动态加载的kext |
kextlibs | 解析kext的依赖性 |
kextload | 一个简单的kext加载器 |
kextunload | 一个简单的kext卸载器 |
textutil | 改进版本的kextload,通过了更多的选项 |
kernelcache
在OS X 中,kernelcache提供完整的内核,针对操作系统运行的特定平台进行优化,预加载所有必要的驱动,从而加快引导进程。在iOS 中,kernelcache 只包含内核要加载的kext,不含其它任何kext。这种机制是的iOS内核更加安全且不容易篡改。在OS X 和 iOS 两个平台上的kernelcache都采用了相同的结果,实现稍有区别:
操作系统|内容
OS X | Mach-O 二进制格式,可能是胖二进制格式,在偏移量384位置处带有complzss
iOS | IMG3 加密个数的kernelcache,和 OS X 一样采用了complzss 压缩
multi-kext
kernelcache 只是OS X 和 iOS中使用的两种预链接形式之一。还有一种形式称为multi-kext存档,检查mkext。这种文件只不过是两个或多个kext的存档,类似于kernelcache,但是不包含内核本身。file命令和其他命令都不失败mkext文件,但是我们可以通过这个二进制文件第一行中的“MKXTMOSX”签名简单地识别出这类文件。
从程序员的视角看kext
从程序员的角度看,kext只不过是一个内核态的目标文件,在内核态链接,而不是用户态链接的用户态库。创建内核扩展本身的步骤如下:
(1)启动Xcode,然后在System Pkug-ins 面板中选中Generic Kernel Extension
(2)Xcode 自动定义了kext入口点和退出点
(3)在Xcode的plist编辑器中直接编辑Info.plist文件
(4)编译代码,可以在GUI中编译,如果更喜欢用CLI的话,可以通过xcodebuild(1)命令编译
kext的内核支持
kext是XNU中独特的组件,因为kext表示的组件既不属于Mach也不属于BSD。此外,尽管内核中的大部分代码都是用C语言编写的,而XNU中管理kext部分的代码是用C++编写的。I/O Kit是在kext支持的基础上创建的,也是用C++编写的。
Mach 的 kmod 支持
XNU 的 Mach 层被扩赞为支持内核模块(kernel module)。尽管Mach 层并不知道kext的存在,但是支持kmod对象,即内核模块对象。libKern
尽管kmod_info_t 仍然是kext的基本机构,但是大部分kext处理逻辑都转移到libkern目录中,并且用C++重写了。kext 的维护逻辑现在在likern/c++/OSKext.cpp 文件中,并且通过I/O Kit 框架暴露给用户态。kext加载的幕后原理
加载kext的最初请求来自于用户态,但是实际的kext加载需要涉及内核的内存操作,因此必须在内核态中完成。为了连接用户态和内核态,kext的加载机制使用了Mach 消息。所有的kext操作都封装为序列化的XML格式,并放在Mach kext_request 消息的 oll_descriptors 数据中。这些消息属于host_ptiv 子系统的一部分,自然需要访问主机的特权端口。Mach 消息最终会涉及mach_msg_trap,从用户态转移到内核态。