SSDT Hook实现内核级的过程保护

    添加时间:2013-7-1 点击量:

    目次



    1. SSDT Hook结果图

    2. SSDT简介

    3. SSDT布局

    4. SSDT HOOK道理

    5. Hook前筹办

    6. 如何获得SSDT中函数的地址呢

    7. SSDT Hook流程

    8. SSDT Hook实现过程保护

    9. Ring3与Ring0的通信

    10. 如何安装启动停止卸载办事

    11. 参考文献

    12. 源码附件

    13. 版权




    SSDT Hook结果图



    加载驱动并成功Hook  NtTerminateProcess函数:



    当对 指定的过程进行保护后,测验测验应用“任务经管器”停止过程的时辰,会弹出“拒绝接见”的窗口,申明,我们的目标已经达到:






    SSDT简介





    SSDT 的全称是 System Services Descriptor Table,体系办事描述符表。


    这个表就是一个把 Ring3 的 Win32 API 和 Ring0 的内核 API 接洽起来。


    SSDT 并不仅仅只包含一个重大的地址索引表,它还包含着一些其它有效的信息,诸如地址索引的基地址、办事函数个数等。


    经由过程批改此表的函数地址可以对常用 Windows 函数及 API 进行 Hook,从而实现对一些关怀的体系动作进行过滤、监控的目标。


    一些 HIPS、防毒软件、体系监控、注册表监控软件往往会采取此接口来实现本身的监控模块。



    SSDT布局


    SSDT即体系办事描述符表,它的布局如下(参考《Undocument Windows 2000 Secretes》第二章):



    // KSYSTEM_SERVICE_TABLE 和 KSERVICE_TABLE_DESCRIPTOR
    
    // 用来定义 SSDT 布局
    typedef struct _KSYSTEM_SERVICE_TABLE
    {
    PULONG ServiceTableBase;
    // SSDT (System Service Dispatch Table)的基地址
    PULONG ServiceCounterTableBase; // 用于 checked builds, 包含 SSDT 中每个办事被调用的次数
    ULONG NumberOfService; // 办事函数的个数, NumberOfService 4 就是全部地址表的大小
    ULONG ParamTableBase; // SSPT(System Service Parameter Table)的基地址
    } KSYSTEM_SERVICE_TABLE, PKSYSTEM_SERVICE_TABLE;

    typedef
    struct _KSERVICE_TABLE_DESCRIPTOR
    {
    KSYSTEM_SERVICE_TABLE ntoskrnl;
    // ntoskrnl.exe 的办事函数
    KSYSTEM_SERVICE_TABLE win32k; // win32k.sys 的办事函数(GDI32.dll/User32.dll 的内核支撑)
    KSYSTEM_SERVICE_TABLE notUsed1;
    KSYSTEM_SERVICE_TABLE notUsed2;
    }KSERVICE_TABLE_DESCRIPTOR,
    PKSERVICE_TABLE_DESCRIPTOR;


        内核中有两个体系办事描述符表,一个是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来触发(想当初不熟悉打听这点,蓝屏死机了N次都想不熟悉打听是怎么回事)。



    SSDT HOOK道理


    关于内核 Hook 有多种类型,下面也给出一副图示:


    SSDT HOOK只是此中一种Hook技巧,本篇文章首要讲解SSDT Hook的应用。

    SSDT HOOK道理图


    经由过程Kernel Detective对象,我们可以发明,SSDT Hook前后,NtTerminateProcess的当前地址会产生变更,此中,变更后的当前地址:0 xF885A110为我们自定义的Hook函数(即:HookNtTerminateProcess)的地址。如许,今后每次履行NtTerminateProcess的时辰,就会按照履行“当前地址”所指向的函数了,这也就是SSDT Hook的道理。

    别的,看雪的出错天才写的不错,我直接引用下:

    SSDT HOOK 的道理其实很是简单,我们先实际看看KeServiceDescriptorTable是什么样的。 




     lkd> dd KeServiceDescriptorTable
    
    8055ab80 804e3d20
    00000000 0000011c 804d9f48
    8055ab90
    00000000 00000000 00000000 00000000
    8055aba0
    00000000 00000000 00000000 00000000
    8055abb0
    00000000 00000000 00000000 00000000


      如上,80587691 805716ef 8057ab71 80581b5c 这些就是体系办事函数的地址了。比如在ring3调用OpenProcess时,进入sysenter的ID是0 x7A(XP SP2),然后体系查KeServiceDescriptorTable,可能是如许KeServiceDescriptorTable.ntoskrnel.ServiceTableBase(804e3d20) + 0 x7A  4 = 804E3F08,然后804E3F08 ->8057559e 这个就是OpenProcess体系办事函数地点,我们再跟踪看看: 





    lkd> u 8057559e
    
    nt
    !NtOpenProcess:
    8057559e 68c4000000 push 0C4h
    805755a3 6860b54e80 push offset nt
    !ObReferenceObjectByPointer+0 x127 (804eb560)
    805755a8 e8e5e4f6ff call nt
    !InterlockedPushEntrySList+0 x79 (804e3a92)
    805755ad 33f6 xor esi,esi


      本来8057559e就是NtOpenProcess函数地点的肇端地址。  
        嗯,若是我们把8057559e改为指向我们函数的地址呢?比如 MyNtOpenProcess,那么体系就会直接调用MyNtOpenProcess,而不是本来的NtOpenProcess了。这就是SSDT HOOK 道理地点。

    别的,关于Ring3层转入Ring0层的具体流程,可以参考下我的这篇博文,对加深懂得SSDT Hook技巧还是有帮助的:Ring3转入Ring0跟踪



    Hook前筹办





    我们要批改SSDT表,起首这个表必须是可写的,但在xp今后的体系中他都是只读的,三个办法来批改内存保护机制


    (1) 更改注册表 

    恢复页面保护:HKLM\SYSTEM\CurrentControlset\Control\Session Manger\Memory Management\EnforceWriteProtection=0

    去掉页面保护:HKLM\SYSTEM\CurrentControlset\Control\Session Manger\Memory Management\DisablePagingutive=1




    (2)改变CR0存放器的第1位

    Windows对内存的分派,是采取的分页经管。此中有个CR0存放器,如下图:


    此中第1位叫做保护属性位,把握着页的读或写属性。若是为1,则可以读/写/履行;若是为0,则只可以读/履行。

    SSDT,IDT的页属性在默认下都是只读,可履行的,但不克不及写。





    代码如下:



    //设置为不成写
    
    void DisableWrite()
    {
    __try
    {
    _asm
    {
    mov eax, cr0
    or eax, 10000h
    mov cr0, eax
    sti
    }
    }
    __except(
    1
    {
    DbgPrint(
    DisableWrite履行失败!);
    }
    }
    // 设置为可写
    void EnableWrite()
    {
    __try
    {
    _asm
    {
    cli
    mov eax,cr0
    and eax,not 10000h
    //and eax,0FFFEFFFFh
    mov cr0,eax
    }
    }
    __except(
    1
    {
    DbgPrint(
    EnableWrite履行失败!);
    }
    }


    (3)经由过程Memory Descriptor List(MDL)


    具体做法可以google下,这里就不介绍了



    如何获得SSDT中函数的地址呢?


      这里首要应用了两个宏:


    ①获取指定办事的索引号:SYSCALL_INDEX


    ②获取指定办事的当前地址:SYSCALL_FUNCTION


    这两个宏的具体定义如下:


    //按照 ZwServiceFunction 获取 ZwServiceFunction 在 SSDT 中所对应的办事的索引号 #define SYSCALL_INDEX(ServiceFunction) ((PULONG)((PUCHAR)ServiceFunction + 1)) //按照ZwServiceFunction 来获得办事在 SSDT 中的索引号,然后再经由过程该索引号来获取ntServiceFunction的地址 #define SYSCALL_FUNCTION(ServiceFunction) KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[SYSCALL_INDEX(ServiceFunction)]




    SSDT Hook流程



    在驱动的进口函数中(DriverEntry),对未进行SSDT Hook前的SSDT表进行了备份(用一个数组保存),备份时,一个索引号对应一个当前地址,如上图所示。


    如许,在解除Hook的时辰,就可以从全局数组中按照索引号获取未Hook前的办事名的当前地址,以便将本来的地址写归去,这一步很首要。


    当用户选择保护某个过程的时辰,就会经由过程DeviceIoControl发送一个IO_INSERT_PROTECT_PROCESS把握码给驱动法度,此时驱动法度会生成一个IRP:IRP_MJ_DEVICE_CONTROL,我们事先已经在驱动法度中为


    IRP_MJ_DEVICE_CONTROL指定了一个调派函数:SSDTHook_DispatchRoutine_CONTROL。在该调派函数中:我们经由过程获取把握码(是保护过程还是作废保护过程),若是是要保护某个过程,则经由过程
    
    DeviceIoControl的第3个参数将要保护的过程的pid传递给驱动法度。然后在调派函数SSDTHook_DispatchRoutine_CONTROL中从缓冲区中读取该pid,若是是要保护过程,则将要“保护过程”的pid添加到一个数组中,若是是要“作废保护过程”,则将要作废保护的过程PID从数组中移除。

    在Hook NtTermianteProcess函数后,会履行我们自定义的函数:HookNtTerminateProcess,在HookNtTerminateProcess函数中,我们断定当前过程是否在要保护的过程数组中,若是该数组中存在该pid,则我们返回一个“权限不敷”的异常,若是过程保护数组中不存在该pid,则直接调用本来 SSDT 中的 NtTerminateProcess 来停止过程。



    SSDT Hook实现过程保护


    有了上方的理论根蒂根基之后,接下来可以谈谈SSDT Hook实现过程保护的具体实现了。

    实现过程保护,可以Hook NtTermianteProcess,别的也可以Hook NtOpenProcess,这里,我是Hook NtTermianteProcess。

    SSDT Hook道理一节中已经说过,SSDT Hook道理的本质是:自定义一个函数(HookNtTerminateProcess),让体系办事NtTermianteProcess的当前地址指向我们自定义函数地址。

    这一步工作是在驱动进口函数中履行的。当驱动加载的时辰,将自定义函数的地址写入SSDT表中NtTermianteProcess办事的当前地址:



    // 实现 Hook 的安装,主如果在 SSDT 顶用 newService 来调换掉 oldService
    
    NTSTATUS InstallHook(ULONG oldService, ULONG newService)
    {
    __try
    {
    ULONG uOldAttr
    = 0;
    EnableWrite();
    //去掉页面保护
    KdPrint((捏造NtTerminateProcess地址: %x\n,(int)newService));
    //KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[SYSCALL_INDEX(oldService)] = newService;
    SYSCALL_FUNCTION(oldService) = newService;//
    DisableWrite(); //恢复页面保护
    return STATUS_SUCCESS;
    }
    __except(
    1
    {
    KdPrint((
    安装Hook失败!));
    }
    }


    这里须要重视的是:在Hook前,须要去掉内存的页面保护属性,Hook后,须要答复内存的页面保护属性。

    HookNtTerminateProcess函数的代码如下:



    //
    
    // 函数名称 : HookNtTerminateProcess
    // 描 述 : 自定义的 NtOpenProcess,用来实现 Hook Kernel API
    // 日 期 : 2013/06/28
    // 参 数 : ProcessHandle:过程句柄 ExitStatus:
    // 返 回 值 :
    //
    NTSTATUS HookNtTerminateProcess(__in_opt HANDLE ProcessHandle,__in NTSTATUS ExitStatus)
    {
    ULONG uPID;
    NTSTATUS rtStatus;
    PCHAR pStrProcName;
    PEPROCESS pEProcess;
    ANSI_STRING strProcName;

    // 经由过程过程句柄来获得该过程所对应的 FileObject 对象,因为这里是过程对象,天然获得的是 EPROCESS 对象
    rtStatus = ObReferenceObjectByHandle(ProcessHandle, FILE_READ_DATA, NULL, KernelMode, (PVOID)&pEProcess, NULL);
    if (!NT_SUCCESS(rtStatus))
    {
    return rtStatus;
    }
    // 保存 SSDT 中本来的 NtTerminateProcess 地址
    pOldNtTerminateProcess = (NTTERMINATEPROCESS)oldSysServiceAddr[SYSCALL_INDEX(ZwTerminateProcess)];

    // 经由过程该函数可以获取到过程名称和过程 ID,该函数在内核中本质是导出的(在 WRK 中可以看到)
    // 然则 ntddk.h 中并没有处处,所以须要本身声明才干应用
    uPID = (ULONG)PsGetProcessId(pEProcess);
    pStrProcName
    = _strupr((TCHAR )PsGetProcessImageFileName(pEProcess));//应用微软未公开的PsGetProcessImageFileName函数获取过程名

    // 经由过程过程名来初始化一个 ASCII 字符串
    RtlInitAnsiString(&strProcName, pStrProcName);

    if (ValidateProcessNeedProtect(uPID) != -1
    {
    // 确保调用者过程可以或许停止(这里主如果指 taskmgr.exe)
    if (uPID != (ULONG)PsGetProcessId(PsGetCurrentProcess()))
    {
    // 若是该过程是所保护的的过程的话,则返回权限不敷的异常即可
    return STATUS_ACCESS_DENIED;
    }
    }
    // 对于非保护的过程可以直接调用本来 SSDT 中的 NtTerminateProcess 来停止过程
    rtStatus = pOldNtTerminateProcess(ProcessHandle, ExitStatus);
    return rtStatus;
    }


    Ring3与Ring0的通信


    请看考:张帆《Windows驱动开辟技巧详解》一书第7章:调派函数


    如何安装、启动、停止、卸载办事


    请参考我的另一篇博文:NT式驱动加载器(附带源码)


    参考文献


    SSDT Hook的妙用-对抗ring0 inline hook:http://bbs.pediy.com/showthread.php?t=40832

    过程隐蔽与过程保护(SSDT Hook 实现):http://www.cnblogs.com/BoyXiao/archive/2011/09/03/2164574.html

    张帆的《Windows驱动开辟技巧详解》一书


    源码附件



    SSDT Hook实现内核级的过程保护


    版权


     版权所有,迎转载,但转载请注明: 转载自  曾是土木人







    我所有的自负皆来自我的自卑,所有的英雄气概都来自于我的软弱。嘴里振振有词是因为心里满是怀疑,深情是因为痛恨自己无情。这世界没有一件事情是虚空而生的,站在光里,背后就会有阴影,这深夜里一片寂静,是因为你还没有听见声音。—— 马良《坦白书》
    分享到: