过 DNF TP 驱动保护(二)

01. 博文简介:

02. 环境及工具准备:

03. 分析 TP 所做的保护:

04. 干掉 NtOpenProcess 中的 Deep InLine Hook:

05. 干掉 NtOpenThread 中的 Deep InLine Hook:

06. 干掉 NtReadVirtualMemory 中的 InLine Hook:

07. 干掉 NtWriteVirtualMemory 中的 InLine Hook:

08. 干掉 KiAttachProcess 的 InLine Hook:

09. 干掉 NtGetContextThread 中的 InLine Hook:

10. 干掉 NtSetContextThread 中的 InLine Hook:

11. 干掉 DbgkpQueueMessage 中的 InLine Hook:

12. 干掉 DbgkpSetProcessDebugObject 中的 InLine Hook:

13. 干掉 Debug 清零:

                                            

                                            

共四篇,本篇为第二篇。

                                            

                                

06. 干掉 NtReadVirtualMemory 中的 InLine Hook:

                                

前面已经干掉了 TP 对 NtOpenProcess 以及 TP 对 NtOpenThread 所做的 Hook,

这样的话,我们已经可以使用 OD 或者 CE 看到 DNF 的游戏进程了,

弄过一些游戏方面内容的朋友应该都是知道 CE 的,这东东是开源的,不过是基于 Delphi 的,

工具做得很不错,可以通过 CE 来修改一些游戏进程的内存数据,比如可以修改一些简单的游戏的血值啊之类的,

但是,此时我们可以用 CE 来扫描一下 DNF 游戏进程的内存,你会发现根本扫描不到任何数据,

其实原因也很简单,TP 对 NtReadVirtualMemory 进行了浅层的 InLine Hook,

从而可以达到防止其他进程读取 DNF 游戏进程内存的目的,

而 CE 的话,在 Ring3 下是通过 ReadProcessMemory 来读取游戏内存的,

而 ReadProcessMemory 进入 Ring0 后又是调用的 NtReadVirtualMemory,

由于 NtReadVirtualMemory 被 TP 干掉了,所以自然用 CE 是扫描不出任何东西的,

要干掉 TP 对 NtReadVirtualMemory 所作的浅层 InLine Hook 其实是比较简单的,

因为 TP 并没有对 NtReadVirtualMemory 做检测,所以可以直接用 SSDT 来对抗掉浅层的 InLine Hook 就 OK。

至于原理的话,很简单,直接用 SSDT Hook 替换掉系统服务 NtReadVirtualMemory,

然后在 SSDTHookNtReadVirtualMemory 这个我们自己写的系统服务中判断,

如果是 DNF 进程的话,直接调用原来的 SSDT 系统服务就好,如果不是 DNF 进程的话,

我就跳过 TP 对 NtReadVirtualMemory 所做的 InLine Hook,也就是跳过前面 7 个字节就 OK 了。

对于这种干掉 InLine Hook 的原理,堕落天才的文章讲的最清楚了,我这里就不再班门弄斧了。

(继续为堕落天才打广告)

对于用 SSDT Hook 干掉浅层的 InLine Hook 可以参考看雪上堕落天才的文章:

文章名称:《SSDT Hook 的妙用 - 对抗 Ring0 InLine Hook》

文章地址:http://bbs.pediy.com/showthread.php?t=40832

有的童鞋可能会问,咱是如何知道 NtReadVirtualMemory 被 TP 干掉了呢 ?

很简单,还是采用前面的做法,用 Kernel Detective 就可以看到了,具体可以看下面的截图:

image_thumb[108]

image_thumb[103]

                                

至于如何安装 SSDT Hook 或者 SSDT Hook 是啥玩意来着的话,大家有兴趣的可以参考我的下面博文系列:

《进程隐藏与进程保护(SSDT Hook 实现)》系列,共三篇。

http://www.cnblogs.com/BoyXiao/archive/2011/09/03/2164574.html

http://www.cnblogs.com/BoyXiao/archive/2011/09/04/2166596.html

http://www.cnblogs.com/BoyXiao/archive/2011/09/05/2168115.html

                                

下面先贴出安装 SSDT 钩子的代码,该代码用来干掉 TP 对 NtReadVirtualMemory 的 InLine Hook:

   1:  /************************************************************************/
   2:  /* 安装钩子从而过掉 TP 保护所 Hook 的 NtReadVirtualMemory - 让 TP 失效
   3:  /* 保存 NtReadVirtualMemory 第 4,5,6,7 个字节(其实就是一个 ULONG 跳转地址)
   4:  /* 因为这几个字节在不同的 XP 上会改变,所以在 SSDT Hook 之前保存下来
   5:  /* 从而避免在此处进行硬编码
   6:  /************************************************************************/
   7:  VOID InstallPassTPNtReadVirtualMemory()
   8:  {
   9:      if(g_SSDTHookNtReadVirtualMemory > 0)
  10:      {
  11:          /* 获得 NtReadVirtualMemory 的地址 */
  12:          ULONG uNtReadVirtualMemoryAddr = oldSysServiceAddr[g_SSDTHookNtReadVirtualMemory];
  13:   
  14:          /* 如果是 DNF 进程,则跳到 NtReadVirtualMemory 执行,即不处理,从而让 DNF InLine Hook 生效 */
  15:          uTPHookedNtReadVirtualMemoryJmpAddr = uNtReadVirtualMemoryAddr;
  16:          /* 如果不是 DNF 进程,则跳过 TP 的 InLine Hook,从而使 TP 失效 */
  17:          uMyHookedNtReadVirtualMemoryJmpAddr = uNtReadVirtualMemoryAddr + 7;
  18:          /* 保存下未 Hook 之前的 NtReadVirtualMemory 的第 4,5,6,7 个字节 */
  19:          uNtReadVirtualMemoryAddr_3 = *((ULONG *)(uNtReadVirtualMemoryAddr + 3));
  20:   
  21:          InstallSysServiceHookByIndex(g_SSDTHookNtReadVirtualMemory, SSDTHookNtReadVirtualMemory);
  22:   
  23:          KdPrint(("Pass TP - NtReadVirtualMemory Installed."));
  24:      }
  25:  }

                                

下面再给出 SSDT Hook 的中继 API 的实现代码:

   1:  /************************************************************************/
   2:  /* 自定义的 NtReadVirtualMemory,用来实现 SSDT Hook Kernel API
   3:  /************************************************************************/
   4:  NTSYSHOOKAPI VOID SSDTHookNtReadVirtualMemory()
   5:  {
   6:      /* 开始过滤 */
   7:      if(ValidateCurrentProcessIsDNF() == TRUE)
   8:      {
   9:          __asm
  10:          {
  11:              /* 如果是 DNF 进程调用的话,则调用已经被 TP Hook 的 NtReadVirtualMemory */
  12:              jmp    uTPHookedNtReadVirtualMemoryJmpAddr
  13:          }
  14:      }
  15:   
  16:      __asm
  17:      {
  18:          /* 已经做了针对硬编码的处理 */
  19:          /* 如果不是 DNF 进程调用的话,则跳过 TP Hook 的 NtReadVirtualMemory */
  20:          push    0x1C
  21:          push    uNtReadVirtualMemoryAddr_3
  22:          jmp     uMyHookedNtReadVirtualMemoryJmpAddr
  23:      }
  24:  }

                              

好,到这里就已经干掉了 TP 对 NtReadVirtualMemory 所做的 InLine Hook了,

对此最直白的效果就是用 CE 打开 DNF 游戏进程进行内存扫描,你会发现,On Year,可以正常扫描到 DNF 内存了。

image

image

                                                

                                                

07. 干掉 NtWriteVirtualMemory 中的 InLine Hook:

                                                

上面又干掉了 TP 对 NtReadVirtualMemory 的 InLine Hook 了,从而实现了 CE 读取 DNF 进程的内存。

但是你可以试着用 CE 修改 DNF 进程的内存,你很快就会发现,虽然可以扫描内存,但是并不可以读取内存,

而后你也肯定能够想到,既然有防止读取内存,那肯定也有防止写入内存,

而后你又自然会找到 NtWriteVirtualMemory 上来,等你找到 NtWriteVirtualMemory 后,

你又基本能够确定,搞定这个 API 和搞定 NtReadVirtualMemory 应该是差不多的,

因为这两个 API 就是一对啊,一个读,一个写,所以 TP 肯定也是采用相同的做法来处理这两个 API,

当然,你上面的猜想都是正确的,所以咱还是用 SSDT 来干掉 TP 对 NtWriteVirtualMemory 所做的 InLine。

代码和上面的 NtReadVirtualMemory 差不多,这里也还是贴出来一下吧,俺先放两幅截图,然后再贴代码出来:

image_thumb[98]

image_thumb[93]

                                                

下面先贴出安装 SSDT 钩子的代码,该代码用来干掉 TP 对 NtWriteVirtualMemory 的 InLine Hook:

   1:  /************************************************************************/
   2:  /* 安装钩子从而过掉 TP 保护所 Hook 的 NtWriteVirtualMemory - 让 TP 失效
   3:  /* 保存 NtWriteVirtualMemory 第 4,5,6,7 个字节(其实就是一个 ULONG 跳转地址)
   4:  /* 因为这几个字节在不同的 XP 上会改变,所以在 SSDT Hook 之前保存下来
   5:  /* 从而避免在此处进行硬编码
   6:  /************************************************************************/
   7:  VOID InstallPassTPNtWriteVirtualMemory()
   8:  {
   9:      if(g_SSDTHookNtWriteVirtualMemory > 0)
  10:      {
  11:          /* 获得 NtWriteVirtualMemory 的地址 */
  12:          ULONG uNtWriteVirtualMemoryAddr = oldSysServiceAddr[g_SSDTHookNtWriteVirtualMemory];
  13:   
  14:          /* 如果是 DNF 进程,则跳到 NtWriteVirtualMemory 执行,即不处理,从而让 DNF InLine Hook 生效 */
  15:          uTPHookedNtWriteVirtualMemoryJmpAddr = uNtWriteVirtualMemoryAddr;
  16:          /* 如果不是 DNF 进程,则跳过 TP 的 InLine Hook,从而使 TP 失效 */
  17:          uMyHookedNtWriteVirtualMemoryJmpAddr = uNtWriteVirtualMemoryAddr + 7;
  18:          /* 保存下未 Hook 之前的 NtReadVirtualMemory 的第4,5,6,7 个字节 */
  19:          uNtWriteVirtualMemoryAddr_3 = *((ULONG *)(uNtWriteVirtualMemoryAddr + 3));
  20:   
  21:          InstallSysServiceHookByIndex(g_SSDTHookNtWriteVirtualMemory, SSDTHookNtWriteVirtualMemory);
  22:   
  23:          KdPrint(("Pass TP - NtWriteVirtualMemory Installed."));
  24:      }
  25:  }

                               

下面再给出 SSDT Hook 的中继 API 的实现代码:

   1:  /************************************************************************/
   2:  /* 自定义的 NtWriteVirtualMemory,用来实现 SSDT Hook Kernel API
   3:  /************************************************************************/
   4:  NTSYSHOOKAPI VOID SSDTHookNtWriteVirtualMemory ()
   5:  {
   6:      /* 开始过滤 */
   7:      if(ValidateCurrentProcessIsDNF() == TRUE)
   8:      {
   9:          __asm
  10:          {
  11:              /* 如果是 DNF 进程调用的话,则调用已经被 TP Hook 的 NtWriteVirtualMemory */
  12:              jmp    uTPHookedNtWriteVirtualMemoryJmpAddr
  13:          }
  14:      }
  15:   
  16:      __asm
  17:      {
  18:          /* 已经做了针对硬编码的处理 */
  19:          /* 如果不是 DNF 进程调用的话,则跳过 TP Hook 的 NtWriteVirtualMemory */
  20:          push    0x1C
  21:          push    uNtWriteVirtualMemoryAddr_3
  22:          jmp     uMyHookedNtWriteVirtualMemoryJmpAddr
  23:      }
  24:  }

                                                

好,到这里就已经拿下了 TP 对 NtWriteVirtualMemory 所做的 InLine Hook了,

对此最直白的效果大家也都可以想象得到了,那就是直接用 CE 打开 DNF 游戏进程进行内存修改,

你会发现,此时可以正常修改掉 DNF 游戏的内存了。

image

                                     

                                

08. 干掉 KiAttachProcess 中的 InLine Hook:

                                                

前面已经干掉 NtOpenProcess 和 NtOpenThread 了,按道理来说,咱可以开始用 OD 来调试 DNF 游戏进程了,

不过你可以用 OD 尝试附加一下 DNF 的游戏进程,而后你会发现根本附加不上去,这又是为何呢 ?

image

首先需要明白 Ring3 在何种操作下,会导致内核调用 KiAttachProcess,

从字面意思上看,就是附加进程,在 Ring3 下的附加进程操作一般会出现在调试进程的时候,

比如用 OD 附加进程来进行调试,或者用 Visual Studio 附加进程进行调试,

其实咱猜的没错,就是当附加进程的时候会导致内核调用 KiAttachProcess,

TP 对这个未导出的 API 进行了 InLine Hook,从而使得 DNF 游戏进程不能被附加,

这样的话,咱的 OD 或者 Visual Studio 都是不能够附加上 DNF 的游戏进程来进行调试了。

正如前面分析的 NtReadVirtualMemory 和 NtWriteVirtualMemory 这两个 API 一样,

TP 对 KiAttachProcess 也是做的浅层的 InLine Hook,也就是只是 Hook 了函数头 7 个字节,

且 TP 对 KiAttachProcess 的 InLine Hook 也没有检测,所以干掉这个 API 还是比较简单的,

不过我们不可以像干掉 NtRead/WriteVirtualMemory 一样用 SSDT Hook 来对抗掉,

因为 KiAttachProcess 并没有在 SSDT 表中,不过我们可以直接恢复 TP 对 KiAttachProcess 的 Hook 即可。

                                                

前面也提到了 KiAttachProcess 是一个未导出的内核 API,

所以我们不能使用 MmGetSystemRoutineAddress 来获得它的地址,

不过好在 KeAttachProcess 这是一个导出的内核 API,

(一般 Kixxx 都是未导出的 API,而 Kexxx 则是导出的 API)

在 KeAttachProcess 的内部实质上是调用的 KiAttachProcess 来完成功能的,

所以咱可以通过 KeAttachProcess 来定位到 KiAttachProcess 的地址,

并且让人欣喜的是在 KeAttachProcess 中的第一个 call 就是 call KiAttachProcess,

所以搜索特征码也更加简单了,直接搜索第一个 e8 就 OK。

如果用 WinDbg 的话,你可以输几个命令就可以把 KeAttachProcess 的地址打印出来,但是现在咱走点弯路,

在驱动里面用 KdPrint 打印出 KeAttachProcess 的地址,然后我们再分析。

   1:  KdPrint(("KeAttachProcess: %x.", MmGetSystemFunAddress(L"KeAttachProcess")));

                                

image

                                

好,这里得到了 KeAttachProcess 的地址了,

那么咱就可以在 Kernel Detective 中来看看 KeAttachProcess 的反汇编代码了,

具体的就看图说话,俺也就不多说了,因为截图里面都明明白白摆着在哪里,

image

image

image

image

image

                                 
下面给出获取 KiAttachProcess 地址的代码:

   1:  /************************************************************************/
   2:  /* 获取函数 KiAttachProcess 的地址
   3:  /* KiAttachProcess 在 KeAttachProcess 中的第一个 Call(e8 指令) 位置
   4:  /************************************************************************/
   5:  ULONG GetKiAttachProcessAddr()
   6:  {
   7:      ULONG uCallAddr = 0;
   8:      ULONG uKeAttachProcessAddr = 0;
   9:      ULONG uKiAttachProcessAddr = 0;
  10:      CHAR szCode[1] = 
  11:      {
  12:          (char)0xe8
  13:      };
  14:   
  15:      /* 获取 KeAttachProcess 的地址 */
  16:      uKeAttachProcessAddr = MmGetSystemFunAddress(L"KeAttachProcess");
  17:   
  18:      /* 搜索特征码 e8 */
  19:      uCallAddr = SearchFeature(uKeAttachProcessAddr, szCode, 1);
  20:      if (uCallAddr == 0)
  21:      {
  22:          uKiAttachProcessAddr = 0;
  23:      }
  24:      else
  25:      {
  26:          /* 获取 KiAttachProcess 的地址 */
  27:          uKiAttachProcessAddr = *((ULONG *)uCallAddr) + uCallAddr + 4;
  28:      }
  29:   
  30:      return uKiAttachProcessAddr;
  31:  }

                              

得到了 KiAttachProcess 的地址,那么下面就要来干掉 KiAttachProcess 了,

为了简单实现,我一开始干掉 KiAttachProcess 的做法是采用的硬编码,

在得到 KiAttachProcess 地址后,可以用 Xuetr 来反汇编这个地址,

从而看到 KiAttachProcess 的反汇编指令,所以我的做法就是,

直接将 Xuetr 中 KiAttachProcess 的头 7 个字节以硬编码的形式保存在数组中,

然后等 TP 启动后,我用保存下来的这 7 个字节直接去恢复 KiAttachProcess 就 OK,

这种方式在我的虚拟机 XP 里面是 OK 的,因为我就是从这台 XP 上获取的 7 个字节的硬编码,

但是在别的 XP 系统上就不 OK 了,原因是 KiAttachProcess 头 7 个字节并不是所有的 XP 都相同的,

直接拿不一致的硬编码去恢复肯定是会 BSOD 的,不过后来我用了一种简单的办法就干掉这个问题了,

办法很简单,在 TP 启动之前,我动态去读取 KiAttachProcess 的头 7 个字节,并且保存在数组中,

等 TP 启动后,我就用数组中的这 7 个字节去恢复 TP 对 KiAttachProcess 所做的 InLine Hook。

                                

实现的具体代码很简单,下面也贴出来:

先在 TP 启动之前保存 KiAttachProcess 的头 7 个字节:

   1:      PUCHAR pKiAttachProcessAddr = NULL;
   2:   
   3:      pKiAttachProcessAddr = (PUCHAR)GetKiAttachProcessAddr();
   4:   
   5:      KdPrint(("KiAttachProcess: %x.", uKiAttachProcessAddr));
   6:   
   7:      /* 保存 KiAttachProcess 被 DNF Hook 之前的头 API_HOOK_HEADER_LEN 个字节 */
   8:      for(uIndex = 0; uIndex < API_HOOK_HEADER_LEN; uIndex++)
   9:      {
  10:          szKiAttachProcessOriginCode[uIndex] = pKiAttachProcessAddr[uIndex];
  11:      }

                                

TP 启动之后恢复被 TP 修改掉的 KiAttachProcess 的头 7 个字节:

   1:  /************************************************************************/
   2:  /* 恢复 KiAttachProcess 头 9 个字节从而恢复 KiAttachProcess - 让 TP 失效
   3:  /* 这里使用了硬编码,去掉硬编码的思路如下:
   4:  /* 在 TP 未启动之前,先保存下 KiAttachProcess 的头 9 个字节
   5:  /* 然后在 TP 启动之后,恢复这 9 个字节就 OK
   6:  /************************************************************************/
   7:  VOID RecoveryTPHookedKiAttachProcess()
   8:  {
   9:      ULONG uIndex = 0;
  10:      ULONG uOldAttr = 0;
  11:   
  12:      KIRQL kOldIRQL = PASSIVE_LEVEL;
  13:      PUCHAR pKiAttachProcessAddr = NULL;
  14:   
  15:      /* 获取到 KiAttachProcess 的地址 */
  16:      pKiAttachProcessAddr = (PUCHAR)GetKiAttachProcessAddr();
  17:   
  18:      EnableWriteProtect(&uOldAttr);
  19:      kOldIRQL = KeRaiseIrqlToDpcLevel();
  20:   
  21:      /* 恢复 KiAttachProcess 的头 API_HOOK_HEADER_LEN 个字节 */
  22:      for(uIndex = 0; uIndex < API_HOOK_HEADER_LEN; uIndex++)
  23:      {
  24:          pKiAttachProcessAddr[uIndex] = szKiAttachProcessOriginCode[uIndex];
  25:      }
  26:   
  27:      KeLowerIrql(kOldIRQL);
  28:      DisableWriteProtect(uOldAttr);
  29:   
  30:      KdPrint(("Pass TP - KiAttachProcess Installed."));
  31:  }

 

                                

到这里,我们又把 KiAttachProcess 给搞定了,所以此时咱可以用 OD 来附加 DNF 游戏进程试试看了,

此时你会发现咱可以将 OD 附加上去了,不过可惜的是,就算附加上去了,离使用 OD 来调试 DNF 还远着呢,

因为 TP 对 DNF 游戏进程还有其他的保护措施,它在内核里面 Hook 的 API 可不止这么点哦。

                                                

                                                

总结:

                                                

《过 DNF TP 驱动保护》的第二篇到这里就结束了,经过上面的处理,

我们已经过掉了 TP 所做 InLine Hook 的 5 个 API 了,

首先是 NtOpenProcess 和 NtOpenThread 的深层 InLine Hook,

然后是 SSDT 系统服务函数 NtReadVirtualMemory 和 NtWriteVirtualMemory 的浅层次 InLine Hook,

最后我们也干掉了 TP 对未导出内核函数 KiAttachProcess 所做的浅层次 InLine Hook。

虽然也干掉了不少 TP 在内核中 Hook 的 API 了,但是这离过 DNF TP 驱动保护还比较远的,

详情还得留到下回分解了。

posted @ 2012-06-14 01:29  einyboy  阅读(3267)  评论(1编辑  收藏  举报