Windows驱动级云安全主动防御系统

OrdinAry

发布日期: 2018-11-06 18:48:31 浏览量: 1019
评分:
star star star star star star star star star star
*转载请注明来自write-bug.com

1 使用说明

本系统分成四个模块,包含一个应用层可执行文件(.exe),四个内核层驱动(.sys),一个规则库数据库文件(.mdb),请保证六个文件在同一目录。

1.1 安装服务与启动监控

程序不需要安装,双击即可打开可执行文件。打开后,如果需要启动服务,请点击“安装服务”,请切换至“主要”标签页,接着“启动进程监控”、“启动注册表监控”、“启动文件监控”、“启动内存加载监控”,程序开始拦截系统调用。

1.2 停止监控与停止服务

如果需要停止监控,请先在各标签页“停止功能”,例如停止注册表监控,请先点击注册表监控标签页“停止功能”按钮。接着打开“主要”标签页,停止相当服务即可, 如图:

2 系统设计与架构

2.1 开发环境

本系统应用层采用Visual Studio 2008(C++)开发,驱动层开发环境为WDK 6001.18002、Notepad++,测试环境为VMware 6.0、Windows XP Sp3。

2.2 系统结构

病毒在计算机运行之后将根据自身的目的呈现出一系列的动作,包括写注册表项,生成文件,远程线程注入等等。本系统通过拦截系统调用对程序行为进行监控,将监控的行为信息交给监控中心和网络服务器分析处理,根据程序行为分析判断病毒,云安全概念的加入,本地特征库极小,占用系统资源很少。本系统设想根据这一系列的动作所组成的行为进行智能的逻辑判断该程序是不是病毒。

系统结构示意图

2.3 系统流程

本系统分为六大模块:进程监控模块、注册表监控模块、内存加载监控模块、文件创建加载模块、云安全模块、安全监控中心模块。各个模块相互独立,系统流程图如下:

3 系统实现原理

3.1 Hook SSDT

在 Windows NT 下,用户模式(User mode)的所有调用,如Kernel32.dll,User32.dll, Advapi32.dll等提供的API,最终都封装在Ntdll.dll中,然后通过Int 2E或SYSENTER进入到内核模式,通过服务ID,在System Service Dispatcher Table中分派系统函数。这些本地系统服务的地址在内核结构中称为系统服务调度表(System Service Dispatch Table,SSDT)中列出,该表可以基于系统调用编号进行索引,以便定位函数的内存地址。下图是Windows NT系列操作系统的体系结构,系统服务调用只是一个接口,它提供了将用户模式下的请求转发到Windows 2000内核的功能,并引发处理器模式的切换。在用户看来,系统服务调用接口就是Windows内核组件功能实现对外的一个界面。系统服务调用接口定义了Windows内核提供的大量服务。

KeServiceDescriptorTable是由内核导出的表,这个表是访问SSDT的关键,具体结构是:

  1. typedef struct ServiceDescriptorTable
  2. {
  3. PVOID ServiceTableBase;
  4. PVOID ServiceCounterTable(0);
  5. unsigned int NumberOfServices;
  6. PVOID ParamTableBase;
  7. }

其中:

  • ServiceTableBase是 System Service Dispatch Table 的基地址
  • NumberOfServices 由 ServiceTableBase 描述的服务的数目
  • ServiceCounterTable 包含着 SSDT 中每个服务被调用次数的计数器
  • ParamTableBase 包含每个系统服务参数字节数表的基地址

System Service Dispath Table(SSDT):系统服务分发表,给出了服务函数的地址,每个地址4子节长。

System Service Parameter Table(SSPT):系统服务参数表,定义了对应函数的参数字节,每个函数对应一个字节。

为了调用特定函数,系统服务调度程序KiSystemService将该函数的ID编号乘以4以获取它在SSDT中的偏移量。注意KeServiceDescriptorTable包含了服务数目,该值用于确定在SSDT或SSPT中的最大偏移量。下图也描述了SSPT。该表中的每个元素为单字节长度,以十六进制指定了它在SSDT中的相应函数采取多少字节作为参数。在这个示例中,地址0x804AB3BF处的函数采用0x18个字节的参数。

当调用INT 2E或SYSENTER指令时会激活系统服务调度程序。这导致进程通过调用该程序转换到内核模式。若将SSDT改为指向rootkit所提供的函数,而不是指向Ntoskrnl.exe或Win32k.sys,当非核心的应用程序调用到内核中时,该请求由系统服务调度程序处理,并且调用了rootkit的函数。这时,rootkit可以将它想要的任何假信息传回到应用程序,从而有效地隐藏自身以及所用的资源。

由于Windows系统版本对某些内存区域启用了写保护功能,SSDT就包括在其中,若向只读内存区域中执行写入操作,则会发生蓝屏死机(Blue Screen of Death,BSoD)。Windows中可以通过Memory Descriptor List(MDL)修改内存属性, 把原来SSDT的区域映射到我们自己的MDL区域中,并把这个区域设置成可写。MDL的结构:

  1. typedef struct _MDL
  2. {
  3. struct _MDL *Next;
  4. CSHORT Size;
  5. //设置成MDL_MAPPED_TO_SYSTEM_VA ,这块区域就可写
  6. CSHORT MdlFlags;
  7. struct _EPROCESS *Process;
  8. PVOID MappedSystemVa;
  9. PVOID StartVa;
  10. ULONG ByteCount;
  11. ULONG ByteOffset;
  12. } MDL, *PMDL;

知道KeServiceDscriptorTable的基址和入口数后,用MmCreateMdl创建一个与KeServiceDscriptorTable对应地址和大小的内存区域,然后把这个MDL结构的flag改成MDL_MAPPED_TO_SYSTEM_VA ,那么这个区域就可以写了。最后把这个内存区域调用MmMapLockedPages锁定在内存中。

通过微软已经导出的几个宏可以很方便地Hook SSDT。

SYSTEMSERVICE 宏采用由ntoskrnl.exe导出的Zw*函数的地址,并返回相应的Nt*函数在SSDT中的地址。Nt*函数是私有函数,其地址列于SSDT中。Zw*函数是由内核为使用设备驱动程序和其他内核组件而导出的函数。注意,SSDT中的每一项和每个Zw*函数之间不存在一对一的对应关系。

SYSCALL_INDEX宏采用Zw*函数地址并返回它在SSDT中相应的索引号。该宏和SYSTEMSERVICE宏发挥作用的原因在于Zw*函数起始位置的操作码。通过将该函数的第二个字节看作ULONG类型,这些宏能得到该函数的索引号。

HOOK_SYSCALL和UNHOOK_SYSCALL宏采用被钩住的Zw*函数的地址,获取其索引号,并自动将SSDT中该索引的相应地址与_Hook函数的地址进行交换。

  1. #define SYSTEMSERVICE(_func) \
  2. KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_func+1)]
  3. #define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)
  4. #define HOOK_SYSCALL(_Function, _Hook, _Orig ) \
  5. _Orig = (PVOID) InterlockedExchange( (PLONG) \
  6. &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)
  7. #define UNHOOK_SYSCALL(_Func, _Hook, _Orig ) \
  8. InterlockedExchange((PLONG) \
  9. &MappedSystemCallTable[SYSCALL_INDEX(_Func)], (LONG) _Hook)

3.2 监视进程创建和销毁原理

监视系统进程开始和结束的方法是通过DDK中的PsSetCreateProcessNotifyRoutine函数设置一个CALLBACK函数。该函数的原形如下:

  1. NTSTATUS PsSetCreateProcessNotifyRoutine(
  2. IN PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine,
  3. IN BOOLEAN Remove
  4. );
  • NotifyRoutine指定了当进程被创建和结束的时候所需要调用的回调函数

  • Remove是用来告诉该函数是设置该回调还是移除

NotifyRoutine的类型为PCREATE_PROCESS_NOTIFY_ROUTINE,其定义为:

  1. VOID (*PCREATE_PROCESS_NOTIFY_ROUTINE) (
  2. IN HANDLE ParentId,
  3. IN HANDLE ProcessId,
  4. IN BOOLEAN Create
  5. );

NotifyRoutine的传入参数中,ParentId和ProcessId用来标识进程,Create则是用来表示该进程是正在被创建还是正在被结束。这样,每当进程被创建或者结束的时候,操作系统就会立刻调用NotifyRoutine这个回调函数并正确提供参数。

由于回调函数NotifyRoutine传入参数的类型为HANDLE,而PID为DWORD类型,可以通过强制转换为DWORD得到父进程、正在创建的进程的PID。

但此时仍不能得到父进程和正在创建的路径,通过调用一个未公开函数 PsLookupProcessByProcessId得到该进程的PEPROCESS,每个进程都有一个 EPROCESS 结构,里面保存着进程的各种信息,和相关结构的指针。这个函数的原型如下:

  1. NTSTATUS PsLookupProcessByProcessId(
  2. IN ULONG ulProcId,
  3. OUT PEPROCESS * pEProcess
  4. );

得到该进程的PEPROCESS对象后,由于PEPROCESS是微软未公开的结构,而且在不同的操作系统下结构还不一样。EPROCESS 偏移0X1fc中保存着一个给人看的进程名,更准确的叫法是映像名称。

  1. EPROCESS +1fc byte ImageFileName[16]

ImageFileName[16] 是一个16个字节长的字节数组,保存着进程名。当进程名的长度大于等于16个字节时,在 ImageFileName[16] 只保存进程名的前15个字节,ImageFileName[16] 最后一个字节为0,字符串的结束符。不同进程的进程名可以相同,比如打开多个记事本,那么每个记事本的 ImageFileName[16] 都是 “NOTEPAD.EXE”,进程名只是给人看的,每个进程的 进程ID 都是不同的。对于父进程,在PEPROCESS保存进程名,通过自定义的一个函数当前进程可以得到完整的路径,该函数定义如下:

  1. PCWSTR GetCurrentProcessFileName()
  2. {
  3. DWORD dwAddress = (DWORD)PsGetCurrentProcess();
  4. DWORD dwAddress1;
  5. if(dwAddress == 0 || dwAddress == 0xFFFFFFFF) return NULL;
  6. dwAddress += 0x1B0;
  7. if((dwAddress = *(DWORD*)dwAddress) == 0) return 0;
  8. dwAddress += 0x10;
  9. if((dwAddress = *(DWORD*)dwAddress) == 0) return 0;
  10. dwAddress1 = dwAddress;//2000
  11. dwAddress += 0x3C;
  12. if((dwAddress = *(DWORD*)dwAddress) == 0) return 0;
  13. if (dwAddress < dwAddress1) dwAddress = dwAddress+dwAddress1;
  14. return (PCWSTR)dwAddress;
  15. }

但是对于正在创建的进程,并不能如此。必须使用ObOpenObjectByPointer得到进程名柄,ObOpenObjectByPointer的原型如下:

  1. NTSTATUS ObOpenObjectByPointer(
  2. IN PVOID Object, // 进程的pEProcess
  3. IN ULONG HandleAttributes,
  4. IN PACCESS_STATE PassedAccessState OPTIONAL,
  5. IN ACCESS_MASK DesiredAccess,
  6. IN POBJECT_TYPE ObjectType,
  7. IN KPROCESSOR_MODE AccessMode,
  8. OUT PHANDLE Handle //此处将得到名柄
  9. );

得到名柄后可进一步通过未微软未公开的函数ZwQueryInformationProcess 可得到进程的镜像路径。ZwQueryInformationProcess原型如下:

  1. NTSTATUS ZwQueryInformationProcess(
  2. IN HANDLE ProcessHandle, //传入进程句柄
  3. IN PROCESSINFOCLASS ProcessInformationClass,// 指定此处为ProcessImageFileName
  4. OUT PVOID ProcessInformation,//此处得到进程镜像信息
  5. IN ULONG ProcessInformationLength,
  6. OUT PULONG ReturnLength OPTIONAL
  7. );

得到镜像路径、进程PID等信息后可通知应用层用进程创建,应用层便能迅速取得进程信息,从而可以对进程在未完全加载完成时处理,在进程监控中,应该有内核事件对象、驱动层与用户层、用户层与驱动层通信的知识,首先是内核事件对象。

在内核中创建的事件对象,用户层只能读,不能给事件置信号,这多少给应用程序与驱动程序之间同步造成麻烦。

在使用KEVENT 事件对象前,需要首先调用内核函数KeInitialize Event 对其初始化,这个函数的原型如下所示:

  1. VOID KeInitializeEvent(
  2. IN PRKEVENT Event,
  3. IN EVENT_TYPE Type,
  4. IN BOOLEAN State
  5. );
  • 第一个参数Event 是初始化事件对象的指针

  • 第二个参数Type 表明事件的类型,事件分两种类型:

    • 一类是“通知事件”,对应参数为NotificationEvent
    • 另一类是“同步事件”,对应参数为SynchronizationEvent
  • 第三个参数State 如果为TRUE,则事件对象的初始化状态为激发状态,否则为未激发状态。如果创建的事件对象是“通知事件”,当事件对象变为激发态时,需要我们手动将其改回未激发态。如果创建的事件对象是“同步事件”,当事件对象为激发态时,如果遇到相应的KeWaitForXXXX 等内核函数,事件对象会自动变回到未激发态

设置事件的函数是KeSetEvent,可通过该函数修改事件对象的状态,这个函数的原型如下:

  1. VOID KeClearEvent(
  2. IN PRKEVENT Event
  3. );

在本系统进程监控模块中,我采用的方案是在内核监视进程的创建和销毁,并且应用程序开启新线程一直等待驱动中创建的命名事件。

在驱动层中,一旦检测到有进程进程的创建立即将事件对象置上信号, 这时应用层立即取得进程PID等信息,利用ZwSuspendProcess将进程挂起,从而可以对进程的信息进行进一步的检验,但是,ZwSuspendProcess是NTDLL.DLL中一个未导出的函数,在《Windows NT 2000 Native API Reference》中找不到其原型。我尝试过在驱动中申明NTSYSAPI的方法尝试利用其在内核层中挂起进程,可惜的是编译不能通过。

当然在用户层中挂起线程与在内核层中挂起进程是没有差别的,于是采用了在用户态下挂起进程的方法。

在用户态中使用未导出的内核函数,必须先将ZwSuspendProcess导出。导出.DLL内的函数方法之一是利用GetProcAddress ,这个函数的原型是:

  1. FARPROC WINAPI GetProcAddress(
  2. __in HMODULE hModule,
  3. __in LPCSTR lpProcName
  4. );

传入的HMODULE的原型是:

  1. HMODULE WINAPI LoadLibrary(
  2. __in LPCTSTR lpFileName
  3. );

于是通过下面两行代码可以将ZwSuspendProcess内核函数导出:

  1. HANLDE hdll=LoadLibrary(L"NTDLL.DLL");
  2. SuspendProcess=(pfsuspend)GetProcAddress(hdll, "ZwSuspendProcess");

同时,还得利用相同方法将ZwResumeProcess函数导出,当然,必须得先申明函数原型。经过查阅《Windows System Call Table》,这两函数的原型如下(Nt与Zw系统函数完全相同):

  1. NTSYSAPI NTSTATUS NTAPI NtSuspendProcess(
  2. IN HANDLE Process
  3. );
  4. NTSYSAPI NTSTATUS NTAPI NtResumeProcess(
  5. IN HANDLE Process
  6. );

使用OpenProcess传入PID即可得到进程句柄,将获得的进程句柄传入 ZwSuspendProcess/ZwResumeProcess 中即可实现进程的挂起与恢复。

应用层传入信息的时候,可以使用WriteFile,也可以使用DeviceIoControl。

DeviceIoControl是双向的,在读取设备的信息也可以使用。DeviceIoControl 函数会使操作系统产生一 IRP_MJ_DEVICE_CONTROL 类型的IRP,然后这个IRP 会被分发到相应的派遣例程中。因此在驱动入口函数中需要先设置 IRP_MJ_DEVICE_CONTROL 的派遣例程,如

  1. DriverObject->MajorFunctions[IRP_MJ_DEVICE_CONTROL] = MyDeviceIoControl;

DeviceIoControl 函数的原型如下:

  1. BOOL DeviceIoControl(
  2. HANDLE hDevice, // handle to device
  3. DWORD dwIoControlCode, // operation
  4. LPVOID lpInBuffer, // input data buffer
  5. DWORD nInBufferSize, // size of input data buffer
  6. LPVOID lpOutBuffer, // output data buffer
  7. DWORD nOutBufferSize, // size of output data buffer
  8. LPDWORD lpBytesReturned, // byte count
  9. LPOVERLAPPED lpOverlapped// overlapped information
  10. );

第二个参数dwIoControlCode,它是I/O 控制码,即IOCTL 值,是一个32 位的无符号整型数值。实际上DeviceIoControl 与ReadFile 和WriteFile 相差不大,不过它可以同时提供输入/输出缓冲区,而且还可以通过控制码传递一些特殊信息。IOCTL 值的定义必须遵循DDK 的规定,使用宏CTL_CODE 来声明,如下:

  1. #define MY_DVC_IN_CODE \
  2. (ULONG)CTL_CODE( FILE_DEVICE_UNKNOWN, \
  3. 0x900, \ // 自定义IOCTL 码
  4. METHOD_BUFFERED, \ // 缓冲区I/O
  5. FILE_ALL_ACCESS)

当然在应用DeviceIoControl时,应用程序应该与驱动程序包含相同的IOCTL定义,最好的方法是共享一个头文件,并且应用程序需要包含winioctl.h头文件。

下为本系统危险进程主动防御模块的流程图

3.3 监视注册表修改/创建原理

注册表监视原理是通过HOOK_SYSCALL宏勾住ZwSetValueKey, 该宏定义如下:

  1. #define HOOK_SYSCALL(_Function, _Hook, _Orig ) \
  2. _Orig = (PVOID) InterlockedExchange( (PLONG) \

具体挂接语句为:

  1. HOOK_SYSCALL(ZwSetValueKey,HookZwSetValueKey,RealZwSetValueKey);

其中ZwSetValueKey用于更新或者新建一个键值,它的原型如下:

  1. NTSTATUS ZwSetValueKey(
  2. IN HANDLE KeyHandle,
  3. IN PUNICODE_STRING ValueName,
  4. IN ULONG TitleIndex,
  5. IN ULONG Type,
  6. IN PVOID Data,
  7. IN ULONG DataSize
  8. );

由于DDK文档中没有该函数,在驱动中使用它时必须申明其原型,方法如下:

  1. NTSYSAPI NTSTATUS NTAPI ZwSetValueKey(
  2. IN HANDLE KeyHandle,
  3. IN PUNICODE_STRING ValueName,// The name of the value to be set.
  4. IN ULONG TitleIndex,
  5. IN ULONG Type,
  6. IN PVOID Data, // Points to a caller-allocated buffer or variable that contains the data of the value.
  7. IN ULONG DataSize
  8. );

HOOK_SYSCALL时,为了保存ZwSetValueKey 函数的真实地址在RealZwSetValueKey中,须定义一个与之类似的结构:

  1. typedef NTSTATUS (*REALZWSETVALUEKEY)(
  2. IN HANDLE KeyHandle,
  3. IN PUNICODE_STRING ValueName,
  4. IN ULONG TitleIndex OPTIONAL,
  5. IN ULONG Type,
  6. IN PVOID Data,
  7. IN ULONG DataSize
  8. );
  9. REALZWSETVALUEKEY RealZwSetValueKey=NULL;//初始化

Hook ZwSetValueKey后,当应用层修改注册时,总会先进入我们挂接的函数HookZwSetValueKey,假若在HookZwSetValueKey函数中不再调用真实的ZwSetValueKey,那么应用层无法修改、创建注册表键键值。HookZwSetValueKey定义与ZwSetValueKey一致,如下:

  1. NTSTATUS HookZwSetValueKey(
  2. IN HANDLE KeyHandle,
  3. IN PUNICODE_STRING ValueName,
  4. IN ULONG TitleIndex OPTIONAL,
  5. IN ULONG Type,
  6. IN PVOID Data,
  7. IN ULONG DataSize
  8. );

HookZwSetValueKey传入参数包含了将要修改的信息:ValueName指向了要修改的键名。

在Win32 API中,管理内核对象是通过HANDLE,而在内核内部是通过Object来管理内核对象的。下面是Object属性结构:

  1. typedef struct _OBJECT_ATTRIBUTES {
  2. ULONG Length; //结构长度
  3. HANDLE RootDirectory; //根目录
  4. UNICODE_STRING *ObjectName; //Object的名称
  5. ULONG Attributes; //属性
  6. PSECURITY_DESCRIPTOR SecurityDescriptor;//安全描述符
  7. PSECURITY_QUALITY_OF_SERVICE SecurityQualityOfService;
  8. } OBJECT_ATTRIBUTES,*POBJECT_ATTRIBUTES;

我们感兴趣的是ObjectName成员,因为它包含了注册表名。

但要想进一步得到要修改的键值则必须进一步通过ObReferenceObjectByHandle得到对象,这是一个在驱动开发中经常用到的函数,该函数原型如下:

  1. NTSTATUS
  2. ObReferenceObjectByHandle(
  3. IN HANDLE Handle, //此处传入HookZwSetValueKey的参数KeyHandle
  4. IN ACCESS_MASK DesiredAccess,
  5. IN POBJECT_TYPE ObjectType OPTIONAL,
  6. IN KPROCESSOR_MODE AccessMode,
  7. OUT PVOID *Object, //这里返回Object指针
  8. OUT POBJECT_HANDLE_INFORMATION HandleInformation OPTIONAL
  9. );

得到PObject后,理论上可以其通过获得键名信息,由于OBJECT_ATTRIBUTES是不公开的结构,不同操作系统可能不同。为了跨越不同的版本,这里采用未公开的内核函数ObQueryNameString,才能得到键名信息,至此,只得到了操作注册表方面的信息,因为还需得到是哪个进程引发的操作,进程名可通过当前进程偏移量方法获取,而进程的完整路径同样是通过一个自定义函数取得:

  1. PCWSTR GetCurrentProcessFileName()
  2. {
  3. DWORD dwAddress = (DWORD)PsGetCurrentProcess();
  4. DWORD dwAddress1;
  5. if(dwAddress == 0 || dwAddress == 0xFFFFFFFF) return NULL;
  6. dwAddress += 0x1B0;
  7. if((dwAddress = *(DWORD*)dwAddress) == 0) return 0;
  8. dwAddress += 0x10;
  9. if((dwAddress = *(DWORD*)dwAddress) == 0) return 0;
  10. dwAddress1 = dwAddress;//2000
  11. dwAddress += 0x3C;
  12. if((dwAddress = *(DWORD*)dwAddress) == 0) return 0;
  13. if (dwAddress < dwAddress1) dwAddress = dwAddress+dwAddress1;
  14. return (PCWSTR)dwAddress;
  15. }

相关方面的信息获取成功后,下一步是检查这些信息,或者让用户决定是否执行这个操作,如果执行,在HookZwSetValueKey只需将传入的各参数原样交给真实的RealZwSetValueKey的处理即可,否则不交给真实RealZwSetValueKey处理,从而导致应用程度修改操作失败。

由于应用层不能简单地通知驱动层的事件,所以本系统采用了双事件:驱动层通知应用层事件、应用层通知驱动层事件,其中应用层通知驱动层事件通过传递IOCTL ,驱动在IOCTL的派遣函数中设置事件。IOCTL定义如下:

  1. #define IOCTL_NTPROCDRV_SET_APPEVENT_OK CTL_CODE(FILE_DEVICE_UNKNOWN,0x0911, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)

在驱动的派遣例程中KeSetEvent置信号方法如下:

  1. case IOCTL_NTPROCDRV_SET_APPEVENT_OK:
  2. {
  3. KdPrint(("放行!应用层通知驱动层的事件"));
  4. RegPass=1; //全居变量,通过,事件设置必须在后面
  5. KeSetEvent(RegAppEvent,IO_NO_INCREMENT,FALSE);
  6. status=STATUS_SUCCESS;
  7. }
  8. break;

下为本系统注册表主动防御模块的流程图

3.4 监视文件修改/创建原理

文件监视原理是通过HOOK_SYSCALL宏勾住ZwWriteFile, 该宏定义如下:

  1. #define HOOK_SYSCALL(_Function, _Hook, _Orig ) \
  2. _Orig = (PVOID) InterlockedExchange( (PLONG) \

具体挂接语句为:

  1. HOOK_SYSCALL(ZwWriteFile,HookZwWriteFile,RealZwWriteFile);

ZwWriteFile用于向文件写入数据. 为了能成功挂接ZwWriteFile,与注册表监控相似,必须先申明其原型、Hook函数的原型、用于保存真实地址的RealZwWriteFile。

  1. NTSYSAPI NTSTATUS NTAPI ZwWriteFile(
  2. IN HANDLE FileHandle,
  3. IN HANDLE Event OPTIONAL,
  4. IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
  5. IN PVOID ApcContext OPTIONAL,
  6. OUT PIO_STATUS_BLOCK IoStatusBlock,
  7. IN PVOID Buffer,
  8. IN ULONG Length,
  9. IN PLARGE_INTEGER ByteOffset OPTIONAL,
  10. IN PULONG Key OPTIONAL
  11. );

申明保存真实地址的RealZwWriteFile:

  1. typedef NTSTATUS (*ZWWRITEFILE)(
  2. IN HANDLE FileHandle,
  3. IN HANDLE Event OPTIONAL,
  4. IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
  5. IN PVOID ApcContext OPTIONAL,
  6. OUT PIO_STATUS_BLOCK IoStatusBlock,
  7. IN PVOID Buffer,
  8. IN ULONG Length,
  9. IN PLARGE_INTEGER ByteOffset OPTIONAL,
  10. IN PULONG Key OPTIONAL
  11. );
  12. ZWWRITEFILE RealZwWriteFileNULL;

申明Hook函数原型:

  1. NTSTATUS HookZwWriteFile(
  2. IN HANDLE FileHandle,
  3. IN HANDLE Event OPTIONAL,
  4. IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
  5. IN PVOID ApcContext OPTIONAL,
  6. OUT PIO_STATUS_BLOCK IoStatusBlock,
  7. IN PVOID Buffer,
  8. IN ULONG Length,
  9. IN PLARGE_INTEGER ByteOffset OPTIONAL,
  10. IN PULONG Key OPTIONAL
  11. );

由于本软件不是做文件加密,因些目标是从传入HookZwWriteFile的参数中解析出进程名、进程完整路径、写入(新建)文件的完整路径。其中,进程名、进程完整路径与注册表监视中的原理相似,通过进程上下文和偏移量可获得。

将要写入的文件的完整路径(以下称目标路径)获得需几步,首先通过传入的FileHandle句柄,使用ObReferenceObjectByHandle得到文件对象,ObReferenceObjectByHandle原型如下:

  1. NTSTATUS ObReferenceObjectByHandle(
  2. IN HANDLE Handle, //传入FileHandle
  3. IN ACCESS_MASK DesiredAccess,
  4. IN POBJECT_TYPE ObjectType OPTIONAL,//指定为*IoFileObjectType
  5. IN KPROCESSOR_MODE AccessMode,
  6. OUT PVOID *Object, //得到文件对象
  7. OUT POBJECT_HANDLE_INFORMATION HandleInformation OPTIONAL
  8. );

由于FileObject是一个没有被文档化的对象(This information class is not implementedby any of the supported file systems.),仅知道在文件对象中FileName域保存着部分目标路径,DeviceObject域保存着卷标信息,但是对于本系统这已经够用了。

以”C:\windows\1.txt”为例,FileName这个UNICODE_STRING的Buffer中保存着“ \windows\1.txt”,经过转换后的卷标将保存着“C:”,使用的函数是IoVolumeDeviceToDosName,以下是函数原型:

  1. NTSTATUS IoVolumeDeviceToDosName(
  2. IN PVOID VolumeDeviceObject, //传入((PFILE_OBJECT)fileobj)->DeviceObject
  3. OUT PUNICODE_STRING DosName //得到卷标
  4. );

文件监控做为本系统的一个辅助模块,主要监视的操作系统内进程的文件操作行为,是必需的一个模块,病毒的破坏操作系统的时,往往伴随着文件的释放与生成,例如木马下载者,本身可能无毒,但是后期可以从网上下载其它木马,再比如本机中了蠕虫后,exe大面积感染必然是对各文件进行了写入操作,通过文件监控即可检测到这种行为。同时,能过文件监控,在查打出木马之后,可以对其“同伙”一伙打尽,下图是本系统文件监控的流程图

10

3.5 内存映射监控原理

内存监视原理是通过HOOK_SYSCALL宏勾住ZwCreateSection, ZwCreateSection用于routine creates a section object.Hook方法与上类似。该函数原型为:

  1. NTSYSAPI NTSTATUS NTAPI ZwCreateSection(
  2. OUT PHANDLE SectionHandle,
  3. IN ACCESS_MASK DesiredAccess,
  4. IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
  5. IN PLARGE_INTEGER MaximumSize OPTIONAL,
  6. IN ULONG SectionPageProtection,
  7. IN ULONG AllocationAttributes,
  8. IN HANDLE FileHandle OPTIONAL//此处包含文件路径信息
  9. );

其中最后一个参数FileHandle句柄中包含有目标文件的路径信息,与文件监视原理相同,使用ObReferenceObjectByHandle得到文件对象,在FileObject中可进一步取得文件路径与卷标。进程名可通过当前进程偏移量方法获取,进程的完整路径同样是通过一个自定义函数取得,以上原理中已多次提到。

一个程序得以运行必然要经过内存映射,勾住ZwCreateSection可以拦截记录风险程序的行为,与注册表监控模块、文件监控模块、进程监控模块共同组成完整的程序行为监控系统。

以下是本系统内存加载监控的流程图

3.6 云安全模块原理

“云安全(Cloud Security)”计划是网络时代信息安全的最新体现,它融合了并行处理、网格计算、未知病毒行为判断等新兴技术和概念,通过网状的大量客户端对网络中软件行为的异常监测,获取互联网中木马、恶意程序的最新信息,传送到Server端进行自动分析和处理,再把病毒和木马的解决方案分发到每一个客户端。

未来杀毒软件将无法有效地处理日益增多的恶意程序。来自互联网的主要威胁正在由电脑病毒转向恶意程序及木马,在这样的情况下,采用的特征库判别法显然已经过时。云安全技术应用后,识别和查杀病毒不再仅仅依靠本地硬盘中的病毒库,而是依靠庞大的网络服务,实时进行采集、分析以及处理。整个互联网就是一个巨大的“杀毒软件”,参与者越多,每个参与者就越安全,整个互联网就会更安全。

本系统云安全模块分成服务器,客户端两块。

服务器采用windows+apache+php+mysql架构,复责返回客户端查询文件信息,处理客户端上传的可疑文件。

客户端集成在本系统中,可获取文件名、文件详细路径、文件MD5、文件签名等信息,在必要的情况下以这些信息向服务器查询验证信息,并且在客户机允许的情况下自动上传可疑文件,让服务器处理并且反馈结果。

同时客户端也会将众多用户处理程序行为的信息传至服务器,服务器经过统计分析,可更新各客户机上的规则,实现云规则。

下图为云模块原理

3.7 规则动态加载原理

本系统规则存储于一access数据库文件中,程序运行时将从数据库文件初始化规则,规则在程序中是以几条STL中的双向链表(LIST)实现的。

在规则未经修改时,避免了多次查询数据库而耗时浪费资源。当本系统模块修改、删除数据库时,保存着规则的双向链表将会更新,实现了规则的动态更新。

下图为规则动态更新原理图

4 性能与测试

4.1 测试环境

本系统的测试环境为:

  • VMware 6.0

  • Windows XP SP3

  • 驱动级云安全主动防御

  • 迅雷

  • 木马

  • 等等

4.2 进程监控测试

  • 测试项目:测试本系统是否能够有效监控进程创建

  • 测试工具:驱动级云安全主动防御、记事本程序、SRVINSTW.EXE(病毒)

  • 测试过程:

    • 开启本系统进程监控
    • 运行”记事本” 程序
    • 将”记事本” 程序行为加入白名单,再次运行
    • 运行SRVINSTW.EXE(病毒)
    • 将SRVINSTW.EXE(病毒)行为加入黑名单,再次运行
  • 结果及分析如下所示:

下图为在部署了本系统的主机上打开桌面” 新建 文本文档.txt”时, ” 新建 文本文档.txt”并没有被打开,而是弹出了本系统的用户确认对话框,在云安全模块已经开启的情况下,系统自动联网查询该文件信息,在行为描述一栏有提示“windows自带记事本,该程序是安全的”,用户点击”放行一次”, ” 新建 文本文档.txt”将被打开;用户点击”我认识它,永久放行”, 以后打开记事本程序将都不再提醒。

下图为把本次程序行为加入白名单,即始终允许本程序启动。

加入白名单后,再次打开桌面”新建 文本文档.txt”,本系统不再弹出提示信息框,”新建 文本文档.txt”被直接打开,如图:

此时桌面有一病毒文件SRVINSTW.EXE,在云安全模块已经开启情况下,双击打开,系统将自动联网查询该文件信息,进程打开被阻塞,本系统弹出用户确认对话框,在行为描述一栏有提示“该程序为病毒,请谨慎运行!”,用户选择“禁止一次”,该程序被阻止运行;用户选择“这个很危险,永久禁止”,将自动添加黑名单规则,该程序被阻止运行。如下图:

用户选择“禁止一次”,进程结束。接着将此进程信息加入黑名单:

再次双击桌面图标,双击后没有反应,从进程监控主页面拦截信息可知,该程序还没完全开启时已经被主动防御进程结束了。

从实验结果可以看出,本系统能成功拦截进程创建,在云安全模块和用户维护的规则下能成功阻击病毒的运行。

4.3 注册表监控测试

  • 测试项目:测试本系统是否能够有效监控注册表创建、修改

  • 测试工具:卓然驱动级云安全主动防御、迅雷、DAEMON Tools(虚拟光驱软件)

  • 测试过程:

    • 开启本系统注册表监控功能,运行迅雷,运行DAEMON Tools
    • 修改迅雷配置,设置“开机启动迅雷”,并放行一次
    • 将修改“开机启动迅雷”程序行为加入白名单,再次修改“开机启动迅雷”
    • 修改DAEMON Tools开机启动,并拒绝一次
    • 将修改DAEMON Tools开机启动行为加入黑名单,再次修改DAEMON Tools开机启动,查看结果
  • 结果及分析如下所示:

打开迅雷,配置迅雷,勾选“开机启动迅雷”, 如图:

本系统立即暂停该操作,并且弹出对话框,请求用户确认是不是要进程该操作,在云安全模块开启的情况下,行为描述一栏会从网络获取该行为的信息,此处为“迅雷开机启动”,如图:

若此时选择“放行一次”,修改行为将生效;选择“相同一直放行”,修改行为将生效,且以后相同行为不再提醒;选择“禁止一次”,修改请求将被拒绝;选择“相同一直禁止”,修改请求将被拒绝,且以后相同行为不再提醒,直接拒绝。

测试时选择“放行一次”,再次打开迅雷的配置,可见修改已经成功。

接着,把迅雷添加启动项行为加入白名单规则,如图:

再次打开迅雷,修改配置,去掉“开机启动迅雷”前的勾,点击确认,本系统没有弹出确认对话框,修改生效。

打开DAEMON Tools,修改DAEMON Tools开机启动,如图:

并且弹出对话框,请求用户确认是不是要进程该操作,在云安全模块开启的情况下,行为描述一栏会从网络获取该行为的信息,此处为“daemon.exe添加启动项可能影响您电脑的开机速度”,如图:

因为该行为可能影响开机速度,测试时选择禁止一次,拒绝该操作,daemon.exe弹出修改注册表出错,即修改启动不成功,如图:

将DAEMON Tools添加开机启动行为加入黑名单,如图:

再次修改DAEMON Tools开机启动,本系统没有弹出确认对话框,DAEMON Tools直接报错,修改不成功,如图:

4.4 文件监控测试

  • 测试项目:测试本系统是否能够有效监控文件修改/创建

  • 测试工具:卓然驱动级云安全主动防御、记事本程序

  • 测试过程:

    • 开启本系统文件监控
    • 在桌面新建一文本文档,任意写入文本信息,保存。查看结果
    • 修改本系统注册表规则,保存。查看结果
  • 结果及分析如下所示:

在桌面新建一文本文档,任意写入文本信息,回到本系统文件监控模块,查看信息如下:

由文件监控信息可知,explorer.exe先创建了“最近打开文件”的链接,接着notepad.exe修改了桌面“新建 文本文档.txt”,完整地记录了修改文件信息。

在注册表监控测试时,由于本程序需要修改数据库记录,查看文件监控信息,此行为也记录了下来,如图:

文件监控在新建或修改.exe/.dll/.sys等可执行文件时,在云安全模块开启的情况下,能向服务器验证MD5、签名、文件名等信息,保证不被下载者等木马从网络下载有害程序破坏系统。

此外,在有蠕虫感染系统时,同一进程将对系统众多可执行文件感染,必然要有打开的修改操作,在文件监控功能与监控中心的配合下,能在早期就察觉出这种危险行为,进而阻止。

4.5 内存加载监控测试

  • 测试项目:测试本系统是否能够有效监控内存加载

  • 测试工具:卓然驱动级云安全主动防御、记事本程序,

  • 测试过程:

    • 开启本系统内存加载监控
    • 在桌面新建一文本文档,任意写入文本信息,保存。查看结果
    • 停止本系统进程监控服务,再次启动,查看结果
  • 结果及分析如下所示:

停止本系统进程监控服务,并再次启动,查看内存加载信息,由加载信息可看出,本系统的进程监控服务服务加载时需要一个名为ProcMon.sys加载进入内存,如图:

在桌面新建一文本文档,任意写入文本信息,回到本系统内存加载模块,查看信息如下:

从内存加载信息中,可以查看notepad.exe进程加载时哪些文件映射进入了内存,得到结果后可进一步处理。

在云安全模块开启的情况下,本系统会自己向服务器验证加载进入内存的文件信息,与其它模块相互配合,可将病毒及期衍生物一网打尽。

参考文献

[1] Mark E.Russinovich, David A.Solomon .深入解析Windows操作系统. 电子工业出版社, 2007年6月.

[2] 张帆.Windows驱动开发详解.电子工业出版社.2008年2月.

[3] 张静盛.windows编程循序渐进. 机械工业出版社. 2008年6月.

[4] Jeffrey Richter.WINDOWS核心编程. 清华大学出版社. 2008年9月

[5] 谭文,杨潇,邵坚磊.寒江独钓—Windows内核安全编程.电子工业出版社.2009.6

[6] 谭文,邵坚磊.天书夜读—从汇编语言到windows内核编程.电子工业出版社.2009.3

[7] Greg Hoglund/James Butler .RootKits――Windows内核的安全防护. 清华大学出版社. 2007.4

[8] liuke_blue.黑客防线.再论进程防火墙.200809

[9] SVEN B. SCHREIBER .Undocumented Windows 2000 Secrets

[10] Windows NT 2000 Native API Reference

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
经过站内管理员的致电联系教育,听说有人投诉我了,在此声明:这个软件不是我写的,我只是网络的搬运工!!!

资源是我很早之前从CSDN上下载的,下载自:https://download.csdn.net/download/doomlord/4071653
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

上传的附件 cloud_download Windows驱动级云安全主动防御系统.7z ( 1.94mb, 1次下载 )
error_outline 下载需要12点积分

keyboard_arrow_left上一篇 : 基于80x86汇编实现的双任务管理系统-贪吃蛇小游戏 基于Python和PyQt5库实现的面向英文文献的编辑与检索 : 下一篇keyboard_arrow_right



OrdinAry
2018-11-06 18:49:03
Windows内核层下主动防御系统的框架
只想睡个好觉
2018-11-30 11:30:25
这是楼书写的吗,之前在别的编程网站上也看到过
OrdinAry
2018-12-17 12:12:24
不是啊,是很久以前从网上淘的

发送私信

有没有那样一种永远,永远永远不改变

12
文章数
32
评论数
最近文章
eject