1.原理介绍:
typedef struct SystemServiceDescriptorT |
其中ServiceTableBase指向系统服务程序的地址(SSDT),ParameterTableBase则指向SSPT中的参数地址,它们都包含了NumberOfService这么多个数组单元。在windows 2000 sp4中NumberOfService的数目是248个。
我们的任务管理器,是通过用户层的API来枚举当前的进程的。Ring3级枚举的方法:
? PSAPI – EnumProcesses() ? ToolHelp32 – Process32First() - Process32Next() |
来对进程进行枚举。而她们最后都是通过NtQuerySystemInformation
2. Hook
Windows2000中NtQuerySystemInformation
OldFuncAddress = KeServiceDescriptorTable
KeServiceDescriptorTable
在其他系统中这个号就不一定一样。所以必须找一种通用的办法来得到这个索引号。在《Undocument Nt》中介绍了一种办法可以解决这个通用问题,从未有效的避免了使用硬编码。在ntoskrnl 导出的 ZwQuerySystemInformation
kd> u ZwQuerySystemInformation 804011aa 804011af 804011b3 804011b5 |
所以只需要把ZwQuerySystemInformation
例如: ID = *(PULONG)((PUCHAR)ZwQuerySystemInformation RealZwQuerySystemInforma ((PServiceDescriptorTableE |
3.对NtQuerySystemInformation
NtQuerySystemInformation
NtQuerySystemInformation |
NtQuerySystemInformation
struct _SYSTEM_PROCESSES { }; |
当NextEntryDelta域等于0时表示已经到了进程信息链的末尾。我们要做的仅仅是把要隐藏的进程从链中删除。
4. 核心实现
//系统服务表入口地址 extern PServiceDescriptorTableE NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath) { RealZwQuerySystemInforma ((PServiceDescriptorTableE } { ((PServiceDescriptorTableE } { if(5 == SystemInformationClass) //如果系统查询类型是SystemProcessesAndThread if(RtlCompareUnicodeString(&hide_process_name, &curr->ProcessName, 1) == 0) ((char*)curr+=curr->NextEntryDelta); ((char*)curr+=curr->NextEntryDelta); } |
通过IOCTL和Ring3级的应用程序通过DeviceIoControl(API)交互信息。Ring3级的用户程序使用,
DeviceIoControl(Handle,IOCTL_EVENT_MSG,ProcessName,ProcessNameLen,
NULL,0,& BytesReturned,NULL)来通知驱动程序要隐藏的进程的名字。
枚举和修改活动进程链表来检测和隐藏进程
1. 介绍EPROCESS块(进程执行块)
每个进程都由一个EPROCESS块来表示。EPROCESS块中不仅包含了进程相关了很多信息,还有很多指向其他相关结构数据结构的指针。例如每一个进程里面都至少有一个ETHREAD块表示的线程。进程的名字,和在用户空间的PEB(进程环境)块等等。EPROCESS中除了PEB成员块在是用户空间,其他都是在系统空间中的。
2. 查看EPROCESS结构
kd> !processfields !processfields |
3. 什么是活动进程链表
EPROCESS块中有一个ActiveProcessLinks成员,它是一个PLIST_ENTRY机构的双向链表。当一个新进程建立的时候父进程负责完成EPROCESS块,然后把ActiveProcessLinks链接到一个全局内核变量PsActiveProcessHead链表中。
在PspCreateProcess内核API中能清晰的找到:
InsertTailList(&PsActiveProcessHead,&Process->ActiveProcessLinks);
当进程结束的时候,该进程的EPROCESS结构当从活动进程链上摘除。(但是EPROCESS结构不一定就马上释放)。
在PspExitProcess内核API中能清晰的找到:
RemoveEntryList(&Process->ActiveProcessLinks);
所以我们完全可以利用活动进程链表来对进程进行枚举。
4. 进程枚举检测Hook SSDT隐藏的进程。
kd> dd PsActiveProcessHead L 2 dd PsActiveProcessHead L 2 8046e460 81829780 ff2f4c80 void DisplayList() { PLIST_ENTRY List = PsActiveProcessHead->Blink; while( List != PsActiveProcessHead ) { } } |
首先把List指向表头后的第一个元素。然后减去0xa0,因为这个时候List指向的并不是EPROCESS块的头,而是指向的它的ActiveProcessLinks成员结构,而ActiveProcessLinks在EPROCESS中的偏移量是0xa0,所以需要减去这么多,得到EPROCESS的头部。在EPROCESS偏移0x1fc处是进程的名字信息,所以再加上0x1fc得到进程名字,并且在Dbgview中打印出来。利用Hook SSDT隐藏的进程很容易就被查出来了。
5.
在上面我们的PsActiveProcessHead是通过硬编码的形式得到的,在不同的系统中这
值不一样。在不同的SP版本中这个值一般也不一样。这就给程序的通用性带来了很大的问题。下面就来解决这个PsActiveProcessHead的硬编码的问题。
kd> dd PsInitialSystemProcess L 1 dd PsInitialSystemProcess L 1 8046e450 818296e0 kd> !process 818296e0 0 !process 818296e0 0 PROCESS 818296e0 SessionId: 0 Cid: 0008 Image: System 可以看出由PsInitialSystemProcess得到的818296e0正是指向System的EPROCESS. kd> dd 818296e0+0xa0 L 2 dd 818296e0+0xa0 L 2 81829780 814d1a00 8046e460 |
上面又可以看出System EPROCESS的ActiveProcessLinks域的Blink指向8046e460正好就是我们的PsActiveProcessHead.
6.
由于Windows是基于线程调度的。所以如果我们把要隐藏的进程的EPROCESS块从活动进程链上摘除,就能有效的绕过基于通过活动进程链表检测进程的防御系统。因为是以线程为基本单位进行调度,所以摘除过后并不影响隐藏进程的线程调度。
void DelProcessList() { } |
首先和上面的程序一样得到PsActiveProcessHead 头的后面第一个EPROCESS块。然后和我们要隐藏的进程名字进行对比,如果不是指针延链下移动。如果是就把EPROCESS块从活动进程链上摘除。一直到遍历完一次活动进程的双向链表。当摘除指定进程的EPROCESS块后可以发现任务管理器里面的指定的进程消失了,然后又用上面的基于活动进程链表检测进程的程序一样的发现不到隐藏的进程。