欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]第2弹

[原创]第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。每个检测器的具体实现,与这个反外挂系统的详细设计下个帖子再说。

上传的附件 GX%)%4R~KY0@Y0_PZDX{}LJ.png

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功