欢迎来到 嗅灵易学

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

TP驱动保护-浅谈-总结

TP驱动保护-浅谈-总结

今天写一篇关于某款游戏的TP保护学习总结(分析谈不上)吧,搞这驱动大概弄了2个星期,中间有点事情等于断断续续的加起来搞了2个星期,因为TP、HP、GPK等等,当前比较流行的游戏保护,都有很多大牛已经研究透了,并且非常潇洒的公布出来了。所有对我们的学习有了很大的帮助,在这里向他们致敬!你们永远活在我们的心中。。。
  类似的文章也已经有很多了,都写的非常好,超越他们是很难的了,之所以写这篇文章,主要有几点,一来我最近没有在搞驱动保护,算是个复习吧,所以想写一篇文章总结梳理下驱动那点事儿,二来在驱动中我还是有很多地方有些不明白,还有些问题,写出来大家想想,帮我想想,也帮自己想想,指不定哪天也遇到了可以迅速的解决。
  好了,进入正题:
  如果想学习驱动保护或者驱动方面的技术,你首先要补充下自己的知识,否则你会一头雾水,不知去向。建议大家如果是想做windows驱动方面的 就学习下Win32汇编,因为Windows底层不是完全开源的,有些东西必须反汇编才能看到,然后就是Windows底层实现原理,也就是了解下Windows是怎么协调工作的其中就包括我们要用到的SSDT,如果你在学习windows底层的话 这个你就会明白的,还有就是编程语言了,C、C++、delphi、汇编  根据自己擅长的用。语言只是工具而已。当然了,调试工具你还是要会用的。WinDBG、syser、等。根据自己需要。
  上面说了一些相关的东西,现在我们来说说TP保护。TP保护我就不介绍了,各种百度。。。。。
  TP需要处理的内容:
  SSDT HOOK:
  NtOpenProcess
  NtOpenThread
  NtReadVirtualMemory
  NtWriteVirtualMemory
  KiAttachPRocess
  清零和监控:
  两处清零和一处监控
  因为不是D*F 所有清零不是4处
  注意在检查调试的时候一定要等游戏全部进入到账号登陆的时候,因为TP会运行两次,第一次是假的,所以我们必须至少等游戏进入账号登陆那个界面才行。
  首先解决前两个因为 NtOpenProcess和NtOpenThread的处理是一样的,但后面三个是不一样的,这样个有一点点的难度,其实都一样。。。。
  如果,大家不知道TP都现在这些函数的那些部分,那你就那TP没运行直接的函数体和运行后的函数体进行文本对比就行了。
   push    dword ptr [ebp-34h]
   push    dword ptr [nt!PsProcessType (8055b258)]
   push    ebx
   call    nt!ObOpenObjectByName (805b1f7a)
  通过对比你会发现就是这里出来问题。。上面的代码是没有运行TP之前的。
  TP的做法是:把call    nt!ObOpenObjectByName (805b1f7a) 替换成他自己的函数名字。也就是说,TP来控制什么时候调用ObOpenObjectByName函数,所以TP就机会对我们做一些处理了,具体TP在调用ObOpenObjectByName函数之前都做了什么,我也没有具体的读代码(主要是技术不到家。。)但大牛们都说是和游戏客户端进行通信用的。比如:游戏调用OpenProcess的时候他需要给某个全局变量赋值。然后再调用ObOpenObjectByName函数让系统正常的走下去,如果游戏端在检测这个变的时候发现没有变动,或者值不对,那么TP就会认为,没有按照他的步骤走,就报异常了。。。这是打个比方,主要是让大家明白,我们不能直接把他改回去,所以,我们需要在进入他的CALL之前先进入我们的CALL,我们先判断下,是谁在调用这个OpenProcess 如果是游戏进程的话,我们就让他进入他CALL(也就是让它去修改那个全局变量,保证游戏的验证)如果是我们的调试器的话就直接调用ObOpenObjectByName函数就可以了。、。。。这是解决OpenProcess、OpenThread HOOK的思路,以前都已经有人写过了,我今天只是加了一点我自己的理解,希新手朋友们能够理解这点。下面发下具体实现:
  代码可以分为两部分看:一部分是在OpenProcess函数中插入我们的CALL ,另一部分是到我们CALL里面的处理代码。
  修改OpenProcess:
  __asm
     {
    //我们写入OpenProcess的被HOOK地址  
        mov ebx,p_MyOpenProcessHook//我们自己的函数地址
      mov byte ptr [ebx+0],0xe9 //jmp
      lea eax,My_NtOpenPrcoess //通过公式计算
      sub eax,p_MyOpenProcessHook
      sub eax,5
      mov [ebx+1],eax
      mov [ebx+5],0x90  //nop
     } 
        __asm
     {
    //我们写入OpenThread的被HOOK地址  
        mov ebx,p_MyOpenThreadHook//我们自己的函数地址
      mov byte ptr [ebx+0],0xe9 //jmp
      lea eax,My_NtOpenThread //通过公式计算
      sub eax,p_MyOpenThreadHook
      sub eax,5
      mov [ebx+1],eax
      mov [ebx+5],0x90  //nop
     } 
  第二部分:我们的函数实现:
  //我们自己的:NtOpenPrcoess 处理函数
  __declspec(naked) VOID My_NtOpenPrcoess()
  {  
    //获得调用者的EPROCESS
      processEPROCESS = IoGetCurrentProcess();
          RtlInitAnsiString(&p_str1,SG_EXE);
    //获得调用者的EPROCESS
      RtlInitAnsiString(&p_str2,(PCSZ)((ULONG)processEPROCESS+0x174));
      if (RtlCompareString(&p_str1,&p_str2,TRUE) == 0)
      {
          __asm
          {
          push  dword ptr [ebp-38h]
              push  dword ptr [ebp-24h]
              mov   eax,p_TpHookAddress // CALL B2165421 ;
              jmp   eax
          }        
         }else
         
           {
      __asm
          {
          push    dword ptr [ebp-38h]
          push    dword ptr [ebp-24h]
          push    p_ReturnAddress  //8BF8 MOV EDI,EDI 0x80572907
          mov     eax,ObOpenObjectByPointerAddress
          jmp     eax     
            }
           }
  }
  //我们自己的NtOpenThread 实现函数
  __declspec(naked)VOID My_NtOpenThread()
  {  
    //获得调用者的EPROCESS
      processEPROCESS = IoGetCurrentProcess();
          RtlInitAnsiString(&p_str1,SG_EXE);
    //获得调用者的EPROCESS
      RtlInitAnsiString(&p_str2,(PCSZ)((ULONG)processEPROCESS+0x174));
      if (RtlCompareString(&p_str1,&p_str2,TRUE) == 0)
      {
          __asm
          {
        push    dword ptr [ebp-34h]
        push    dword ptr [ebp-20h]
            mov     eax,p_TpHookAddress_Thread
            jmp     eax
          }
      }else
         
           {
      __asm
      {
       push    dword ptr [ebp-34h]
       push    dword ptr [ebp-20h]
       push    p_ReturnAddress_Thread
       mov     eax,ObOpenObjectByPointerAddress
           jmp     eax  
        
      }  
            }  
  }
  
下面开始处理剩下的三个函数
1.NtReadVirtualMemory
2.NtWriteVirtualMemory
3.KiAttachPRocess
这三个函数非常好处理,他们都是函数头部有个JMP跳转,处理的方法直接对照着没有HOOK之前用汇编写回去就可以了。。
直接上代码:
  /**
    功能:将原来没有被HOOK前的参数或者是被HOOK的部分还原
    并且调用原来的函数地址+被HOOK的前面一部分
    这样就 实现了跳过HOOK
  **/
  __declspec(naked) NTSTATUS __stdcall MyKiAttachProcess(HANDLE ProcessHandle,
                  PVOID BaseAddress,
                  PVOID Buffer,
                  ULONG NumberOfBytesToKiAttach,
            PULONG NumberOfBytesReaded) 
  {
     //跳过去
     __asm
     {
       mov  edi,edi
       push ebp
       mov  ebp,esp  
       push ebx
       mov  ebx,dword ptr [ebp+0Ch]
       mov  eax,Old_KiAttach_Address  //KiAttachPRocess函数的地址
       add  eax,7    
       jmp  eax    
     }
  }
  /**
    当调用NtReadVirtualMemory函数的时候  跳转到这个函数中
    这个函数会模式出 NtReadVirtualMemory原来的参数并跳转
    JmpAddress:原来没有HOOK前的NtReadVirtualMemory函数+7个的地址 
    因为 前7个没TP给修改了  所有不能用了!
  **/
  __declspec(naked) NTSTATUS __stdcall MyNtReadVirtualMemory(HANDLE ProcessHandle,
                 PVOID BaseAddress,
                 PVOID Buffer,
                 ULONG NumberOfBytesToRead,
           PULONG NumberOfBytesReaded) 
  {
    //跳过去
    __asm
    {
      push    0x1C
      push    804F8E28h  //共十个字节
      jmp     [JmpAddress]   
    }
  }
  /**
    当调用NtWriteVirtualMemory函数的时候  跳转到这个函数中
    这个函数会模式出 NtWriteVirtualMemory
    JmpAddress:原来没有HOOK前的NtWriteVirtualMemory函数+7个的地址 
    因为 前7个没TP给修改了  所有不能用了!
  **/
  __declspec(naked) NTSTATUS __stdcall MyNtWriteVirtualMemory(HANDLE ProcessHandle,
                 PVOID BaseAddress,
                 PVOID Buffer,
                 ULONG NumberOfBytesToWrite,
           PULONG NumberOfBytesReaded) 
  {
    //跳过去
    __asm
    {
      push    0x1c
      push    804F8E40h  //共十个字节
      jmp     [JmpAddress1] 
    }
  }
这三个函数没有什么好说的 不懂的朋友可以看看其他大牛写的类似文章。
接下来就是清零和监控的处理了。。。。。
  我先简单的说下思路吧,首先要搞明白清零是指  EPROCESS结构中的DebugPort 它是调试端口,传送一些调试信息的,所以,如果TP不断的把这个属性的值给清空的话,那么我们是不是就没有办法获取调试信息了?那我们就应该找到是哪里对DebugPort做了手脚,说白了就是哪里对DebugPort作了读和写的操作,我们直接在DebugPort的地址下读和写的断点,我用的是syser 然后让游戏运行一会的时候syser就断下来,那就证明这个地方对DebugPort做了手脚,我们可以找到函数的头部 NOP掉就可以了。当然不会只有一个函数在对DebugPort读和写 ,有多少NOP多少。
  注意在NOP 之前  还有个东西要处理。。那就是有一个专门的方法在不断的检测着 对DebugPort操作的函数,所以我们要先解决它  再NOP上面的函数。找监控的方法和上面的一样  只不过把下断的地址 换成  你要NOP的函数的头地址就可以了。。记住先 NOP监控再来NOP清零。。。。
  代码:
  NTSTATUS My_Recovery_Debugport()
  {
    ULONG ModuleAddress,ModuleSize;
    NTSTATUS stats; 
    stats = MyEnumKernelModule("\\??\\c:\\windows\\system32\\tessafe.sys",&ModuleAddress,&ModuleSize);
    //if (stats == FAILED_TO_OBTAIN_FUNCTION_ADDRESSES) 
    //{ 
      //return FAILED_TO_OBTAIN_FUNCTION_ADDRESSES; 
    //} 
    KdPrint("Address:%0X Size:%d \n",ModuleAddress,ModuleSize); 
    port1Address=ModuleAddress+port1;
    port2Address=ModuleAddress+port2;
    port3Address=ModuleAddress+port3;
    DbgPrint("清零的地址1111111:%x",(ULONG)port1Address);
    DbgPrint("清零的地址2222222:%x",(ULONG)port2Address);
    DbgPrint("清零的地址3333333:%x",(ULONG)port3Address);
    WPOFF();
        __asm
      {
        mov ax,0c390h
        mov edx,port3Address
        mov WORD ptr [edx],ax
      }
      
          __asm
      {
        mov ax,0c390h
        mov edx,port2Address
        mov WORD ptr [edx],ax
      }
      
      __asm
      {
        mov ax,0c390h
        mov edx,port1Address
        mov WORD ptr [edx],ax
      }
      
    WPON();
  }
  
  好了,事情发展到现在,已经基本收尾了,当然了  还有很多其他的要处理,根据自己的需要  比如:双击调试、硬件断点、等等。。今天说的主要是思路,不是代码。需要代码的朋友看大牛贴。。。。
  经过我的测试 CE OD  都可以出数据。。但是会报非法,至今困惑啊。。。。
  不过换别的内存工具就没事,可能TP还有别的定时器之类的东东在检测OD和CE吧
  如果有知道的大牛请赐教。。。。
  最后  希望有志同道合的朋友一起讨论学习。由于有的论坛不能发扣扣 所以站内联系。
  
  
  没有什么技术含量的帖子,大牛莫拍砖。。。

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

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

0 0 0 举报
复制成功