SSDT

2、系统服务调度表SSDT及SSSDT Shadow
系统服务:由操作系统提供的一组函数(内核函数),API可以间接或者直接的调用系统服务。操作系统以动态链接库(DLL)的形式提供API。
SSDT:系统服务调度表(System  Service  Dispatch Table),该表可以基于系统服务编号进行索引,来定位函数内存地址。
SSPT:系统服务参数表(System  Service  Parameter  Table),指定系统服务函数的参数字节数。
系统有2个SSDT表, 一个是KeServiceDescriptorTable(ntoskrnl.exe导出),一个是KeServieDescriptorTableShadow(ntoskrnl.exe未导出)。两者的区别是,KeServiceDescriptorTable仅有ntoskrnel.exe中的函数一项,KeServieDescriptorTableShadow包含了ntoskrnel.exe以及win32k.sys中包含的函数。一般的ntdll.dll中的Native API的函数地址由KeServiceDescriptorTable分派,gdi.dll/user.dll的内核API调用服务地址由KeServieDescriptorTableShadow分派。

1)KeServiceDescriptorTable是内核导出的一张表,该表含有一个指针指向SSDT中包含Ntoskrnl.exe实现的核心服务,还包含一个指针指向SSPT。
KeServiceDescriptorTable结构如下:
typedef struct _ServiceDescriptorEntry {  
  unsigned int *ServiceTableBase;        //SSDT基址
  unsigned int *ServiceCounterTableBase; //SSDT中服务被调用次数的计数器
  unsigned int NumberOfServices;         //SSDT服务个数
  unsigned char *ParamTableBase;         //SSPT基址
}SSDT, *PSSDT;

下面是在windbg进行实验:
lkd> dd KeServiceDescriptorTable   //导出表
80563520  804e58b0 00000000 0000011c 805120cc
80563530  00000000 00000000 00000000 00000000
80563540  00000000 00000000 00000000 00000000
80563550  00000000 00000000 00000000 00000000
80563560  00000002 00002710 bf80c339 00000000
80563570  baecda80 f753c4a0 8a09655c 807120c0
80563580  00000000 00000000 ffea8ad6 ffffffff
80563590  52841216 01ca0418 00000000 00000000
lkd> dd 804e58b0               //SSDT基址
804e58b0  80591bfb 80585358 805e1f35 805dbc4a
804e58c0  805e1fbc 80640ce4 80642e75 80642ebe
804e58d0  805835aa 80650be3 806404a3 805e1787
804e58e0  806387ba 80586fa3 805e08e8 8062f462
804e58f0  805d9781 80571edd 805e8258 805e939e
804e5900  804e5ec4 80650bcf 805cd537 804ed822

lkd> dd 805120cc     //SSPT基址
805120cc  2c2c2018 44402c40 1818080c 0c040408
805120dc  08081810 0808040c 080c0404 2004040c
805120ec  140c1008 0c102c0c 10201c0c 20141038
805120fc  141c2424 34102010 080c0814 04040404
8051210c  0428080c 1808181c 1808180c 040c080c
8051211c  100c0010 10080828 0c08041c 00081004
8051212c  0c080408 10040828 0c0c0404 28240428
8051213c  0c0c0c30 0c0c0c18 0c10300c 0c0c0c10

2)KeServiceDescriptorTableShadow是内核未导出的另一张表,包含Ntoskrnel.exe和win32k.sys服务函数。某些网游通过挂钩按键相关函数(NtUserSendInput)防止模拟按键、(NtUserFindWindowEx)防止搜索窗口、Anti_Virus通过挂钩窗口相关的函数(NtUserPostMessage 、 NtUserQueryWindow)来防止被关闭。KeServiceDescriptorTableShadow实际上是SSDT结构 数组,也就是KeServiceDescriptorTableShadow是一组系统描述表。XP SP3下组数是4。在XP系统下,KeServiceDescriptorTableShadow表位于KeServiceDescriptorTable表上方,偏移0x40处。
下面是windbg进行实验:
lkd> dd KeServiceDescriptorTableShadow  
805634e0  804e58b0 00000000 0000011c 805120cc         //SSDT表 Ntoskrnel.exe
805634f0  bf99a000 00000000 0000029b bf99ad10         //SSDT Shdow表 Win32k.sys
80563500  00000000 00000000 00000000 00000000
80563510  00000000 00000000 00000000 00000000
80563520  804e58b0 00000000 0000011c 805120cc       //KeServiceDescriptorTable 表
80563530  00000000 00000000 00000000 00000000
80563540  00000000 00000000 00000000 00000000
80563550  00000000 00000000 00000000 00000000
由于KeServiceDescriptorTableShadow表属于未导出,因此我们需要定位地址。
定位未导出函数和结构的思想就是利用已导出函数和结构,暴力搜索内存空间。
方法一、依据KeServiceDescriptorTable的地址和两者之间的偏移
方法二、搜索KeAddSystemServiceTable导出函数
方法三、搜索线程的ServiceTable指向
方法四、MJ提出的搜索有效内存地址

=====================================================================================================================

 

下面来看下具体的结构:
typedef struct _SYSTEM_SERVICE_TABLE
{
      PNTPROC  ServiceTable;  // array of entry points
      PDWORD  CounterTable;  // array of usage counters
      DWORD  ServiceLimit;    // number of table entries
      PBYTE    ArgumentTable;  // array of byte counts
}SYSTEM_SERVICE_TABLE,*PSYSTEM_SERVICE_TABLE,**PPSYSTEM_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
}SYSTEM_DESCRIPTOR_TABLE,*PSYSTEM_DESCRIPTOR_TABLE,**PPSYSTEM_DESCRIPTOR_TABLE;
其中KeServiceDescriptorTableShadow包含4个子结构,第一个就是ntoskrnl.exe ( native api )和KeServiceDescriptorTable指向一样, 我们真正需要获得的是第二个win32k.sys (gdi/user support),第三个和第四个一般不使用.

1)KeServiceDescriptorTable是内核导出的一张表,该表含有一个指针指向SSDT中包含Ntoskrnl.exe实现的核心服务,还包含一个指针指向SSPT。
KeServiceDescriptorTable结构如下:
typedef struct _ServiceDescriptorEntry {  
  unsigned int *ServiceTableBase;        //SSDT基址
  unsigned int *ServiceCounterTableBase; //SSDT中服务被调用次数的计数器
  unsigned int NumberOfServices;         //SSDT服务个数
  unsigned char *ParamTableBase;         //SSPT基址
}SSDT, *PSSDT;

4、SSDT HOOK 通用简易模块
   SSDT HOOK可以说已经泛滥成河,但是对于初入内核编程领域的新手,拿这来作为入门第一课来练习时必不可少的。服务表的HOOK用一个成语来总结是再恰当不过了:偷梁换柱。服务表根据服务号来索引函数地址,那么我们就把服务表记录的函数地址替换成我们自己的过滤函数地址,待用完后再把被我们替换的地址写回去,这就是服务表HOOK,也可以推广到EAT HOOK(导出表 HOOK)。
   SSDT在XP系统中一共记录了284个函数地址,而且这个表是系统导出表,也就意味着我们可以直接使用KeServiceDescriptorTable这个变量。SSDT HOOK需要的变量:
1)函数服务号:一、解析ntdll.dll导出表   二、根据操作系统硬编码。
2)KeServiceDescriptorTable表在内存的地址:由于是导出表,直接使用变量就代表地址。
我们这里所说的通用模块指的是具有通用性、扩展性、稳定性,通用性意味着可以适合不同的windows平台,扩展性意味着可以很方便简单的添加HOOK函数。
我们利用的技术:
1)利用映射MDL方式避开SSDT表的只读保护,而不是暴力修改CR0寄存器的WP标志位,因为经过我们多次试验,证明这种方式稳定性高。
2)利用InterlockedExchange函数来修改函数地址,而不是暴力的直接修改。
3)增加了保护模块、利用每隔5ms执行DPC例程检查SSDT表函数地址进行HOOK保护。不过这种方法由于DPC中断级太高,而且DPC资源有限,应该尽量避免用DPC,我们可以采用系统线程+定时器来实现,具体请读者自己编程,有什么不理解的欢迎读者交流。
不过我采用的DPC已经稳定,也是可以用的。
多次实验证明暴力修改CR0寄存器和暴力替换地址的方式是不稳定的,因此建议读者以后做SSDT HOOK采用我们这个模块。
下面我把核心代码给出:
利用MDL同时利用宏进行地址替换
//初始化系统服务号
VOID InitSystemCallIndex()
{
   ZwCreateProcessExIndex       = GetFunctionId( "ZwCreateProcessEx" );
   KdPrint(("[InitSystemCallIndex]ZwCreateProcessExIndex:0x%x\n",ZwCreateProcessExIndex));

   ZwLoadDriverIndex            = GetFunctionId( "ZwLoadDriver" );
   KdPrint(("[InitSystemCallIndex]ZwLoadDriverIndex:0x%x\n",ZwLoadDriverIndex));

   ZwSetSystemInformationIndex = GetFunctionId( "ZwSetSystemInformation" );
   KdPrint(("[InitSystemCallIndex]ZwSetSystemInformationIndex:0x%x\n",ZwSetSystemInformationIndex));

}
//硬编码方法初始化服务号
VOID InitSystemCallIndex1()
{
//这里硬编码也可以
ULONG majorVersion;
ULONG minorVersion;
PsGetVersion( &majorVersion,&minorVersion,NULL,NULL );
if( majorVersion == 5 && minorVersion == 1)
{
     //XP
     ZwCreateProcessExIndex          = 0x30 ; 
     ZwLoadDriverIndex               = 0x61 ;  
     ZwSetSystemInformationIndex     = 0xF0;
}
//下边添加其他windows平台

}
//保护已经挂钩的函数
VOID   ProtectHookOfSysCall(
             IN PKDPC   Dpc,
             IN PVOID   DeferredContext,
             IN PVOID   SystemArgument1,
             IN PVOID   SystemArgument2)
{  
  if( *(KeServiceDescriptorTable->ServiceTableBase+ZwCreateProcessExIndex) != (ULONG)MyZwCreateProcessEx   )
   {
     HOOK_SYSCALL( ZwCreateProcessExIndex,       MyZwCreateProcessEx,       OrigZwCreateProcessEx       );
       HOOK_SYSCALL( ZwLoadDriverIndex,            MyZwLoadDriver,            OrigZwLoadDriver            );
       HOOK_SYSCALL( ZwSetSystemInformationIndex, MyZwSetSystemInformation, OrigZwSetSystemInformation );
   KdPrint(("[HookSSDT]Hook Success\n"));
   }
   KdPrint(("yes\n"));
}
//-----------------------Hook对外接口--------------------------------------------------
VOID HookSSDT()
{
   LARGE_INTEGER timeout;
   //初始化服务号
   InitSystemCallIndex();
   //映射MDL
   g_pMdlSystemCall = MmCreateMdl(   NULL, 
                  KeServiceDescriptorTable->ServiceTableBase,
                  KeServiceDescriptorTable->NumberOfServices*4);
    if(!g_pMdlSystemCall)
     return ;
     MmBuildMdlForNonPagedPool(g_pMdlSystemCall);
     g_pMdlSystemCall->MdlFlags = g_pMdlSystemCall->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;
     MappedSystemCallTable = MmMapLockedPages(g_pMdlSystemCall, KernelMode);
   //调用HOOK宏
   HOOK_SYSCALL( ZwCreateProcessExIndex,       MyZwCreateProcessEx,       OrigZwCreateProcessEx       );
   HOOK_SYSCALL( ZwLoadDriverIndex,            MyZwLoadDriver,            OrigZwLoadDriver            );
   HOOK_SYSCALL( ZwSetSystemInformationIndex, MyZwSetSystemInformation, OrigZwSetSystemInformation );
   KdPrint(("[HookSSDT]Hook Success\n"));

   //定时器防止HOOK恢复
   gTimer = ExAllocatePool( NonPagedPool, sizeof(KTIMER) );
   gDPC    = ExAllocatePool( NonPagedPool, sizeof(KDPC) );
  if(gTimer == NULL || gDPC == NULL)
   {
     KdPrint(("Timer failed\n"));
   }
   ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
   timeout.QuadPart = -10;
   KeInitializeTimer( gTimer );
   KeInitializeDpc( gDPC, ProtectHookOfSysCall, NULL ); //ProtectHookOfSysCall为回调函数
    //通过KeSetTimerEx()调用初始化并启动计时器,调用中的第三个参数(5ms)是两个事件之间间隔的毫秒数
  if(KeSetTimerEx( gTimer, timeout, 5, gDPC ) == TRUE )//5ms timer
   {
     KdPrint(("Timer was already queued \n"));
   }
}

VOID UnHookSSDT()
{
   KeCancelTimer( gTimer );
   KeFlushQueuedDpcs();

   UNHOOK_SYSCALL( ZwCreateProcessExIndex, MyZwCreateProcessEx,OrigZwCreateProcessEx );
   UNHOOK_SYSCALL( ZwLoadDriverIndex, MyZwLoadDriver,OrigZwLoadDriver );
   UNHOOK_SYSCALL( ZwSetSystemInformationIndex, MyZwSetSystemInformation,OrigZwSetSystemInformation );
   KdPrint(("[HookSSDT]UnHook Success\n"));

   ExFreePool( gTimer );
   ExFreePool( gDPC );
   //Unlock and Free MDL
  if(g_pMdlSystemCall)
   {
     MmUnmapLockedPages( MappedSystemCallTable, g_pMdlSystemCall );
     IoFreeMdl(g_pMdlSystemCall);
   }
}
这么做后,安全性也保证了同时做到了简单的扩展性,如果想扩展函数,只需要在InitSystemCall()、HookSSDT()、UnHookSSDT()、ProtectHookOfSysCall()
四个函数里面各自添加相应的一句代码就OK了。限于篇幅就不再多加介绍了,读者可以自行实验,详细代码请参考随文提供HookSSDT.h和HookSSDT.c文件。

5、SSDT   Shadow   HOOK 通用简易模块
   看到这有的读者可能就会想,服务表既然都是根据服务号索引,那么KeServiceDescriptorTable和KeServiceDescriptorTableShadow表是一样的就没必要分开讲,直接套用SSDT HOOK模块不就行了,不就是改下服务号而已。读者这么想也没什么不对,但是具体做的时候你就会发现虽然服务表都是一样的,但是却不能照搬过来。为什么呢?
1)KeServiceDescriptorTableShadow表不是导出表,我们需要自己定位地址
2)KeServiceDescriptorTableShadow表不是常驻虚拟内存,会被换出内存到磁盘文件,如果被换出那么表地址就无效,访问就会导致蓝屏的发生。
3)XP系统KeServiceDescriptorTableShadow表记录的667个函数的服务号无法通过解析PE得到,只能通用硬编码。
首先就是KeServiceDescriptorTableShadow地址的定位,在上篇文章已经说过有四种常用方法:
1、KeServiceDescriptorTable -0x40 + 0x10(XP系统下)
lkd> dd KeServiceDescriptorTableShadow
805634e0   804e58b0 00000000 0000011c 805120cc  
805634f0   bf99ac00 00000000 0000029b bf99b910
80563500   00000000 00000000 00000000 00000000
80563510   00000000 00000000 00000000 00000000
80563520   804e58b0 00000000 0000011c 805120cc    KeServiceDescriptorTable
80563530   00000000 00000000 00000000 00000000
80563540   00000000 00000000 00000000 00000000
80563550   00000000 00000000 00000000 00000000

//方式1,XP下导出KeServiceDescriptorTable-0x40;
ULONG GetSSDTShadowAddress1()
{
   ULONG address;
   ULONG ssdt;
   ssdt = (ULONG)KeServiceDescriptorTable;
   address = ssdt - 0x30;
   KdPrint(("[GetSSDTShadowAddress] ssdt:0x%x\n",ssdt));
   KdPrint(("[GetSSDTShadowAddress] address:0x%x\n",address));
   return address;
}
2、搜索导出函数KeAddSystemServiceTable的地址空间
nt!KeAddSystemServiceTable+0x1a:
805b4013 8d88e0345680     lea      ecx,nt!KeServiceDescriptorTableShadow (805634e0)[eax]
805b4019 833900            cmp      dword ptr [ecx],0
805b401c 7534        jne      nt!KeAddSystemServiceTable+0x6b (805b4052)
在KeAddSystemServiceTable函数的地址空间根据特征码sp_code1=0x8d88
sp_code2=0x8339即可定位到KeServiceDescriptorTableShadow地址

//方式2   搜索KeAddSystemServiceTable
ULONG GetSSDTShadowAddress()
{
   ULONG address;
   PUCHAR addr;
   PUCHAR p;
   addr = (PUCHAR)KeAddSystemServiceTable;
   for( p=addr; p<addr+PAGE_SIZE; p++)
   {
    if(*(PUSHORT)p == 0x888D && *(PUSHORT)(p+6) == 0x8339)
     {
       address = *(PULONG)((ULONG)p+2);
       break;
     }
   }
   address = address + 0x10;
   KdPrint(("[GetSSDTShadowAddress] address:0x%x\n",address));
   return address;
}
3、线程KTHREAD结构偏移0x0e0的ServiceTable   PVOID指针根据线程类型代表不同的表的地址
lkd> dt _kthread
ntdll!_KTHREAD
    ……….
    +0x0e0 ServiceTable      : Ptr32 Void
    ………
    非GUI线程    是KeServiceDescriptorTable表地址
       GUI线程    是KeServiceDescriptortableShadow表地址

//方式3   遍历系统线程   TableCode指向
ULONG GetSSDTShadowAddress3()
{
   //遍历线程
   ULONG i;
   NTSTATUS status;
   ULONG thread;
   ULONG address=0;
   for( i=8; i<32768; i=i+4)
   {
     Status = PsLookupThreadByThreadId( (HANDLE)i,&(PETHREAD)thread);
    if(NT_SUCCESS(status))
     {
      if( *(PULONG)(thread+0x0e0) != (ULONG)KeServiceDescriptorTable )
       {
         address =   *(PULONG)(thread+0x0e0);
         break ;
       }
     }
   }
   return address;
}
4、方法四   MJ所说的暴力搜索有效地址,这个我觉得不是很好,就不多说了。
其次,KeServiceDescriptorTableShadow表不是常驻内存,在非GUI进程空间,
由于不常使用,这个表就被交换到磁盘文件了,我们访问这个表的地址就出问题了,怎么解决这个问题呢?目前大家的做法也就2种:
强调一点:DriverEntry例程是运行在system进程空间,Shadow表在system进程空间被换出到磁盘了,因此我们不能在DriverEntry例程中HOOK,要想HOOK需要Attach上一个GUI进程。
1、利用GUI程序加载驱动,在DispatchIoctl派遣例程中进行HOOK,这时候这个地址就是有效地了,因为DispatchIoctl派遣例程是在GUI进程空间。
2、因为csrss.exe进程是windows 子系统,此进程是GUI进程,而且是windows必有的,因此我们可以Attach到这个进程空间,然后再进行HOOK。
VOID
   KeStackAttachProcess (
     IN PEPROCESS   Process,
     OUT PRKAPC_STATE   ApcState
     );
要想Attach上csrss.exe进程我们需要获得进程的PEPROCESS指针,这个有很多方法可以得到,网上广泛流传的是通过ZwQuerySystemInformation函数遍历系统的所有句柄,根据句柄打开的对象来得到csrss.exe的进程PID,然后通过
函数PsLookupProcessByProcessId得到进程的PEPROCESS,代码如下:
PVOID GetInfoTable( ULONG TableType )
{
    ULONG mSize = 0x4000;
    PVOID mPtr = NULL;
    NTSTATUS ntStatus;

    do
    {
       mPtr = ExAllocatePoolWithTag( PagedPool, mSize, 'kdD' );
      if( mPtr != NULL )
       {
          ntStatus = ZwQuerySystemInformation( TableType, mPtr, mSize, NULL );
       } else 
          return NULL;

      if( ntStatus == STATUS_INFO_LENGTH_MISMATCH)
       {
          ExFreePoolWithTag( mPtr, 'kdD' );
          mSize = mSize * 2;
       }
    } while( ntStatus == STATUS_INFO_LENGTH_MISMATCH );

   if ( NT_SUCCESS(ntStatus) )
    {
       DbgPrint("CCRootkit: GetInfoTable Success!");
       DbgPrint("CCRootkit: Info table in memory size - %d", mSize);
       return mPtr; 
    } 
    else 
    {
       ExFreePoolWithTag( mPtr, 'kdD' );
    }

    return NULL;
}

//获取csrss.exe进程
ULONG GetCsrPid()
{
    NTSTATUS ntStatus;
    HANDLE Process, hObject;
    NTSTATUS St;
    ULONG CsrId = 0;
    OBJECT_ATTRIBUTES obj;
    CLIENT_ID cid;
    POBJECT_NAME_INFORMATION ObjName;
    UNICODE_STRING ApiPortName;
    PSYSTEM_HANDLE_INFORMATION_EX Handles;
    int i;

    RtlInitUnicodeString(&ApiPortName, L"\\Windows\\ApiPort");
  
    Handles = GetInfoTable( SystemHandleInformation );
   if( Handles == NULL )
       return 0;

    ObjName = ExAllocatePool( PagedPool, 0x2000 );
    DbgPrint("CCRootkit: Number of handles %d", Handles->NumberOfHandles);
    for( i = 0; i != Handles->NumberOfHandles; i++ )
    {   //打开的对象的类型是否为21
      if (Handles->Information[i].ObjectTypeNumber == 21) //Port object
       {
          InitializeObjectAttributes(
              &obj, 
              NULL, 
              OBJ_KERNEL_HANDLE, 
              NULL, 
              NULL);
          cid.UniqueProcess = (HANDLE)Handles->Information[i].ProcessId;
          cid.UniqueThread   = 0;

          ntStatus = ZwOpenProcess(&Process, PROCESS_DUP_HANDLE, &obj, &cid);
         if( NT_SUCCESS(ntStatus) )
          {
             ntStatus = ZwDuplicateObject(
                Process, 
                (HANDLE)Handles->Information[i].Handle,
                NtCurrentProcess(), 
                &hObject, 
                0,
                0, 
                DUPLICATE_SAME_ACCESS);
            if( NT_SUCCESS(ntStatus) )
             {
                ntStatus = ZwQueryObject(
                   hObject, 
                   ObjectNameInformation, 
                   ObjName, 
                   0x2000, 
                   NULL);
               if( NT_SUCCESS(ntStatus) )             
                {
                  if (ObjName->Name.Buffer != NULL)
                   {
                     if ( wcsncmp(ApiPortName.Buffer,
                            ObjName->Name.Buffer, 20) == 0 )
                      {
                         DbgPrint("CCRootkit: Csrss PID:%d", 
                                  Handles->Information[i].ProcessId);
                         DbgPrint("CCRootkit: Csrss Port - %ws", ObjName->Name.Buffer);
                         CsrId = Handles->Information[i].ProcessId;
                         ZwClose( Process );
                         ZwClose( hObject );
                         CsrId = Handles->Information[i].ProcessId;
                         ExFreePool( Handles );
                         ExFreePool( ObjName );
                         return CsrId;
                      }
                   } 
                } else 
                    DbgPrint("CCRootkit: Error in Query Object");
                ZwClose(hObject);
             } else 
                DbgPrint("CCRootkit: Error on duplicating object");
             ZwClose(Process);
          } else 
             DbgPrint("CCRootkit: Could not open process");  
       }
    }
   
    ExFreePool( Handles );
    ExFreePool( ObjName );
  
    return 0;
}
我觉得这个方法繁琐,我是直接利用ZwQuerySystemInformation函数遍历系统所有进程,然后匹配csrss.exe名字得到PEPROCESS。但是这里可能存在问题,就是有人伪造csrss.exe进程名,不过ZwQuerySystemInformation是根据PID大小先后的,因此就算它伪造我们还是能得到正确的csrss.exe的PEPROCESS的。
代码如下:
//通过ZwQuerySystemInformation获得进程EPROCESS
ULONG GetCsrssProcess( char *ProcessName )
{
   NTSTATUS status;
   ULONG BufferSize=0x8000;   //初始化缓冲区32K
   PVOID pBuffer;    //分配内存后,返回指针
   PEPROCESS eProcess;
   PSYSTEM_PROCESS_INFORMATION   pInfo;
   //动态分配缓冲区大小
   do
   {
     pBuffer=ExAllocatePool(NonPagedPool,BufferSize);
    if(pBuffer==NULL)   //分配内存失败
       return 0;
     status=ZwQuerySystemInformation(SystemProcessesAndThreadsInformation,pBuffer,BufferSize,NULL);
    if(status==STATUS_INFO_LENGTH_MISMATCH)    //缓冲区太小
     {
       ExFreePool(pBuffer);
       BufferSize++;                        //增加32K缓冲区
     }
     else if(!NT_SUCCESS(status))
     {
       ExFreePool(pBuffer);
       return 0;
     }    
   }while(status==STATUS_INFO_LENGTH_MISMATCH);
   pInfo=(PSYSTEM_PROCESS_INFORMATION)pBuffer;
   pInfo=(PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo)+ pInfo->NextEntryDelta);
   for(;;)
   {   
     PsLookupProcessByProcessId(pInfo->ProcessId,&eProcess);
     //硬编码偏移0x174
    if(strcmp((PCHAR)((ULONG)eProcess+0x174),ProcessName) == 0)
     {
       KdPrint(("[GetCsrssProcess]PID:%d\n",pInfo->ProcessId));
       ExFreePool(pBuffer);
       return (ULONG)eProcess;
     }
    if(pInfo->NextEntryDelta== 0)
     {
       ExFreePool(pBuffer);
       return 0;
     }

     pInfo=(PSYSTEM_PROCESS_INFORMATION)(((PUCHAR)pInfo)+ pInfo->NextEntryDelta);
   }
}
到此SSDT Shadow所要解决的问题都搞定了,余下的部分就完全可以照搬SSDT方法了,仍然利用MDL映射和宏来做更加稳定和安全,这里就不加详说,具体参考HookSSDTShadow.h和HookSSDTShadow.c文件。
6、SSDT   Shadow   HOOK的检测与恢复
SSDT Shadow HOOK的检测与恢复比SSDT简单多了,除了注意下KeServiceDescriptorTableShadow不是常驻内存外,其他就没什么了。
检测与恢复需要获取的内容:
1、KeServiceDescriptorTableShadow地址
2、服务号:直接把函数数量转换成服务号
2、函数当前地址:没什么可说的
3、函数原始地址:解析win32k.sys文件,读取原始地址即可,注意win32k.sys不需要重定位。
4、模块名:传入当前地址查找得到模块名。跟SSDT一样
5、函数名:硬编码或者解析符号文件,而解析符号文件需要联网,而且需要符号文件很大,因此我选择硬编码。
当前地址的获取直接利用即可得到了:


base=KeServiceDescriptorTableShadow->ServiceTableBase;
//函数地址
address=*(base+index);

要说的就是获得原始函数地址:
解析PE,定位到KeServiceDescriptorTableShadow在win32k.sys的偏移地址即可,直接利用KeServiceDescriptorTableShadow的地址减去win32k.sys的基址即得文件偏移,核心代码如下:
/*********************************************************************************
*
*   获得win32k.sys基址
*   1、ZwQuerySystemInformation
*   2、遍历DriverSection链表
*
**********************************************************************************/
ULONG GetWin32Base1()
{
   NTSTATUS status;
   ULONG i;
   ULONG size;
   ULONG address;
   PSYSMODULELIST List;
  
   ZwQuerySystemInformation( SystemModuleInformation ,&size,0,&size);
   KdPrint(("[FindModuleByAddress] size:0x%x\n",size));
   List=(PSYSMODULELIST)ExAllocatePool(NonPagedPool,size);
  if (List==NULL)
   {
     KdPrint(("[FindModuleByAddress] malloc memory failed\n"));
     ExFreePool( List );
     return 0;
   }

   status=ZwQuerySystemInformation(SystemModuleInformation,List,size,0);
  if (!NT_SUCCESS(status))
   {
     KdPrint(("[FindModuleByAddress] query failed\n"));
     //打印错误
     KdPrint(("[FindModuleByAddress] status: 0x%x\n",status));
     ExFreePool( List );
     return 0;
   }
   for ( i=0; i < List->ulCount; i++ )
   {
    
      if( strcmp(List->smi[i].ImageName,"\\SystemRoot\\System32\\win32k.sys") == 0)
       {
         KdPrint(("[GetWin32Base]name :%s\n",List->smi[i].ImageName));
         address = (ULONG)List->smi[i].Base;
         KdPrint(("[GetWin32Base1] win32k.sys address:0x%x\n",address));  
       }
      
   }
   return address;
}
/*********************************************************************************************
*
*   驱动对象DRIVER_OBJECT中的DRIVER_SECTION
*   LDR_DATA_TABLE_ENTRY结构包含系统加载模块链表及基址
*
*
**********************************************************************************************/
ULONG   GetWin32Base2( PDRIVER_OBJECT driver)
{
   PLIST_ENTRY pList = NULL;
   PLDR_DATA_TABLE_ENTRY pLdr = NULL;
   ULONG BaseAddress = 0;
   pList = ( (PLIST_ENTRY)driver->DriverSection )->Flink;
   do
   {
     pLdr = CONTAINING_RECORD(
       pList,
       LDR_DATA_TABLE_ENTRY,
       InLoadOrderLinks
       );
    if( pLdr->EntryPoint != NULL && pLdr->FullDllName.Buffer!= NULL )
     {
      if( !_wcsicmp( pLdr->FullDllName.Buffer, L"\\SystemRoot\\System32\\win32k.sys"))
       {
         BaseAddress = (ULONG )pLdr->DllBase;
         KdPrint(("[GetWin32Base2] win32k.sys address:0x%x\n",BaseAddress));
         break ;
       }
     }
     pList = pList->Flink;
   }while( pList != ((PLIST_ENTRY)driver->DriverSection)->Flink );
   return BaseAddress;
}
/****************************************************************************************
*
*   根据传入的服务号得到Shadow 函数原始地址
*
****************************************************************************************/
ULONG FindShadowOriAddress( ULONG index )
{
   //内核文件win32k.sys基地址
   //得到SSDT Shadow表的地址
   //得到文件偏移
   NTSTATUS status;
   ULONG size;
   ULONG BaseAddress;
   ULONG ShadowBase;
   ULONG ShadowAddress;
   ULONG SsdtRva;
   ULONG FileOffset = 0;
   UNICODE_STRING modulename;


   OBJECT_ATTRIBUTES   object_attributes;
   IO_STATUS_BLOCK io_status = {0};
   HANDLE hFile;
   //读取的位置
   ULONG location;
   LARGE_INTEGER offset;
   ULONG address;

   BaseAddress = GetWin32Base1();
   KdPrint(("[FindShadowOriAddress] BaseAddress:0x%x\n",BaseAddress));
   //经验证地址正确
   ShadowBase = GetSSDTShadowAddress2();
   ShadowAddress = *(PULONG)ShadowBase;
   KdPrint(("[FindShadowOriAddress] ShadowAddress:0x%x\n",ShadowAddress));
   //得到SSDT表的Rva
   SsdtRva = ShadowAddress - BaseAddress;
   //验证
   KdPrint(("[FindOriAddress] SsdtRva:0x%x\n",SsdtRva));
   //读取的位置
   location = SsdtRva + index * 4;
   offset.QuadPart =location;
   KdPrint(("[FindOriAddress] location:0x%x\n",location));
   //利用ZwReadFile读取文件
   //初始化OBJECT_ATTRIBUTES结构
   RtlInitUnicodeString(&modulename, L"\\SystemRoot\\system32\\win32k.sys");
   InitializeObjectAttributes( 
     &object_attributes,
     &modulename,
     OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
     NULL,
     NULL);
   //打开文件
   status = ZwCreateFile(
     &hFile,
     FILE_EXECUTE | SYNCHRONIZE,
     &object_attributes,
     &io_status,
     NULL,
     FILE_ATTRIBUTE_NORMAL,
     FILE_SHARE_READ,
     FILE_OPEN,
     FILE_NON_DIRECTORY_FILE |
     FILE_RANDOM_ACCESS |
     FILE_SYNCHRONOUS_IO_NONALERT,
     NULL,
     0);
  if( !NT_SUCCESS( status ))
   {
     KdPrint(("[FindOriAddress] open error\n"));
     KdPrint(("[FindOriAddress] status = 0x%x\n", status));
     ZwClose( hFile );
     return 0;
   }
   status = ZwReadFile(
     hFile,
     NULL,
     NULL,
     NULL,
     NULL,
     &address,
     sizeof(ULONG),
     &offset,
     NULL);
  if( !NT_SUCCESS( status ))
   {
     KdPrint(("[FindOriAddress] read error\n"));
     KdPrint(("[FindOriAddress] status = 0x%x\n", status));
     ZwClose( hFile );
     return 0;
   }
   KdPrint(("[FindOriAddress] address:0x%x\n",address));
   address = address;
   KdPrint(("[FindOriAddress] Oriaddress:0x%x\n",address));
   ZwClose( hFile );
   return address;
}

至此,SSDT及Shadow已经完全解析完了,我也提供了SSDT HOOK及SSDTShaodw HOOK的通用框架。至于SSDT及SSDTShadow的其他方面,比如如何更好的做到保护和稳定

 

 

 

 

==================================================================================================================================

谈到进程的隐藏和隐藏进程的检测,我们不得不提到这个windows未导出zwquerysysteminformation这个函数。。

NTSTATUS
NTAPI
ZwQuerySystemInformation(
       ULONG SystemInformationClass,//5 
       PVOID SystemInformation,
       ULONG SystemInformationLength,
       PULONG ReturnLength
       );

 

第一个参数是一个枚举类型,传入的是你需要查询的信息的类型,如果你要查询进程的相关信息,则你需要传入SystemProcessesAndThreadsInformation,以下是这个enmu类型的定义。

typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation, // N
SystemProcessorInformation, // N
SystemPerformanceInformation, // N
SystemTimeOfDayInformation, // N
SystemNotImplemented1, // N
SystemProcessesAndThreadsInformation// N
SystemCallCounts, // N
SystemConfigurationInformation, // N
SystemProcessorTimes// N
SystemGlobalFlag, // Y
SystemNotImplemented2, // 10 N
SystemModuleInformation, // 11 N
SystemLockInformation, // 12 N
SystemNotImplemented3, // 13 N
SystemNotImplemented4, // 14 N
SystemNotImplemented5, // 15 N
SystemHandleInformation, // 16 N
SystemObjectInformation, // 17 N
SystemPagefileInformation, // 18 N
SystemInstructionEmulationCounts, // 19 N
SystemInvalidInfoClass1, // 20
SystemCacheInformation, // 21 Y
SystemPoolTagInformation// 22 N
SystemProcessorStatistics, // 23 N
SystemDpcInformation, // 24 Y
SystemNotImplemented6, // 25 N
SystemLoadImage, // 26 Y
SystemUnloadImage, // 27 Y
SystemTimeAdjustment, // 28 Y
SystemNotImplemented7, // 29 N
SystemNotImplemented8, // 30 N
SystemNotImplemented9, // 31 N
SystemCrashDumpInformation, // 32 N
SystemExceptionInformation, // 33 N
SystemCrashDumpStateInformation, // 34 Y/N
SystemKernelDebuggerInformation, // 35 N
SystemContextSwitchInformation, // 36 N
SystemRegistryQuotaInformation, // 37 Y
SystemLoadAndCallImage, // 38 Y
SystemPrioritySeparation// 39 Y
SystemNotImplemented10, // 40 N
SystemNotImplemented11, // 41 N
SystemInvalidInfoClass2, // 42
SystemInvalidInfoClass3, // 43
SystemTimeZoneInformation, // 44 N
SystemLookasideInformation, // 45 N
SystemSetTimeSlipEvent, // 46 Y
SystemCreateSession, // 47 Y
SystemDeleteSession, // 48 Y
SystemInvalidInfoClass4, // 49
SystemRangeStartInformation, // 50 N
SystemVerifierInformation, // 51 Y
SystemAddVerifier, // 52 Y
SystemSessionProcessesInformation // 53 N
}SYSTEM_INFORMATION_CLASS;

第二个参数是你用来接收信息的一片内存区域,第三个参数是这边内存的大小,第四个参数不是必选的.

当我们第一个参数传入的是SystemProcessesAndThreadsInformation则返回的一片内存空间一个PSYSTEM_PROCESSES的结构。

当然我们也要看看这个结构的内容:

typedef struct _SYSTEM_PROCESSES
{

ULONG NextEntryDelta; //构成结构序列的偏移量;
ULONG ThreadCount; //线程数目;
ULONG Reserved1[6];
LARGE_INTEGER CreateTime; //创建时间;
LARGE_INTEGER UserTime;//用户模式(Ring 3)的CPU时间;
LARGE_INTEGER KernelTime; //内核模式(Ring 0)的CPU时间;
UNICODE_STRING ProcessName; //进程名称;
KPRIORITY BasePriority;//进程优先权;
ULONG ProcessId; //进程标识符;
ULONG InheritedFromProcessId; //父进程的标识符;
ULONG HandleCount; //句柄数目;
ULONG Reserved2[2];
VM_COUNTERS  VmCounters; //虚拟存储器的结构,见下;
IO_COUNTERS IoCounters; //IO计数结构,见下;
SYSTEM_THREADS Threads[1]; //进程相关线程的结构数组,见下;

}SYSTEM_PROCESSES,*PSYSTEM_PROCESSES;

如果要遍历系统中的进程,我们只需要使用NextEntryDelta这个指针即可。

posted @ 2013-08-17 12:31  vcerror  阅读(484)  评论(0编辑  收藏  举报