Rootkit检测技术研究

来源:互联网 发布:php黑客技术 编辑:程序博客网 时间:2024/05/15 05:06

1、现有检测技术

1.1 特征码扫描

 

 特征码扫描是检测已知恶意代码的最简单的方法,我在反外挂和反木马过程中,几乎每天要提各式病毒的特征。方法快速,简便,不过经验不足会造成严重后果。

 早期的特征码扫描只检测可执行文件,其原理是打开被检测文件,扫描其中是否含有特征数据库中的恶意代码特征串,如果含有则判断该文件含有恶意代码。不过,在游戏反外挂中,提取内存特征,分节提取。其中特征数据库是在确定某个程序是恶意代码后,通过静态反汇编或动态调试,手工提取其中不同于其他程序的指令片断组成的数据库。

然而随着越来越多的恶意代码使用了变形技术,即使对已知的恶意代码,仅扫描文件也不足以检测出来。当前的特征码扫描技术同时扫描文件和内存,以提高恶意代码的检测率。

特征码扫描的主要优点是误报率低,检测准确。

特征码扫描的主要缺点是:

1.恶意程序的搜集很困难,每天都会有大量的恶意代码产生,需要建立一个庞大的恶意代码的搜集网络才能有效搜集。

2.特征码的提取需要人工完成,费时费力。

3.对于未知的不在特征数据库中的恶意代码无能为力。

1.2 完整性检测

完整性检测是一种针对文件感染型恶意代码的检测技术。其原理是假设一个新的文件并未感染恶意代码,而后通过某种HASH算法算出文件的HASH值,放入安全的数据库。检测时再次计算被检测文件的HASH值,与数据库中原有的值比较,若不同则判断原文件被修改过,可能含有恶意代码。完整型检测中使用的常见的 HASH算法有CRC32MD5

 

完整性检测技术的优点是能够有效检测恶意代码对于文件的修改。

完整性检测有很明显的局限性:

1.完整性检测需要假设刚装入系统中的文件是没有感染恶意代码的,这个假设当然是不成立的,也就是说完整性检测无法检测已被感染的文件。

2.修改文件并不意味着就是恶意代码。因此,使用完整性检测有很高的误报和漏报率。

检测HOOK等,效果不错。

1.3 虚拟机检测

使用访问接口对Hive文件进行Dump分析,来获取其中记录的原始信息;

2.利用Windows提供的注册表访问API进行相应的注册表查询和遍历,作为系统数据;

3.对比上述不同来源的两种数据信息,检测隐藏的注册表项,标识出恶意程序隐藏的行为及其所在;

4.通过分析注册表中的启动项,提供恶意程序自启动信息。

获取可信文件信息:

Windows系统目前主要支持FAT32NTFS两种文件系统。

在推出FAT32文件系统之前,通常PC机使用的文件系统是FAT16。像基于MS-DOSWin 95等系统都采用了FAT16文件系统。在Win 9X下,FAT16支持的分区最大为2GB。我们知道计算机将信息保存在硬盘上称为的区域内。使用的簇越小,保存信息的效率就越高。在FAT16的情况下,分区越大簇就相应的要增大,存储效率就越低,势必造成存储空间的浪费。并且随着计算机硬件和应用的不断提高,FAT16文件系统已不能很好地适应系统的要求。在这种情况下,推出了增强的文件系统FAT32。同FAT16相比,FAT32主要具有以下特点:

FAT16相比FAT32最大的优点是可以支持的磁盘大小达到2TB2047GB),但是不能支持小于512MB的分区。基于FAT32Win 2000可以支持分区最大为32GB;而基于FAT16Win 2000支持的分区最大为4GB
  2. 由于采用了更小的簇,FAT32文件系统可以更有效率地保存信息。如两个分区大小都为2GB,一个分区采用了FAT16文件系统,另一个分区采用了FAT32文件系统。采用FAT16的分区的簇大小为32KB,而FAT32分区的簇只有4KB的大小。这样FAT32就比FAT16的存储效率要高很多,通常情况下可以提高15%

FAT32文件系统可以重新定位根目录和使用FAT的备份副本。另外FAT32分区的启动记录被包含在一个含有关键数据的结构中,减少了计算机系统崩溃的可能性。

NTFS文件系统是一个基于安全性的文件系统,是Windows NT所采用的独特的文件系统结构,它是建立在保护文件和目录数据基础上,同时照顾节省存储资源、减少磁盘占用量的一种先进的文件系统。使用非常广泛的Windows NT 4.0采用的就是NTFS 4.0文件系统,相信它所带来的强大的系统安全性一定给广大用户留下了深刻的印象。Win 2000采用了更新版本的NTFS文件系统——NTFS 5.0,它的推出使得用户不但可以像Win 9X那样方便快捷地操作和管理计算机,同时也可享受到NTFS所带来的系统安全性。

 NTFS 5.0的特点主要体现在以下几个方面:
  1. NTFS可以支持的分区(如果采用动态磁盘则称为卷)大小可以达到2TB。而Win 2000中的FAT32支持分区的大小最大为32GB
  2. NTFS是一个可恢复的文件系统。在NTFS分区上用户很少需要运行磁盘修复程序。NTFS通过使用标准的事物处理日志和恢复技术来保证分区的一致性。发生系统失败事件时,NTFS使用日志文件和检查点信息自动恢复文件系统的一致性。
  3. NTFS支持对分区、文件夹和文件的压缩。任何基于Windows的应用程序对NTFS分区上的压缩文件进行读写时不需要事先由其他程序进行解压缩,当对文件进行读取时,文件将自动进行解压缩;文件关闭或保存时会自动对文件进行压缩。
    4. NTFS
采用了更小的簇,可以更有效率地管理磁盘空间。在Win 2000FAT32文件系统的情况下,分区大小在2GB8GB时簇的大小为4KB;分区大小在8GB16GB时簇的大小为8KB;分区大小在16GB32GB,簇的大小则达到了16KB。而Win 2000NTFS文件系统,当分区的大小在2GB以下时,簇的大小都比相应的FAT32簇小;当分区的大小在2GB以上时(2GB2TB),簇的大小都为4KB。相比之下,NTFS可以比FAT32更有效地管理磁盘空间,最大限度地避免了磁盘空间的浪费。

5. NTFS分区上,可以为共享资源、文件夹以及文件设置访问许可权限。许可的设置包括两方面的内容:一是允许哪些组或用户对文件夹、文件和共享资源进行访问;二是获得访问许可的组或用户可以进行什么级别的访问。访问许可权限的设置不但适用于本地计算机的用户,同样也应用于通过网络的共享文件夹对文件进行访问的网络用户。与FAT32文件系统下对文件夹或文件进行访问相比,安全性要高得多。另外,在采用NTFS格式的Win 2000,应用审核策略可以对文件夹、文件以及活动目录对象进行审核,审核结果记录在安全日志中,通过安全日志就可以查看哪些组或用户对文件夹、文件或活动目录对象进行了什么级别的操作,从而发现系统可能面临的非法访问,通过采取相应的措施,将这种安全隐患减到最低。这些在FAT32文件系统下,是不能实现的。
  6. Win 2000NTFS文件系统下可以进行磁盘配额管理。磁盘配额就是管理员可以为用户所能使用的磁盘空间进行配额限制,每一用户只能使用最大配额范围内的磁盘空间。设置磁盘配额后,可以对每一个用户的磁盘使用情况进行跟踪和控制,通过监测可以标识出超过配额报警阈值和配额限制的用户,从而采取相应的措施。磁盘配额管理功能的提供,使得管理员可以方便合理地为用户分配存储资源,避免由于磁盘空间使用的失控可能造成的系统崩溃,提高了系统的安全性。
  7. NTFS使用一个
变更日志来跟踪记录文件所发生的变更。

和进程、TCP端口等查询一样,对文件的查询在也会通过调用ntdll.dll导出NtQueryDirectoryFile进入内核态,查询SDT并调用相应的例程处理。不同的是,由于支持多种文件系统,内核态的处理例程会根据文件系统的不同调用不同的驱动程序完成具体的文件查询。对于NTFS文件系统,会调用ntfs.sys;对FAT32文件系统会调用fastfat.sys

 

通过直接调用文件系统驱动程序ntfs.sysfastfat.sys获取的文件信息是可信的。

虚拟机检测是一种新的恶意代码检测手段,主要针对使用代码变形技术的恶意代码,现在已经在商用反恶意软件上得到了广泛的应用。

反病毒用虚拟机并不是像VMware为待查可执行程序创建一个虚拟的执行环境,提供它可用到的一切元素,包括硬盘、端口等,让程序在其上自由发挥,最后根据其行为判断是否为病毒。应该说这是一个不错的想法,但是其实现难度过大,消耗资源过多,只能作为虚拟机检测技术未来可能的发展方向。当前使用的虚拟机检测技术从严格意义上来说应该称为虚拟CPU技术,即通过程序来模拟CPU的执行。

它像真正的CPU一样取指、译码、执行,它可以模拟指令在真正CPU上执行的结果。给出一组机器码序列,虚拟机会自动从中取出第一条指令操作码部分,判断操作码类型和寻址方式以确定指令长度,然后在相应函数中执行该指令,并根据执行后的结果确定下一条指令的位置,如此反复执行直到某个特定情况发生结束工作。

当恶意软件使用代码段加密(加壳)这类变形技术时,若不脱壳,普通的特征码扫描无法检测。而使用虚拟机检测技术可实现自动脱壳,虚拟机从文件入口点处一条一条的取指令执行,直至解密段指令执行完成,此时文件脱壳完毕,可以进行特征检测。

虚拟机检测技术的主要优点是可以有效检测通常的加密变形的恶意代码。

其缺点是:

1.  仍然需要配合特征扫描,因此无法检测未知的恶意代码

2.  变形代码可以使用一些特殊的指令来绕过虚拟机检测

2.传统的反检测技术

2.1.5 反虚拟机技术

随着恶意代码越来越多的使用变形技术,简单的特征码扫描技术越来越难以检测到恶意代码。现有的商业恶意代码检测软件如卡巴斯基、Macfee开始使用虚拟机技术进行检测。但是道高一尺魔高一丈,恶意代码的反虚拟机技术也孕育而生。下面介绍几种典型的反虚拟机技术。

首先是插入特殊指令技术,即在病毒的解密代码部分人为插入诸如浮点,3DNOWMMX等特殊指令以达到反虚拟执行的目的。尽管虚拟机使用软件技术模拟真正CPU的工作过程,它毕竟不是真正的CPU,由于精力有限,虚拟机的编码者可能实现对整个Intel指令集的支持,因而当虚拟机遇到其不认识的指令时将会立刻停止工作。但通过对这类病毒代码的分析和统计,我们发现通常这些特殊指令对于病毒的解密本身没有发生任何影响,它们的插入仅仅是为了干扰虚拟机的工作,换句话说就是病毒根本不会利用这条随机的垃圾指令的运算结果。这样一来,我们可以仅构造一张所有特殊指令对应于不同寻址方式的指令长度表,而不必为每个特殊指令编写一个专用的模拟函数。有了这张表后,当虚拟机遇到不认识的指令时可以用指令的操作码索引表格以求得指令的长度,然后将当前模拟的指令指针(EIP )加上指令长度来跳过这条垃圾指令。当然,还有一个更为保险的办法那就是:得到指令长度后,可以将这条我们不认识的指令放到一个充满空操作指令(NOP)的缓冲区中,接着我们将跳到缓冲区中去执行,这等于让真正的CPU帮我们来执行这条指令,最后一步当然是将执行后真实寄存器中的结果放回我们的模拟寄存器中。

这虚拟执行和真实执行参半方法的好处在于:即便在特殊指令对于病毒是有意义的,即病毒依赖其返回结果的情况下,虚拟机仍可保证虚拟执行结果的正确。

其次是结构化异常处理技术,即病毒的解密代码首先设置自己的异常处理函数,然后故意引发一个异常而使程序流程转向预先设立的异常处理函数。这种流程转移是CPU和操作系统相互配合的结果,并且在很大程度上,操作系统在其中起了很大的作用。由于目前的虚拟机仅仅模拟了没有保护检查的CPU的工作过程,而对于系统机制没有进行处理。所以面对引发异常的指令会有两种结果:其一是某些设计有缺陷的虚拟机无法判断被模拟指令的合法性,所以模拟这样的指令将使虚拟机自身执行非法操作而退出;其二虚拟机判断出被模拟指令属于非法指令,如试图向只读页面写入的指令,则立刻停止虚拟执行。通常病毒使用该技术的目的在于将真正循环解密代码放到异常处理函数后,如此虚拟机将在进入异常处理函数前就停止了工作,从而使解密子有机会逃避虚拟执行。因而一个好的虚拟机应该具备发现和记录病毒安装异常过滤函数的操作并在其引发异常时自动将控制转向异常处理函数的能力。

再次是入口点模糊(EPO)技术,即病毒在不修改宿主原入口点的前提下,通过在宿主代码体内某处插入跳转指令来使病毒获得控制权。通过前面的分析,我们知道虚拟机扫描病毒时出于效率考虑不可能虚拟执行待查文件的所有代码,通常的做法是:扫描待查文件代码入口,假如在规定步数中没有发现解密循环,则由此判定该文件没有携带加密变形病毒。这种技术之所以能起到反虚拟执行的作用在于它正好利用了虚拟机的这个假设:由于病毒是从宿主执行到一半时获得控制权的,所以虚拟机首先解释执行的是宿主入口的正常程序,当然在规定步数中不可能发现解密循环,因而产生了漏报。如果虚拟机能增加规定步数的大小,则很有可能随着病毒插入的跳转指令跟踪进入病毒的解密子,但确定规定步数大小实在是件难事:太大则将无谓增加正常程序的检测时间;太小则容易产生漏报。但我们对此也不必过于担心,这类病毒由于其编写技术难度较大所以为数不多。在没有反汇编和虚拟执行引擎的帮助下,病毒很难在宿主体内定位一条完整指令的开始处来插入跳转,同时很难保证插入的跳转指令的深度大于虚拟机的规定步数,并且没有把握插入的跳转指令一定会被执行到。

最后还有多线程技术,即病毒在解密部分入口主线程中又启动了额外的工作线程,并且将真正的循环解密代码放置于工作线程中运行。由于多线程间切换调度由操作系统负责管理,所以我们的虚拟机只能在假定被执行线程独占处理器时间,即保证永远不被抢先,的前提下进行。如此一来,虚拟机对于模拟启用多线程工作的代码将很难做到与真实效果一致。多线程和结构化异常处理两种技术都利用了特定的操作系统机制来达到反虚拟执行的目的,所以在虚拟CPU中加入对特定操作系统机制的支持将是我们今后改进的目标。

技术方案:

获取可信进程信息:

Windows系统提供的进程查看工具taskmgr.exe是通过调用NtQuerySystemInformation函数获得进程信息的,这个函数最终将通过遍历双向链表PsActiveProcessLink来获得所有进程的信息。

Windows为每一个进程赋予一个唯一的进程ID,进程ID是一个无符号的16位整数,系统提供API函数OpenProcess来通过进程ID打开进程(即获取一个进程句柄)。该函数定义如下:

HANDLE OpenProcess(

  DWORD dwDesiredAccess,

  BOOL bInheritHandle,

  DWORD dwProcessId

);

其中dwProcessId就是进程ID,当ID不存在时OpenProcess函数将返回一个空句柄。利用这一函数,可以使用一个简单的方法来检测当前系统的进程——将dwProcessId0开始一直增加到6553516位的最大值),不断调用OpenProcess函数,若不返回空句柄则调用成功,该ID号的进程存在,若返回空句柄则进程不存在。试验发现,采用上述方法可以得到当前运行的所有的进程ID,那些使用了hook SDT或者修改PsActiveProcessLink链表隐藏的进程。但有趣的是,除了当前确实存在的进程的ID外,还有大量别的ID使得OpenProcess函数返回有效的句柄值。如有一个进程ID号为8,则ID号为91011也可以使得OpenProcess返回有效句柄。难道系统中存在如此多的“隐藏进程”?

通过对OpenProcess函数的跟踪调试发现OpenProcess直接调用ntdll.dll导出的NtOpenProcess完成其功能,NtOpenProcess通过中断调用进入内核态,通过查找SDT表获得ntoskrnl.exe中的同名例程的地址,调用执行,而ntoskrnl.exeNtOpenProcess函数将调用PsLookupProcessByProcessId函数来打开进程,该函数是被ntoskrnl.exe导出但没有被记录在DDK文档中的,通过对其逆向工程可以分析出其原型如下:

NTSTATUS PsLookupProcessByProcessId

HANDLE ProcessId,

EPROCESS*       pEPROCESS

);

其中ProcessId就是用户需要查询的进程ID,该函数将返回指向进程对象EPROCESS的指针。通过逆向工程发现,该函数将ProcessId作为句柄去查询一个名叫PspCidTable的多级表,经过三级查找后得到的就是EPROCESS的指针。

为了保证系统的安全性,应用程序不能直接访问内核对象,而必须通过句柄访问。句柄是一个与进程相关的不透明32位值。Windows为每一个进程维护一个句柄表,句柄值是在句柄表中的索引,在不同的进程中同一个对象的句柄值并不相同。句柄表结构是未被微软公开且随着Windows版本变化的,在Windows2000系统中,其结构如下:

typedef struct _HANDLE_LAYER3

  {

/*000*/ HANDLE_ENTRY Entries [HANDLE_LAYER_SIZE]; // bits 2 to 9

/*800*/ }HANDLE_LAYER3,* PHANDLE_LAYER3;

typedef struct _HANDLE_LAYER2

{

/*000*/ PHANDLE_LAYER3 Layer3 [HANDLE_LAYER_SIZE]; // bits 10 to 17

/*400*/

}HANDLE_LAYER2,* PHANDLE_LAYER2;

typedef struct _HANDLE_LAYER1

 {

/*000*/ PHANDLE_LAYER2 Layer2 [HANDLE_LAYER_SIZE]; // bits 18 to 25

/*400*/ } HANDLE_LAYER1,* PHANDLE_LAYER1

typedef struct _HANDLE_TABLE

{

/*000*/ DWORD             Reserved;

/*004*/ DWORD             HandleCount;

/*008*/ PHANDLE_LAYER1    Layer1;

/*00C*/ struct _EPROCESS *Process; // passed to PsChargePoolQuota ()

/*010*/ HANDLE            UniqueProcessId;

/*014*/ DWORD             NextEntry;

/*018*/ DWORD             TotalEntries;

/*01C*/ ERESOURCE         HandleTableLock;

/*054*/ LIST_ENTRY        HandleTableList;

/*05C*/ KEVENT            Event;

/*06C*/ }HANDLE_TABLE,* PHANDLE_TABLE

为了查找句柄的HANDLE_ENTRY,系统将句柄的32位值划分为38位分段,0位、1位和高6位不使用。

使用这三个8位分段,句柄解析机制以如下方式进行:

句柄的1825位作为HANDLE_LAYER1结构中的Layer2数组的索引,HANDLE_LAYER1结构由HANDLE_TABLE结构中的Layer1引用。

2.句柄的1017位作为第一步中所选择的HANDLE_LAYER2结构中的Layer3数组的索引。

3 句柄的29位作为第二步中所选择的HANDLE_LAYER3结构中的Entries数组的索引。

4 从上一步的Entries数组中取出的HANDLE_ENTRY结构包含一个指向OBJECT_HANDER(该对象头就是与给定Handle相关的对象头)的指针。

可以看出,通过读取PspCidTable句柄表中的有效值进行映射就可以实现对当前所有进程的映射,这种方法将不会前面所提到的进程隐藏技术的影响,可以获得可信的进程信息。

获取可信TCP端口信息:

恶意代码的TCP端口隐藏大多是利用hook NtDeviceIoControlFile函数来实现的。因此,可行的TCP端口信息应该来源于更下层。直接获取操作系统提供的与TCP连接相关的内核数据看起来是一个不错的主意。

通过逆向工程,可以发现在查询TCP端口时,由ntdll.dll导出的NtDeviceIoControlFile函数会通过中断调用进入内核态查询SDT调用与ntoskrnl.exe中其同名的函数,而后该函数会打开/Device/Tcp设备,调用该设备的分派例程。设备分派例程会根据NtDeviceIoControlFileIoControlCode决定调用哪个例程进行处理。

最终,tcpip.sys中的某一个例程将遍历两个叫做TCBTableTWTCBTablehash表来获得当前TCP连接的信息。也就是说用户自己也可以通过遍历这两个表来获得TCP连接的信息。

然而,TCBTableTWTCBTable是未被微软公司公开的文档中记录的,其结构、位置、用途都是未知的,而且在微软发布的安全更新补丁中,这两个hash表的位置和结构被频繁的修改。采用遍历TCBTableTWTCBTable来检测TCP端口的检测程序在某个特定版本中可以运行,可能在过若干天后微软发布一个新的安全补丁时就不再能使用了。因此在一个实用的系统中,不能通过遍历上述两个hash表来获得可新的TCP端口信息。

由于当前的恶意代码通常都是通过hook NtDeviceIoControlFile函数实现隐藏的,通过直接调用内核态ntoskrnl.exe中的NtDeviceIoControlFile函数来读取的文件相关的信息是未被修改可信的。

但是hook SDT时指向NtDeviceIoControlFile的表项已经被修改了,怎样才能获得真正的ntoskrnl.exe中的NtDeviceIoControlFile函数呢?可以通过直接读取ntoskrnl.exe文件中的SDT表项来完成,这是由于Windows提供了文件保护机制(Windows File Protection, WFP当一个应用程序试图替换一个受保护的文件,WFP检查替换文件的数字签名,以确定此文件是否是来自微软和是否是正确的版本。如果这两个条件都符合,则允许替换。正常情况下,允许替换系统文件的文件种类包括Windows的服务包,补丁和操作系统升级程序。

系统文件还可以由Windows更新程序或Windows设备管理器/类安装程序替换。如果这两个条件没有同时满足,受保护文件将被新文件替换,但将很快被正确的文件替换回来。当这种情况发生时,Windows会从Windows安装CD或者计算机的DLLCache文件夹中复制正确版本的文件。Hook SDT只能在内存中完成,在系统文件中数据是可信的。

 

由于恶意代码可能采用inline hook内核态的ntoskrnl.exeNtDeviceIoControl File函数,只是从文件中读取该函数的地址仍然不能保证不被hook。为防止inline hook,我使用了代码覆盖技术。简单的说,就是找到文件中NtDeviceIoControlFile函数的偏移,读取若干该偏移后的数据,这也就是该函数的指令,将这些数据覆盖到内存中该函数的地址处,这样inline hook设置的jmp指令就会被覆盖掉,也就保证了获得TCP端口信息的可信。具体操作步骤:

//1、重新映射某个模块,并获取地址,比如ntoskrnl.exe
ULONG
GetMappedModuleAddr(IN  PWCHAR pwstrModuleName)
{
 UNICODE_STRING strModuleName;
 OBJECT_ATTRIBUTES ObjecttAttr;
 HANDLE hFile = NULL, hSection = NULL;
 IO_STATUS_BLOCK IoStatus;
 PVOID MapFileBaseAddress = NULL;
 NTSTATUS status;
 SIZE_T size=0;

 RtlInitUnicodeString(&strModuleName, pwstrModuleName);
 InitializeObjectAttributes(&ObjecttAttr, &strModuleName,OBJ_CASE_INSENSITIVE,0,0);

 status =ZwOpenFile(&hFile,
  FILE_EXECUTE | SYNCHRONIZE,
  &ObjecttAttr,
  &IoStatus,
  FILE_SHARE_READ,
  FILE_SYNCHRONOUS_IO_NONALERT);
    if ( !NT_SUCCESS(status) )
    {
  DbgPrint("winsun: GetMappedModuleAddr ZwOpenFile fail/n");
  return 0 ;
    }
 ObjecttAttr.ObjectName = NULL;

 #define  SEC_IMAGE 0x1000000
 
 status = ZwCreateSection(&hSection,
        SECTION_ALL_ACCESS,
        &ObjecttAttr,
        0,
        PAGE_EXECUTE,
        SEC_IMAGE,
        hFile);
 if ( !NT_SUCCESS(status) )
    {
  DbgPrint("winsun: GetMappedModuleAddr ZwCreateSection fail/n");
  return 0 ;
    }

 status = ZwMapViewOfSection(hSection,
  PsGetCurrentProcessId(),
  &MapFileBaseAddress,
  0,
  1024,
  0,
  &size,
  ViewShare,
  MEM_TOP_DOWN,
  PAGE_READWRITE);
 if ( !NT_SUCCESS(status) )
    {
  DbgPrint("winsun: GetMappedModuleAddr ZwMapViewOfSection fail 0x%x/n", status);
  ZwClose(hSection);
  ZwClose(hFile);
  return 0 ;
    }
 //ZwClose(hSection);
 ZwClose(hFile);
 DbgPrint("winsun: GetMappedModuleAddr baseadress:0x%x/n",MapFileBaseAddress);
 
 return (ULONG)MapFileBaseAddress;
}

2、获取NtDeviceIoControlFile的地址

3、修改inline hook

获取可信注册表信息:

注册表查询程序可以通过Advapi32.dll导出的RegEnumKeyRegEnumValue函数来列举注册表键和键值。下图显示了RegEnumKey的调用情况

可以看到,RegEnumKey函数将调用Ntdll.dll提供的NtEnumerateKey,并通过其进入内核态,查询SDT并调用相应的ntoskrnl.exe提供的NtEnumerateKey例程,该例程将调用CmEnumerateKey函数来完成功能。前面已经提到了,恶意代码主要是通过hook SDT来实现注册表隐藏的,因此直接调用CmEnumerateKey就可以获取真实的注册表信息。然而,CmEnumerateKey函数是未被微软公开也未导出的,确定其地址只能通过在NtEnumerateKey中进行特征串搜索,这会给程序的稳定性带来一定的隐患,因此该方案未被采用。

由于注册表中的信息要求在系统重启后仍然有效,也就不可能保存在内存中,所以,注册表信息只能保存在磁盘的文件上。这种文件被称为hive文件,其格式并没有对开发者公开,但是针对它们的研究可以通过逆向工程和其他方法进行。下面是hive文件的组织形式

注册表中HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Control/ hivelist记录着系统注册表Hive文件的保存路径,而系统许多功能的实现是依赖于里面记录的这些文件提供信息的。正是由于这一性质,决定了系统Hive文件应该是安全的,内容也是最完整的。同时,由于这些文件对于系统的重要作用,一般情况下操作系统是不允许其他程序在系统范围内去访问这些文件的,这就需要我们通过编写文件驱动来绕过系统对这些文件的保护,从而实现文件的读取。

Windows系统中,我们可以通过使用API RegSaveKey / RegSaveKeyEx 函数来将指定键下的注册表信息转储为Hive格式的文件。当然,这些文件是通过API调用来实现的,这也就给恶意程序留下了可能的漏洞,虽然现在还没有发现恶意程序这么做,但从理论上说,恶意程序可以通过hook API来避免被dump出来以逃避检测。

在获取了注册表Hive文件的基础上,就可以实现对它们进行完整的检测。我们借鉴了Petter Nordahl-Hagen所编写的NT Registry Hive access library,通过中对Hive文件的分析,另外设计了一个实用的Hive文件访问接口,利用该接口可以用来分析Hive文件存储内容,获取其中信息。从而,基于注册表Hive文件的恶意程序隐藏检测可以按照如下的步骤来进行:

PspCidTable是一个特殊的全局的句柄表,它不输入任何进程,它所对应的句柄项也很特殊,就是进程的ID。因此,调用PsLookupProcessByProcessId的过程实际上就是一个进程ID号映射成对象指针的过程,如图1

 

恶意代码的反检测就是要使恶意代码做到“免杀”,亦即避免被查杀。

2.1 变形技术

变形技术分为加密变形、单变形、准变形和全变形技术,以下将分别介绍:

2.1.1 加密变形

这是变形技术的初级阶段,其目的在于隐藏恶意代码本体。它以一个解密器开始(Decryptor) ,然后对其后的恶意代码进行加密,并且每次加密的密钥都不相同。

2.1.2 单变形

单变形技术是在加密技术的基础上发展而来的,其对加密病毒的最大改进点在于对解密器进行保护,这也正是针对特征码技术的一种病毒设计方法。它的目的在于使得作为特征的解密器也隐藏起来,让反病毒软件无从下手。

之所以将这类变形技术定义为单变形,就是因为这类技术只能生成有限种的解密器变体。必须指出的一点是,这里提到的变体是指解密器的变体,因为恶意代码本题仍然是由加密算法来进行保护的。单变形技术与加密技术的另一个区别就是变形机雏形的出现。虽然这些变形机的功能还不完善,生成变体有限,甚至漏洞颇多,但在出现之初仍然给恶意代码检测技术造成了很大的挑战。作为特征码技术,必须对病毒代码有一个全面的了解,也就是说必须对解密器的不同变体都有相应的解决办法,虽然存在理论上的可能性,但实际操作起来却难度很大,如果某个恶意代码有100 个变体,那么对检测软件来说,就相当于有100 个不同的恶意代码要处理,这个工作量是相当大的。迄今为止对于大多数的此类恶意代码来说,还没有哪款恶意代码检测软件能够100 %地侦测到。

2.1.3 准变形技术

准变形技术,也被称为多形技术,目前大多数人所认为的变形病毒大都是使用这一技术。其相对于单变形技术的最大变化在于真正的变形机的出现,它已经可以生成无穷多的变体,这也就使得利用传统的特征码技术对采用这种技术的恶意代码进行侦测的理论上的可能性也不存在了。在变形机出现以后,恶意代码的自我保护有了一个很大的变化,从只保护病毒体发展到了不但利用加密来保护病毒体,同时用变形机来保护解密器。值得注意的是,在准变形病毒阶段,加密技术也有了一定的发展,那就是从过去的单层加密发展到了多层加密。同时变形机开始采用随机解密算法(RDA Random Decryption Algo2rithm),即利用随机数来生成不同的变体,病毒也就有了无穷多的变体。

2.1.4 全变形技术

如果简要地解释一下什么是全变形技术,那就可以描述为“变形机能够对恶意代码本体进行变形的技术”,稍加注意就会发现无论是加密技术,单变形技术,还是准变形病毒,都有一个共同的特点,那就是对病毒体的保护都是依靠加密技术来实现的。而加密的最大的弱点就是无论密钥如何变化,其病毒体的程序都是静态的,也就是说只要破译解密器,解密后得到的恶意代码体代码都是一样的。全变形病毒则是在这方面有了很大的变化,它不再采用加密对病毒体进行保护,而是直接应用变形机来对整个恶意代码本体变形,而且它的变形不在保持恶意代码结构,变体之间无论结构还是代码都是完全不同的。这就克服了前三种技术的最大缺点,即使已经被发现并“记录在案”,恶意代码检测程序也很难利用以前发现的特征检测现在的代码,因为恶意代码本体已经变形了。

这种变形技术是最难对付的,不过对恶意代码编写者的要求很高,而且所有所有代码都需要使用汇编语言来实现,这又限制了恶意代码的功能。采用全变形技术的恶意代码相对很少。

如果某恶意代码体包含指令mov eax ,ebx,并采用加密技术,而且每次加密的密钥为一随机数,同时加密采用xor 操作,该指令机器码为8BC3h ,假设第一次运行密钥为1234h ,第二次运行密钥为3421 h ,其结果是第一次加密后机器码为99F7h ,第二次加密后机器码为B9E2h。这样每经过一次加密由于密钥的不同,mov eax ,ebx指令对应的内容就要变化一次,其他的指令情况也类似,这就是说经过加密的恶意代码,每次出现只要密钥不同,其内容就不同,也就初步达到了变换代码的目的。

然而,加密技术有一个很大的漏洞,那就是其解密器不能被加密,而且解密器的代码大都不同。足以作为特征码,所以对加密病毒反病毒软件不需要对其进行解密就可以侦测到,因此加密病毒病毒体的变体已经没有意义,反病毒软件并不关心它,而只是注意解密器就可以了。同时改善这一漏洞也成为加密病毒进一步发展的动力。

原创粉丝点击