SSDT Shadow Hook

1.系统服务调度表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分派。

下面来看下具体的结构:
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;

下面是在windbg查看之:
lkd> dd KeServiceDescriptorTable    //导出表
8055d700  80505480 00000000 0000011c 805058f4 
8055d710  00000000 00000000 00000000 00000000
8055d720  00000000 00000000 00000000 00000000
8055d730  00000000 00000000 00000000 00000000
8055d740  00000002 00002710 bf80c3d1 00000000
8055d750  89a205b0 b9f14320 89b58a90 806f80c0
8055d760  008583b0 00000000 008583b0 00000000
8055d770  8be45ef0 01cc2d68 00000000 00000000

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   //未导出
8055d6c0  80505480 00000000 0000011c 805058f4          //SSDT表 -> Ntoskrnel.exe
8055d6d0  bf99c800 00000000 0000029b bf99d510          //SSDT Shdow表 -> Win32k.sys
8055d6e0  00000000 00000000 00000000 00000000
8055d6f0  00000000 00000000 00000000 00000000
8055d700  80505480 00000000 0000011c 805058f4          //KeServiceDescriptorTable表
8055d710  00000000 00000000 00000000 00000000
8055d720  00000000 00000000 00000000 00000000
8055d730  00000000 00000000 00000000 00000000

由于KeServiceDescriptorTableShadow表属于未导出,因此我们需要定位地址。
定位未导出函数和结构的思想就是利用已导出函数和结构,暴力搜索内存空间。
方法一、依据KeServiceDescriptorTable的地址和两者之间的偏移
方法二、搜索KeAddSystemServiceTable导出函数
方法三、搜索线程的ServiceTable指向
方法四、MJ提出的搜索有效内存地址

如下给出两种方法:
在KeAddSystemServiceTable导出函数中按特征码搜索
ULONG GetKeServiceDescriptorTableShadowAddr()
{
     /*
    805a2362 8d88c0d65580    lea     ecx,nt!KeServiceDescriptorTableShadow (8055d6c0)[eax]
    805a2368 833900          cmp     dword ptr [ecx],0
    805a236b 7546            jne     nt!KeAddSystemServiceTable+0x6b (805a23b3)
    */

    ULONG sp_code1=0x888d,sp_code2=0x83;   //特征码
    ULONG address=0;
    PUCHAR addr;
    PUCHAR p;
    addr=(PUCHAR)GetFunctionAddr(L "KeAddSystemServiceTable");
     if (addr == 0)
    {
        KdPrint(( "GetFunctionAddr [KeAddSystemServiceTable] Error!\n"));
         return 0;
    }

     for(p=addr;p<p+PAGE_SIZE;p++)
    {
         if(*(PUSHORT)p==sp_code1&&(*(p+6)==sp_code2))
        {
            address=*(PULONG)(p+2);
             break;
        }
    }

     if(address == 0)
    {
        KdPrint(( "Get KeServiceDescriptorTableShadow Error!\n"));
         return 0;
    }

    KdPrint(( "[KeAddSystemServiceTable] addr %x\n",(ULONG)addr));
    KdPrint(( "[KeServiceDescriptorTableShadow] address %x\n",address));
     return address;
}

下面这个方法很巧妙, 充分利用了KeServiceDescriptorTable和KeServieDescriptorTableShadow的异同
unsigned  int getAddressOfShadowTable()
{
     unsigned  char *p;
    ULONG dwordatbyte;

    ULONG KeAddSystemServiceTable = GetFunctionAddr(L "KeAddSystemServiceTable");
    p = ( unsigned  char*) KeAddSystemServiceTable;
    
     for(; p < p + 4096; p++)
    {
        dwordatbyte = *(PULONG)p;
         if(MmIsAddressValid((PVOID)dwordatbyte))
        {
            KdPrint(( "0x%08x\n", dwordatbyte));
             // KeServiceDescriptorTable和KeServieDescriptorTableShadow的前16字节是一样的
             if(memcmp((PVOID)dwordatbyte, &KeServiceDescriptorTable, 16) == 0)
            {
                 // 如果地址就是KeServiceDescriptorTable的则继续
                 if((PVOID)dwordatbyte == &KeServiceDescriptorTable)  continue;
                 // 地址不一样了, 则是KeServieDescriptorTableShadow
                 return dwordatbyte;
            }
        }
    }
    
     return 0;
}

找到了KeServieDescriptorTableShadow的地址之后, 余下的代码就是在系统中找一个不是System和smss.exe的进程, 并将当前线程挂靠到这个进程中
PVOID GetInfoTable(ULONG ATableType)
{
    ULONG mSize = 0x4000;
    PVOID mPtr = NULL;
    NTSTATUS  St;
     do
    {
        mPtr = ExAllocatePool(PagedPool, mSize);
        memset(mPtr, 0, mSize);
         if (mPtr)
        {
             St = ZwQuerySystemInformation(ATableType, mPtr, mSize, NULL);
        }  else  return NULL;
         if ( St == STATUS_INFO_LENGTH_MISMATCH)
        {
            ExFreePool(mPtr);
            mSize = mSize * 2;
        }
    }  while ( St == STATUS_INFO_LENGTH_MISMATCH);
     if ( St == STATUS_SUCCESS)  return mPtr;
    ExFreePool(mPtr);
     return NULL;
}

HANDLE GetCsrPid()
{
    HANDLE Process, hObject;
    HANDLE CsrId = (HANDLE)0;
    OBJECT_ATTRIBUTES obj;
    CLIENT_ID cid;
    UCHAR Buff[0x100];
    POBJECT_NAME_INFORMATION ObjName = (PVOID)&Buff;
    PSYSTEM_HANDLE_INFORMATION_EX Handles;
    ULONG r;
    
    Handles = GetInfoTable(SystemHandleInformation);
    
     if (!Handles)  return CsrId;
    
     for (r = 0; r < Handles->NumberOfHandles; r++)
    {
         if (Handles->Information[r].ObjectTypeNumber == 21)  //Port object
        {
            InitializeObjectAttributes(&obj, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
            
            cid.UniqueProcess = (HANDLE)Handles->Information[r].ProcessId;
            cid.UniqueThread = 0;
             // 根据CID打开这个进程对象
             if (NT_SUCCESS(NtOpenProcess(&Process, PROCESS_DUP_HANDLE, &obj, &cid)))
            {
                 // 拷贝这个进程中的当前句柄对象到当前进程
                 if (NT_SUCCESS(ZwDuplicateObject(Process, 
                        (HANDLE)Handles->Information[r].Handle,NtCurrentProcess(), &hObject, 0, 0, DUPLICATE_SAME_ACCESS)))
                {
                     // 查询这个句柄对象的名字信息
                     if (NT_SUCCESS(ZwQueryObject(hObject, ObjectNameInformation, ObjName, 0x100, NULL)))
                    {
                         // 根据句柄对象的名字知道他的所有者为csrss.exe(当然这里你要寻找的进程不一定是csrss.exe, 只要不是System和smss.exe就可以了)
                         if (ObjName->Name.Buffer && !wcsncmp(L "\\Windows\\ApiPort", ObjName->Name.Buffer, 20))
                        {
                             // 找到了就返回csrss.exe进程的PID
                            CsrId = (HANDLE)Handles->Information[r].ProcessId;
                        } 
                    }
                    
                    ZwClose(hObject);
                }
                
                ZwClose(Process);
            }
        }
    }
    
    ExFreePool(Handles);
     return CsrId;
}

将当前线程挂靠到csrss.exe进程中. 因为DriverEntry是由System中的线程调用的, 而这个进程中的KeServieDescriptorTableShadow的地址是不允许访问的, 因此需要挂靠到别的进程
status = PsLookupProcessByProcessId((ULONG)GetCsrPid(), &crsEProc);
if (!NT_SUCCESS( status ))
{
    DbgPrint( "PsLookupProcessByProcessId() error\n");
     return status;
}

KeAttachProcess(crsEProc);


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本实例由VS2008开发,在提供了一套驱动开发框架的同时,又演示了如何获取Shadow SSDT表函数原始地址的办法。 主要函数:ULONG GetShadowSSDT_Function_OriAddr(ULONG index); 原理说明: 根据特征码搜索导出函数KeAddSystemServiceTable来获取Shadow SSDT基址,以及通过ZwQuerySystemInformation()函数获取win32k.sys基址,然后解析PE定位到Shadow SSDT在win32k.sys的偏移地址,并通过进一步计算来得到Shadow SSDT表函数的原始地址。 这里只测试了三个函数:(460)NtUserMessageCall、(475)NtUserPostMessage和(502)NtUserSendInput,具体使用时可以举一反三,网上完整的源代码实例并不太多,希望可以帮到真正有需要的朋友。 系统环境: 在WinXP SP3系统 + 瑞星杀毒软件 打印输出: [ LemonInfo : Loading Shadow SSDT Original Address Driver... ] [ LemonInfo : 创建“设备”值为:0 ] [ LemonInfo : 创建“设备”成功... ] [ LemonInfo : 创建“符号链接”状态值为:0 ] [ LemonInfo : 创建“符号链接”成功... ] [ LemonInfo : 驱动加载成功... ] [ LemonInfo : 派遣函数(DispatchRoutine) IRP 开始... ] [ LemonInfo : 派遣函数(DispatchRoutine) IRP Enter IRP_MJ_DEVICE_CONTROL... ] [ LemonInfo : 获取ShadowSSDT表 (460)NtUserMessageCall 函数的“当前地址”为:0xB83ECFC4,“起源地址”为:0xBF80EE6B ] [ LemonInfo : 获取ShadowSSDT表 (475)NtUserPostMessage 函数的“当前地址”为:0xB83ECFA3,“起源地址”为:0xBF8089B4 ] [ LemonInfo : 获取ShadowSSDT表 (502)NtUserSendInput 函数的“当前地址”为:0xBF8C31E7,“起源地址”为:0xBF8C31E7 ] [ LemonInfo : 派遣函数(DispatchRoutine) IRP_MJ_DEVICE_CONTROL 成功执行... ] [ LemonInfo : 派遣函数(DispatchRoutine) IRP 结束... ] [ LemonInfo : UnLoading Shadow SSDT Original Address Driver... ] [ LemonInfo : 删除“符号链接”成功... ] [ LemonInfo : 删除“设备”成功... ] [ LemonInfo : 驱动卸载成功... ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值