泄露的 driver-macos-master 项目是一个Mac OS X Rootkit 病毒,从源码看,它可能就是当年红极一时的病毒“OSX.Crisis”,因为两者之间连一些函数名都一模一样(比如关键函数hide_proc、hide_kext_osarray,甚至连废弃的hide_kext_leopard也有),逻辑也基本相同。更为牛逼之处的是,Crisis是在2012年爆发的,而该份代码在2009就已完工,想想之间的差距吧,这也再一次证明Hacking Team的技术实力有多强大。
##【源码分析】
下面分析下该 OSX rootkit 技术内幕:
1、先从入口函数 mchook_start 开始分析,主要就是注册字符设备,然后在文件系统上创建设备节点,常规的驱动入口行为。
对应的字符设备转换表如下:
主IOCTL回调函数就下面3个,其中cdev_open和cdev_close为空,整个处理逻辑都包含在cdev_ioctl函数中:
2、关键看下cdev_ioctl 回调函数,里面包括各种潜伏隐藏的行为。在mchook.h文件头中就定义了一些cdev_ioctl中调用到的函数,从函数名上基本可以推测出该rootkit包含文件隐藏、进程隐藏、内核模块隐藏等功能。
3、进程隐藏:在mac osx上,每个进程的上下文都保存在proc结构中,而在allproc链表中就保存着所有进程proc结构的指针,通过allproc链表移除相应进程的proc结构可隐藏正在运行的进程,下面是关于隐藏进程的代码,位于hide_proc函数中(还有另一个未被调用到的函数hide_proc_l,也是用于实现相同功能),它相应进程从进程链表和进程Hash表里都移除掉。之前笔者在分析 rubilyn osx rootkit 时,发现它就没有从Hash表里移除进程相关信息,导致可通过“ps -p pid ”命令查找到进程。
4、内核模块隐藏:早期针对leopard系统的内核模块隐藏是调用 hide_kext_leopard 函数,现在已经不再使用,它只是简单地遍历kmod_info 内核模块链表结构,找到相匹配的模块名,然后将从它链表中踢除,这样当执行kextstat命令时就查看不到隐藏的内核模块,但这种方法现在无效。
为了支持多个系统版本,后来重写了个 hide_kext_osarray 函数。在“雪豹”苹果系统之后,有个叫sLoadedKexts 的 IOKit OSArray类引用到内核模块列表,不过它并没有导出符号,只要能够找到它,那么就可以对sLoadedKexts 数组进行修改,以达到隐藏内核模块的目的。
庆幸的是,OSKext::lookupKextWithLoadTag 函数里面引用到sLoadKexts(源码参见:http://www.opensource.apple.com/source/xnu/xnu-2782.1.97/libkern/c++/OSKext.cpp),通过它可以定位到sLoadKexts地址。
它对应的内核模块位于/System/Library/Extensions/System.kext/PlugIns/LibKern.kext/LibKern,不过当用IDA加载分析时,发现它只有导入表的函数信息,并无实际函数,包括PlugIns目录下的其它驱动也是大多如此。进一步分析,发现这些驱动其实都链接到/System/Library/Kernels/kernel里面,可以发现OSKext::lookupKextWithLoadTag函数对应的符号名为__ZN6OSKext21lookupKextWithLoadTagEj。
通过该符号即可找到OSKext::lookupKextWithLoadTag函数,然后开始搜索机器 E8,即CALL指令,从上面的源码看,调用的第一个函数是IORecursiveLockLock,然后跳过call指令(共5字节)进入下一条指令。
再根据32位/64位系统进行区分,对于32位比较简单,call下一条指令就包含有sLoadedKexts地址,下图就是32位系统Snow Leopard 10.6.8的OSKext::lookupKextWithLoadTag函数,由于笔者缺乏该环境,因此图片是从网上扣来的:
但对于64位系统会相对繁琐一些,它先找到机器码 48 8B,即mov指令,获取第2个操作数的实际内存地址,即为sLoadKexts,不过笔者在最新版10.10.4上发现必须是在第2个 48 8B才有效,因此该份rookit只适用于低版本的 64位Leopard系统
定位到sLoadedKext这个OSArray数组之后, sLoadedKext[OFFT_KEXT_COUNT] = sLoadedKext[0x14] = 元素个数,即已加载的内核模块个数。接着找到最后一个kext模块的kmod_info结构信息,判断该内核模块是否为com.apple.mdworker,若是则将递减模块数量,进而隐藏kext模块,所以实际要隐藏哪个模块就得去更改com.apple.mdworker为相应的模块名。
5、文件隐藏:为了对列出文件的相应系统函数进行挂钩,我们需要先对finder和ls所使用的函数进行进程跟踪,在mac上已经用Dtrace代替ktrace,在finder上主要是使用getdirentriesattr函数,而ls主要是使用getdirentries64,下面是用Dtrace分别对finder和ls的进程跟踪情况:
下面是查看finder进程2841的调用函数,其中的getdirentriesattr在最新版10.10.4上未发现被调用,以下测试是之前在10.9系统上测试的,但是在10.10.4中getdirentriesattr函数依然在syscall.h中被定义着。
1 2 3 4 5 6 7 riusksk@macosx:/usr/include/sys$ sudo dtrace -s ~/Reverse\ engineering/Dtrace/calltrace.d -p 2841 | grep getdir dtrace: script '/Users/riusksk/Reverse engineering/Dtrace/calltrace.d' matched 573227 probes 2 1078881 getdirentriesattr:entry 2 1363229 getdirentriesattr:return =1 ……
下面是ls命令(64位系统)调用的函数:
因此,我们若想在finder和ls中隐藏文件,只要对这两个函数 getdirentriesattr 和 getdirentries64 (32位的为getdirentries)进行挂钩处理即可。在系统调用函数表中,主要是由sysent结构数组构成,每个sysent结构中都包括参数个数sy_narg,执行函数sy_call 这些重要数据。sysent结构如下:
为了实现对上述系统函数的挂钩,通过修改相应函数sysent结构的sy_call来进行偷梁换柱,关于各系统函数的调用号和宏名均可在 /usr/include/sys/syscall.h中找到:
回头看源码,发现该rootkit也是对上面这3个函数进行hook:
看下其中的hook_getdirentiries64函数,其它类似,主要还是移除指定文件的dirent结构,其中dirent结构原型如下:
先调用原始函数,获取真实的文件信息(源码中的direntry对应的其实是dirent结构,在新版版本中这两个结构是独立存在的了):
然后遍历文件链表,找到相匹配的文件名,然后用后一个dirent文件结构覆盖当前找到的dirent文件结构,这样就相当于指定的文件结构信息从链表中移除,从而实现文件隐藏的目的。
10、官方另外在testuint目录下放有用于测试的rootkit的工具kextControl.c 和 solveKernel.c。
【总结】
实际测试时,由于没有合法签名,导致驱动也无法被正常加载,因此未能作实际测试。
1 15/7/17 下午2:23:27.921 com.apple.kextd[44]: ERROR: invalid signature for com.revenge.kext.machooker, will not load
此次泄露的OSX rootkit 相对还是比较常规的技术,毕竟是2009的源码了,年代久远,但在最新OSX 10.10上稍作修改,应该还是可以用的。