在安全检测工具中,一般都比较重视隐藏进程的检测。道理很简单,正常的进程没有必要去隐藏自己,毕竟隐藏进程花费的代价还是很高的。为什么这么说呢?系统中安装了钩子,运行效率自然不会很高,人正不怕影子歪,所以说没有必要去隐藏自己。当然也有一些例外的网游,为了保护自己从而隐藏进程,常见的有韩国nProtect公司的跑跑卡丁车,就存在隐藏进程GameMon.des的运行。
目前大多数流行的木马,如PcShare、灰鸽子的隐藏进程方法一般都采用Hook SSDT,其好处就是从Windows 2000到最新的Windows Vista操作系统都兼容且运行稳定。对于通过Win32 API,如EnumProcess或CreateToolhelp32Snapshot这样的Ring3 API枚举进程的工具都可以完美地绕过,也包括Windows任务管理器使用的NtQuerySystemInformation(它由 Ntdll.dll导出)。这种隐藏进程的原理也很清晰,正常情况下,这些用户态的程序是从Ring3一层一层进入到Ring0中。常规的进程管理器软件,如Windows优化大师都是从CreateToolhelp32Snapshot(通过反编译Windows优化大师,发现没有采用 EnumProcess来枚举,因为该函数由Psapi.dll中导出,Win9x系统不支持)=> NtQuerySystemInformation=>Hook ZwQuerySystemInformation=>ZwQuerySystemInformation, 如图1所示。
图1
这样,每通过Win32 API枚举进程时,在返回数据时到了Hook ZwQuerySystemInformation这一步时,会过滤需要隐藏的进程(这一过程是跳转到自己的一个特定的地址处理),隐藏进程的操作,也就是链表的删除其实没有什么神秘的。
检测的方法同样也很简单,今天我们说的方法算是最稳定、最简单的在Ring0下检测隐藏进程的了,不需要牵扯到稍微复杂的 PspCidTable(进程句柄表),毕竟PspCidTable的结构对于每一个系统来说是不一样的,在Windows 2000中为两层表,到了XP以后就变成三层了;同时,它在每个系统中获得的地址也不一样,所以我们仍使用 ZwQuerySystemInformation来枚举。或许有人会说它只能枚举出Ring3中Hook EnumProcess或Hook CreateToolhelp32Snapshot这样的Win32 API隐藏的进程。由于篇幅的问题,如何获取真实的SSDT(系统服务描述表)地址,我会在下一期中告诉大家通用的方法获取该值,所以大家不用担心无法检测到灰鸽子、PcShare木马。首先要说的是在驱动中分配内存的函数不再是new和delete,而是ExAllocatePool和 ExFreePool。如果系统进程过多,ZwQuerySystemInformation的调用可能会失败,本文采用一个循环重新分配缓冲区的大小,每次增加两倍的缓冲区大小,直到成功为止。文中缓冲区的初始值大小为32KB,如果首次分配过多会导致SYSTEM内核进程内存占用过多,影响系统性能等问题,同时内核驱动为Unicode编码,这点需要大家注意。具体的代码如下,注释我写的很详细,应该都能看得懂的。
view plainprint?
NTSTATUS Ring0EnumProcess()
{
ULONG cbBuffer = 0x8000; // 初始化缓冲大小 32kb
PVOID pBuffer = NULL;
NTSTATUS Status;
PSYSTEM_PROCESS_INFORMATION pInfo;
do
{
pBuffer = ExAllocatePool (NonPagedPool, cbBuffer); //分配内存缓冲区
if (pBuffer == NULL) //如果内存分配失败
return 1; //失败了返回1并退出
Status = ZwQuerySystemInformation(SystemProcessesAndThreadsInformation, pBuffer, cbBuffer, NULL);
if (Status == STATUS_INFO_LENGTH_MISMATCH) //如果缓冲区太小
{
ExFreePool(pBuffer); //释放缓冲区
cbBuffer *= 2; //增加缓冲区到原来的两倍大小
}
else if (!NT_SUCCESS(Status)) //如果执行失败
{
ExFreePool(pBuffer); //释放分配的内存
return 1; //失败了返回1并退出
}
}while (Status == STATUS_INFO_LENGTH_MISMATCH);
pInfo = (PSYSTEM_PROCESS_INFORMATION)pBuffer;
for (;;) //死循环
{
LPWSTR pszProcessName = pInfo->ProcessName.Buffer;
if (pszProcessName == NULL)
pszProcessName = L"NULL"; //如果获取映像名失败返回空
DbgPrint("pid %d ps %S\n",pInfo->ProcessId,pInfo->ProcessName.Buffer);//调试输出结果
if (pInfo->NextEntryDelta == 0)
break; //没有后继了,退出链表循环
pInfo = (PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo)+ pInfo->NextEntryDelta);
}
ExFreePool(pBuffer); //释放分配的内存
return 0;
}
我们使用本文附件中的DbgView工具来捕获DbgPrint函数打印的调试信息。由于DbgView支持Win32消息的捕获,会产生很多其他Win32垃圾信息,这里我们仅选择Capture Kernel捕获内核态的信息就可以了,也可以通过一个用户态的程序来读取这个驱动传递的消息,这里我就不再赘述了,大家可以搜索一些驱动通讯方面的资料。最后用附件中的DrvLoader工具来载入本文编译后ring0.sys文件,单击“Load”即可,如图2所示。
图2
最后要说明的是,编译该例程需要至少Windows 2000 NTDDK才可以。由于ZwQuerySystemInformation的结构很复杂,所有成员写出来要占用大约占五六页的篇幅,这里就不再赘述,但它和NtQuerySystemInformation的结构相似,大家可以在MSDN中通过查找NtQuerySystemInformation得到,本文在源代码中有部分成员的描述。本文介绍的方法只能检测出Win32 API隐藏的进程,在下一期中,我将告诉大家如何获取真实的系统服务描述表(SSDT)地址,实现Hook SSDT的隐藏进程的检测方法。
阅读(4897) | 评论(0) | 转发(0) |