zoukankan      html  css  js  c++  java
  • Win64 驱动内核编程-19.HOOK-SSDT

    HOOK SSDT

        在 WIN64 上 HOOK SSDT 和 UNHOOK SSDT 在原理上跟 WIN32 没什么不同,甚至说 HOOK 和 UNHOOK 在本质上也没有不同,都是在指定的地址上填写一串数字而已

    (填写代理函数的地址时叫做 HOOK,填写原始函数的地址时叫做 UNHOOK)。不过实现起来还是很大不同的。

    一 、 HOOK SSDT

        要挂钩 SSDT,必然要先得到 ServiceTableBase 的地址。和 SSDT 相关的两个结构体 SYSTEM_SERVICE_TABLE 以及 SERVICE_DESCRIPTOR_TABLE 并没有发生

    什么的变化(除了整个结构体的长度胖了一倍):

    typedef struct _SYSTEM_SERVICE_TABLE{
    PVOID  	ServiceTableBase; 
    PVOID  	ServiceCounterTableBase; 
    ULONGLONG  	NumberOfServices; 
    PVOID  	ParamTableBase; 
    } SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;
     
    typedef struct _SERVICE_DESCRIPTOR_TABLE{
    SYSTEM_SERVICE_TABLE ntoskrnl;  // ntoskrnl.exe (native api)
    SYSTEM_SERVICE_TABLE win32k;    // win32k.sys   (gdi/user)
    SYSTEM_SERVICE_TABLE Table3;    // not used
    SYSTEM_SERVICE_TABLE Table4;    // not used
    }SERVICE_DESCRIPTOR_TABLE,*PSERVICE_DESCRIPTOR_TABLE;


    得到 ServiceTableBase 的地址后,就能得到每个服务函数的地址了。但和WIN32 不一样,这个表存放的并不是 SSDT 函数的完整地址,而是其相对于ServiceTableBase[Index]>>4 的数据(我称它为偏移地址),每个数据占四个字节,所以计算指定 Index 函数完整地址的公式是:ServiceTableBase[Index]>>4+ ServiceTableBase。代码如下(这个上一个博客已经说过):

    ULONGLONG GetSSDTFuncCurAddr(ULONG id)
    {
    LONG dwtmp=0;
    PULONG ServiceTableBase=NULL;
    ServiceTableBase=(PULONG)KeServiceDescriptorTable->ServiceTableBase;
    dwtmp=ServiceTableBase[id];
    dwtmp=dwtmp>>4;
    return (LONGLONG)dwtmp + (ULONGLONG)ServiceTableBase;
    }
    

    反之,从函数的完整地址获得函数偏移地址的代码也就出来了:

    ULONG GetOffsetAddress(ULONGLONG FuncAddr)
    {
    ULONG dwtmp=0;
    PULONG ServiceTableBase=NULL;
    ServiceTableBase=(PULONG)KeServiceDescriptorTable->ServiceTableBase;
    dwtmp=(ULONG)(FuncAddr-(ULONGLONG)ServiceTableBase);
    return dwtmp<<4;
    }

    以下是我目前正在看的资料的作者 胡文亮 的吐槽:

        知道了这一套机制,HOOK SSDT 就很简单了,首先获得待 HOOK 函数的序号Index,然后通过公式把自己的代理函数的地址转化为偏移地址,然后把偏移地址的数据填入 ServiceTableBase[Index]。也许有些读者看到这里,已经觉得胜利在望了,我当时也是如此。但实际上我在这里栽了个大跟头,整整郁闷了很长时间!因为我低估了设计这套算法的工程师的智商,我没有考虑一个问题,为什么 WIN64 的 SSDT 表存放地址的形式这么奇怪?只存放偏移地址,而不存放完整地址?难道是为了节省内存?这肯定是不可能的,要知道现在内存白菜价。那么不是为了节省内存,唯一的可能性就是要给试图挂钩 SSDT 的人制造麻烦!要知道,WIN64 内核里每个驱动都 不在同一个 B 4GB  里,而 4 字节的整数只能表示 4GB的范围!所以无论你怎么修改这个值,都跳不出 ntoskrnl 的手掌心。如果你想通过修改这个值来跳转到你的代理函数,那是绝对不可能的。 因为你的驱动的地址不 可能跟 l ntoskrnl  在同一个 B 4GB  里。然而,这位工程师也低估了我们中国人的智商,在中国有两句成语,这位工程师一定没听过,叫“明修栈道,暗渡陈仓”以及“上有政策,下有对策”。虽然不能直接用 4 字节来表示自己的代理函数所在的地址,但是还是可以修改这个值的。要知道,ntoskrnl 虽然有很多地方的代码通常是不会被执行的,比如 KeBugCheckEx。所以我的办法是: 修改这个偏移地址的值,使之跳转到  KeBugCheckEx ,然后在 x KeBugCheckEx  的头部写一个 2 12  字节的  mov - - jmp ,这是一个可以跨越  4GB  ! 的跳转,跳到我们的函数里!代码如下:

    VOID FuckKeBugCheckEx()
    {
    KIRQL irql;
    ULONGLONG myfun;
    UCHAR jmp_code[]="x48xB8xFFxFFxFFxFFxFFxFFxFFx00xFFxE0";
    myfun=(ULONGLONG)Fake_NtTerminateProcess;
    memcpy(jmp_code+2,&myfun,8);
    irql=WPOFFx64();
    memset(KeBugCheckEx,0x90,15);
    memcpy(KeBugCheckEx,jmp_code,12);
    WPONx64(irql);
    }
     
    
    VOID HookSSDT()
    {
    KIRQL irql;
    ULONGLONG dwtmp=0;
    PULONG ServiceTableBase=NULL;
    //get old address
    NtTerminateProcess=(NTTERMINATEPROCESS)GetSSDTFuncCurAddr(41);
    dprintf("Old_NtTerminateProcess: %llx",(ULONGLONG)NtTerminateProcess);
    //set kebugcheckex
    FuckKeBugCheckEx();
    //show new address
    ServiceTableBase=(PULONG)KeServiceDescriptorTable->ServiceTableBase;
    OldTpVal=ServiceTableBase[41];	//record old offset value
    irql=WPOFFx64();
    ServiceTableBase[41]=GetOffsetAddress((ULONGLONG)KeBugCheckEx);
    WPONx64(irql);
    dprintf("KeBugCheckEx: %llx",(ULONGLONG)KeBugCheckEx);
    dprintf("New_NtTerminateProcess: %llx",GetSSDTFuncCurAddr(41));
    }
    回调函数保护计算器不被结束:

    NTSTATUS __fastcall Fake_NtTerminateProcess(IN HANDLE ProcessHandle, IN NTSTATUS ExitStatus)
    {
    PEPROCESS Process;
    NTSTATUS st = ObReferenceObjectByHandle (ProcessHandle, 0, *PsProcessType, KernelMode, &Process, NULL);
    DbgPrint("Fake_NtTerminateProcess called!");
    if(NT_SUCCESS(st))
    {
    if(!_stricmp(PsGetProcessImageFileName(Process),"calc.exe"))
    return STATUS_ACCESS_DENIED;
    else
    return NtTerminateProcess(ProcessHandle,ExitStatus);
    }
    else
    return STATUS_ACCESS_DENIED;
    }

    完整测试代码:

    #include <ntddk.h>
    typedef struct _SYSTEM_SERVICE_TABLE{
    PVOID  	ServiceTableBase; 
    PVOID  	ServiceCounterTableBase; 
    ULONGLONG  	NumberOfServices; 
    PVOID  	ParamTableBase; 
    } SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;
     
    typedef struct _SERVICE_DESCRIPTOR_TABLE{
    SYSTEM_SERVICE_TABLE ntoskrnl;  // ntoskrnl.exe (native api)
    SYSTEM_SERVICE_TABLE win32k;    // win32k.sys   (gdi/user)
    SYSTEM_SERVICE_TABLE Table3;    // not used
    SYSTEM_SERVICE_TABLE Table4;    // not used
    }SERVICE_DESCRIPTOR_TABLE,*PSERVICE_DESCRIPTOR_TABLE;
     
    typedef NTSTATUS (__fastcall *NTTERMINATEPROCESS)(IN HANDLE ProcessHandle, IN NTSTATUS ExitStatus);
     
    NTKERNELAPI
    UCHAR *
    PsGetProcessImageFileName(PEPROCESS Process);
     
    PSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;
    NTTERMINATEPROCESS NtTerminateProcess=NULL;
    ULONG OldTpVal;
     
    NTSTATUS __fastcall Fake_NtTerminateProcess(IN HANDLE ProcessHandle, IN NTSTATUS ExitStatus)
    {
    PEPROCESS Process;
    NTSTATUS st = ObReferenceObjectByHandle (ProcessHandle, 0, *PsProcessType, KernelMode, &Process, NULL);
    DbgPrint("Fake_NtTerminateProcess called!");
    if(NT_SUCCESS(st))
    {
    if(!_stricmp(PsGetProcessImageFileName(Process),"calc.exe"))
    return STATUS_ACCESS_DENIED;
    else
    return NtTerminateProcess(ProcessHandle,ExitStatus);
    }
    else
    return STATUS_ACCESS_DENIED;
    }
     
    KIRQL WPOFFx64()
    {
    KIRQL irql=KeRaiseIrqlToDpcLevel();
    UINT64 cr0=__readcr0();
    cr0 &= 0xfffffffffffeffff;
    __writecr0(cr0);
    _disable();
    return irql;
    }
     
    void WPONx64(KIRQL irql)
    {
    UINT64 cr0=__readcr0();
    cr0 |= 0x10000;
    _enable();
    __writecr0(cr0);
    KeLowerIrql(irql);
    }
     
    ULONGLONG GetKeServiceDescriptorTable64() 
    {
    PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082);
    PUCHAR EndSearchAddress = StartSearchAddress + 0x500;
    PUCHAR i = NULL;
    UCHAR b1 = 0, b2 = 0, b3 = 0;
    ULONG templong = 0;
    ULONGLONG addr = 0;
    for (i = StartSearchAddress; i<EndSearchAddress; i++)
    {
    if (MmIsAddressValid(i) && MmIsAddressValid(i + 1) && MmIsAddressValid(i + 2))
    {
    b1 = *i;
    b2 = *(i + 1);
    b3 = *(i + 2);
    if (b1 == 0x4c && b2 == 0x8d && b3 == 0x15) //4c8d15
    {
    memcpy(&templong, i + 3, 4);
    addr = (ULONGLONG)templong + (ULONGLONG)i + 7;
    return addr;
    }
    }
    }
    return 0;
    }
     
    ULONGLONG GetSSDTFuncCurAddr(ULONG id)
    {
    LONG dwtmp=0;
    PULONG ServiceTableBase=NULL;
    ServiceTableBase=(PULONG)KeServiceDescriptorTable->ServiceTableBase;
    dwtmp=ServiceTableBase[id];
    dwtmp=dwtmp>>4;
    return (LONGLONG)dwtmp + (ULONGLONG)ServiceTableBase;
    }
     
    ULONG GetOffsetAddress(ULONGLONG FuncAddr)
    {
    ULONG dwtmp=0;
    PULONG ServiceTableBase=NULL;
    ServiceTableBase=(PULONG)KeServiceDescriptorTable->ServiceTableBase;
    dwtmp=(ULONG)(FuncAddr-(ULONGLONG)ServiceTableBase);
    return dwtmp<<4;
    }
     
    VOID FuckKeBugCheckEx()
    {
    KIRQL irql;
    ULONGLONG myfun;
    UCHAR jmp_code[]="x48xB8xFFxFFxFFxFFxFFxFFxFFx00xFFxE0";
    myfun=(ULONGLONG)Fake_NtTerminateProcess;
    memcpy(jmp_code+2,&myfun,8);
    irql=WPOFFx64();
    memset(KeBugCheckEx,0x90,15);
    memcpy(KeBugCheckEx,jmp_code,12);
    WPONx64(irql);
    }
     
    /*
    填写KeBugCheckEx的地址
    在KeBugCheckEx填写jmp,跳到Fake_NtTerminateProcess
    不能直接填写Fake_NtTerminateProcess的地址,因为它们不再同一个4GB
    */
    VOID HookSSDT()
    {
    KIRQL irql;
    ULONGLONG dwtmp=0;
    PULONG ServiceTableBase=NULL;
    //get old address
    NtTerminateProcess=(NTTERMINATEPROCESS)GetSSDTFuncCurAddr(41);
    dprintf("Old_NtTerminateProcess: %llx",(ULONGLONG)NtTerminateProcess);
    //set kebugcheckex
    FuckKeBugCheckEx();
    //show new address
    ServiceTableBase=(PULONG)KeServiceDescriptorTable->ServiceTableBase;
    OldTpVal=ServiceTableBase[41];	//record old offset value
    irql=WPOFFx64();
    ServiceTableBase[41]=GetOffsetAddress((ULONGLONG)KeBugCheckEx);
    WPONx64(irql);
    dprintf("KeBugCheckEx: %llx",(ULONGLONG)KeBugCheckEx);
    dprintf("New_NtTerminateProcess: %llx",GetSSDTFuncCurAddr(41));
    }
     
    VOID UnhookSSDT()
    {
    KIRQL irql;
    PULONG ServiceTableBase=NULL;
    ServiceTableBase=(PULONG)KeServiceDescriptorTable->ServiceTableBase;
    //set value
    irql=WPOFFx64();
    ServiceTableBase[41]=GetOffsetAddress((ULONGLONG)NtTerminateProcess);	//OldTpVal;//直接填写这个旧值也行
    WPONx64(irql);
    //没必要恢复KeBugCheckEx的内容了,反正执行到KeBugCheckEx时已经完蛋了。
    dprintf("NtTerminateProcess: %llx",GetSSDTFuncCurAddr(41));
    }
     
    调用:
    DriverEntry里
        KeServiceDescriptorTable = (PSYSTEM_SERVICE_TABLE)GetKeServiceDescriptorTable64();
    HookSSDT();
    DriverUnload里
        UnhookSSDT();

    执行结果:


        下一节是UNHOOK SSDT 取消掉机器上别人的HOOK 这个资源的作者其实是写在一起的,我是分两次学习这个,所以总结两次笔记。声明一下,我这里所有的思路和笔记都来源于资料,我只是理解,把所有的例子都做了一遍,学习整理,有一些没理解了的我会去别的资料里面找,然后把所有相关整理在同一个资料里。大家一起学习,一起进步。

  • 相关阅读:
    父div的透明度不影响子div透明度
    vue-组件命名
    前端页面优化技巧
    Webstorm添加新建.vue文件功能并支持高亮vue语法和es6语法
    防止被坑
    vue安装教程总结
    vue找错
    前段进阶之路
    VM4061 layui.js:2 Layui hint: form is not a valid module
    三月十一号
  • 原文地址:https://www.cnblogs.com/csnd/p/12062006.html
Copyright © 2011-2022 走看看