SSDT hook技术中KeServiceDescriptorTable 结构及获取

来源:互联网 发布:淘宝手机端宝贝图片 编辑:程序博客网 时间:2024/05/22 01:48
KeServiceDescriptorTable 结构 
KeServiceDescriptorTable:是由内核(Ntoskrnl.exe)导出的一个表,这个表是访问SSDT的关键,具体结构是
   typedef struct ServiceDescriptorTable {
   PVOID ServiceTableBase;
   PVOID ServiceCounterTable(0);
   unsigned int NumberOfServices;
   PVOID ParamTableBase;
   }
   其中,
   ServiceTableBase :System Service Dispatch Table 的基地址。
   NumberOfServices 由 ServiceTableBase 描述的服务的数目。
   ServiceCounterTable 此域用于操作系统的 checked builds,包含着 SSDT 中每个服务被调用次数的计数器。这个计数器由 INT 2Eh 处理程序 (KiSystemService)更新。 
   ParamTableBase 包含每个系统服务参数字节数表的基地址。
   System Service Dispath Table(SSDT):系统服务分发表,给出了服务函数的地址,每个地址4子节长。
   System Service Parameter Table(SSPT):系统服务参数表,定义了对应函数的参数字节,每个函数对应一个字节。如在0x804AB3BF处的函数需0x18字节的参数。
Hook前的准备-改变SSDT内存的保护
我们要修改SSDT表,首先这个表必须是可写的,但在xp以后的系统中他都是只读的,三个办法来修改内存保护机制。
(1) 更改注册表

HKLM\SYSTEM\CurrentControlset\Control\Session Manger\

Memory Management\EnforceWriteProtection=0

HKLM\SYSTEM\CurrentControlset\Control\Session Manger\

Memory Management\DisablePagingExecutive=1


(2)改变CR0寄存器的第1位
   Windows对内存的分配,是采用的分页管理。其中有个CR0寄存器,如下图:
其中第1位叫做保护属性位,控制着页的读或写属性。如果为1,则可以读/写/执行;如果为0,则只可以读/执行。SSDT,IDT的页属性在默认下都是只读,可执行的,但不能写。所以现在要把这一位设置成1。
其中第1位叫做保护属性位,控制着页的读或写属性。如果为1,则可以读/写/执行;如果为0,则只可以读/执行。SSDT,IDT的页属性在默认下都是只读,可执行的,但不能写。所以现在要把这一位设置成1。

__asm

{

push eax

mov eax,CR0

and eax,0FFFEFFFFh

mov CR0,eax

pop eax

}

__asm

{

push eax

move eax,CR0

or eax, NOT 0FFFEFFFFh

mov CR0,eax

pop eax

}
   (3)通过Memory Descriptor List(MDL)
   也就是把原来SSDT的区域映射到我们自己的MDL区域中,并把这个区域设置成可写。MDL的结构:
   typedef struct _MDL {
   struct _MDL *Next;   
   CSHORT Size;       
   CSHORT MdlFlags;   //关键在这里,将来设置成MDL_MAPPED_TO_SYSTEM_VA ,这样一来,这块区域就可写
   struct _EPROCESS *Process;
   PVOID MappedSystemVa;
   PVOID StartVa;
   ULONG ByteCount;
   ULONG ByteOffset;
   } MDL, *PMDL;
   首先需要知道KeServiceDscriptorTable的基址和入口数,这样就可以用MmCreateMdl创建一个有起始地址和大小的内存区域。然后把这个MDL结构的flag改成
   MDL_MAPPED_TO_SYSTEM_VA ,那么这个区域就可以写了。最后把这个内存区域调用MmMapLockedPages锁定在内存中。大体框架如下:
   //先声明一个System Service Descriptor Table,我们知道SSDT及SSPT都从这个表中指向
   #pragma pack(1)
   typedef struct ServiceDescriptorEntry {
           unsigned int *ServiceTableBase;
           unsigned int *ServiceCounterTableBase;
           unsigned int NumberOfServices;
           unsigned char *ParamTableBase;
   } SSDT_Entry;
   #pragma pack()
   __declspec(dllimport) SSDT_Entry KeServiceDescriptorTable;
   /
   PMDL   g_pmdlSystemCall;
   PVOID *MappedSystemCallTable;
   // 代码
   // 保存原系统调用位置
   // 映射我们的区域
   g_pmdlSystemCall = MmCreateMdl(NULL,
                      KeServiceDescriptorTable.ServiceTableBase,
                      KeServiceDescriptorTable.NumberOfServices*4);
   if(!g_pmdlSystemCall)
      return STATUS_UNSUCCESSFUL;
   MmBuildMdlForNonPagedPool(g_pmdlSystemCall);
   // 改变MDL的flags
   g_pmdlSystemCall->MdlFlags = g_pmdlSystemCall->MdlFlags |
                                MDL_MAPPED_TO_SYSTEM_VA;
   //在内存中索定,不让换出
   MappedSystemCallTable = MmMapLockedPages(g_pmdlSystemCall, KernelMode);
   现在遇到的第一个问题解决了,但接着面临另外一个问题,如何获得SSDT中函数的地址呢?
   3.四个有用的宏
   SYSTEMSERVICE macro:可以获得由ntoskrnl.exe导出函数,以Zw*开头函数的地址,这个函数的返回值就是Nt*函数,Nt*函数的地址就在SSDT中
   SYSCALL_INDEX macro:获得Zw*函数的地址并返回与之通信的函数在SSDT中的索引。
   这两个宏之所以能工作,是因为所有的Zw*函数都开始于opcode:MOV eax, ULONG,这里的ULONG就是系统调用函数在SSDT中的索引。
   HOOK_SYSCALL和UNHOOK_SYSCALL macros:获得Zw*函数的地址,取得他的索引,自动的交换SSDT中索引所对应的函数地址和我们hook函数的地址。
   这四个宏具体是:
   #define SYSTEMSERVICE(_func) \
           KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_func+1)]
   #define SYSCALL_INDEX(_Function) *(PULONG)((PUCHAR)_Function+1)
   #define HOOK_SYSCALL(_Function, _Hook, _Orig )        \
           _Orig = (PVOID) InterlockedExchange( (PLONG) \
           &MappedSystemCallTable[SYSCALL_INDEX(_Function)], (LONG) _Hook)
   #define UNHOOK_SYSCALL(_Func, _Hook, _Orig )   \
           InterlockedExchange((PLONG)            \
           &MappedSystemCallTable[SYSCALL_INDEX(_Func)], (LONG) _Hook)
原创粉丝点击