• 菜鸟开始学习SSDT HOOK((附带源码)


    看了梦无极的ssdt_hook教程,虽然大牛讲得很细,但是很多细节还是要自己去体会,才会更加深入。在这里我总结一下我的分析过程,若有不对的地方,希望大家指出来。
    首先我们应该认识 ssdt是什么?从梦无极的讲解过程中,我联想到了这样一个场景:

    古时候有一户人家,姓李,住在皇宫外面,他们家有一个女儿进皇宫当宫女。有一天,李家的老大爷要送东西给他女儿,我们假定他可以进皇宫,于是就开始了这样一个过程。进皇宫只有一条路,那就是走正门,于是李大爷带着东西从家走到了城外,进皇宫前必须从这门口进去,通过一条路后才能到达皇宫,守门的侍卫挺友善的,把老大爷的东西让一个侍卫拿到一个地方去放着,然后叫了一个手下带着老大爷往里走,当走到皇宫门口时,守城门的侍卫就离开了,让另一个人带着老大爷继续走,然后老大爷就进了皇宫,进了皇宫后,有一个侍卫来问李大爷的女儿是在哪边,这里皇宫只有东边和西边。于是李大爷跟他说在东边,于是侍卫把李大爷带到了东边,然后见到一个总管,李大爷给出女儿是几号房的,总管根据一个皇室总表,找到了李大爷的女儿住址,于是李大爷就找到了他女儿,总管还告诉他,你带的东西可以在哪 里去取。

    这里拿OpenProcess函数比作李大爷,NtOpenProcess比作李大爷的女儿,OpenProcess通过普通的调用规则进行调用来到城外,再通过ZwOpenProcess进行接见,带着它往皇宫走,然后遇到KiFastSystemCall,带他进入了皇宫。进了ring0后,需要知道是ssdt还是shawssdt,于是分东边的西边,我们假定ssdt就是东西。于是从东边 的路走过去,遇到总管KiFastCallEntry,给出函数偏移号,总管手里的皇室总表就是ssdt这个数据结构,于是找到了NtOPenProcess,而OpenProcess的参数即李大爷带的东西在ParamTableBase这里去找。

    好吧,故事讲完了,我们继续分析。
    ssdt竟然是一个数组,那么我们就需要先得到这个数组的首地址,于是这个结构就出来了。
    代码:

    typedef struct ServiceDescriptorEntry {
      unsigned int *ServiceTableBase;
      unsigned int *ServiceCounterTableBase;
      unsigned int NumberOfServices;
      unsigned char *ParamTableBase;
    } ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;

    这个结构只需要知道两个参数的作用,一个是ServiceTableBase,这个参数是ssdt数组的基址,有了它,我们再给出具体函数的偏移,就能找到正确的函数地址了。
    另一个是NumberOfService,这个是ssdt这个数组的最大值,也就是ssdt中函数的个数。
    有了这个结构过后,按照偏移就可以找到想要Hook的函数的地址了。但是hook之前,需要修改内核保护,否则会无情蓝屏。
    代码:

    void PageProtectOff()
    {
      __asm{
        cli
          mov  eax,cr0
          and  eax,not 10000h
          mov  cr0,eax
        }
    }

    Cli表示让其他人都休息,看我一个人表演。
    Cro这个寄存器就保存了内核保护的标志位,用 10000h取反再和他进行与运算,就使标志位从1变成0了,就可以修改内核了。当然,完成后得修改回来,否则会出现这样的情况,你在上面跳舞,别人看你表演,你跳完了,别人还一直盯着舞台,以为你还在表演。
    代码:

    void PageProtectOn()
    {
      __asm{
        mov  eax,cr0
        or   eax,10000h
        mov  cr0,eax
        sti
      }
    }

    修改了内核保护后,就可以把自己定义的函数地址写在ssdt里面了吗?当然可以,但是为了系统安全,我们需要把之前的函数地址保存下来,在我们卸载驱动的时候恢复回去,以免导致系统不能正常工作。

    还是以OpenProcess函数作参考 吧。
    在我电脑里这个函数的偏移是122,如图:
    点击图片以查看大图 图片名称: 3.png 查看次数: 6 文件大小: 30.9 KB 文件 ID : 81351
    代码:

    NTSTATUS ssdt_hook()
    {
      O_NtOpenProcess=KeServiceDescriptorTable.ServiceTableBase[122];
      PageProtectOff();
      KeServiceDescriptorTable.ServiceTableBase[122]=(unsigned int)MyNtOpenProcess;
      PageProtectOn();
      return STATUS_SUCCESS;
    }

    为了使驱动完成指定的任务,比如我们要保护记事本这个进程不被OpenProcess打开,于是我们在自己的NtOpenProcess函数中进行过滤,那么该怎样进行过滤呢?来看看NtOpenProcess函数的声明
    代码:

    NTSTATUS MyNtOpenProcess (
      __out PHANDLE ProcessHandle,
      __in ACCESS_MASK DesiredAccess,
      __in POBJECT_ATTRIBUTES ObjectAttributes,
      __in_opt PCLIENT_ID ClientId
      )

    这里有一个参数:ClientId,这个按我的理解是目标进程的一个结构,目标进程也就是其它进程想到打开的进程。这个结构体是这样定义的
    代码:

    typedef struct _CLIENT_ID {
        HANDLE UniqueProcess;
        HANDLE UniqueThread;
    } CLIENT_ID;

    我们可以通过他的第一个成员UniqueProcess找到该进程的进程对象,就是eprocess,那么该怎么得到eprocess这个结构体呢,于是有了下面这个函数。
    代码:

    NTSTATUS
      PsLookupProcessByProcessId(
      IN HANDLE ProcessId,
      OUT PEPROCESS *Process
      );

    EPROCESS结构体打开是这个样子的
    代码:

    lkd> dt _eprocess
    ntdll!_EPROCESS
       +0x000 Pcb              : _KPROCESS
       +0x06c ProcessLock      : _EX_PUSH_LOCK
       +0x070 CreateTime       : _LARGE_INTEGER
       +0x078 ExitTime         : _LARGE_INTEGER
       +0x080 RundownProtect   : _EX_RUNDOWN_REF
       +0x084 UniqueProcessId  : Ptr32 Void
       +0x088 ActiveProcessLinks : _LIST_ENTRY
       +0x090 QuotaUsage       : [3] Uint4B
       +0x09c QuotaPeak        : [3] Uint4B
       +0x0a8 CommitCharge     : Uint4B
       +0x0ac PeakVirtualSize  : Uint4B
       +0x0b0 VirtualSize      : Uint4B
       +0x0b4 SessionProcessLinks : _LIST_ENTRY
       +0x0bc DebugPort        : Ptr32 Void
       +0x0c0 ExceptionPort    : Ptr32 Void
       +0x0c4 ObjectTable      : Ptr32 _HANDLE_TABLE
       +0x0c8 Token            : _EX_FAST_REF
       +0x0cc WorkingSetLock   : _FAST_MUTEX
       +0x0ec WorkingSetPage   : Uint4B
       +0x0f0 AddressCreationLock : _FAST_MUTEX
       +0x110 HyperSpaceLock   : Uint4B
       +0x114 ForkInProgress   : Ptr32 _ETHREAD
       +0x118 HardwareTrigger  : Uint4B
       +0x11c VadRoot          : Ptr32 Void
       +0x120 VadHint          : Ptr32 Void
       +0x124 CloneRoot        : Ptr32 Void
       +0x128 NumberOfPrivatePages : Uint4B
       +0x12c NumberOfLockedPages : Uint4B
       +0x130 Win32Process     : Ptr32 Void
       +0x134 Job              : Ptr32 _EJOB
       +0x138 SectionObject    : Ptr32 Void
       +0x13c SectionBaseAddress : Ptr32 Void
       +0x140 QuotaBlock       : Ptr32 _EPROCESS_QUOTA_BLOCK
       +0x144 WorkingSetWatch  : Ptr32 _PAGEFAULT_HISTORY
       +0x148 Win32WindowStation : Ptr32 Void
       +0x14c InheritedFromUniqueProcessId : Ptr32 Void
       +0x150 LdtInformation   : Ptr32 Void
       +0x154 VadFreeHint      : Ptr32 Void
       +0x158 VdmObjects       : Ptr32 Void
       +0x15c DeviceMap        : Ptr32 Void
       +0x160 PhysicalVadList  : _LIST_ENTRY
       +0x168 PageDirectoryPte : _HARDWARE_PTE_X86
       +0x168 Filler           : Uint8B
       +0x170 Session          : Ptr32 Void
       +0x174 ImageFileName    : [16] UChar
       +0x184 JobLinks         : _LIST_ENTRY
       +0x18c LockedPagesList  : Ptr32 Void
       +0x190 ThreadListHead   : _LIST_ENTRY
       +0x198 SecurityPort     : Ptr32 Void
       +0x19c PaeTop           : Ptr32 Void

    得到进程的EPROCESS结构体后,就可以用windbg找到基中的imagebase成员,在我电脑里是偏移0x174,这个成员就表示为目标进程的名字,我们可以通过这个名字与想要保护的进程的名字进行比较,如果是一样的,则直接返回打开不成功,这样就保护到了我们想要保护的进程。
    代码:

      if(ProtectProcess(ClientId->UniqueProcess,"notepad.exe"))
      {
        KdPrint(("hooked %s",(char *)PsGetCurrentProcess()+0x174));
        return STATUS_UNSUCCESSFUL;
      }


    status=PsLookupProcessByProcessId(ProcessId,&process_obj);
      if(!NT_SUCCESS(status))
      {
        KdPrint(("error code:%X---ProcessId:%d",status,ProcessId));
        return FALSE;
      }
      if(!strcmp((char *)process_obj+0x174,str_ProtectObjName))
      {
        ObDereferenceObject(process_obj);
        return TRUE;
      }

    为了让其它进程不受影响,我们在处理完这些东西后,应该再用原来的函数基址进行正常调用,使不被保护的进程能够正常工作
    代码:

     
    //KdPrint(("Hook Success!"));
      return ((MYNTOPENPROCESS)O_NtOpenProcess)(ProcessHandle,
        DesiredAccess,
        ObjectAttributes,
        ClientId);
    }

    写到这里我们的任务就完成了,但是为了让我们驱动卸载后,系统还能正常工作,我们需要把保存到的以前的函数地址还原到ssdt结构中,于是有了下面的代码 。
    代码:

    void UnHookSsdt()
    {
      PageProtectOff();
      KeServiceDescriptorTable.ServiceTableBase[122]=O_NtOpenProcess;
      PageProtectOn();
    }

    好了,分析就到这里,写完这篇日志,我又对ssdt_hook加深了印象。
    介于零散的代码大家看起来很乱, 这里我把整 个代码 都 发出来,就容易学习了。
    代码:

    #include "ntddk.h"

    #pragma pack(1)
    typedef struct ServiceDescriptorEntry {
      unsigned int *ServiceTableBase;
      unsigned int *ServiceCounterTableBase;
      unsigned int NumberOfServices;
      unsigned char *ParamTableBase;
    } ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
    #pragma pack()

    NTSTATUS
      PsLookupProcessByProcessId(
      IN HANDLE ProcessId,
      OUT PEPROCESS *Process
      );
    __declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;

    typedef NTSTATUS(*MYNTOPENPROCESS)(
      OUT PHANDLE             ProcessHandle,
      IN ACCESS_MASK          AccessMask,
      IN POBJECT_ATTRIBUTES   ObjectAttributes,
      IN PCLIENT_ID           ClientId );//定义一个指针函数,用于下面对O_NtOpenProcess进行强制转换
    ULONG O_NtOpenProcess;

    BOOLEAN ProtectProcess(HANDLE ProcessId,char *str_ProtectObjName)
    {
      NTSTATUS status;
      PEPROCESS process_obj;
      if(!MmIsAddressValid(str_ProtectObjName))//这个条件是用来判断目标进程名是否有效
      {
        return FALSE;
      }
      if(ProcessId==0)//这个条件是用来排除System Idle Process进程的干扰
      {
        return FALSE;
      }
      status=PsLookupProcessByProcessId(ProcessId,&process_obj);//这句用来获取目标进程的EPROCESS结构
      if(!NT_SUCCESS(status))
      {
        KdPrint(("我错了,这个是错误号:%X---这个是进程ID:%d",status,ProcessId));
        return FALSE;
      }
      if(!strcmp((char *)process_obj+0x174,str_ProtectObjName))//进行比较
      {
        ObDereferenceObject(process_obj);//对象计数器减1,为了恢复对象管理器计数,便于回收
        return TRUE;
      }
      ObDereferenceObject(process_obj);
      return FALSE;
    }
    NTSTATUS MyNtOpenProcess (
      __out PHANDLE ProcessHandle,
      __in ACCESS_MASK DesiredAccess,
      __in POBJECT_ATTRIBUTES ObjectAttributes,
      __in_opt PCLIENT_ID ClientId
      )
    {
      //KdPrint(("%s",(char *)PsGetCurrentProcess()+0x174));
      if(ProtectProcess(ClientId->UniqueProcess,"calc.exe"))
      {
        KdPrint(("%s想打开我吗?不可能。哈哈。。",(char *)PsGetCurrentProcess()+0x174));
        return STATUS_UNSUCCESSFUL;
      }
      //KdPrint(("Hook Success!"));
      return ((MYNTOPENPROCESS)O_NtOpenProcess)(ProcessHandle,//处理完自己的任务后,调用原来的函数,让其它进程正常工作
        DesiredAccess,
        ObjectAttributes,
        ClientId);
    }

    void PageProtectOff()//关闭页面保护
    {
      __asm{
        cli
          mov  eax,cr0
          and  eax,not 10000h
          mov  cr0,eax
        }
    }
    void PageProtectOn()//打开页面保护
    {
      __asm{
        mov  eax,cr0
        or   eax,10000h
        mov  cr0,eax
        sti
      }
    }

    void UnHookSsdt()
    {
      PageProtectOff();
      KeServiceDescriptorTable.ServiceTableBase[122]=O_NtOpenProcess;//恢复ssdt中原来的函数地址
      PageProtectOn();
    }
    NTSTATUS ssdt_hook()
    {
      //int i;
      //for(i=0;i<KeServiceDescriptorTable.NumberOfServices;i++)
      //{
      //  KdPrint(("NumberOfService[%d]-------%x",i,KeServiceDescriptorTable.ServiceTableBase[i]));
      //}
      O_NtOpenProcess=KeServiceDescriptorTable.ServiceTableBase[122];//保存原来的函数地址
      PageProtectOff();
      //将原来ssdt中所要hook的函数地址换成我们自己的函数地址
      KeServiceDescriptorTable.ServiceTableBase[122]=(unsigned int)MyNtOpenProcess;
      PageProtectOn();
      return STATUS_SUCCESS;
    }
    void DriverUnload(PDRIVER_OBJECT pDriverObject)
    {
      UnHookSsdt();
      KdPrint(("Driver Unload Success !"));
    }
    NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING pRegsiterPath)
    {
      DbgPrint("This is My First Driver!");
      ssdt_hook();
      pDriverObject->DriverUnload = DriverUnload;
      return STATUS_SUCCESS;
    }




    附件中的工程文件是我用vs2010配置好的,如果是新手的话,只需要在电脑里安装vs2010,和winddk,这里winddk我用的是7600.16385.1这个版本,安装在C盘,就可以直接编译我的这个工程 文件了,不需要再设置那些麻烦的东西。

    http://pan.baidu.com/share/link?shareid=3264102389&uk=3895584076

  • 相关阅读:
    BIO与NIO、AIO的区别
    Java虚拟机
    PV模型
    HashMap、HashSet源代码分析其 Hash 存储机制
    单节点到分布式集群
    Oracle表分区
    ZooKeeper原理
    oracle中的 exists 和 in 的效率问题
    OQL对象查询语言
    keepalived openssl 报错
  • 原文地址:https://www.cnblogs.com/microzone/p/3235990.html
Copyright © 2020-2023  润新知