吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 9311|回复: 22
上一主题 下一主题
收起左侧

[游戏安全] 围观过tp驱动保护,详解debugport清0 过驱动保护的一点想法

  [复制链接]
跳转到指定楼层
楼主
ghfhou 发表于 2021-9-9 17:44 回帖奖励
好了, 进入正题。
  众所周知的几个函数有:
  双机调试
  Debugport清0
  NtOpenProcess
  NtOpenThread
  KiAttachProcess
  NtReadVirtualMemory
  NtWriteVirtualMemory
  下面这两个函数是调试器接受发送信息用的,如果不处理这两个调试器无法发送接受到信息
  DbgkpSetProcessDebugObject
  DbgkpQueueMessage
  这些在网上已经有很多的文章中讲解了,这里只做简单的讲解,对于debugport清零我认为是tp中的一个难点,而对于这个网上的讲解很少,而且看了下并不完全。
  先来看下双机调试,tp不断的调用KdDisableDebugger禁用调试,对于这个函数,我们对它做了直接返回的处理,因为函数头并没有什么检测,而通过wrk源码中看,这个函数的处理中有对于调试列程和一个值的赋值,这两处地方地方的赋值我们还需要处理一下。
  在KdDisableDebugger下断后跟踪发现,
  第一次TP加载
  TesSafe+0x56d5:     改成0x74   2字节90
  b13c76d5 75b0            jne     TesSafe+0x5687 (b13c7687)
  
  TesSafe+0x5803:   改成0xeb
  b13c7803 7402            je      TesSafe+0x5807 (b13c7807)
  
  第二次TP加载
  TesSafe+0x59dd:    改成0x9090
  b10419dd 75b0            jne     TesSafe+0x598f (b104198f)
  
  TesSafe+0x5b0b:     改成0xeb
  b1041b0b 7402            je      TesSafe+0x5b0f (b1041b0f)
  这是两次tp加载,分别要处理的位置。
  
  Tp会加载两次,第一次是假的。对于NtOpenProcess的中继函数处理。
  Tp对NtOpenProcess的hook
  // 805c2646 ff75c8          push    dword ptr [ebp-38h]
  // 805c2649 ff75dc          push    dword ptr [ebp-24h]
  // 805c264c e80d5e9f30      call    TesSafe+0xb45e (b0fb845e)
  我们要处理的是在push的位置处进行hook,过滤掉dnf游戏自身访问时候的判断,
  __declspec(naked) void FuckNtOpenProcess()
  {
    __asm
    {
      //判断如果是游戏自身调用的话,则执行被hook的call
      //如果是其他进程调用的话,就跳过被hook的call,执行g_uObOpenObjectByPointerAddr
      pushad
      pushfd
      call isGameAccess
      mov g_isGameAccess, al
      popfd
      popad
      cmp g_isGameAccess,1
      jnz NOISGAME
      push dword ptr [ebp-38h]
      push dword ptr [ebp-24h]
      mov eax, g_uNtOpenProcessHookAddr
      add eax, 6            //hook的代码字节数
      jmp eax
  NOISGAME:
      push dword ptr [ebp-38h]
      push dword ptr [ebp-24h]
      mov eax,g_uObOpenObjectByPointerAddr
      call eax
      push g_uHookNtOpenProcessRet
      ret
    }
  }
  
  下面是NtOpenThread函数的hook和NtOpenProcess同理 ,处理的方法相同,这里贴出中继函数的处理,
  //中继函数处理
  __declspec(naked) void FuckNtOpenThread()
  {
    __asm
    {
      //判断如果是游戏自身调用的话,则执行被hook的call
      //如果是其他进程调用的话,就跳过被hook的call,执行g_uObOpenObjectByPointerAddr
      pushad
      pushfd
      call isGameAccess
      mov g_isGameAccess, al
      popfd
      popad
      cmp g_isGameAccess,1
      jnz NOISGAME
      push dword ptr [ebp-34h]
      push dword ptr [ebp-20h]
      mov eax, g_uNtOpenThreadHookAddr
      add eax, 6            //hook的代码字节数
      jmp eax
  NOISGAME:
      push dword ptr [ebp-34h]
      push dword ptr [ebp-20h]
      mov eax,g_uObOpenObjectByPointerAddr
      call eax
      push g_uHookNtOpenThreadRet
      ret
    }
  }
  两个方法相同,这里不做过多的叙述。下面的是tp对KiAttachProcess的hook
  
对于KiAttachProcess 的处理, 这里我做了直接恢复。这里的KiAttachProcess并不是导出函数,所以这里我先获得了KeAttachProcess的地址,通过特征码搜索的方式找到了KiAttachProcess 的地址。直接贴代码。
VOID My_HookKiAttachProcess()
{
  BYTE bJmpAddr[7] = {0x8b, 0xff, 0x55, 0x8b, 0xec, 0x53, 0x8b};
  KIRQL klrql;

  // 获取KeAttachProcess地址
  BYTE *bKeAttachProcessAddr = (BYTE *)GetFunAddress(L"KeAttachProcess");

  if (bKeAttachProcessAddr == NULL)
    return;
  
  // 特征码搜索call nt!KiAttachProcess (804f87c8)
  while (TRUE)
  {
    if (*(bKeAttachProcessAddr) == 0xE8 &&
      *(bKeAttachProcessAddr + 5) == 0x5F &&
      *(bKeAttachProcessAddr + 6) == 0x5E &&
      *(bKeAttachProcessAddr + 7) == 0x5D &&
      *(bKeAttachProcessAddr + 8) == 0xC2 &&
      *(bKeAttachProcessAddr - 1) == 0x56 &&
      *(bKeAttachProcessAddr - 2) == 0x57 &&
      *(bKeAttachProcessAddr - 5) == 0xFF &&
      *(bKeAttachProcessAddr - 6) == 0x50)
    {
      g_uKiAttachProcessAddr = *(ULONG *)(bKeAttachProcessAddr + 1) + (ULONG)(bKeAttachProcessAddr + 5);
      break;
    }
   
    bKeAttachProcessAddr++;
  }

  CleanPageProtect(TRUE);
  klrql =  KeRaiseIrqlToDpcLevel();
  
  // Hook目标地址
  RtlCopyMemory((BYTE *)g_uKiAttachProcessAddr, bJmpAddr, 7);
  
  KeLowerIrql(klrql);
  CleanPageProtect(FALSE);
}
下面的是这4个函数的处理, 处理的方法差不多,按照hook前的汇编代码写回去即可。这里也没有什么好说的
  NtReadVirtualMemory
  NtWriteVirtualMemory
  DbgkpSetProcessDebugObject
  DbgkpQueueMessage
直接在下面贴段代码。
对读写内存的两处直接做了恢复。
VOID My_HookNtReadAndNtWriteVirtualMemory()
{
  BYTE bReadAndWritePush[2] = {0x6a, 0x1c};
  BYTE bReadPush[5] = {0x68, 0xe0, 0xa4, 0x4d, 0x80};
  BYTE bWritePush[5] = {0x68, 0xf8, 0xa4, 0x4d, 0x80};
  KIRQL klrql;
  BYTE *uNtReadVirtualMemoryAddr;
  BYTE *uNtWriteVirtualMemoryAddr;

  // 获取NtReadVirtualMemory在SSDT表中的地址
  uNtReadVirtualMemoryAddr = (BYTE *)GetSsdtFunAddress(0xBA);
  if (uNtReadVirtualMemoryAddr == NULL)
    return;
  
  // 获取NtWriteVirtualMemory在SSDT表中的地址
  uNtWriteVirtualMemoryAddr = (BYTE *)GetSsdtFunAddress(0x115);
  if (uNtWriteVirtualMemoryAddr == NULL)
    return;
  
  CleanPageProtect(TRUE);
  klrql =  KeRaiseIrqlToDpcLevel();
  
  // 恢复NtReadVirtualMemory
  RtlCopyMemory(uNtReadVirtualMemoryAddr, bReadAndWritePush, 2);
  RtlCopyMemory(uNtReadVirtualMemoryAddr + 2, bReadPush, 5);
  // 恢复NtWriteVirtualMemory  
  RtlCopyMemory(uNtWriteVirtualMemoryAddr, bReadAndWritePush, 2);
  RtlCopyMemory(uNtWriteVirtualMemoryAddr + 2, bWritePush, 5);
  
  KeLowerIrql(klrql);
  CleanPageProtect(FALSE);  
}
后面的两个函数  同理也是直接做了恢复,没什么好说的。
接下来就是debugport清零和监控的处理了,我想大多数人关心的也就是这里了,这是我认为tp中的一个难点,EPROCESS结构中的DebugPort 它是调试端口,把这一处的位置下内存访问断点。会发现有4处的清0位置,和2处的检测代码。

来说一下这几处地址是怎么找出来的,大家可以设想一下,把自己当做一个游戏开发者的角度去想,要检测debugport清0的位置处,那就必然游戏如果不对这个地址处进行访问,那又怎么来做这个检测呢,游戏既然要检测,必然就会对这个地址处进行访问,所以我们在debugport的位置处下内存访问断点(ba r4 地址),既然是检测,那同段检测代码到达的肯定就不止是一次而已了,也就是说会多次的不断的到达,反复的观察断下来的原因,结合反汇编代码分析。这一处的地方到底是因为什么而断下来的,这就需要有一点的汇编基础了。因为有几处的代码会被VM,所以如果是遇到被VM后的代码,就不能乱加修改了,以免破坏掉原先要执行的功能。我认为在做这些东西的时候,都可以把自己的方向定位成设计者的角度,反向的思考,别人会是怎么做的,去验证,那就有可能,这个猜想才能得到成立。

第一处位置
TesSafe+0x219e:
b0f8219e 8b09            mov     ecx,dword ptr [ecx]
函数首地址 b0f82124 8bff            mov     edi,edi
TesSafe+0x2124:
b0f82124 8bff            mov     edi,edi
第一处的位置,我们对函数的首地址下个内存访问断点看看, 函数头直接返回的时候是不是会有检测,发现这是一处常规的清零操作。函数头并没有检测。对于这一处我们直接返回即可。
第二处位置
TesSafe+0x58a7:
函数首地址 b0f8585e 8bff            mov     edi,edi
TesSafe+0x585e:
这里需要注意的是这处的函数清0代码不能直接返回,因为函数后还有重要的代码要执行,如果执行返回,即时干掉了两处检测函数,那样也是会有问题的。而我们的办法就是改掉清0的代码,改为nop
在第二处的位置处,我们下内存访问断点的时候发现有检测的代码。
TesSafe+0x3a02:
b0d1fa02 8bff            mov     edi,edi
对于这处的检测我们直接返回了即可。
持续运行多次的时候我们还会发现有一处地方的检测
TesSafe+0x22cc2:
b0edccc2 ff32            push    dword ptr [edx]

TesSafe+0x22cc4:
b0edccc4 e98bf4ffff      jmp     TesSafe+0x22154 (b0edc154)
对于这一处的代码,因为这处的代码有VM处理。所以我们不能对这处的地址乱改。
在push的位置处,我们对代码进行hook的处理,当判断edx是检测函数首地址的时候,我们对其做一下处理,而当不是函数首地址的时候,而执行原来的代码。下面的是这一处的处理代码
__asm
{
pushfd
pushad
mov eax, g_uTpZero1
cmp edx,eax      
je Detour
popad
popfd
push dword ptr [edx]
jmp g_uTPDetourPushAddr   //这是本身程序要jmp过去的位置,jmp到tp模块里的位置处。
Detour:
popad
popfd
push 0
jmp g_uTPDetourPushAddr1
}
接下来是第三处的清0 位置
第三处位置
TesSafe+0x1ccd0
b0f9ccd0 ff32            push    dword ptr [edx]

TesSafe+0x1ccd2:
b0f9ccd2 e9dfddffff      jmp     TesSafe+0x1aab6 (b0f9aab6)
同理我们对push的位置处进行hook处理,下面是这一处的中继函数处理。
当判断edx是debugport的地址的时候,我们对其进行处理,而不是的时候则就不处理,执行hook前的代码。
__asm
{
pushfd
pushad
mov eax,g_uGetGameBaseAddr
add eax,0xbc      //debugport地址
cmp edx,eax
je Detour
popad
popfd
push dword ptr [edx]
jmp g_uTPDetourPushAddr  
Detour:
popad
popfd
push 0
jmp g_uTPDetourPushAddr
}
第四处位置
TesSafe+0x1e736:
b0f9e736 8f02            pop     dword ptr [edx]

TesSafe+0x1e738:
b0f9e738 e979c3ffff      jmp     TesSafe+0x1aab6 (b0f9aab6)
这一处的处理方法和第三处的处理相同,只需要同样的进行判断下就好了,直接贴上代码。
__asm
{
pushfd
pushad
mov eax,g_uGetGameBaseAddr
add eax,0xbc        //debugport地址
cmp edx,eax
je Detour
popad
popfd
pop dword ptr [edx]
jmp g_uTPDetourPushAddr
Detour:
popad
popfd
add esp,4
jmp g_uTPDetourPushAddr
}
再说一下tp对deport清0 做的一个手脚,在游戏跑起来后的时候,用windebug看的时候会有两个进程,而如果我们得到的是第一个游戏进程的EPROCESS位置的话,那是错的,要进行下判断,得到第二个进程的EPROCESS才是正确的。
好了,写到这里的话已经差不多了,当然还有硬件断点,枚举系统回调等的没处理,网上资料也很多,这里就不讲了。
以后如果有研究出好的东西也会放出来的。如果有好东西也记得与我分享哈。

免费评分

参与人数 9吾爱币 +9 热心值 +9 收起 理由
junjia215 + 1 + 1 热心回复!
hsyz1016 + 1 + 1 谢谢@Thanks!
chengjiu + 1 + 1 谢谢@Thanks!
junjia215 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
sam喵喵 + 1 + 1 谢谢@Thanks!
雪莱鸟 + 1 马老板看了一定会拍手叫好!
zhczf + 1 + 1 我很赞同!
liuxingbo12138 + 1 + 1 用心讨论,共获提升!
niucaidi + 2 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
llkbkh 发表于 2021-9-10 17:10
我是回到10年前了吗?
推荐
max2012 发表于 2021-9-10 21:34
慢慢的都没人搞Windows了,曾经的各种驱动,只能是怀念一下
4#
niucaidi 发表于 2021-9-10 23:07
加油楼主!这种过驱动的讨论很难得了,我看到了都会收藏起来!评分送上
5#
蜀黍 发表于 2021-9-11 11:55
所以能让WIN10下QQ堂不会闪退吗
6#
antclt 发表于 2021-9-11 12:04
厉害,虽然看不太懂~
7#
nur11111 发表于 2021-9-11 15:28
max2012 发表于 2021-9-10 21:34
慢慢的都没人搞Windows了,曾经的各种驱动,只能是怀念一下

现在是研究哪方面比较流行呢
8#
qiujun268998898 发表于 2021-9-12 12:06
现在流行debugport 移位, 重写调试框架啥的,我是这么实现的,一劳永逸
9#
cp8497 发表于 2021-9-20 00:51
qiujun268998898 发表于 2021-9-12 12:06
现在流行debugport 移位, 重写调试框架啥的,我是这么实现的,一劳永逸

大佬可以开个贴讲解一下吗
10#
qiujun268998898 发表于 2021-9-20 16:23
cp8497 发表于 2021-9-20 00:51
大佬可以开个贴讲解一下吗

看雪有人分享源码了,你搜下我就不献丑了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-5 16:31

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表