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 这个资源的作者其实是写在一起的,我是分两次学习这个,所以总结两次笔记。声明一下,我这里所有的思路和笔记都来源于资料,我只是理解,把所有的例子都做了一遍,学习整理,有一些没理解了的我会去别的资料里面找,然后把所有相关整理在同一个资料里。大家一起学习,一起进步。

  • 相关阅读:
    LeetCode 623. Add One Row to Tree
    LeetCode 894. All Possible Full Binary Trees
    LeetCode 988. Smallest String Starting From Leaf
    LeetCode 979. Distribute Coins in Binary Tree
    LeetCode 814. Binary Tree Pruning
    LeetCode 951. Flip Equivalent Binary Trees
    LeetCode 426. Convert Binary Search Tree to Sorted Doubly Linked List
    LeetCode 889. Construct Binary Tree from Preorder and Postorder Traversal
    LeetCode 687. Longest Univalue Path
    LeetCode 428. Serialize and Deserialize N-ary Tree
  • 原文地址:https://www.cnblogs.com/csnd/p/12062006.html
Copyright © 2011-2022 走看看