摘要本文描述了一种检测内核与用户级rootkit的新技术此技术利用处理器的单步执行模式来测定系统内核与DLL中执行指令的数量从而达到检测rootkit和后门的目的同时还讨论了其在Wink下的代码实现 背景知识 一个在计算机安全领域中重要的问题是如何判断给定的主机是否已被入侵由于以下两点这项工作变的非常困难 攻击者可以利用未知漏洞进入系统 当进入系统后入侵者可通过安装rootkit和后门来隐藏自身(例如隐藏进程通讯渠道文件等)本文将集中讨论在WinK系统下rootkit的检测问题 传统rootkit检测技术中的一些问题 传统的rootkit检测程序(那些我们经常在UNIX系统中见到的)只能检测一些已知的rootkit(这点使它变的像反病毒程序)或进行一些内核存储的扫描例如Linux中就有一些工具扫描内核中的syscall table这显然不够好因为已经有了很多并不更改syscall table的rootkit而Wink下也可开发出类似的rootkit 那检测程序是不是还应该扫描内核代码空间?这样我们就有了一个运行在内核模式中的Tripwire但这还不够好因为在大多数的操作系统中我们可以写出即不更改SST(syscall table)也不更改代码的内核级rootkit在系统中有很多可以勾连的函数指针(例见[]) 以我看存储扫描技术决不会成为rootkit检测的终结这主要是因为我们不能确定具体的监测存储区域 那到底怎样检测出我们系统中的入侵者呢? 首先我们以rootkit中所使用的技术将其分为两类 *通过更改系统结构来隐藏某对象的(如运行的进程)和 *更改内核执行路径(例勾连那些负责枚举活动进程的内核函数)来达到同样目的的 更改系统数据结构的rootkit 这类的rootkit不太多有意思的例子包括fu rootkit(见[])通过删除内核中PsActiveProcessList链上的进程对象来隐藏进程ZwQuerySystemInformation等函数是不能发现这些隐藏进程的但同时因为Windows的线程分派器(dispatcher scheduler)使用另外的数据结构这些隐藏进程仍可运行(被分配到CPU使用时间)Windows的线程分派器使用以下三个(注)数据结构 *pKiDispatcherReadyListHead *pKiWaitInListHead *pKiWaitOutListHead 后两个是处于等待状态的线程链头他们之间稍有不同但对我们来说并不重要 从上面的信息我们可以找到一种检测隐藏进程的方法及读取线程分派器使用的数据结构而不是PsActiveProcessList 当检测rootkit时我们应该尽可能的触及更底层的内核数据结构 有一点应该注意直接从线程分派器使用的链中删除要隐藏的进程是不可能的因为隐藏的进程将分配不到CPU使用时间 更改执行路径的rootkit 这类的rootkit较为普及他们通过修改或增加内核或系统DLL中的指令来达到目的检测这类rootkit的问题是我们不知道rootkit在什么地方做了那些修改它可以勾连DLL中的函数系统服务列表(System Service Table)改变内核函数的内容或修改内核中一些奇怪的函数指针 执行路径分析(Execution Path Analysis) EPA关注这样一个事实如果入侵者通过修改执行路径隐藏了一些对象那么当调用一些典型的系统和库的函数时系统将运行一些多余的指令 举个例子如果入侵者为了隐藏文件和进程而修改了ZwQueryDirectoryFile()和ZwQuerySysteminformation()那样和干净的系统相比这些系统函数就会执行更多的指令不管入侵者采用勾连或在代码中加入jmp指令或其他任何方法指令增加的原因是rootkit必须进行它的任务(在这个例子中是隐藏文件和进程) 但是Windows 的内核是一个非常复杂的程序即使在干净的系统中某些系统函数每次运行的指令个数也是不同的然而我们可以用统计学来解决这个问题但首先我们需要一种测定指令个数的方法 指令计数器的实现 单步执行模式是Intel处理器的一个好的特性我们可以用它来实现指令计数当处理器处在这个模式时每执行完一条指令系统将产生一个除错异常(debug exception #DB)要进入这个模式需要设置EFLAGS寄存器中的TF位(详见[]) 当执行int指令时处理器会自动清TF位并进行权限切换这意味着如果要进行内核模式下的指令计数则必须在中断处理程序开头设置TF位因为我们将要测定一些系统服务中的指令个数勾连中断向量xe也就是Windows 下的系统服务调用门会变得很有效 但是因为存在用户模式的rootkit也应该测定在ring级运行的指令个数只要在用户模式下设置TF位一次就可以了从内核返回时处理器会自动恢复这一位 以上的计数方法通过内核驱动器实现如图所示驱动加载后勾连IDT x和xe为和用户级程序交互驱动勾连一个系统调用用户级程序通过这个特定的系统调用来开关计数器 一些测试 我们可以使用上一段中描述的方法来测定任意系统服务中执行的指令个数 例如来检查是否有人试图隐藏任意文件我们可以开始一个简单的测试 pfStart(); FindFirstFile(C\\WINNT\\system\\drivers &FindFileData); int res = pfStop(); 如果有rootkit隐藏任意文件则执行的指令数要比干净的系统多 如果运行这个测试上百次并计算执行指令数的平均值我们会发现这个值是非常不确定的考虑Wink的复杂性这一点也不让人吃惊但对于我们的检测目的来说这种现象是不能接受的但如果我们将得到的那些值的频率分布用条形图表示出来会发现图中有一个明显的频率高点如图和中所表示那样的即使是在系统负载很大时频率高点所对应的数值保持不变很难解释这个令人吃惊的现象可能是因为在循环中同一个系统服务被调用几百次后与这个系统服务相关的缓沖最后会被填入固定的值 假想现在有人安装了隐藏文件的rootkit如果我们重复测试并绘制相应的条形图就会发现频率高点向右移了这是因为rootkit需要进行隐藏文件的工作 在现在的代码实现中只进行了少量的测试包括典型的服务如文件系统读取枚举进程枚举注册表项以及Socket读取 这些测试将有效地检测出着名的NTRootkit(见[])或最近比较流行的Hacker Defender(见[])包括它自带的网络后门当然还包括很多其他的后门但要检测出一些更好的后门还要加入一些新的测试 误报和执行路径跟蹤 虽然对频率高点的检测有助于我们处理系统的不确定因素但有时会发现测试得到的值有小的差值一般来说不大于 有时这会是一个很严重的问题因为我们不能确定那些多出来的指令意味着被入侵或只是正常的误差 为解决这个问题我们使用了执行路径记录模式和单一的EPA模式比较系统增加了对执行路径的记录(包括地址和运行的指令)首先系统记录下正常情况下的执行路径以后的每一次运行将产生diff文件(正常系统和现行系统之间的比较) 我们应该使用好的反编译器来分析那些不一样的地方以此判定他们是否可疑图是一个diff文件的例子 现阶段的diff文件只记录下指令的地址以后可能将两次测试的不同结果存为PE格式文件并可用IDA等工具分析 检测 offsetinthecode 的变化 想象有这样一个rootkit它基本和上面提到的 fu rootkit (见[]) 一样但不从PsActiveProcessList中而是从分派器使用的数据结构中移除进程我说过那不可能因为隐藏的进程将分配不到运行时间 然而rootkit可以同时更改分派器代码中所使用数据结构的地址(offset)换句话说就是使其使用不同的链表但只有分派器使用这个新的 链表而系统其他地方还是使用旧的链表 (见图) 虽然这种技术不会改变执行指令的个数我们还是能检测到它但需要进一步的完善现有的工具这项功能现在还没有实现但应该不是很难 针对EPA的攻防 我们可以想到一些能骗过EPA类检测工具的方法先把它们分为两类 针对特定工具的欺骗 对EPA类技术的通用攻击 首先我们考虑一下通用的攻击方法和怎样防止这类攻击接着讨论针对特定工具的攻击以及怎样通过多态来预防 对EPA类技术的通用攻击 首先恶意程序可以勾连包含除错处理程序(debug handler)地址的IDT入口这样将暂停记录运行的指令数当它完成工作时再恢复 IDT入口这样rootkit中所执行的指令数不会被记录 我们可以使用intel的除错寄存器来防止这类的攻击可以使用DR和DR寄存器对IDT入口进行写保护并且为防止rootkit向除错处理程序的开始处写入Jmp指令还需对其进行读保护换句话说我们不想让rootkit发现除错处理程序的地址但单纯对IDT入口进行读保护是不行的系统会蓝屏但有一简单的解决方法就是增加额外的一层见图 还有一种攻击的方法在rootkit运行时其将TF位清零并在恶意操作完成时恢复TF位这样检测工具也只能发现运行的指令数和正常的系统有细微差别 另外rootkit还能检查TF位 如发现被跟蹤则不进行恶意操作这种行为并不会影响rootkit的正常工作因为只有被检测的进程才被设置TF位 我们可以防止这种攻击应该注意到的是运行每一个系统指令前都会运行我们的除错处理程序以下是简单的防预方法 如果除错处理程序发现上一个运行指令是pushf(将EFLAGS寄存器压入堆栈)则运行 |