一,前言
二,简介
三,rootkit的一些以公开的隐藏技术
四,一些隐藏技术的应对方法
五,about ring0 rootkit
六,rootkit的检测
七,参考资料,推荐 :)
*************************
一.先说几句与技术无关的话。
现在很多人对rootkit认识不够,可以说空白。而此愚文的目的就是让菜鸟认识rootkit→了解rootkit。也让一些想研究它的人把这篇文章当作一个参考或是入门级的指导。文章中介绍的rootkit的隐藏方法只是一部分。还有很多技术没有提到,另外还有一些未公开的技术。有些地方我未引用代码,因为不想占用过多的篇幅。以免有玩弄代码的嫌疑,不过写完以后还是觉得代码太多,请个位见谅。一-三的内容适合菜鸟看,也许第四部分之后对很多人来说都有些意义吧。如果高手不幸看到了,请准备好,不要吐到屏幕上或身上。
以往的文章写的比较乱,而且格式不公正,这样的形式写文章也是第一次。以前文章中引用代码和文字也没有详细说明,再此也对作者表示谦意。感谢XX提的这个建议。
今天是9月11号,庆祝一下童童的生日。顺便为911事件中的遇难者祈祷。同时也感谢MGF病毒的作者指点。
*************************
二.简单的说说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,OutputBufferLength。
摘自《在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.....
**************************
七,参考资料:
1.http://www.phrack.org/phrack/62/p62-0x06_Kernel_Mode_Backdoors_for_Windows_NT.txt <firew0rker>
2.http://www.blackhat.com/presentations/bh-usa-03/bh-us-03-rutkowski/rutkowski-antirootkit.zip <Jan K. Rutkowski>
3.http://www.eviloctal.com/forum/htm_data/15/0506/11387.html <Jan Krzysztof Rutkowski>
4.http://www.xfocus.net/articles/200403/680.html <Holy_Father>
5.http://www.eviloctal.com/forum/htm_data/24/0509/14187.html <The Undocumented Functions-Microsoft Windows NT_2000>
6.http://he4dev.e1.bmstu.ru/HookSysCall/
7.http://blog.csdn.net/sunwear/archive/2004/10/03/123751.aspx <sunwear>
8.http://www.xfocus.net/articles/200309/610.html <pjf>
9.http://www.eviloctal.com/forum/htm_data/13/0502/7858.html <pjf>
10.http://www.eviloctal.com/forum/htm_data/26/0412/5226.html <jiurl>
11.http://www.rootkit.com/newsread_print.php?newsid=170 <kimmo >
12.http://www.xfocus.net/articles/200404/693.html
13.http://www.xfocus.net/projects/Xcon/2005/Xcon2005_Baiyuanfan.pdf <baiyuanfan>
14.http://www.xfocus.net/projects/Xcon/2005/Xcon2005_Shok.pdf <shok>
推荐:
1.http://www.rootkit.com/
2.http://www.phrack.org/
3.http://www.eviloctal.com/
4.http://www.xfocus.net/
5.http://www.securiteam.com/
6.http://www.retcvc.com/
7.http://www.driverdevelop.com/
二,简介
三,rootkit的一些以公开的隐藏技术
四,一些隐藏技术的应对方法
五,about ring0 rootkit
六,rootkit的检测
七,参考资料,推荐 :)
*************************
一.先说几句与技术无关的话。
现在很多人对rootkit认识不够,可以说空白。而此愚文的目的就是让菜鸟认识rootkit→了解rootkit。也让一些想研究它的人把这篇文章当作一个参考或是入门级的指导。文章中介绍的rootkit的隐藏方法只是一部分。还有很多技术没有提到,另外还有一些未公开的技术。有些地方我未引用代码,因为不想占用过多的篇幅。以免有玩弄代码的嫌疑,不过写完以后还是觉得代码太多,请个位见谅。一-三的内容适合菜鸟看,也许第四部分之后对很多人来说都有些意义吧。如果高手不幸看到了,请准备好,不要吐到屏幕上或身上。
以往的文章写的比较乱,而且格式不公正,这样的形式写文章也是第一次。以前文章中引用代码和文字也没有详细说明,再此也对作者表示谦意。感谢XX提的这个建议。
今天是9月11号,庆祝一下童童的生日。顺便为911事件中的遇难者祈祷。同时也感谢MGF病毒的作者指点。
*************************
二.简单的说说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,OutputBufferLength。
摘自《在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.....
**************************
七,参考资料:
1.http://www.phrack.org/phrack/62/p62-0x06_Kernel_Mode_Backdoors_for_Windows_NT.txt <firew0rker>
2.http://www.blackhat.com/presentations/bh-usa-03/bh-us-03-rutkowski/rutkowski-antirootkit.zip <Jan K. Rutkowski>
3.http://www.eviloctal.com/forum/htm_data/15/0506/11387.html <Jan Krzysztof Rutkowski>
4.http://www.xfocus.net/articles/200403/680.html <Holy_Father>
5.http://www.eviloctal.com/forum/htm_data/24/0509/14187.html <The Undocumented Functions-Microsoft Windows NT_2000>
6.http://he4dev.e1.bmstu.ru/HookSysCall/
7.http://blog.csdn.net/sunwear/archive/2004/10/03/123751.aspx <sunwear>
8.http://www.xfocus.net/articles/200309/610.html <pjf>
9.http://www.eviloctal.com/forum/htm_data/13/0502/7858.html <pjf>
10.http://www.eviloctal.com/forum/htm_data/26/0412/5226.html <jiurl>
11.http://www.rootkit.com/newsread_print.php?newsid=170 <kimmo >
12.http://www.xfocus.net/articles/200404/693.html
13.http://www.xfocus.net/projects/Xcon/2005/Xcon2005_Baiyuanfan.pdf <baiyuanfan>
14.http://www.xfocus.net/projects/Xcon/2005/Xcon2005_Shok.pdf <shok>
推荐:
1.http://www.rootkit.com/
2.http://www.phrack.org/
3.http://www.eviloctal.com/
4.http://www.xfocus.net/
5.http://www.securiteam.com/
6.http://www.retcvc.com/
7.http://www.driverdevelop.com/