[原创]第2弹
warning:文中贴有大量代码。
前言:
上次说了一种动态的检测方案,这次则说说网游中比较常见的反外挂解决方案吧。一般来说有些公司有钱有技术,投入人力物力自己研发一套反外挂系统出来自己用。而有些公司技术实力没这么强,没有相关的专业人员,就会购买现成的反外挂系统来使用,比如常见的nProtect GameGuard,HackShield,XignCode3等等。又有些公司会出于技术沉淀或某些原因,既会购买成熟的反外挂系统,也会投入精力自己进行研发反外挂系统。今天要讲的就是一个基于nProctect + 自研发反外挂 的双层反外挂系统。
正文:
开始之前有必要谈谈nProctect,这个反外挂系统是韩国人开发的,目前应该来说应算得上流行的吧,我先简述下这东西是怎么用的。首先你自己得搭建一个用于他更新版本的HTTP服务器,然后把FTP账户密码发给他们的技术人员,他们会往上面上传网页文件,等到他们上传好后,他们的人会通知你叫你把client跟server端的编译环境信息发给他们,他们会发把用于client跟server用的lib文件还有ini文件发给你,然后就可以开始写代码测试啦。
如果不考虑np的cs模块的话,它的开启开始比较简单的,
//////////////////////////////////////////////////////////////////////////////////
bool CProtectGameGuard::initializeGameGuard()
{
// 不要启动GameGuard
std::string cmdline = my_t2utf8(GetCommandLine());
bool isDebugMode = cmdline.find(" nonp") != std::string::npos;
if ( isDebugMode==true )
{
return true;
}
//ShowWindow((HWND)gGlobalClient->getHWND(),SW_HIDE);
mNPGameLib = new CNPGameLib(_T("DanXing"));
DWORD dwResult = mNPGameLib->Init();
if (dwResult != NPGAMEMON_SUCCESS)
{
// Result of GameGuard initialization error
switch (dwResult)
{
case NPGAMEMON_ERROR_EXIST:
break;
case NPGAMEMON_ERROR_GAME_EXIST:
break;
case NPGAMEMON_ERROR_INIT:
break;
case NPGAMEMON_ERROR_AUTH_GAMEGUARD:
break;
case NPGAMEMON_ERROR_NFOUND_GG:
break;
case NPGAMEMON_ERROR_AUTH_INI:
break;
case NPGAMEMON_ERROR_NFOUND_INI:
break;
case NPGAMEMON_ERROR_CRYPTOAPI:
break;
case NPGAMEMON_ERROR_EXECUTE:
break;
case NPGAMEMON_ERROR_ILLEGAL_PRG:
break;
case NPGMUP_ERROR_ABORT:
break;
case NPGMUP_ERROR_CONNECT:
break;
case NPGAMEMON_ERROR_GAMEGUARD:
break;
case NPGMUP_ERROR_PARAM:
break;
case NPGMUP_ERROR_INIT:
break;
case NPGMUP_ERROR_DOWNCFG:
break;
case NPGMUP_ERROR_AUTH:
break;
case NPGAMEMON_ERROR_NPSCAN:
break;
case NPGG_ERROR_COLLISION:
break;
default:
break;
}
char msg[512];
sprintf( msg,_GT("nProtect GameGuard 初始化产生错误:%d"),dwResult );
SendLogToServer( dwResult,msg,strlen(msg) );
MessageBox( (HWND)gGlobalClient->getHWND(),my_utf82t(msg), my_utf82t(_GT("初始化失败")),MB_OK );
char faq_url[256];
sprintf( faq_url,"http://danxing/service/gameguard_faq.html#A%d",dwResult );
shellExecute( "open",faq_url );
g_pApp->exit();
return false;
}
mNPGameLib->SetHwnd( (HWND)gGlobalClient->getHWND() );
mNPGameLib->Send(my_utf82t(GetMacAddress()));
gGlobalClient->getTimerAxis()->SetTimer( 0,100,this );
gGlobalClient->getMessageDispatch()->registerMessageHandler( MSG_MODULEID_GAMEGUARD,this );
return true;
}我们只需要简简单单调的new一个全局的np对象,再调用nplib文件中的NPGameLib->Init();就可以开启了,直到最后它开了一个定时器和注册了一条网络消息。
讲到这里就得介绍一下nProtect的cs模块了,我们来看看官方简述:
7.1.2.为什么需要CS认证 当GameGuard 被强行终止,游戏用户试图运用被修改的客户端连接服务器时; 当GameGuard 因篡改后的客户端而无法运行时; 当黑客程序连接服务器而非客户端连接服务器时; 当较老GameGuard版本和黑客程序同时运行,GameGuard不能更新时。 服务器内安装CS认证后,能够确认GameGaurd是否正常运行,并最终解决以上四项漏洞。
所谓的cs模块,原来就是np客户端跟np服务端的网络通讯模块,当你打开这项功能后,就算你把np客户端给破坏掉了(比如我直接把上文中的initializeGameGuard函数给nop掉 了),np服务端会检测到没有通讯了,也会将你从游戏中踢出断线。
来张图更好理解

我们看下定时器里面是怎么处理的
//////////////////////////////////////////////////////////////////////////////////
void CProtectGameGuard::OnTimer( unsigned long dwTimerID )
{
if ( mNPGameLib==0 )
{
return ;
}
// 检查错误
if ( GetTickCount()-mLastCheckTime>1000*10 )
{
mLastCheckTime = GetTickCount();
DWORD dwReturn = mNPGameLib->Check();
if (dwReturn != NPGAMEMON_SUCCESS)
{
char msg[256];
sprintf( msg,"npgl.Check() fail.error code=%d",dwReturn);
MessageBox((HWND)gGlobalClient->getHWND(),my_utf82t(msg),_T("error"),MB_OK);
g_pApp->exit();
}
}
// 弹出错误信息
if ( mErrorCode!=0 )
{
switch (mErrorCode)
{
case NPGAMEMON_COMM_ERROR:
MessageBox( (HWND)gGlobalClient->getHWND(),my_utf82t(_GT("nProtect GameGuard非正常关闭")),my_utf82t(_GT("防外挂保护")),MB_OK );
break;
case NPGAMEMON_INIT_ERROR:
MessageBox( (HWND)gGlobalClient->getHWND(),my_utf82t(_GT("nProtect GameGuard初始化失败")),my_utf82t(_GT("防外挂保护")),MB_OK );
break;
case NPGAMEMON_SPEEDHACK:
MessageBox( (HWND)gGlobalClient->getHWND(),my_utf82t(_GT("nProtect GameGuard检测到加速程序,请关闭后再启动游戏")),my_utf82t(_GT("防外挂保护")),MB_OK );
break;
case NPGAMEMON_GAMEHACK_KILLED:
case NPGAMEMON_GAMEHACK_DETECT:
MessageBox( (HWND)gGlobalClient->getHWND(),my_utf82t(_GT("nProtect GameGuard检测到非法程序,请关闭可疑程序后再启动游戏")),my_utf82t(_GT("防外挂保护")),MB_OK );
break;
case NPGAMEMON_GAMEHACK_DOUBT:
MessageBox( (HWND)gGlobalClient->getHWND(),my_utf82t(_GT("nProtect GameGuard检测到可疑程序,请关闭后再启动游戏")),my_utf82t(_GT("防外挂保护")),MB_OK );
break;
default:
break;
}
mErrorCode = 0;
g_pApp->exit();
}
// 看看有没有数据包要发送
obuf128 * pBuf = 0;
while( mRequestSendQueue.pop(pBuf) )
{
if ( pBuf!=0 && gGlobalClient->getNetConnection() )
{
gGlobalClient->getNetConnection()->SendData( pBuf->begin(),pBuf->size() );
delete pBuf;
pBuf = 0;
}
}
}
定时器里面就干了2件的事情:
1. 检查np的状态是否正常。
2. 定时发送cs回应包给服务端。
客户端是先收到心跳包后再发送回应包的,我们接着看当客户端收到了cs心跳包的时候是怎么处理的
//////////////////////////////////////////////////////////////////////////////////
void CProtectGameGuard::onMessage(ulong actionId, SGameMsgHead* head, void* data, size_t len)
{
if ( mNPGameLib==0 )
{
return;
}
switch( head->wKeyAction )
{
case MSG_GATEWAY_GAMEGUARD_AUTH:
{
if ( len!=sizeof(GG_AUTH_DATA) || data==0 )
return;
GG_AUTH_DATA * pAuthData = (GG_AUTH_DATA *)data;
mNPGameLib->Auth2(pAuthData);
}
break;
default:
break;
}
}把接收到的消息交给np的Auth2函数处理,然后NP就会自动在你的回调函数中通知你要回应的心跳包是什么了,如下:
//////////////////////////////////////////////////////////////////////////////////////////////////////
// nProtect保护回调
//////////////////////////////////////////////////////////////////////////////////////////////////////
BOOL CALLBACK NPGameMonCallback(DWORD dwMsg, DWORD dwArg)
{
char szHackInfo[MAX_PATH] = {0,};
strcpy(szHackInfo, gGlobalClient->getnProtectGameGuard()->GetInfo());
switch (dwMsg)
{
case NPGAMEMON_COMM_CLOSE:
return FALSE;
case NPGAMEMON_COMM_ERROR:
case NPGAMEMON_INIT_ERROR:
case NPGAMEMON_SPEEDHACK:
case NPGAMEMON_GAMEHACK_KILLED:
case NPGAMEMON_GAMEHACK_DETECT:
case NPGAMEMON_GAMEHACK_DOUBT:
CProtectGameGuard::getInstance().SetErrorCode(dwMsg);
break;
case NPGAMEMON_GAMEHACK_REPORT:
{
DWORD dwHackInfoSize = 0;
LPBYTE pHackInfo = NULL;
pHackInfo = gGlobalClient->getnProtectGameGuard()->GetHackInfo(&dwHackInfoSize); // C++ 老 版快.
if (pHackInfo && dwHackInfoSize > 0)
{
CProtectGameGuard::getInstance().SendLogToServer( dwArg,pHackInfo,dwHackInfoSize );
}
return TRUE;
}
case NPGAMEMON_CHECK_CSAUTH2:
{
GG_AUTH_DATA GGAnswer;
// 3. NPGameMonCallback 窃荐甫 烹秦辑 函版等 菩哦阑 罐澜
// 3. Receiving modified packet from NPGameMonCallback function
memcpy( &GGAnswer, (PVOID)dwArg, sizeof( GG_AUTH_DATA ) );
// 向服务器发送认证数据包,这时应同步callback 函数,因为GameGuard正在使用该函数。
obuf128 * pBuf = new obuf128;
obuf128 & buf = *pBuf;
SGameMsgHead header;
header.SrcEndPoint= MSG_ENDPOINT_CLIENT;
header.DestEndPoint = MSG_ENDPOINT_GATEWAY;
header.wKeyModule = MSG_MODULEID_GAMEGUARD;
header.wKeyAction = MSG_GATEWAY_GAMEGUARD_AUTH;
buf << header;
buf << GGAnswer;
// 只能在主线程发送数据包
CProtectGameGuard::getInstance().RequestSendData(pBuf);
return TRUE;
}
}
return TRUE;
}///////////////////////////////////
....
....
//////////////////////////////////
Np在客户端的部署就这些了,在服务端的部署,我看你们也不敢兴趣就不写了,上个帖子里面写了没啥响应。
我们接着看它自己研发的反外挂系统,同样,只讲客户端。
这个反外挂系统实现了支持检测进程名、模块名、窗口标题、SHA1值和特征代码的检测。,有着较良好的设计,其中每个检测模块都被称为Detect (检测器),
// 检测结果
struct SDetectResult
{
SDetectInfo detectInfo; // 检测信息
SDetectRecord detectRecord; // 检测记录
};
// 检测接口
struct IDetect
{
/*******************************************************************************
函数名称:Execute
函数描述:执行检测器
输入参数:listDetectRecord 检测记录列表
输出参数:detectResult 检测结果信息
返回值 :检测到:true,未检测到:false
备注 :N/A
*******************************************************************************/
virtual bool Execute(list<SDetectRecord> &listDetectRecord, SDetectResult &detectResult) = 0;
/*******************************************************************************
函数名称:Release
函数描述:释放函数
输入参数:N/A
输出参数:N/A
返回值 :void
备注 :N/A
*******************************************************************************/
virtual void Release() = 0;
};其中检测器的实现使用了命令模式,每个具体的检测器实现都必须继承该类,
// 检测客户端主机的进程类 class CDetectProcess : public IDetect // 检测与客户端的游戏进程相关的模块的类 class CDetectModule : public IDetect // 检测与客户端的游戏进程相关的模块的SHA1的类 class CDetectSHA1 : public IDetect // 检测与客户端的游戏进程相关的模块的特征代码的类 class CDetectFeatureCode : public IDetect // 检测客户端主机系统的窗口的类 class CDetectWindow : public IDetect
然后在游戏中定时检测与上报结果
void CAntiCheatingClient::Startup()
{
if (NULL == gGlobalClient)
{
return;
}
// 标志未完成检测
m_bIsFinishedDetect = false;
if (0 == m_dwTimerID[DETECT_TIMER_ID_INDEX])
{
// 启动定时检测
m_dwTimerID[DETECT_TIMER_ID_INDEX] = 1116;
gGlobalClient->getTimerAxis()->SetTimer(m_dwTimerID[DETECT_TIMER_ID_INDEX],
m_sAntiCheatingConfig.nDetectInterval * 60 * 1000,
this);
}
if (0 == m_dwTimerID[REPORT_TIMER_ID_INDEX])
{
// 启动定时上报检测结果
m_dwTimerID[REPORT_TIMER_ID_INDEX] = 1111;
gGlobalClient->getTimerAxis()->SetTimer(m_dwTimerID[REPORT_TIMER_ID_INDEX],
m_sAntiCheatingConfig.nReportInterval * 1000,
this);
}
}写的太长了,这个帖子还是主要讲NP。每个检测器的具体实现,与这个反外挂系统的详细设计下个帖子再说。
