现在大部分的主流外挂,包括按键精灵,自动做一些动作之类的外挂。
都是通过HOOK进游戏窗口,并且用不同的HOOK类型来完成的,比如对于
网络,通常是通过HOOK消息,把DLL弄到我们的游戏程序中,然后通过
GetProcAddress得到DLL中它们卑劣的函数的地址和真实的函数地址,
然后通过WriteProcessMemory来把我们的函数地址改成他们他们的API
地址。
那么在我们的游戏执行的时候,收到消息就会先触发他们的钩子,等
他们布置好邪恶的陷阱,然后再执行我们的程序。那么,对于这类型
的外挂,该怎么防呢?俗话说得好,以彼之道,还制彼身。所谓魔高
一尺,道高一丈。所谓邪不胜正。所谓天网恢恢。所谓做贼心虚。
恩,WINDOWS的钩子有个特点,就是钩子链,因为对于同一个进程,即
使是同一类型的钩子,能同时有多个,也就是说,对于同一个游戏,开
两个功能相同的外挂也可以。那么,怎么样来决定钩子的顺序呢?所谓
后来者先得,WINDOWS的做法是,最后一个HOOK某个进程的钩子最先执
行。并且振奋人心的消息是,在钩子里面可以控制下一个钩子是否执行。
这个函数是CallNextHookEx,也就是说,如果在某个钩子里面不执行这
个函数的话,钩子链就会在这中断,那么我们的思路就很简单了,在游
戏运行中,开一个进程,每隔一段时间就hook我们的主程序,然后在钩
子里面,不执行CallNextHookEx,这样就可以避免别人的钩子执行。
所谓,仅仅发现敌人还不够,还要消灭敌人。不急,我们来看看如何彻
底把敌人打成粉碎性骨折。恩,要消灭敌人就要复杂一点了,要针对不
同类型的钩子来采取不同类型的方法了,因为敌人的钩子不管怎么做,
无非是出于两种目的,一种是修改数据,另外就是过滤数据。其核心思
想就是我们自己的游戏注册两个钩子,一个总是在钩子链的最底层,另
外一个总是在钩子链的最上层,一比较两个钩子收到的消息,就知道中
间有没有别的钩子了。一但发现有别的钩子,不用想了,肯定是外挂,
最少也用了按键精灵,封号,杀档,想怎么干就怎么干。
当然,敌人也不是这么脆弱的,据说有人用raw socket来截获所有的网
络消息,这个跟钩子无关,这个更底层一些。不过不用怕,俗话说得好,
以彼之道,还制彼身。所谓魔高一尺,道高一丈。所谓邪不胜正。所谓
天网恢恢。所谓做贼心虚。对付拦截raw socket的外挂,将在下一集中
播出,请关注。。
==============================================================
《老子看不惯外挂系列 之 防止拦截网络消息》
这里首先要把敌人分类,对于水平最次的敌人,方法也相应要简单得多。
最次的敌人一般用的方法是自己写一个wsock32.dll放在和游戏相同的目录
下,来替换掉系统的wsock32.dll。对于这种愚蠢的方法,解决的办法有很
多,把load time的载入wsock32.dll改成run time的载入,然后指定一下
路径,就什么问题都没有了。更简单点,运行游戏的时候检查一下当前目录
下有没有wsock32.dll,有的话,那就肯定是外挂钩子了。
比最等级高一点点的敌人,会喜欢用钩子来直接钩,一般的做法是,先通过
GetProcAddress来获取SOCKET API的地址,然后通过WriteProcessMemory的
方法来修改入口地址,将其改成jmp 自己的函数地址。这种方法其实很卑劣
的,所谓无毒不丈夫,我们可以通过修改通用的GetProcAddress的代码来防
止别人拦截SOCKET,这样,直接连防火墙都可以突破了。
这里的技术难点在于,我们不能用类似WriteProcessMemory的方法来写内存
因为我们不知道究竟外挂是哪个进程,所以我们需要修改WINDOWS的代码段,
这样讲理论上是不可能的,可惜WINDOWS自己给自己留了个后门,在kernel.dll
里面,UINT AllocCsToDsAlias(UINT),通过把API的代码段的选择符传给它,
可以返回一个可以写的数据段的选择符,然后把新的选择符和API的入口地址
加在一起,就可以得到一个可以写的代码段的指针。
国内好多取词软件和全屏翻译软件都是用的这个原理,具体的例子如下:
比如GetProcAddress这个API,在kernel.dll里面,我们要做针对它的通用
钩子就应该按照以下的步簇:
typedef UINT (WINAPI* FOO)(UINT);
FOO AllocCsToDsAlias;
HMODULE hKernel = GetModuleHandle("kernel");
AllocCsToDsAlias = (FOO)GetProcAddress(hKernel, "AllocCsToDsAlias");
FARPROC entry = GetProcAddress(hKernel, "GetProcAddress");
WORD offset = (WORD)(FP_OFF(entry));
UINT selector = AllocCsToDsAlias(FP_SEG(entry));
BYTE *addr = (BYTE *)MK_FP(selector, offset);
然后就可以往addr这个地址写5个BYTE的东西,第一个BYTE是jmp,不同的CPU
可能会不同,之后的一个DWORD是你的函数的地址。。
这样,可以通过保存两份addr的数据来做到钩子的开关,当钩子打开的时候,
所有的GetProcAddress的调用都会调到我们的函数,这个时候可以通过检查
如果有人想GetProcAddress wsock32.dll里的东西,我们就干掉它。。
同样,如果有人通过开启socket这个API的SOCK_RAW参数来调用RAW SOCKET监
视网络,我们也可以通过上面的方法来做到先入为主,看谁在监视我们的网络
传输。
===================================================================
《老子看不惯外挂系列 之 还是得从社会风气入手》
其实查外挂的原理和捉病毒的原理一样,只是现在做外挂的技术还不成熟。
当做外挂的技术和防外挂的技术在同一条线上的时候,想从技术上防住外挂
是不可能的。
其实是无奈,杀毒软件的做法是出来一种新的病毒,在第一时间内公布其特
征码,然后更新所有的客户端,看到这种特征的,就杀。其实查杀外挂也该
如此,其实CS的Cheating-Death就是这个原理,不管出了什么新的外挂,CD
总是在第一时间内更新,然后客户端也就傻傻的只要一看到有这个特征的东
西就把它干掉。其实仔细想想,这是个很好的主意,从外挂开发商的动机来
分析。他们之所以要开发外挂,估计炫耀技术是一方面,更重要的还是想赚
钱,或者用来挂机之类的,既然要赚钱,或者挂机,就必然会被其他玩家发
现或者举报,这个时候应该做的就是尽快的下载一份外挂,或者根据其行为
研究其特征,并在防外挂的特征码上记下一笔,其实不用太复杂,最简单的
做法就是记住其外挂窗口的名称,然后只要看到这个外挂窗口,做些处理就
好了。
那么这样一来,关键的技术就落在如何设计查外挂这个结构上了,首先客户
端需要的是一个查外挂引擎,和一个外挂特征库,根据引擎和特征库生成一
个固定的版本号,然后每次登陆的时候就用这个版本号,跟服务器上的版本
号对比,如果不一样的话,则从服务器自动下载最新的查外挂引擎和外挂特
征库。
这样虽然不能将外挂赶尽杀绝,却能比较大的限度上围剿使用外挂的风气,
相信使用外挂的玩家也只是想更好的玩游戏,只不过动的念头有些歪了,但
其出发点仍是好的,只要在他使用外挂的时候多些阻拦或者诱导他不使用外
挂,这样效果就会好很多。设想一下,谁愿意每天等一个外挂的更新,而不
去玩他很想玩的游戏呢。这样一来,制作外挂的人也会逐渐减少,从而进入
一个良性循环。
就好象如今写病毒,并不如当年那么流行了。。-。-
===========================
The CallNextHookEx function passes the hook information to the next hook procedure in the current hook chain. A hook procedure can call
this function EITHER BEFORE OR AFTER processing the hook information.
Calling CallNextHookEx is OPTIONAL !!!l, but it is highly recommended; otherwise, other applications that have installed hooks will not receive hook notifications and may behave incorrectly as a result. You should call CallNextHookEx unless you absolutely need to prevent the notification from being seen by other applications.
仔细看大写部分,你这个办法根本行不通
=======================================
CallNextHookEx()是传消息没错。
而钩子的回调函数的返回值是控制是否往下个钩子走的。
如果你返回0,那么就呼叫钩子链上的下一个钩子。
如果这个时候你不CallNextHookEx(),只是return 0的话。
下个钩子会被呼叫,但是没有消息。
如果return 1的话,呼叫了CallNextHookEx()也没有用。
做过实验了,钩住键盘,直接return 1可以屏蔽掉。
===================================
同样的键盘钩子,只有KeyboardProc不一样
程序1 是
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
int i = CallNextHookEx(g_hhook, nCode, wParam ,lParam);
CString str;
str.Format("i's value %d",i);
AfxMessageBox(str);
return 0;
}
程序2 是
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
int i = CallNextHookEx(g_hhook, nCode, wParam ,lParam);
CString str;
str.Format("i's value %d",i);
AfxMessageBox(str);
return 1;
}
1、如果只启动程序1
键入字母m效果是:
i's value 0;
i's value 0;
在记事本显示:m
2、如果只启动程序1
键入字母m效果是:
i's value 0;
i's value 0;
1、如果先启动程序1,再启动程序2。
键入字母m效果是:
i's value 0;
i's value 0;
i's value 0;
i's value 0;
2、如果先启动程序2,再启动程序1。
键入字母m效果是:
i's value 0;
i's value 1;
i's value 0;
i's value 1;
在记事本显示:m
谁能解释下这个现象的本质 ,和CallNextHookEx在钩子传递过程中的一些功能啊~
==========================================================
同样的键盘钩子,只有KeyboardProc不一样
程序1 是
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
int i = CallNextHookEx(g_hhook, nCode, wParam ,lParam);
CString str;
str.Format("i's value !!! %d",i);
AfxMessageBox(str);
return 0;
}
程序2 是
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
int i = CallNextHookEx(g_hhook, nCode, wParam ,lParam);
CString str;
str.Format("i's value %d",i);
AfxMessageBox(str);
return 1;
}
2、如果先启动程序2,再启动程序1。
键入字母m效果是:
i's value 0; 2
i's value !!! 1; 1
i's value 0; 2
i's value !!! 1; 1
在记事本显示:m
1、如果先启动程序1,再启动程序2。
键入字母m效果是:
i's value !!! 0; 1
i's value 0; 2
i's value !!! 0; 1
i's value 0; 2
1、如果只启动程序1
键入字母m效果是:
i's value !!! 0;
i's value !!! 0;
在记事本显示:m
2、如果只启动程序2
键入字母m效果是:
i's value 0;
i's value 0;
1、如果先启动程序1,再启动程序2。
键入字母m效果是:
i's value !!! 0; 1
i's value 0; 2
i's value !!! 0; 1
i's value 0; 2
2、如果先启动程序2,再启动程序1。
键入字母m效果是:
i's value 0; 2
i's value !!! 1; 1
i's value 0; 2
i's value !!! 1; 1
在记事本显示:m
谁能解释下这个现象的本质 ,和CallNextHookEx在钩子传递过程中的一些功能啊~