看流星社区

 找回密码
 注册账号
查看: 2153|回复: 0

围观tp驱动保护。详解debugport清0

[复制链接]

该用户从未签到

发表于 2017-6-1 17:21:06 | 显示全部楼层 |阅读模式
标 题:围观tp驱动保护。详解debugport清0
作 者: 诶一
时 间: 2012-08-25,22:15:56
链 接: http://bbs.pediy.com/showthread.php?t=155139

刚毕业,找工作的时候郁闷中,还没找到理想的工作,刚好回到家又没什么事情,就研究下tp保护,闲闲散散的也搞了2个星期了。索性弄好了也就发篇文章了,求个精华阿,第一个精华不知道有木有的。对于tp网上已经有不少的文章了,但貌似的看了一些也有一些的不足,本文完全出于兴趣研究的目的,请勿用于商业用途。第一次写这样的文章,文中有不足的地方,还希望大家指正。
欢迎志同道合的朋友一起研究,一起讨论,只做研究为目的,不作于商业用途。附上本人QQ905050630请注明看雪,谢谢。
下面的是我求职的简历,真心求正规公司,求机会,顺带发下我的简历。
http://bbs.pediy.com/showthread.php?t=154765
好了,进入正题。
  众所周知的几个函数有:
  双机调试
  Debugport清0
  NtOpenProcess
  NtOpenThread
  KiAttachProcess
  NtReadVirtualMemory
  NtWriteVirtualMemory
  下面这两个函数是调试器接受发送信息用的,如果不处理这两个调试器无法发送接受到信息
  DbgkpSetProcessDebugObject
  DbgkpQueueMessage
  这些在网上已经有很多的文章中讲解了,这里只做简单的讲解,对于debugport清零我认为是tp中的一个难点,而对于这个网上的讲解很少,而且看了下并不完全。
  先来看下双机调试,tp不断的调用KdDisableDebugger禁用调试,对于这个函数,我们对它做了直接返回的处理,因为函数头并没有什么检测,而通过wrk源码中看,这个函数的处理中有



对于调试列程和一个值的赋值,这两处地方地方的赋值我们还需要处理一下。
  在KdDisableDebugger下断后跟踪发现,
  第一次TP加载
  TesSafe+0x56d5:改成0x742字节90
  b13c76d575b0jneTesSafe+0x5687(b13c7687)
  
  
  TesSafe+0x5803:改成0xeb
  b13c78037402jeTesSafe+0x5807(b13c7807)
  
  
  第二次TP加载
  TesSafe+0x59dd:改成0x9090
  b10419dd75b0jneTesSafe+0x598f(b104198f)
  
  
  TesSafe+0x5b0b:改成0xeb
  b1041b0b7402jeTesSafe+0x5b0f(b1041b0f)
  这是两次tp加载,分别要处理的位置。
  
  Tp会加载两次,第一次是假的。对于NtOpenProcess的中继函数处理。
  Tp对NtOpenProcess的hook
  //805c2646ff75c8pushdwordptr[ebp-38h]
  //805c2649ff75dcpushdwordptr[ebp-24h]
  //805c264ce80d5e9f30callTesSafe+0xb45e(b0fb845e)
  我们要处理的是在push的位置处进行hook,过滤掉dnf游戏自身访问时候的判断,
  __declspec(naked)voidFuckNtOpenProcess()
  {
  __asm
  {
  //判断如果是游戏自身调用的话,则执行被hook的call
  //如果是其他进程调用的话,就跳过被hook的call,执行g_uObOpenObjectByPointerAddr
  pushad
  pushfd
  callisGameAccess
  movg_isGameAccess,al
  popfd
  popad
  cmpg_isGameAccess,1
  jnzNOISGAME
  pushdwordptr[ebp-38h]
  pushdwordptr[ebp-24h]
  moveax,g_uNtOpenProcessHookAddr
  addeax,6//hook的代码字节数
  jmpeax
  NOISGAME:
  pushdwordptr[ebp-38h]
  pushdwordptr[ebp-24h]
  moveax,g_uObOpenObjectByPointerAddr
  calleax
  pushg_uHookNtOpenProcessRet
  ret
  }
  }
  
  下面是NtOpenThread函数的hook和NtOpenProcess同理,处理的方法相同,这里贴出中继函数的处理,
  //中继函数处理
  __declspec(naked)voidFuckNtOpenThread()
  {
  __asm
  {
  //判断如果是游戏自身调用的话,则执行被hook的call
  //如果是其他进程调用的话,就跳过被hook的call,执行g_uObOpenObjectByPointerAddr
  pushad
  pushfd
  callisGameAccess
  movg_isGameAccess,al
  popfd
  popad
  cmpg_isGameAccess,1
  jnzNOISGAME
  pushdwordptr[ebp-34h]
  pushdwordptr[ebp-20h]
  moveax,g_uNtOpenThreadHookAddr
  addeax,6//hook的代码字节数
  jmpeax
  NOISGAME:
  pushdwordptr[ebp-34h]
  pushdwordptr[ebp-20h]
  moveax,g_uObOpenObjectByPointerAddr
  calleax
  pushg_uHookNtOpenThreadRet
  ret
  }
  }
  两个方法相同,这里不做过多的叙述。下面的是tp对KiAttachProcess的hook




对于KiAttachProcess的处理,这里我做了直接恢复。这里的KiAttachProcess并不是导出函数,所以这里我先获得了KeAttachProcess的地址,通过特征码搜索的方式找到了KiAttachProcess的地址。直接贴代码。
VOIDMy_HookKiAttachProcess()
{
BYTEbJmpAddr[7]={0x8b,0xff,0x55,0x8b,0xec,0x53,0x8b};
KIRQLklrql;

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

if(bKeAttachProcessAddr==NULL)
return;

//特征码搜索callnt!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
直接在下面贴段代码。
对读写内存的两处直接做了恢复。
VOIDMy_HookNtReadAndNtWriteVirtualMemory()
{
BYTEbReadAndWritePush[2]={0x6a,0x1c};
BYTEbReadPush[5]={0x68,0xe0,0xa4,0x4d,0x80};
BYTEbWritePush[5]={0x68,0xf8,0xa4,0x4d,0x80};
KIRQLklrql;
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的位置处下内存访问断点(bar4地址),既然是检测,那同段检测代码到达的肯定就不止是一次而已了,也就是说会多次的不断的到达,反复的观察断下来的原因,结合反汇编代码分析。这一处的地方到底是因为什么而断下来的,这就需要有一点的汇编基础了。因为有几处的代码会被VM,所以如果是遇到被VM后的代码,就不能乱加修改了,以免破坏掉原先要执行的功能。我认为在做这些东西的时候,都可以把自己的方向定位成设计者的角度,反向的思考,别人会是怎么做的,去验证,那就有可能,这个猜想才能得到成立。

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

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

TesSafe+0x1ccd2:
b0f9ccd2e9dfddffffjmpTesSafe+0x1aab6(b0f9aab6)
同理我们对push的位置处进行hook处理,下面是这一处的中继函数处理。
当判断edx是debugport的地址的时候,我们对其进行处理,而不是的时候则就不处理,执行hook前的代码。
__asm
{
pushfd
pushad
moveax,g_uGetGameBaseAddr
addeax,0xbc//debugport地址
cmpedx,eax
jeDetour
popad
popfd
pushdwordptr[edx]
jmpg_uTPDetourPushAddr
Detour:
popad
popfd
push0
jmpg_uTPDetourPushAddr
}
第四处位置
TesSafe+0x1e736:
b0f9e7368f02popdwordptr[edx]

TesSafe+0x1e738:
b0f9e738e979c3ffffjmpTesSafe+0x1aab6(b0f9aab6)
这一处的处理方法和第三处的处理相同,只需要同样的进行判断下就好了,直接贴上代码。
__asm
{
pushfd
pushad
moveax,g_uGetGameBaseAddr
addeax,0xbc//debugport地址
cmpedx,eax
jeDetour
popad
popfd
popdwordptr[edx]
jmpg_uTPDetourPushAddr
Detour:
popad
popfd
addesp,4
jmpg_uTPDetourPushAddr
}
再说一下tp对deport清0做的一个手脚,在游戏跑起来后的时候,用windebug看的时候会有两个进程,而如果我们得到的是第一个游戏进程的EPROCESS位置的话,那是错的,要进行下判断,得到第二个进程的EPROCESS才是正确的。
好了,写到这里的话已经差不多了,当然还有硬件断点,枚举系统回调等的没处理,网上资料也很多,这里就不讲了。
以后如果有研究出好的东西也会放出来的。如果有好东西也记得与我分享哈。*转载请注明来自看雪论坛@PEdiy.com
点击按钮快速添加回复内容: 支持 高兴 激动 给力 加油 苦寻 生气 回帖 路过 感恩
您需要登录后才可以回帖 登录 | 注册账号

本版积分规则

小黑屋|手机版|Archiver|看流星社区 |网站地图

GMT+8, 2024-4-19 12:53

Powered by Kanliuxing X3.4

© 2010-2019 kanliuxing.com

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