32位的SSDT表结构浅析

以win7 x86为例(x64就不是我等菜鸟玩的了)

 

预备资料:

  本地系统服务的地址在内核结构中称为系统服务调度表(System Service Dispatch Table, SSDT)中列出。该表可以基于系统调用编号进行索引,以便定位函数的内存地址。还有一个系统服务参数表(System Service Parameter Table,SSPT)指定了每个系统服务的函数参数的字节数(每个函数对应一个字节)。

  KeServiceDescriptorTable是由内核导出的表。给出如下结构

复制代码
1 typedef struct _SystemServiceDescriptorTable 
2 { 
3     PVOID    ServiceTableBase; 
4     PULONG   ServiceCounterTableBase; 
5     ULONG    NumberOfService; 
6     ULONG    ParamTableBase; 
7 }SystemServiceTable,*PSystemServiceTable;
复制代码
复制代码
     typedef struct _SERVICE_DESCRIPTOR_TABLE
     {
       SYSTEM_SERVICE_TABLE   ntoskrnel; //ntoskrnl.exe的服务函数
       SYSTEM_SERVICE_TABLE   win32k;     //win32k.sys的服务函数,(gdi.dll/user.dll的内核支持)
       SYSTEM_SERVICE_TABLE   NotUsed1;
       SYSTEM_SERVICE_TABLE   NotUsed2;
     }SYSTEM_DESCRIPTOR_TABLE,*PSYSTEM_DESCRIPTOR_TABLE;
复制代码

   参数说明:

     ServiceTableBase :SSDT的基地址。

   ServiceCounterTable 此域用于操作系统的 checked builds,包含着 SSDT 中每个服务被调用次数的计数器。这个计数器由 INT 2Eh 处理程序 (KiSystemService)更新。 
     NumberOfServices 由 ServiceTableBase 描述的服务的数目。
     ParamTableBase 包含每个系统服务参数字节数表的基地址。

  

  引用pediy的大牛堕落天才的话来说:

 

  内核中有两个系统服务描述符表,一个是KeServiceDescriptorTable(由ntoskrnl.exe导出),一个是KeServieDescriptorTableShadow(没有导出)。两者的区别是,KeServiceDescriptorTable仅有ntoskrnel一项,KeServieDescriptorTableShadow包含了ntoskrnel以及win32k。一般的Native API的服务地址由KeServiceDescriptorTable分派,gdi.dll/user.dll的内核API调用服务地址由KeServieDescriptorTableShadow分派。还有要清楚一点的是win32k.sys只有在GUI线程中才加载,一般情况下是不加载的,所以要Hook KeServieDescriptorTableShadow的话,一般是用一个GUI程序通过IoControlCode来触发。


  需要注意的是,一般情况下R0的驱动内存中是没有win32k.sys的,只有在GUI线程加载的时候才会存在,准确的说只有在PsConvertToGuiThread调用的时候把KeServiceDescriptorTable 切换为KeServieDescriptorTableShadow的时候才会在内存中存在win32k.sys。注意:并不是一定要有GUI存在。

 

准备资料:

 

  Ring3层上的一个程序调用了API OpenProcess函数,那么这个函数具体是怎么跑的呢?

  在应用层上:OpenProcess函数定位于Kernel32.dll中 运行OpenProcess函数后该函数会调用位于ntdll.dll中的NtOpenProcess函数

        NtOpenProcess这个函数可以用Pe分析工具在ntdll.dll的导出表中找到。在调用前,ntdll.dll会完成函数参数的检查

        Ps::应用层上的Zw系列函数和Nt系列函数是一样的

 

  从应用层过渡到内核:调用一个中断 并且要将所要调用的服务号(SSDT数组中的索引值)存放到寄存器eax中

            并且将参数地址放到指定的寄存器edx中,再将参数复制到内核地址空间中

            最后根据eax中的索引值来调用SSDT数组中的指定服务

 

  在内核层上:系统服务分发函数KiSystemService根据eax寄存器中的索引值找到指定的SSDT项,最后根据该SSDT项中所存放的

        系统服务的地址来调用这个系统服务。

 

  那么,KeServiceDescriptorTable到底是怎么样的呢?

  windbg中输入指令

  kd> dd KeServiceDescriptorTable
  83f759c0 83e7c6f0 00000000 00000191 83e7cd38
  83f759d0 00000000 00000000 00000000 00000000
  83f759e0 83ee8493 00000000 00000000 00000bb8
  83f759f0 00000011 00000100 5385d2ba d717548f
  83f75a00 83e7c6f0 00000000 00000191 83e7cd38
  83f75a10 00000000 00000000 00000000 00000000
  83f75a20 00000000 00000000 83f75a24 00000240
  83f75a30 00000240 00000000 00000003 00000000

 

  可知道,服务函数个数是0x191 

  83e7c6f0就是KeServiceDescriptorTable.ntoskrnel.ServiceTableBase

  跟进83e7c6f0

  

  kd> dd 83e7c6f0
  83e7c6f0 8406d0cb 83ec622b 84018e4e 83e316e1
  83e7c700 8408de6e 83f0a48a 840f6b6d 840f6bb6
  83e7c710 840042d7 84110426 8411167b 840126f7
  83e7c720 8401a875 840e9979 84097718 8401b19c
  83e7c730 83faec97 840dbb1c 84005c7d 84058e0f
  83e7c740 8408bcf6 83fe7d73 84060821 83ffb3be
  83e7c750 84017a35 83ffa093 84017773 8402044b
  83e7c760 84025123 840d6ad3 84080906 8401fb6f

 

  这些就是系统服务函数的地址了。

 

  kd> dd 8406d0cb
  8406d0cb 8b55ff8b 24a164ec 66000001 008488ff
  8406d0db 57560000 75ff016a fff6331c 75ff1875
  8406d0eb 75ff5614 0c75ff10 56565656 e80875ff
  8406d0fb ffff9289 240d8b64 8b000001 84818df8
  8406d10b 66000000 b70f00ff c63b6600 418d1575
  8406d11b 74003940 b139660e 00000086 bce80575
  8406d12b 81ffdc97 000703ff bf0575c0 c000000b
  8406d13b 5e5fc78b 0018c25d 90909090 68146a90

  试着跟进8406d0cb

  

  kd> u 8406d0cb
  nt!NtAcceptConnectPort:
  8406d0cb 8bff mov edi,edi
  8406d0cd 55 push ebp
  8406d0ce 8bec mov ebp,esp
  8406d0d0 64a124010000 mov eax,dword ptr fs:[00000124h]
  8406d0d6 66ff8884000000 dec word ptr [eax+84h]
  8406d0dd 56 push esi
  8406d0de 57 push edi
  8406d0df 6a01 push 1

  看,8406d0cb就是NtAcceptConnectPort系统服务函数的地址

  那么,给出公式某个系统服务函数的地址 = KeServiceDescriptorTable.ntoskrnel.ServiceTableBase + eax * 4 指向的值

  不信的朋友们可以动手试一下,会发现,实际上,SSDT就是一个用来保存Windows服务地址的数组。

 

正文:

  以SSDT HOOK ZwCreateUserProcess为例 这里引用Windows内核安全编程从入门到实践的示例代码

  

  DriverEntry中的几行代码来完成SSDT HOOK

if (!NT_SUCCESS(Init_HCode()))
    {
        return STATUS_FAILED_DRIVER_ENTRY;
    }
    RealAddr.NtCreateUserProcess = Hook_SSDT(NtCreateUserProcessID, myNtCreateUserProcess);

 

  给出相关函数源码

复制代码
NTSTATUS Init_HCode()
{
    UNICODE_STRING    uDllName;

KdPrint(("Find Code...\n")); RtlInitUnicodeString(&uDllName, L"\\Device\\HarddiskVolume1\\Windows\\System32\\ntdll.dll"); NtCreateUserProcessID = GetSysServiceId(&uDllName, "ZwCreateUserProcess"); if (!NtCreateUserProcessID) { return STATUS_UNSUCCESSFUL; } return STATUS_SUCCESS; }
复制代码

 

复制代码
ULONG    GetSysServiceId(IN PUNICODE_STRING uDllName, IN char* cSearchFnName)
{
    ULONG uFnAddr = 0;
    ULONG uID = 0;
    int i = 0;

    NTSTATUS ntstatus;
    HANDLE hFile;
    OBJECT_ATTRIBUTES oattr;
    IO_STATUS_BLOCK iosb;

    HANDLE hSection;
    PVOID pBaseAddr = NULL;
    SIZE_T viewSize = 0;

    ULONG uModBase = 0;
    IMAGE_DOS_HEADER *doshdr;
    IMAGE_OPTIONAL_HEADER *opthdr;
    IMAGE_EXPORT_DIRECTORY *pExportTable;
    ULONG *dwAddrFns, *dwAddrNames;
    USHORT *dwAddrNameOrdinals;
    char *cFunName;
    ULONG dwFnOrdinal;

    InitializeObjectAttributes(&oattr, uDllName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
    ntstatus = ZwOpenFile(&hFile, GENERIC_READ, &oattr, &iosb, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SYNCHRONOUS_IO_ALERT);
    if (!NT_SUCCESS(ntstatus))
    {
        //KdPrint(("[GetSysServiceId] ZwOpenFile Failure!\n"));
        return 0;
    }

    ntstatus = ZwCreateSection(&hSection, SECTION_MAP_READ | SECTION_MAP_WRITE, NULL, 0, PAGE_READWRITE, 0x1000000, hFile);
    if (!NT_SUCCESS(ntstatus))
    {
        //KdPrint(("[GetSysServiceId] ZwSectionFailure!\n"));
        ZwClose(hFile);
        return 0;
    }
    ntstatus = ZwMapViewOfSection(hSection, NtCurrentProcess(), &pBaseAddr, 0, 1024, 0, &viewSize, ViewShare, MEM_TOP_DOWN, PAGE_READWRITE);
    if (!NT_SUCCESS(ntstatus))
    {
        //KdPrint(("[GetSysServiceId ZwMapViewOfSectionFailure!\n"));
        ZwClose(hFile);
        ZwClose(hSection);
        return 0;
    }
    uModBase = (ULONG)pBaseAddr;

    do 
    {
        doshdr = (IMAGE_DOS_HEADER *)uModBase;
        if (NULL == doshdr)
        {
            break;
        }
        opthdr = (IMAGE_OPTIONAL_HEADER *)(uModBase + doshdr->e_lfanew + 24);
        if (NULL == opthdr)
        {
            break;
        }
        pExportTable = (IMAGE_EXPORT_DIRECTORY *)(uModBase + opthdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
        if (NULL == pExportTable)
        {
            break;
        }
        dwAddrFns = (ULONG *)(uModBase + pExportTable->AddressOfFunctions);
        dwAddrNames = (ULONG *)(uModBase + pExportTable->AddressOfNames);
        dwAddrNameOrdinals = (USHORT *)(uModBase + pExportTable->AddressOfNameOrdinals);

        for (i = 0; i < pExportTable->NumberOfNames; ++i)
        {
            cFunName = (char *)(uModBase + dwAddrNames[i]);
            if (!_strnicmp(cSearchFnName, cFunName, strlen(cSearchFnName)))
            {
                dwFnOrdinal = pExportTable->Base + dwAddrNameOrdinals[i] - 1;
                uFnAddr = uModBase + dwAddrFns[dwFnOrdinal];
                uID = *(ULONG*)((ULONG)uFnAddr + 1);//取得ID
                break;
            }
        }
    } while (FALSE);

    ZwUnmapViewOfSection(NtCurrentProcess(), pBaseAddr);

    ZwClose(hFile);
    ZwClose(hSection);

    return uID;
}
复制代码

 

复制代码
ULONG    Hook_SSDT(ULONG fnID, PVOID fnName)
{
    ULONG Address;
    ULONG RealAddress;
    unsigned int NumberOfServices;

    Address = (ULONG)KeServiceDescriptorTable->ServiceTableBase + fnID * 4;

    RealAddress = *(ULONG*)Address;

    __asm //解除内存保护
    {
        cli
            push eax
            mov  eax, cr0
            and  eax, not 10000h
            mov  cr0, eax
            pop  eax
    }
    InterlockedExchange((LONG*)Address, (LONG)fnName);//注:原子操作

    __asm
    {
        push eax
            mov  eax, cr0
            or   eax, 10000h
            mov  cr0, eax
            pop  eax
            sti
    }
    return RealAddress;
}
复制代码

 

最后给个UnHook

  

复制代码
VOID     Unhook(ULONG fnID,ULONG RealAddr)
{
    ULONG   Address; 
    KIRQL   oldIrql;

    Address = (ULONG)KeServiceDescriptorTable->ServiceTableBase + fnID * 4; 

    __asm 
    { 
        cli 
            mov    eax, cr0 
            and    eax, not 10000h 
            mov    cr0, eax 
    } 

    oldIrql = KeRaiseIrqlToDpcLevel();

    InterlockedExchange((LONG *)Address,RealAddr);

    KeLowerIrql(oldIrql);
    __asm 
    { 
        mov    eax, cr0 
            or    eax, 10000h 
            mov    cr0, eax 
            sti 
    } 
}
复制代码

至此,SSDT HOOK的简单介绍就到这里了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值