欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[分享]新手学内核第二篇 Shadow SSDT

[分享]新手学内核第二篇 Shadow SSDT

今天再来一篇,再次记录自己学习过程,同时也感慨一下自学的辛苦。
以下内容都是自己自学所理解,如有不对,还望指点。
接上篇:http://bbs.pediy.com/showthread.php?p=1283055#post1283055
上篇主要讲SSDT 的相关内容,这篇说一下Shadow SSDT 相关内容。
对于像我一样,并且想自学的新手来说,Shadow SSDT 学起来要比 SSDT 稍微难一些,有很多东西需要注意,此篇希望对我们新手有所帮助。
Shadow SSDT 是由win32k.sys 未导出的一些函数,所以在微软公开的wrk中查不到对应的代码,学习起来也相对的复杂一些。既然是未导出,那么想要得到KeServiceDescriptorTableShadow 地址,就要麻烦一些。
KeServiceDescriptorTableShadow  跟 KeServiceDescriptorTable 拥有相同的结构。
虽然结构相同,但是 KeServiceDescriptorTableShadow  中 的第一张表存放的依然是SSDT 的函数首地址和个数,想要获取Shadow SSDT 的函数首地址和个数就要 将KeServiceDescriptorTableShadow  + 结构体大小,或者 KeServiceDescriptorTableShadow [1].ServiceTableBase和KeServiceDescriptorTableShadow [1].NumberOfServices
一般,想获取KeServiceDescriptorTableShadow 的话,看到前辈们通常是硬编码或者使用特征码定位的方式。
本人也是使用特征码定位,根据SSDT导出的KeAddSystemServiceTable函数,来定位KeServiceDescriptorTableShadow。
具体看码:
第一:枚举Shadow SSDT 表


/***************************************枚举Shadow 表 START****************************************/

PKeServiceDescriptorTable KeServiceDescriptorTableShadow;

NTSTATUS PsLookupProcessByName(char * processName,OUT PEPROCESS &_processRet);

//通过导出函数KeAddSystemServiceTable 来搜索特征码来定位

extern "C" NTKERNELAPI

VOID

KeAttachProcess (

         __inout PRKPROCESS Process

         );

extern "C" NTKERNELAPI

VOID

KeDetachProcess (

         VOID

         );

extern "C" NTKERNELAPI

BOOLEAN

KeAddSystemServiceTable(

            IN PULONG_PTR Base,

            IN PULONG Count OPTIONAL,

            IN ULONG Limit,

            IN PUCHAR Number,

            IN ULONG Index

            );

ULONG GetKeServiceDescriptorTableShadow()

{

  ULONG ServiceTableShadow=NULL;

  char *p=(char*)KeAddSystemServiceTable;

  for (int i=0;i<0x1024;i++,p++)//转换成char * 的原因是字符串指针每次加1 都是一字节,其他的像int float 每次加1都加4字节

  {

    __try

    {

      ServiceTableShadow=*(ULONG *)p;

    }

    __except(EXCEPTION_EXECUTE_HANDLER)

    {

      return NULL;

    }

    if(MmIsAddressValid((PVOID)ServiceTableShadow))

    {

      if(memcmp((PVOID)ServiceTableShadow,KeServiceDescriptorTable,16)==0)

      {

        if(ServiceTableShadow!=(ULONG)KeServiceDescriptorTable)

        {

          return ServiceTableShadow;

          break;

        }

      }

    }

  }

  return ServiceTableShadow;

}

NTSTATUS ShadowSSDTEnum()

{

  KeServiceDescriptorTableShadow=(PKeServiceDescriptorTable)GetKeServiceDescriptorTableShadow();

  if(KeServiceDescriptorTableShadow!=NULL)

  {

    DbgPrint("KeServiceDescriptorTableShadow=%x \r\n",KeServiceDescriptorTableShadow);

    DbgPrint("Count=%x \r\n",KeServiceDescriptorTableShadow[1].NumberOfServices);

    DbgPrint("ServiceTableBase=%x \r\n",KeServiceDescriptorTableShadow[1].ServiceTableBase);

    

    PEPROCESS keAttactProcess=NULL;

    PsLookupProcessByName("explorer.exe",keAttactProcess);

    KeAttachProcess((PRKPROCESS)keAttactProcess);

    for (int i=0;i<KeServiceDescriptorTableShadow[1].NumberOfServices;i++)

    {

      DbgPrint("索引:%d,地址:%x \r\n",i,KeServiceDescriptorTableShadow[1].ServiceTableBase[i]);

    }

    KeDetachProcess();

  }else

  {

    DbgPrint("定位失败!\r\n");

  }

  return STATUS_SUCCESS;

}

NTSTATUS PsLookupProcessByName(char * processName,OUT PEPROCESS &_processRet)

{

  NTSTATUS statusret=0;

  PEPROCESS _eprocess=PsGetCurrentProcess();

  LIST_ENTRY * CurrentLink;

  LIST_ENTRY * BLink;

  CurrentLink=(LIST_ENTRY *)((ULONG)_eprocess+0x88);

  BLink=CurrentLink->Blink;

  do 

  {

    _eprocess=(PEPROCESS)((ULONG)BLink-0x88);

    char *currentName=(char *)PsGetProcessImageFileName(_eprocess);

    DbgPrint("进程:%s",currentName);

    if(strcmp(currentName,processName)==0)

    {

      _processRet=_eprocess;

      return STATUS_SUCCESS;

      break;

    }

    CurrentLink=(LIST_ENTRY *)((ULONG)_eprocess+0x88);

    BLink=CurrentLink->Blink;

  } while (BLink);

  return statusret;

}

/***************************************枚举Shadow 表 END****************************************/


此处有几个需要说明的。
1.KeServiceDescriptorTableShadow想要使用的话,必须在具有GUI 线程上下文的环境中。具体做法是,查找一个GUI线程上下文环境的进程,附加(KeAttachProcess)
然后,调用KeServiceDescriptorTableShadow,最后退出附加(KeDetachProcess)
2.由于本人很菜,在写PsLookupProcessByName根据进程名获取进程PEPROCESS 时花了不少时间。故在此多写几句,留着以后看。
PEPROCESS 结构体我就不贴了,在windbg下轻松看到。
PEPROCESS +0x88 出的 LIST_ENTRY 活动链。可以获取下一个进程的LIST_ENTRY
再根据LIST_ENTRY - 0x88 来定位下一个进程的 PEPROCESS ,根据进程名来判断是否是自己想要的。PsGetProcessImageFileName(_eprocess);
此函数往上的很多,感觉都不是很明白,干脆在网上copy下来自己改了一下。然后觉得清晰很多。
第二:Shadow SSDT  HOOK NtUserPostThreadMessage
这个直接上码,写的时候,也是很痛苦。

/*************************************Shadow SSDT  HOOK NtUserPostThreadMessage Start****************************/

ULONG OldNtUserPostThreadMessageAddr;

PEPROCESS CurrentkeAttactProcess=NULL;

//函数原型

typedef BOOL 

(*_NtUserPostThreadMessage)(DWORD 

              idThread,UINT 

              Msg,WPARAM 

              wParam,LPARAM lParam);

extern "C" KIRQL

FORCEINLINE

KeGetCurrentIrql (

          VOID

          );

extern "C" NTKERNELAPI

KPROCESSOR_MODE

ExGetPreviousMode(

          VOID

          );

extern "C" PEPROCESS

IoThreadToProcess(

          IN PETHREAD Thread

          );

extern "C" NTKERNELAPI

NTSTATUS

PsLookupThreadByThreadId(

             __in HANDLE ThreadId,

             __deref_out PETHREAD *Thread

             );

_NtUserPostThreadMessage NewUserPostThreadMessage;

/*

*  自定义线程消息函数

*/

NTSTATUS 

MyNtUserPostThreadMessage(DWORD 

              idThread,UINT 

              Msg,WPARAM 

              wParam,LPARAM lParam)

{

  PEPROCESS _eprocess;

  PETHREAD _thread;

  //1.判断自旋锁等级 MyNtUserPostThreadMessage 只能在PASSIVE_LEVEL级别上执行

  if(KeGetCurrentIrql()==PASSIVE_LEVEL)

  {

    //2.判读操作者是否是本身,不是本身操作的就要验证一下

    if(PsGetCurrentProcess()!=CurrentkeAttactProcess)

    {

      //3.判断调用模式,是用户模式还是内核模式

      if(ExGetPreviousMode()!=KernelMode)//用户模式下需要校验是否可读

      {

        __try

        {

          ProbeForRead((PVOID)idThread,sizeof(HANDLE),sizeof(DWORD));

        }

        __except(EXCEPTION_EXECUTE_HANDLER)

        {

          goto _END;

        }

      }

      //通过线程ID 获取PETHREAD

      if(PsLookupThreadByThreadId((HANDLE)idThread,&_thread)==STATUS_SUCCESS)

      {

        //通过PETHREAD 获取PEPROCESS 获取的是被操作者的PEPROCESS

        _eprocess=IoThreadToProcess(_thread);

        if(_eprocess!=NULL)

        {

          ObDereferenceObject(_thread);

          if(_eprocess==CurrentkeAttactProcess)

          {

            return FALSE;

          }

        }else

        {

          ObDereferenceObject(_thread);

          goto _END;

        }

      }

    }

  }

_END:

  return NewUserPostThreadMessage(idThread,Msg,wParam,lParam);

}

NTSTATUS ShadowSSDTHook()

{

  //1.先获取KeServiceDescriptorTableShadow 地址

  KeServiceDescriptorTableShadow=(PKeServiceDescriptorTable)GetKeServiceDescriptorTableShadow();

  if(KeServiceDescriptorTableShadow!=NULL)

  {

    DbgPrint("KeServiceDescriptorTableShadow=%x \r\n",KeServiceDescriptorTableShadow);

    DbgPrint("Count=%x \r\n",KeServiceDescriptorTableShadow[1].NumberOfServices);

    DbgPrint("ServiceTableBase=%x \r\n",KeServiceDescriptorTableShadow[1].ServiceTableBase);

    

    PsLookupProcessByName("explorer.exe",CurrentkeAttactProcess);//explorer可以换成自己要保护的进程

    if(CurrentkeAttactProcess)

    {

      KeAttachProcess((PRKPROCESS)CurrentkeAttactProcess);

      //2.获取要hook 函数的首地址

      OldNtUserPostThreadMessageAddr=KeServiceDescriptorTableShadow[1].ServiceTableBase[476];

      NewUserPostThreadMessage=(_NtUserPostThreadMessage)OldNtUserPostThreadMessageAddr;

      PageClose();

      KeServiceDescriptorTableShadow[1].ServiceTableBase[476]=(ULONG)MyNtUserPostThreadMessage;

      PageOpen();

      KeDetachProcess();

    }else

    {

      return 0;

    }

    

  }else

  {

    DbgPrint("KeServiceDescriptorTableShadow定位失败!\r\n");

    return 0;

  }

  return STATUS_SUCCESS;

}

NTSTATUS ShadowSSDTUnHook()

{

  if(KeServiceDescriptorTableShadow!=NULL)

  {

    PsLookupProcessByName("explorer.exe",CurrentkeAttactProcess);//explorer可以换成自己要保护的进程

    if(CurrentkeAttactProcess)

    {

      KeAttachProcess((PRKPROCESS)CurrentkeAttactProcess);

      PageClose();

      KeServiceDescriptorTableShadow[1].ServiceTableBase[476]=OldNtUserPostThreadMessageAddr;

      PageOpen();

      KeDetachProcess();

    }else

    {

      return 0;

    }

  }else

  {

    DbgPrint("KeServiceDescriptorTableShadow定位失败!\r\n");

    return 0;

  }

  return STATUS_SUCCESS;

}

/*************************************Shadow SSDT  HOOK NtUserPostThreadMessage End******************************/


注意:在hook 时,也是在有GUI线程环境上下文才能HOOK成功,如不然,蓝屏。
学习时碰到的问题:
1.不知道IRQL ,没注意判断,蓝屏十几次,终于无法忍受,查阅资料并改之。
if(KeGetCurrentIrql()==PASSIVE_LEVEL){}
2.不知道如何过滤消息,查百度如何过滤,大概的做法,判断当前操作者和被操作者。
像一些KeGetCurrentIrql、ExGetPreviousMode、IoThreadToProcess、PsLookupThreadByThreadId都可以在WRK 中获得。需要自己声明一下。

上传的附件 code.zip

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功