中期的翻译作业:Advanced Windows 2000 Rootkit Detection - Execution Path Analysis

高级windows 2000 Rootkits 的检测

(可执行路径的分析)

摘要

在本文中,一种新的检测内核模式和用户模式的方法已经被描述了。当前的技术利用进程的单步模式去计算在系统内核和动态链接库中的指令的执行数量,为了检测被恶意代码额外插入的指令,像Rootkits,后门等。对于windows 2000这种检测单元的实现,充分的利用了这种技术,也进行了讨论。


背景

在计算机安全中最重要的问题之一是怎么样判断已给的计算机已经被和解或者没有。这是很困难的任务,主要是由于两个原因:

l 攻击者可以利用软件中未知的bug进入到系统

l 在进入之后,攻击者为了隐藏自己安装高级的Rootkits和后门(i.e隐藏进程,连接的频道,文件等等).

在这篇论文中,我们将把注意力放在windows 2000系统上Rootkits的检测上。


传统的Rootkit的检测的问题

传统的Rootkits的检测程序(我们大部门可以在UNIX系统上遇到它们)要么是只能检测已知的Rootkits(它们类似于反病毒软件)或者是执行一些内核内存的扫描。

例如,一些为 Linux 系统创建的工具能够扫描系统调用表。这显然不是很高效,因为许多已经创建的Rootkits不会接触到系统调用表。类似的Rootkits可以被开发在windows 2000上。

一种观点认为这种检测应该扫描内核区域,所以,我们存在一些类似于著名的Tripwire的地方,但是,运行在内核模式下。令人惊讶的是,这仍然是不够的,因为我们可以写内核Rootkits在许多的操作系统上,它们可以不修改系统调用表或者代码。在系统内核中,有许多的函数指针可以被挂钩。

在作者的观点中,内存扫描的方法将不能完成Rootkits的检测,因为没人可以指出哪块内存区域应该被监控(因为它们的改变以为着错误的发生)和不监控(因为它们是动态的数据,每秒钟改变数以百次)。

怎么样检测我们系统的入侵者呢?


Rootkits的分类

中期的翻译作业:Advanced Windows 2000 Rootkit Detection - Execution Path Analysis_第1张图片

图示1.Rootkit的分类。

首先我们应该区分将Rootkits技术分为两类(如图1所示):为了隐藏信息,修改了一些数据结构(像动态的进程)和通过修改一些内核的可执行路径获取一些相同的结果(例如:挂钩了一些内核函数对于枚举动态进程的通知)。


Rootkits改变内核数据结构

使用这种技术的Rootkits不是很多。在这种分类中,有兴趣的例子是Fu Rootkit(如图3所示),它通过从内核中的PsActiveProcessList链表中将进程对象分离。隐藏的进程对那些类似任务管理器的工具使用ZwQuerySystemInformation函数来获取系统的进程信息是不可见的。

另一方面,当不同的列表被分发器(windows调度)使用的时候,隐藏的进程也能得到执行(得到CPU时间)。实际上,windows分发器使用三个重要的数据结构来管理线程的调度,它们是:

l pKiDispatcherReadyListHead,实际上是一个在就绪状态下32个链表头(一条链对应一个属性)组线程的表。

l pKiWaitInListHead

l pKiWaitOutListHead

最后两个是不同的链表头,它们代表线程正在处于等待状态(像在一些对象下处于休眠)。在它们中,有一些细微的不同,但是这对我们不重要。

从上边我们可以很容易的知道简单的检测隐藏进程的方法。我们应该使用单元(内核模式)能够读取内部的分发器的线程的列表,取代 PsActiveProcessList。这种单元已经被开发和证明可以检测所有的被fu Rootkit隐藏的进程。

现在我们应该知道了检测相同类型的Rootkits是相对简单的:我们应该获取到大多数的内部的内核数据结构,读取它们(当然,我们应该拥有内核模块去获取内核数据的空间),然后与通过官方的API的接口获取到的结果进行比较。

请记住,从刚提到的分发器的内核列表中移除隐藏进程的线程是不可能的,因为那之后,我们的隐藏进程将得不到任何的CPU时间来执行程序。


Rootkits改变可执行的流程

这些是大多数Rootkits的共同之处。它们通过修改或者增加一些额外的代码到内核或者系统的动态链接库中来达到它们的目的。问题是,我们不知道Rootkits将修改什么和修改哪里。它们可以挂钩动态链接库的函数或者系统服务表,改变一些内核函数体或者仅仅修改在内核中的陌生的函数指针。

问题是内核是如此大的数据结构,我们实际上不知道那些内存区域应该被检测为了去检测入侵者。我们不能检测太多,当在内核中有许多快速改变的数据时(进程正在运行,数据包从网络中发过来等情况)和我们不想每秒钟都得到错误。


可执行流程的分析

在EPA中,主要的观点是要小心这种简单的情况:如果攻击者和谐了系统,和在一些典型的系统和库的调用期间,她正在尝试着通过改变可执行的流程来隐藏一些信息,然后,系统将会执行一些额外的代码。

例如:如果她给 ZwQueryDirectoryFile()和ZwQuerySystemInformation()打了补丁,为的是隐藏文件和进程,在执行这些系统服务期间,执行的指令的数量与干净的系统相比将会增多。如果攻击者已经挂钩了系统服务表的入口或者把一些jmp指令放到了代码中或者做了一些其他的什么,其实都没有什么关系。更多的指令被执行是因为Rootkits已经执行了它们的任务(在这个例子中,移除了一些返回列表的文件格式和进程)。

但是,windows 2000的内核是一个复杂的程序。我们假设,在一个清洁的系统的系统调用期间,指令的执行数量将不一样。但是,就像我们随后看到的那样,我们可以简单的使用统计学的方法解决掉这个问题。但是,首先,我们需要一种指令计数的机制。

中期的翻译作业:Advanced Windows 2000 Rootkit Detection - Execution Path Analysis_第2张图片

图示2.通过指令计数来判断挂钩的简化图。


指令计数的实现

为了实现指令计数,我们可以使用英特尔处理器的一种优秀的特性,就是所谓的单步模式。当处理器工作在这种模式下时,每一条指令的执行,它都将会产生一个调试异常。为了让处理器进入到这种模式,我们必须设置EFLAGS寄存器中的TF 位。

当指令int指令的时候并且这个指令正在引起权限的改变的时候,处理器会清除掉TF位。它意味着如果我们想在内核模式下计算指令的执行,我们必须在中断处理之前,设置TF位。因为在一些系统服务期间,我们要计算指令执行的数量,它很方便的挂钩中断向量表0X2E,它是windows 2000系统服务的入口。

但是,因为一些Rootkits是基于用户模式的,我们也应该能够计算环3下的指令。这非常简单,我们仅仅只需要在用户模式下设置TF位,我们不需要在返回内核模式后再次打断设置它,因为处理器会动态的存储位的信息。

这种计数机制已经被作为一种内核驱动所实现。如图2所示,在载入驱动后,它会挂钩IDT的0X1和0X2E的入口。当然,模块必须与用户空间相联系,允许测试进程获取到结果。在当前实现的内核驱动中,挂钩了系统调用,它被作为驱动的一个接口。用户模式可以通过调用这个指定的系统调用开启或者停止计数进程。

中期的翻译作业:Advanced Windows 2000 Rootkit Detection - Execution Path Analysis_第3张图片

图示3.内核模式与用户模式的测试进程通过一个系统服务联系。


测试

在任意的系统服务期间,我们可以使用前边一段所描述的机制去计算指令的执行数量。

例如,为了检测某人正在试图隐藏的文件或者目录,我们可以做一个简单的测试:

pfStart(); 

FindFirstFile("C:\\WINNT\\system32\\drivers",&FindFileData); 

int res = pfStop(); 

我们假设如果攻击者已经安装了Rootkits,它是隐藏任意的文件的,然后,我们将计算更多的指令与未被感染的系统做一个对比。

如果我们重复的测试数以百计的案例和计算平均值,我们可以发现测试是非常不确定的。这不需要很惊讶,考虑到windows 2000是一种复杂的系统。不幸的是,这种行为对于我们的检测目的没有被认可。然而,如果我们收集我们的测试样品,并创建直方图,我们可以观察所有测试的特点是有一个大峰直方图。这个高峰期,因为它可以图示4和5上看到,仍然在同一位置,即使该系统可重载。在这种情况下,直方图已创建的FindFirstFile函数。

中期的翻译作业:Advanced Windows 2000 Rootkit Detection - Execution Path Analysis_第4张图片

图示4.FindFirstFile测试,系统在轻负载下的直方图。指令数量在内核和用户模式执行已计算在内,而测试过程中要求系统返回的第一个文件的形式\WINNT\ SYSTEM32\drivers目录。此目录已被选定,因为它的内容应该不会改变。

中期的翻译作业:Advanced Windows 2000 Rootkit Detection - Execution Path Analysis_第5张图片

图示5.直方图的的FindFirstFile测试,高负荷下的系统。请注意,主峰仍然在同一位置。

现在设想一下,有人安装了一个能够隐藏文件的Rootkit程序。之后,如果我们重复测试和画一个反馈的立方图出来,我们将会注意到峰值已经被移到了右边。在这种情况下,这是因为Rootkits必须执行额外的操作去从返回列表中移除要隐藏的文件。

在当前的实习中,仅仅有一些测试被使用。它们检测这种特殊的服务像读取目录的内容,枚举进程,枚举注册表的键和从网络的套接字中读取。

这些测试能够有效的检测到那种Rootkits像著名的NTRootkit,或者最近非常流行的Hacker Defender 包括哪些很出色的网络后门和其他的。但是,一些新的测试应该被增加,去拦截其他更聪明的后门。


误报和执行路径追踪

虽然峰值的检测技术允许我们非常好的处理测试中的不确定性,有时,我们可以观察到峰值位置的微小的不同之处。偏差大约几个指令(通常不会超过20)。

有时,这是一个很严重的问题,当我们必须决定这些额外的指令以为这系统的妥协或者是一种干扰。

为了解决这种困境,可执行路径的记录应该被使用。就单纯的EPA而言,调试处理记录了整个可执行的路径(在这个地址上的地址和路径)。然后,我们找到大多数的共同的路径(通过最大的峰值)和使这两个最常见的路径的差异(当前一个,保存一个)。

我们应该通过一个好的反汇编工具分析这个额外的指令和决定它们是否是可疑的或者不是。如图6所示的两个不同的测试。

在当前的实现中,仅仅地址被转出到了跟踪的文件中。以后,跟踪的信息可能被保存到PE文件中,它能够使用第三部分的反汇编信息(像IDA)。

中期的翻译作业:Advanced Windows 2000 Rootkit Detection - Execution Path Analysis_第6张图片

图示6.两种不同踪迹的转储(仅仅是地址)。我们可以看到3条额外的指令被执行了两次(在第275和第1007条指令)。


代码中偏移量的改变的检测

设想一个Rootkit工作类似于上边提到过的fu Rootkit,但是取代从PsActiveProcessList中移除对象,而是从内部分发数据结构中移除线程。我们知道,这是不可能的,这会让隐藏的进程无法得到CPU时间去执行。

但是,我们可以设想一下,Rootkits可是修改调度的代码,它将使用不同的偏移(在线程对象中)去维护一个列表。总之,我们改变了调度代码去使用不同(“shadow”)的列表。但,仅仅调度者将会修改这个新的列表,每个人(包括IDS工具,像上边提到过的可以读取内核结构的工具)将会使用这个旧的列表。看图示7。

中期的翻译作业:Advanced Windows 2000 Rootkit Detection - Execution Path Analysis_第7张图片

图示7.“shadow”线程列表的例子(分发器的代码的修改是需要的,实际上仅仅需要修改偏移)。

虽然,这种技术没有可执行指令的数量,但我们应该也能检测到它。我们已经证明了可执行路径的技术是完全可行的(我们将检测两条不同的路径,在那些已经被打补丁的位置)。

这已经被实现,但它应该相对更简单一些去实现。


EPA的穿透和防御

我们可以思考出欺骗当前检测者的方法。让我们将这些攻击方法分成两组:

l 欺骗指定的工具(攻击者能够准确的知道内核驱动和测试程序的二进制代码)。

l 对EPA技术的一般攻击方法。

首先,我们将考虑一般的欺骗检测者的方法和怎么样防御它们。然后,我们会讨论这种指定工具的脆弱性和通过使用多态性来避免它们。

普通的攻击和防御

所有的恶意软件首先都是可以挂钩IDT的入口NO 1.它包含了调试句柄的信息。然后,指令将不会被计数。当Rootkits完成它们的工作后,它们将会解除掉IDT调试的入口的挂钩。这样的后果是,测试程序将不能计算额外的Rootkits的指令。

中期的翻译作业:Advanced Windows 2000 Rootkit Detection - Execution Path Analysis_第8张图片

图示8.简单的攻击EPA.

为了防御这种攻击,我们可以充分的利用英特尔的调试寄存器。我们可以使用DR0和DR1寄存器来保护这个IDT 1的入口,对抗写访问。此外,我们也可以保护这个入口的读取访问,不允许通过插入跳转指令挂钩调试处理程序。总之,我们不想Rootkits能够找到调速处理程序。

但是,我们不能简单的保护对IDT#1的读取访问(通过设置适当的标志DR7)因为系统将会蓝屏,它应该作为一种经验被学习。但是,有一种简单的情况涉及到添加额外的分层。图示9描述了这种情况的解决方案。

中期的翻译作业:Advanced Windows 2000 Rootkit Detection - Execution Path Analysis_第9张图片

图示9.硬件保护#DB的挂钩

另一种的攻击涉及到编写那种Rootkits,它们在自己中清除TF位,无论它们想做什么就做什么(像移除一些列表中的进程),再次设置TF位。测试程序将报告很少的指令执行,这看起来像一个干净的系统。

当它发现自己被跟踪时,动态改变的Rootkits可以检测EFLAGS中的TF位,这时,它并不会执行恶意指令。这种行为不会违背Rootkits,因为Rootkits只有在测试进程的上下文中执行的时候才会检测TF位。

我们可以防御这种攻击。在任何代码被执行前,它应该会被主要到调试程序被调用。

简单的防御方法可以像这样。如果调试程序检测到之前的指令是pushf(它将EFLAFS压入栈)之后它应该执行如下代码:

and [esp], 0xfffffeff; 

它将会模拟TF位在EFLAGS中没有被设置。相似地,如果下一条指令是popf(它会使EFLAGS在栈顶被发现),调试程序应该检测。如果确实是这样,调试程序应该执行如下代码:

or [esp], 0x100; 

它设置了TF位,以便Rootkits在清除它的时候失败。

这种防御似乎是合理的,但是,它并不高效。为了检测有人在跟踪它,Rootkits可以使用如下方法:

setTFbit(); 

if (checkTFbit() != 1) { 

  // we are traced! 

我们发现我们应该修改我们的反检测的机制,以便它能记住TF位是否被跟踪的进程设置过(通过添加额外的变量调用TF位设置)。

这应该注意到,popf/pushf不是唯一能够访问EFLAGS寄存器的指令。我们也可以发现相似功能的指令像iret/int 和其他的一些指令,以便在相似的步骤中被采纳。


攻击特定的工具

如果攻击者了解所有的工具的实习技术,她可以利用很多方法欺骗它们。例如:她知道在内核什么区域存储着计数器变量,它会被调试程序增加。

一旦一个工具变得很受欢迎,这种攻击就会被Rootkits的作者实现出来。如果我们有许多工具的EPA(一个工具的许多版本),那么这种攻击对于攻击者就无利可图了。

但是,我们也想抵抗这种基于攻击的实现。可能,唯一可做的就是很强的多态的代码的实现。在管理员下载了这种工具到系统之后,在安装阶段,一个唯一的内核驱动和测试程序应该被产生。

多态的产生器还没有被实现出来。


相关的工作

正如开始所说,作者还没有从其他的Rootkits的检测方法中惊醒,这种检测方法不是仅仅依靠内存扫描的。

这应该被注意到的是,当前的技术不是特指的操作系统,作者也可以实现一个相似的检测单元在Linux上,更多详细的详细看[5]和 Linux 2.4的内核代码。


引用

[1]  Greg Hoglund, et al, ROOTKIT home, telnet://rootkit.com, 

[2]  palmers, Sub proc_root Quando Sumus, Phrack Magazine, issue 58, 2001. 

[3]  fuzen_op, fu rootkit, telnet://rootkit.com, 

[4]  Holy Father, Hacker Defender Home, http://rootkit.host.sk, 

[5]  Jan Rutkowski, Execution path analysis, Phrack Magazine, issue 59, 2002. 

[6]  IA-32 Intel Architecture Software Developers Manual, vol1-3. 


你可能感兴趣的:(数据结构,windows,测试,Path,工具,作业)