FROM: http://www.cnblogs.com/w413133157/archive/2011/11/28/2266859.html


标 题:【原创】散谈游戏保护那点事~就从_TP开始入手吧
作 者:crazyearl
时 间: 2010-12-20,02:37:22
链 接: http://bbs.pediy.com/showthread.php?t=126802


废话了半天,正式开始吧。
tmd_TP也就是国内比较流行的游戏D_N*F的游戏保护。
它在ring0层一共HOOK了几个地方和一些其他的工作。来达到保护的目的
下面是简报:

引用:

NtOpenThread    //防止调试器在它体内创建线程
NtOpenProcess   //防止OD等在进程列表看到它
KiAttachProcess   //防止其他软件附加它
NtReadVirtualMemory  //防止别人读取它的内存
NtWriteVirtualMemory  //防止别人在它的内存里面乱写乱画
KDCOM.dll:KdReceivePacket  //这两个是COM串口的接受和发送数据
KDCOM.dll:KdSendPacket      //主要用来方式别人双机调试

使用了KdDisableDebugger来禁用双机调试

代码:

.text:010025F0                 jz      short loc_1002622

.text:010025F2                 call    sub_10022A4

.text:010025F7                 call    ds:KdDisableDebugger

.text:010025FD                 push    offset byte_10022EC

.text:01002602                 push    esi

.text:01002603                 push    offset byte_10022DC

.text:01002608                 push    edi

.text:01002609                 push    dword_100CF24


并对debugport进行了疯狂的清零操作
甚至还包括EPROCESS+70\+74\+78等几处位置
clip_p_w_picpath002

处理的手段通常都是向64端口写入FE导致计算机被重启

代码:

.text:01001665                 mov     al, 0FEh

.text:01001667                 out     64h, al         ; AT Keyboard controller 8042.

.text:01001667                                         ; Resend the last transmission

.text:01001669                 popa

.text:0100166A                 retn


下面简单看下他关键的几个HOOK:
KiAttachProcess  
clip_p_w_picpath003

NtReadVirtualMemory  
clip_p_w_picpath004

NtWriteVirtualMemory  
clip_p_w_picpath005

NtOpenThread
clip_p_w_picpath006

NtOpenProcess
clip_p_w_picpath007

引用:

其中,前3个直接恢复即可。
第4个有监视,直接恢复即刻重启
第5个和ring3有通信,直接恢复1分钟内SX非法模块


根据上面的分析,下面给出相应的解决方案
1.直接恢复第1、2、3处HOOK
2.绕过4、5处HOOK
3.将debugport清零的内核线程干掉
4.恢复硬件断点
但是要有一个先后的逻辑顺序
因为内核有一个线程负责监视几个地方,必须要先干掉它。
但是这个内容我写在了处理debugport清零的一起,也就是第3步。所以大家在照搬源码的时候
注意代码执行次序

先从简单的工作讲起,恢复1、2、3处的HOOK
KiAttachProcess的处理

代码:

//

//  名称:  Nakd_KiAttachProcess

//  功能:  My_RecoveryHook_KiAttachProcess的中继函数

//  参数:  

//  返回:  

//

static NAKED VOID  Nakd_KiAttachProcess()

{

 __asm

 {

   mov     edi,edi

   push    ebp

   mov     ebp,esp

   push    ebx

   push    esi

   mov    eax,KiAttachProcessAddress  //注意这个是全局变量 BYTE*

   add    eax,7

   jmp    eax

 }

}

//

//  名称:  RecoveryHook_KiAttachProcess

//  功能:  解除游戏保护对_KiAttachProcess函数的HOOK(DNF)

//  参数:  

//  返回:  状态

//

NTSTATUS My_RecoveryHook_KiAttachProcess()

{

 BYTE    *KeAttachProcessAddress = NULL;  //KeAttachProcess函数地址

 BYTE    *p;

 BYTE    MovEaxAddress[5]  = {0xB8,0,0,0,0};  //

 BYTE    JmpEax[2]      = {0xff,0xe0};

 KIRQL    Irql;

 //特征码

 BYTE  Signature1 = 0x56,  //p-1

     Signature2 = 0x57,  //p-2

     Signature3 = 0x5F,  //p-3

     Signature4 = 0x5E,  //p+5

     Signature5 = 0xE8;  //p第一个字节


 //获得KeAttachProcess地址,然后通过特征码找到

 //KiAttachProcess的地址

 KeAttachProcessAddress = (BYTE*)MyGetFunAddress(L"KeAttachProcess");

 if (KeAttachProcessAddress == NULL)

 {

   KdPrint(("KeAttachProcess地址获取失败\n"));

   return  FAILED_TO_OBTAIN_FUNCTION_ADDRESSES;

 }

 //将p指向KeAttachProcess函数开始处

 p = KeAttachProcessAddress;

 while (1)

 {

   if ((*(p-1) == Signature1) &&

     (*(p-2) == Signature2) &&

     (*(p+5) == Signature3) &&

     (*(p+6) == Signature4) &&

     (*p    == Signature5))

   {

     //定位成功后取地址

     KiAttachProcessAddress = *(PULONG)(p+1)+(ULONG)(p+5);

     break;

   }


   //推动指针

   p++;

 }


 //计算中继函数地址

 *(ULONG *)(MovEaxAddress+1)=(ULONG)Nakd_KiAttachProcess;


 WPOFF();  //清除CR0

 //提升IRQL中断级

 Irql=KeRaiseIrqlToDpcLevel();

 //写入

 RtlCopyMemory(KiAttachProcessAddress,MovEaxAddress,5);

 RtlCopyMemory(KiAttachProcessAddress+5,JmpEax,2);

 //恢复Irql

 KeLowerIrql(Irql);

 WPON();    //恢复CR0


 return  STATUS_SUCCESS;

}


NtReadVirtualMemory
NtWriteVirtualMemory的处理
注意这里,我对他们俩开头的第2句PUSH的处理
我直接写入了push 0x78563412
大家可以根据自己的地址来硬编码一次。
或者干脆这样使用

代码:

//

//  名称:  My_RecoveryHook_NtReadAndWriteMemory

//  功能:  解除游戏保护对NtReadVirtualMemory和

//      NtWriteVirtualMemory的HOOK

//  参数:  

//  返回:  

//

NTSTATUS My_RecoveryHook_NtReadAndWriteMemory()

{

 BYTE  Push1Ch[2]  = {0x6a,0x1c};  //0~2字节

 BYTE  PushAdd[5]  = {0x68,0x12,0x34,0x56,0x78};  //NtReadVirtualMemory[物理机]

 //BYTE  PushAdd2[5]  = {0x68,0xf0,0x6f,0x4f,0x80};  //NtWriteVirtualMemory[物理机]

 KIRQL  Irql;

 BYTE  *NtReadVirtualMemoryAddress    = NULL;  //NtReadVirtualMemory的地址

 BYTE  *NtWriteVirtualMemoryAddress  = NULL;  //NtWriteVirtualMemory的地址


 //从SSDT表中获取NtReadVirtualMemory函数地址

 NtReadVirtualMemoryAddress = (BYTE*)myGetCurrentAddress(0xBA);

 if (NtReadVirtualMemoryAddress == NULL)

 {

   KdPrint(("NtReadVirtualMemory函数地址获取失败! \n"));

   return  FAILED_TO_OBTAIN_FUNCTION_ADDRESSES;

 }

 //从SSDT表中获取NtWriteVirtualMemory函数地址

 NtWriteVirtualMemoryAddress = (BYTE*)myGetCurrentAddress(0x115);

 if (NtWriteVirtualMemoryAddress == NULL)

 {

   KdPrint(("NtWriteVirtualMemory函数地址获取失败! \n"));

   return  FAILED_TO_OBTAIN_FUNCTION_ADDRESSES;

 }


 WPOFF();  //清除CR0

 //提升IRQL中断级

 Irql=KeRaiseIrqlToDpcLevel();

 //写入

 RtlCopyMemory(NtReadVirtualMemoryAddress,Push1Ch,2);

 RtlCopyMemory(NtReadVirtualMemoryAddress+2,PushAdd,5);


 RtlCopyMemory(NtWriteVirtualMemoryAddress,Push1Ch,2);

 RtlCopyMemory(NtWriteVirtualMemoryAddress+2,PushAdd,5);

 //恢复Irql

 KeLowerIrql(Irql);

 WPON();    //恢复CR0


 return  STATUS_SUCCESS;

}


好了,下面来处理
NtOpenProcess
NtOpenThread
这两个函数的处理上不能太鲁莽了。
手法要风骚一点细腻一点了
介于篇幅的原因,我只贴出来前者的处理方法,后者雷同
细微之处大家自行修改。我总不能真的给你方法又给你工具。眼看着自己变成教唆犯

代码:

//NtOpenProcess用到的全局变量[为了方便堆栈平衡的处理使用全局变量]

PEPROCESS  processEPROCESS = NULL;  //保存访问者的EPROCESS

ANSI_STRING  p_str1,p_str2;      //保存进程名称

BYTE    *ObOpenObjectByPointerAddress  = NULL; //ObOpenObjectByPointer的地址

BYTE    *p_TpHookAddress = NULL;        //TP的HOOK函数地址

BYTE    *p_ReturnAddress = NULL;        //返回到的地址

BYTE    *p_MyHookAddress = NULL;        //我们的HOOK函数在哪写入

#define DNF_EXE  "DNF.exe"  //要检索的进程名

//

//  名称:  Nakd_NtOpenProcess

//  功能:  My_RecoveryHook_NtOpenProcess的中继函数

//  参数:  

//  返回:  

//

static NAKED VOID  Nakd_NtOpenProcess()

{

 //获得调用者的EPROCESS

 processEPROCESS = IoGetCurrentProcess();

 //将调用者的进程名保存到str1中

 RtlInitAnsiString(&p_str1,(ULONG)processEPROCESS+0x174);

 //将我们要比对的进程名放入str2

 RtlInitAnsiString(&p_str2,DNF_EXE);

 if (RtlCompareString(&p_str1,&p_str2,TRUE) == 0)

 {

   //说明是DNF进程访问了这里

   __asm

   {

     push    dword ptr [ebp-38h]

     push    dword ptr [ebp-24h]

     push  p_ReturnAddress

     mov    eax,p_TpHookAddress

     jmp    eax

   }

 }

 else

 {

   __asm

   {

     push    dword ptr [ebp-38h]

     push    dword ptr [ebp-24h]

     push  p_ReturnAddress

     mov    eax,ObOpenObjectByPointerAddress

     jmp    eax

   }

 }

}


//

//  名称:  My_RecoveryHook_NtOpenProcess

//  功能:  解除游戏保护对NtOpenProcess的HOOK

//  参数:  

//  返回:  状态

//

NTSTATUS My_RecoveryHook_NtOpenProcess()

{

 BYTE    *NtOpenProcessAddress      = NULL;  //NtOpenProcess的地址

 BYTE    *p = NULL;      //临时

 TOP5CODE  *top5code = NULL;  //保存5字节内容

 BYTE    JmpAddress[6] = {0xE9,0,0,0,0,0x90};

 KIRQL    Irql;


   //获取NtOpenProcess的地址

   NtOpenProcessAddress = (BYTE*)MyGetFunAddress(L"NtOpenProcess");

   if (NtOpenProcessAddress == NULL)

   {

     KdPrint(("NtOpenProcess地址获取失败\n"));

     return  FAILED_TO_OBTAIN_FUNCTION_ADDRESSES;

   }

   //获取ObOpenObjectByPointer的地址

   ObOpenObjectByPointerAddress = (BYTE*)MyGetFunAddress(L"ObOpenObjectByPointer");

   if (ObOpenObjectByPointerAddress == NULL)

   {

     KdPrint(("ObOpenObjectByPointer地址获取失败\n"));

     return  FAILED_TO_OBTAIN_FUNCTION_ADDRESSES;

   }


   //将p指向NtOpenProcess函数开始处

   p = NtOpenProcessAddress;

   //用一个无限循环来判断给定的特征码来确定被HOOK位置

   while (1)

   {

     if ((*(p-7)    == 0x50) &&

       (*(p-0xE)  == 0x56) &&

       (*(p+0xd)  == 0x50) &&

       (*(p+0x16)  == 0x3b) &&

       (*(p+0x17)  == 0xce) &&

       (*p      == 0xE8) &&

       (*(p+5)    == 0x8b) &&

       (*(p+6)    == 0xf8))

     {

       KdPrint(("%0X \n",(ULONG)p));

       break;

     }

     //推动指针向前走

     p++;

   }


   //将top5code指向 p 的当前处

   //用以取出 call [地址] 这5字节里面的地址

   top5code = (TOP5CODE*)p;

   p_TpHookAddress = (BYTE*)((ULONG)p+5+top5code->address);


   //找到我们写入自定义函数的地址

   p_MyHookAddress = p-6;

   //保存调用ObOpenObjectByPointer函数以后的返回地址

   p_ReturnAddress = p+5;


   //将一条JMP Nakd_NtOpenProcess写入到数组中

   *(ULONG *)(JmpAddress+1)=(ULONG)Nakd_NtOpenProcess - ((ULONG)p_MyHookAddress+5);


   WPOFF();  //清除CR0

   //提升IRQL中断级

   Irql=KeRaiseIrqlToDpcLevel();

   //写入

   RtlCopyMemory(p_MyHookAddress,JmpAddress,6);

   //恢复Irql

   KeLowerIrql(Irql);

   WPON();    //恢复CR0


 return  STATUS_SUCCESS;

}

处理之后:
clip_p_w_picpath008