rootkit概述

这个是摘自微信公众号里面的文章;

       rootkit是一个复合词,由root和kit两个词组成。root是用来描述具有计算机最高权限的用户。另一方面,kit被Merrian-Webster定义为工具和实现的集合。因此,rootkit是一组能获得计算机系统root或者管理员权限对计算机进行访问的工具。但在恶意软件领域,我们将rootkit定义为一组在恶意软件中获得root访问权限、完全控制目标操作系统和其底层硬件的技术编码。通过这种控制,恶意软件能够完成一件对其生存和持久性非常重要的一件事,那就是在系统中隐藏其存在。

       rootkit已经成为恶意软件的同义词,并且用来描述具备rootkit能力的恶意软件。严格地说,rootkit不是恶意软件,而是恶意软件用来发挥自身优势的一种技术。同样的技术也被合法程序应用。唯一的区别是意图。恶意软件用它是带有恶意的目的。

1、rootkit运行环境

(1)操作系统内核

内核是操作系统的主要组成部分。它作为应用程序和硬件之间的桥梁,负责管理系统资源和处理来自应用程序的请求。

Windows内核的设计具有较强的灵活性。因此,它可以被修改或扩展。内核的修改是通过使用可装载内核模块(LKM)完成的。LKM是一段可以加载到内核中的代码,使用后可以卸载。它扩展了内核的功能,如添加对新硬件、文件系统或系统调用的支持,而不需要重启系统。设备驱动程序就是一种常见的内核模块,它能让内核访问该驱动程序所对应的具体硬件设备。例如,如果用户想访问和使用内置在笔记本电脑的网络摄像头,必须安装或加载摄像头的相应驱动程序来让它工作。

如果没有可装载内核模块技术,操作系统开发者必须提前考虑内核需要的所有功能,这个要求非常高。另外,把它们全部编译进内核会导致内核非常庞大,它们会占用大量存储空间,因为即使没有用到,所有的功能仍然都会被加载到内存中。该方法的另一个缺点是,当需要增加初始设计中没有的新功能时,每次都要重新编译和重启内核。

NTOSKRNL.exe是Windows的内核。在支持物理地址扩展(PAE)的系统中,内核是NTKRNLPA.exe。

(2)用户态和内核态

Windows内核代码在系统中以最高权限运行,贴切地称之为内核态。因为不是所有的软件都需要操纵系统资源和硬件,这些应用程序只需运行在更低的权限模式下,称为用户态。两者的区别是内核态可以无限制地访问所有系统资源和底层硬件,它的进程空间也是系统范围内的。这就是为什么内核态是为最受信任和最稳定的功能保留的,因为一旦出错,整个系统将会崩溃。同时,用户态更受限制,没有直接访问任何系统资源和硬件的权限,对这些资源的访问是由Windows应用程序编程接口(API)提供的。运行在用户态的应用程序有它们自己的私有虚拟地址空间和私有句柄表。因为它的进程空间是私有的,不像内核态是全局的,一次崩溃只会影响该应用程序,其他运行在用户态的应用程序也不会被影响,最重要的是用户态的程序崩溃不会使整个系统崩溃。

(3)ring

程序可以运行在哪个模式的实际执行机制不是由操作系统提供的,而是英特尔x86处理器提供的一种访问控制的机制,它通过使用ring来实现。在图1中可以看到,英特尔x86处理器的环结构有4个ring。

                     rootkit概述_第1张图片

                                                                图1 英特尔x86处理器的环结构

ring 0是最高权限级别,ring 3是最低的。在这个概念中,代码可以访问与分配给它的数字相等或者更大的ring。所以,ring 3的代码不能访问ring 0中的代码或存储区域。这就保证了进程之间的隔离,并限制了不被信任或者没有合适权限级别的代码去访问只为更高权限级别的代码保留的资源,保证了系统的完整性和稳定性。虽然英特尔x86有4个ring,但是Windows系统只使用了2个ring:ring 0和ring 3。Windows内核运行在ring 0,所以ring 0也称为内核态,而大部分用户的应用程序运行在ring 3,所以也叫用户态。

Windows没有使用ring 1和ring 2是因为兼容性问题,因为不是所有的硬件都支持4个权限级别。

(4)从用户态转换到内核态

英特尔x86处理器的用户态和内核态定义的是当前运行的程序代码段的权限级别。这是因为在x86处理器中没有真正的内核态,不像其他处理器已经内建,并且用处理器状态寄存器中的一个标志位定义正在运行的程序是否在特权模式。x86处理器的一个特性就是正在运行的代码段决定权限模式。

正在运行的程序的每个代码段是由叫做段描述符的八字节数据结构定义的。段描述符包括代码段的元数据,其中有代码段的开始地址、长度和权限级别。权限级别是代码段运行所在的ring,即权限级别是0就是ring 0,权限级别是3就是ring 3。基于这种分配,代码段的权限级别很明显是定义在代码段的段描述符中的一个属性而不是处理器的属性。处理器依赖于段描述符提供的关于代码被允许运行的权限级别的信息。所以,如果代码段的段描述符显示它的权限级别是0,那么x86处理器将只允许它在ring 0中运行。

段描述符存储在加载到系统主存储器的两个表中。它们是全局描述符表(GDT)和本地描述符表(LDT)。整个操作系统中只有一个GDT,而且它是由操作系统在启动时创建的,它包括关于操作系统代码段和数据段的段描述符。GDT能被所有的任务共享,而LDT是被单个任务或者几个相关任务使用。和GDT不同,LDT不是强制性的。基址指针和每个描述符表的大小分别包含在全局描述符表寄存器(GDTR)和本地描述符表寄存器(LDTR)中。

为了确定代码段的段描述符位于哪个描述符表,需要使用段选择符。从图2中可以看到段选择符有3个域。

                               rootkit概述_第2张图片

                                                                                          图2  段选择符

从图2中可以看到,最重要的两个域是描述符位置和被请求的权限级别(RPL)。如果描述符位置为0,索引是位于GDT;如果是1,它位于LDT。RPL域描述了调用代码的权限级别。这两个域是相互关联的,这意味着如果描述符位于GDT,RPL一定是0,否则,将会产生一个通用错误。

所以,如果用户态或者RPL是ring 3的代码段不能调用内核态或ring 0的代码,那么它怎么可能从用户态转换到内核态呢?这是通过下面的系统调用完成:

● INT 2E

● SYSENTER/SYSCALL

1)INT 2E

英特尔x86处理器有一个特性叫做中断门。中断门是从用户态到内核态的通道。为了更进一步解释,我们看一下中断是怎样工作的。中断是由中断服务程序(ISR)处理的。运行在DOS的实模式下,指向ISR的指针包含在一个中断向量表(IVT)中。运行在Windows的保护模式下,IVT被称为中断描述符表(IDT),它包含中断门描述符(IGDs)。IGD包括ISR的代码段位置和它在代码段中的基址。所以在实模式中IVT直接指向ISR,而在保护模式下,IDT使用一个IGD形式的中间人或门。限制用户态的代码直接调用内核态的函数是处理器(CPU)的一个安全特性。通过检查包含ISR的代码段在IGD段选择符中的权限级别来实现这个特性。

开始转换到内核态时,INT 2E被执行。INT 2E本质上就是对KiSystemService ISR的调用。KiSystemService是一个提供系统服务的内核函数。然后处理器在2E入口进入IDT,执行一个软中断,在这个位置的IGD被读取。请记住IGD包括ISR的代码段位置和它在代码段中的基址。它也包括含ISR的代码段的段选择符。基于包含在段选择符中的信息,处理器加载合适的段描述符,并最终在内核态装载ISR代码。但在运行前,处理器需要从用户态栈转换到内核态栈。这确保了内核态函数有足够的栈空间,也是为了预防对用户态栈的修改。作为这种转换的结果,用户态应用程序的栈段(SS)、栈指针(ESP)、代码段(CS)、指令指针(EIP)和EFLAGS寄存器被保存在内核态栈中。然后在INT 2E被调用之前,KiSystemService复制压入用户态栈中的调用参数。ISR查看在EAX寄存器中的值,它包含在KiSystemService复制的内核态栈中的调用参数。这个值表示用户态代码想要在内核态运行的系统函数。正如微软所说的那样,KiSystemService使用在EAX中的系统服务号码作为系统服务调度表的索引,系统服务调度表包含操作系统中程序的调用地址。这就阻止了应用程序调用系统中的任何随机地址。应用程序只能调用列在系统服务调度表中的那些程序。如果一切妥当,系统服务会在内核态按照用户态代码想要它执行的方式运行。

在成功运行系统服务之后,代码执行恢复用户态,并且处理器把一切还原到用户态代码调用INT 2E之前的样子。原来的寄存器被恢复,EIP指向调用INT 2E之后的指令。

2)SYSENTER/SYSCALL

INT 2E执行很慢,因为它带有中断处理的所有开销,处理器加载中断门和段描述符来决定调用哪个ISR,执行权限级别检查和进行几个存储器读取周期。需要这个操作是因为内核态代码位置的改变,并且事件序列决定它真正在哪儿。请记住内核态代码段包括ISR。

但INT 2E导致了额外的开销。随着更快的CPU的引入,从PentiumⅡ和AMD K7开始,更快地进入内核态成为可能。使用了SYSENTER/SYSCALL而不是INT 2E。

SYSENTER/SYSCALL称为快速系统调用指令。SYSENTER和SYSEXIT是对于英特尔处理器来说的。相当于AMD处理器中的SYSCALL和SYSRETURN。它们的工作原理相同。

在INT 2E调用执行时,内核态段的位置改变了,所以需要额外的内存操作。如果系统服务的地址是硬编码的,将没有存储或装载操作来决定系统调用的指向,因此会更快一些。处理器会知道向哪里转换,因为内核态代码段是在相同的位置。这准确地描述了快速系统调用指令是怎么工作的。所以SYSENTER使用固定位置的段描述符来定义硬编码在处理器中的目标代码段。尽管目标代码段是硬编码的,但是函数的地址不是硬编码的,类似于在INT 2E调用过程的ISR代码段。这个函数是KiFastCallEntry,SYSENTER本质上是对KiFastCallEntry的一个调用。KiFastCallEntry使得用户态代码能够访问在系统服务调度表(SSDT)中的原始函数。为了确定KiFastCallEntry函数的地址,处理器使用特定模型寄存器(MSR),如图3所示。MSR,顾名思义,是给系统软件提供管理各种硬件资源能力的特定处理器寄存器,比如启用和禁用某些处理器特性。它们通常用来调试和系统监视。

                                     rootkit概述_第3张图片

                                                                                         图3  特定模型寄存器

SYSENTER_EIP_MSR(176h)包括KiFastCallEntry函数的地址。但是在运行这个函数之前,用户态栈和调用参数必须加载进内核态栈,这和INT 2E的调用过程相似。内核态栈的地址是从SYSENTER_ESP_MSR(175h)得到。一旦所有都准备就绪,处理器就会在内核态运行请求的函数。

在成功运行后,SYSEXIT被激活用来把所有东西恢复到用户态。这和从INT 2E恢复是同样的过程:代码的执行返回到用户态并且处理器把所有东西恢复回SYSENTER被用户态代码调用之前的样子。原来的寄存器被恢复,指令指针指向SYSENTER调用之后的指令。

2、rootkit的类型

代码基本上可以运行在两个模式下:不受限制的内核态和私有的、受限的用户态。rootkit在这些模式下都存在,所以基本上有两种类型的Windows rootkit。它们被称为:

● 用户态rootkit

● 内核态rootkit

用户态rootkit和内核态rootkit也分别称为用户空间rootkit和内核空间rootkit。

(1)用户态rootkit

用户态rootkit是运行在用户态或ring 3的rootkit。它们的影响限制在受感染的应用程序的用户或进程空间。所以,如果它想感染其他应用程序,用户态rootkit需要在那些应用程序的内存空间做相同的工作。

用户态rootkit的执行大部分是通过hooking或者劫持应用程序的系统函数调用。因为执行流是沿着一个事先确定的路径,rootkit可以很轻易地劫持路径上的不同点来使执行流指向它的代码。

(2)内核态rootkit

内核态rootkit是运行在内核态或ring 0的rootkit。它们工作在内核空间,主要做法包括内核修改和在内核空间中进行hooking。这就使内核态rootkit更强大,因为它能把自己放在尽可能的最底层,这意味着对操作系统和底层硬件的更多控制。

大部分内核态rootkit利用hooking执行路径的方法转换到内核态。它们也利用可加载的内核模块,例如,某些驱动程序使用其rootkit代码来“提升”内核的功能。理想情况下,恶意软件的作者希望内核态rootkit就是他们创造时的样子,但是情况并非总是如此,因为这需要对操作系统内部和硬件很熟悉,这是需要时间来掌握这些技能的。这一点非常重要,因为内核态rootkit会影响整个操作系统,写得不好的话将使系统崩溃。

Windows rootkit不是真正地获得root级别访问权限。这和rootkit的真正含义相反。用户必须把root权限授予Windows rootkit,它们才能工作。大部分情况下,这是通过社会工程学的技术欺骗用户实现的。

3、rootkit技术

在恶意软件领域,我们对rootkit的定义是恶意软件用来获得root访问权限并完全控制目标操作系统和底层硬件的一组编码。rootkit通常使用以下三种技术:

● hooking

● DLL注入

● 直接内核对象操纵

(1)hooking

hooking是rootkit最常用的技术。它涉及拦截应用程序的执行流。rootkit重定向正常的执行路径来指向它的代码。这是通过hookingAPI调用和系统函数调用实现的。

rootkit最常用的hooking技术有以下几种:

● IAT和EAT hooking

● 内联hooking

● SSDT hooking

● 内核态内联hooking

● IDT hooking

● INT 2E hooking

● 快速系统调用hooking

1)IAT和EAT hooking

当程序运行和加载内存时,Windows检查它是否调用了任何API。如果调用了,Windows就加载相应的DLL,把那些API导出到内存中,即程序的地址空间。程序需要的API或调用的函数列在导入地址表(IAT)中。IAT是包含指向程序所需的API的指针的列表。要使用IAT是因为API的地址不是静态的。这是一种允许API地址改变而不影响程序在内存中运行的方法。然后Windows生成在IAT中的函数指针,就像加载需要的DLL一样。植入IAT时Windows使用的指针的地址来自DLL导出地址表(EAT)。EAT包括DLL导出的API指针。

IAT hooking发生在恶意程序用指向它自己恶意代码的指针重写DLL的EAT的时候。例如,如果恶意程序想要隐藏被它修改的注册表键值,就可以hooking应用程序用来枚举注册表键值的API。这个应用程序使用RegEnumKey。在这种情况下,恶意软件可以通过修改ADVAPI32.DLL的EAT中的指针,使其指向恶意软件的代码来hooking该动态链接库导出的API函数RegEnumKey。然后恶意软件代码可以不显示它修改的注册表键值。既然恶意软件控制了程序执行流程,那它就可以做任何事情,比如感染导入重定向API函数的程序。

2)内联hooking

内联hooking(也称为“热补丁”)是修改DLL导入的API代码本身的过程。在这种hooking技术中,不管指针如何,保持IAT和EAT不变。这就打败了依靠修改地址表的rootkit检测。在这种情况下,恶意软件改变代码的第一条指令使其跳转到恶意代码的地址。这和寄生的计算机病毒使用的技术是相同的。为了对抗rootkit检测工具,寻找跳转到外部地址的JMP指令,恶意软件可以选择把JMP指令放到其代码的更深处而不是第一条指令。

除了把一个无关联的JMP指令插入API代码使其指向恶意代码之外,重写实际的API函数也是可能的。但是考虑到这样会带来复杂度的增加,如大小的考虑,很少这样做。

3)SSDT hooking

SSDT包括系统调用函数的指针或地址。SSDT也称为系统服务描述符表。

SSDT hooking的主要理念是修改包含在这个表中的指针使其指向恶意代码。这种重定向的结果是系统调用的功能被修改以满足恶意软件的需要。并且因为所有的用户程序都能够访问它,rootkit的范围扩大到全局或系统,这与IAT和EAT hooking的范围局限在单个进程空间不同。

SSDT hooking也称为内核补丁或原生API hooking。

4)内核态内联hooking

这和用户态内联hooking是相同的概念,但和修改IAT指向的API代码不同,恶意软件修改SSDT指向的系统服务代码。

内核态内联hooking也称为内核热补丁。

5)IDT hooking

IDT含有ISR的指针或地址。正如微软定义的,中断服务例程是硬件为了响应中断而调用的软件例程。ISR检查中断并确定如何处理它。

IDT hooking的概念是修改IDT中的指针来指向恶意代码。每当一个中断被触发,执行恶意代码而不是ISR。

6)INT 2E hooking

Hooking INT 2E属于IDT hooking。这里主要涉及修改指向KiSystemService的指针,使其指向恶意代码。这样一来,所有使用KiSystemService的程序都会运行恶意代码。

7)快速系统调用hooking

hooking快速系统调用SYSENTER的意义在于:让含有KiFastCallEntry地址的SYSENTER_EIP_MSR(176h)指向恶意代码。因此,恶意代码能够过滤所有的系统调用而不用操作SSDT。然后它能够控制作用于何种系统服务,并不用运行其恶意代码而跳转到真正的系统服务。但是因为这种hooking很容易被检测到,恶意软件可以保留原始指针并让它指向真正的KiFastCallEntry。这就是Rustock.B在做的事情。然后它重写在NTOSKRNL.EXE虚拟地址中资源部分的字符串FATAL_UNHANDLED_HARD_ERROR,使其跳转到恶意代码。

(2)DLL注入

DLL注入是把DLL(动态链接库)加载进一个正在运行的进程的地址空间的技术。有很多正常的程序使用这种技术,但是在恶意软件中,被注入的DLL是能够导出恶意函数的恶意DLL。

rootkit最常用的DLL注入技术有以下几种:

● AppInit_DLL键值

● 全局Windows hook

● 线程注入

1)AppInit_DLL键值

运行一个能hook IAT、执行热补丁、修改其他DLL的恶意DLL,就如修改注册表HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs键值一样简单。

这个注册表键值也被贴切地称为AppInit_DLL键值。正如微软的定义,AppInit_DLL是一种允许任意DLL列表加载进系统中每个用户态进程的机制。这些DLL作为USER32.DLL初始化的一部分被加载。利用这一特性,恶意DLL很容易被加载。因此,恶意DLL被注入到所有从USER32.DLL导入的进程。

微软在Windows 7和Windows Server 2008 R2中对AppInit DLL进行了修改,新增了代码签名要求。这样就能帮助提高系统的可靠性和性能,同时还能提高应用软件的知名度。

由于对注册表进行了修改,Windows必须重新启动才能加载恶意DLL。此外,通过这种方法感染的只能是与USER32.DLL有关的进程。如果恶意软件指令需要保持隐蔽性,这种方法也不好。想要保持隐蔽的恶意软件通常只把自己注入到少量必要的进程中,使用AppInit_DLL键值技术的恶意软件会把自己加载到调用USER32.DLL的每个应用程序中,这样就会触发大量的可疑行为,降低系统性能,从而引起用户怀疑。

2)全局Windows hook

SetWindowsHookEx函数使得向目标进程注入恶意DLL成为可能。正如微软所定义的,SetWindowsHookEx使应用程序定义的hook过程能够安装到hook链中。

为了完成hooking,恶意软件被部署为hook特定事件的恶意DLL。例如,如果期待的事件是按键消息,恶意DLL必须有能力导出KeyboardProc()。通过SetWindowsHookEx(WH_KEYBOARD,KeyboardProc),这是可hook的。DLL启动器可用来注册恶意DLL。一旦它是活跃的,每当一个按键消息事件发生时,KeyboardProc()都会被应用程序调用,最后恶意代码会运行在程序的地址空间。

3)线程注入

线程注入指的是把恶意DLL注入到目标进程,并使它在进程地址空间中作为线程运行的技术。在这个技术中有三个API函数发挥了重要作用:GetProcAddress、LoadLibrary和CreateRemoteThread。这三个API中的主角是CreateRemoteThread,它有两个重要参数:LPTHREAD_START_ROUTINE lpStartAddress[in]和LPVOID lpParameter[in]。这些参数分别表示线程在远程进程中的起始地址和指向要被传入线程函数的变量的指针。在注入恶意DLL的情况下,第一个参数是DLL的加载地址,第二个是恶意DLL在系统中的位置,其形式如C:\Malicious.DLL。

为了得到将要加载恶意DLL的线程的起始地址,使用了LoadLibrary。这个API可以被任何给定的进程用来动态加载任何DLL。所以,简单地把调用恶意DLL的LoadLibrary API的地址传给LPTHREAD_START_ROUTINE lpStartAddress[in]就足够在目标进程空间启动一个恶意DLL,使其作为一个线程运行。GetProcAddress帮助获得作为参数传入CreateRemoteThread API的LoadLibrary API的地址。

(3)直接内核对象操纵

直接内核对象操纵被认为是恶意软件使用的最高级的rootkit技术。这个技术集中在修改内核结构,绕过内核对象管理器来避免访问检查。因为内核本身被利用并且它的大部分数据结构被修改成恶意软件需要的形式,因此不再需要hooking来获得对执行流的控制。

尽管这是最有效的方法,但它也是最复杂的。操纵内核对象意味着详细地了解这个对象。需要考虑很多方面来保证操纵的最终结果不会破坏系统,并完成恶意目标。这涉及大量的工作,这也就是为什么一些恶意软件开发者又回到hooking的老方法上。

4、应对rootkit

要接受rootkit的挑战需要了解恶意软件所使用的rootkit技术,这是发现rootkit软件存在的关键。但话又说回来,如果系统已被恶意软件感染,并且它使用的是rootkit技术,就为成功检测带来了挑战。这是因为操作系统传递给用户或分析、诊断工具的信息不可信任。假如该操作系统被内核态的rootkit感染,就更难诊断了。

检测是否存在rootkit的最佳方式是在操作系统未启动的情况下对系统进行分析。这可以通过以下方式实现:首先使用其他介质来启动操作系统,然后执行取证工具,或通过卸下被感染系统的硬盘并使用专用工具来分析。这样做的主要目的不仅是为了找出rootkit,还可以通过分析恶意软件,甚至逆向工程来识别它的隐藏技术。收集的信息可以作为最终把rootkit从其他感染的机器移除的检测、部署方案。这就是反病毒公司通常都会要求客户把受感染机器的硬盘发送给他们的原因。这样反病毒工程师就可以进行深度分析,并把恶意软件样本提取出来供以后研究。

在实践中反病毒公司总是建议用户在把硬盘送去分析之前删除硬盘上的机密和专有信息。

下面是一些反病毒工程师常用的rootkit检测工具:

● 微软的rootkit Revealer:http://technet.microsoft.com/en-us/sysinternals/bb897445

● GMER:www.gmer.net

● Mandiant的Memoryze:www.mandiant.com/products/free_software/memoryze/

● 反病毒公司特有的反rootkit技术

rootkit早已出现,并且还会一直存在。只要rootkit能用来掩盖恶意程序,能得到受感染系统的所有控制权,它们将会成为十分吸引攻击者的技术。解决rootkit的最好方法是知道怎样使用反rootkit工具,了解rootkit的趋势和技术,最重要的是熟悉rootkit运行的环境(操作系统和硬件)。

你可能感兴趣的:(计算机网络)