一、DCOO反作弊原理
DCOO启动后通过调用DCOO.DLL生成临时模块~XXX.tmp,然后加载到CS进程,再通过HOOK API或内嵌汇编把CS关键部分JAMP到自己的反作弊模块(~xxx.tmp)内来处理。~xxx.tmp一旦加载就马上开始干“坏事”,挂钩(HOOK)CS关键engines函数如GetEntityByIndex,Getplayerinfo,GetViewAngles,pfnHookUserMsg等等。挂钩方法是通过改写函数头,在函数开始JMP到~xxx.tmp中的替换函数。玩家调用相应的CS Engines函数时,会首先进入到~xxx.tmp模块等待检查,根据判断(CALL)结果,如果发现作弊就弹出CODE XX进行拦截,否则就调用原来的CS ENGINES函数,让你继续。
下面是未加载DCOO启动cs1.6中的GetEntityByIndex的源代码(版本2K sp4 || CS1.6 3266)。
---- 8D4424 04 LEA EAX,DWORD PTR SS:[ESP+4]
---- 50 PUSH EAX
---- FF15 CCD4EC01 CALL DWORD PTR DS:[1ECD4CC]
---- 8B4424 08 MOV EAX,DWORD PTR SS:[ESP+8]
---- 83C4 04 ADD ESP,4
---- 85C0 TEST EAX,EAX
---- 7C 1E JL SHORT hl.01D0ECF4
---- 3B05 E064DB02 CMP EAX,DWORD PTR DS:[2DB64E0]
---- 7D 16 JGE SHORT hl.01D0ECF4
---- 8D0440 LEA EAX,DWORD PTR DS:[EAX+EAX*2]
---- 8B15 049BEF01 MOV EDX,DWORD PTR DS:[01EF9B04h]
---- 8D0480 LEA EAX,DWORD PTR DS:[EAX+EAX*4]
---- 8D0480 LEA EAX,DWORD PTR DS:[EAX+EAX*4]
---- 8D0C80 LEA ECX,DWORD PTR DS:[EAX+EAX*4]
---- 8D04CA LEA EAX,DWORD PTR DS:[EDX+ECX*8]
---- C3 RETN
---- 33C0 XOR EAX,EAX
---- C3 RETN
而下面是加载DCOO启动后cs1.6中的GetEntityByIndex的源代码(版本2K sp4 || CS1.6 3266)
---- 65:E9 853B2F0E JMP ~BS8608.1000284B
---- 15 CCD4EC01 ADC EAX,iza.01ECD4CC
---- 8B4424 08 MOV EAX,DWORD PTR SS:[ESP+8]
---- 83C4 04 ADD ESP,4
---- 85C0 TEST EAX,EAX
---- 7C 1E JL SHORT iza.01D0ECF4
---- 3B05 E064DB02 CMP EAX,DWORD PTR DS:[2DB64E0]
---- 7D 16 JGE SHORT iza.01D0ECF4
---- 8D0440 LEA EAX,DWORD PTR DS:[EAX+EAX*2]
---- 8B15 C0248D00 MOV EDX,DWORD PTR DS:[8D24C0]
---- 8D0480 LEA EAX,DWORD PTR DS:[EAX+EAX*4]
---- 8D0480 LEA EAX,DWORD PTR DS:[EAX+EAX*4]
---- 8D0C80 LEA ECX,DWORD PTR DS:[EAX+EAX*4]
---- 8D04CA LEA EAX,DWORD PTR DS:[EDX+ECX*8]
---- C3 RETN
---- 33C0 XOR EAX,EAX
---- C3 RETN
通过对比我们可以发现,DCOO把GetEntityByIndex函数头原来的8D44240450FF15CCD4EC01十一个字节改为了65E9853B2F0E15CCD4EC01,即将LEA EAX,DWORD PTR SS:[ESP+4],PUSH EAX,
CALL DWORD PTR DS:[1ECD4CC] 三条指令改为了JMP ~BS8608.1000284B, ADC EAX,iza.01ECD4CC。所以用户一旦调用GetEntityByIndex的话,就会跳转到~xxxx.tmp中的0x1000284B中去。
二、过DCOO检测的方法
1,把被DCOO修改了的函数头改回去
上面知道DCOO是通过在关键cs Egines函数头写了一个JMP来进行挂钩的,因此,在理论上我们可以通过把函数头写回去来进行调用。在实际操作的时候,这种方法并不理想。因为~xxx.tmp也挂钩了一些其他相关函数或该了地址,还有它会进行检校判断它挂钩了的函数是不是被修改回去。因此实现起来很困难,随时都会死程序。
2,构建自己的cs engines函数
这种方法适用于代码比较简单的cs函数。下面我们看看GetEntityByIndex的函数源码(随处可找)
//略..........
通过上面的代码我们发现一个GetEntityByIndex函数构建并不复杂因此我们完全可以把上面的代码COPY到自己的作弊程序,用来替代原来的GetEntityByIndex。DCOO启动后依然会拦截原来的那个,但已经没关系啦,因为我们不需要用原来那个GetEntityByIndex了。
这种方法适用于源代码比较简单的CS engines函数,复杂的话实现起来就比较麻烦了,因为其中涉及到N个jmp和Call。 还有要注意在VC6里嵌入汇编经常死VC(这种事太烦人了).
3,进入ring0
由上面可以看到,DCOO用户层的监视不过是修改了一下系统的函数头,进行挂钩监视。因此,要反DCOO用户层监视的话,进入ring0的话很多问题就可以解决了。~xxx.tmp是拦截不到的。但是自己写一个驱动文件。
4,断线程之线程陷阱
为什么要叫“线程陷阱”?因为这确确实实是一个陷阱,在~xxxxx.tmp肯定要经过的地方设置一个“陷阱”,等它来到之后,掉进去自动就死掉了。而搭建陷阱的方法简单得令你难以相信。
上面我们从DCOO的监视原理可以看到,~xxxxx.tmp要来挂钩(HOOK)我们的cs函数,这种的方法我们也会,是不是?哪想想,这种挂钩方法需要用到哪些cs函数呢? 打开进程OpenProcess或GetCurrentProcess,找模块地址GetModelHandle、找函数地址GetProcAddress、改写函数头的内存属性VirtualQuery&VirtualProtect、写内存WriteProcessMemory。嘿嘿,在这些地方设置陷阱就八九不离十了,肯定是~xxx.tmp干那坏勾当要经过的地方。
怎么设陷阱呢?先自己挂钩(嘿嘿,DCOO会我们也会)。等到有人调用的时候,先判断当前的的线程是不是我们程序的,不是的话,那就直接Free掉它吧。大概就像下面这个样子
以系统函数为例子:
HANDLE WINAPI MyGetCurrentProcess(VOID)//替换掉原来的GetCurrentProcess
{
DWORD dwThreadId=GetCurrentThreadId();//得到当前线程ID
if(!IsMyThread(dwThreadId)){//不是我们要保护的线程
ExitThread(0);//断了它吧
}
UnhookGetCurrentProcess(); //是我们要保护的线程调用就恢复函数头
HANDLE hProcess=GetCurrentProcess();//让它调用
RehookGetCurrentProcess();//重新挂钩
return hProcess; //返回调用结果
}
以上仅仅是给大家提供个简单思路,由于时间仓促,略去了很多细节,难免有疏漏,不当的地方,望指正..