Copyright (c) 2010 Czy Invicta <Hack01@Live!cn> All rights reserved.
简介 ~~~~~ 本文概述利用内核代码执行对数字游戏产生的影响,这样做的理由是如果不扫描内核的话,游戏客户端无法扫描外挂代码,所有代码从进程空间无法获取,这和通常的外挂制作方法完全不同。
开工 ~~~~~ 为了达到本文所说的目的,可以将所有应用层代码移至另一台终端机,运行游戏的终端机通过通信链路与第一个终端机通信。这也是内核辅助工具的通用结构,两台机器之间使用TCP/IP通信,游戏客户端进程被注入代码,该代码不通过注入DLL或线程实现,功能也非常简单,从而难以检测。另外,可以通过内核隐藏注入在游戏空间的代码。关键部分就是注入游戏客户端的代码,它和Java虚拟机很类似,注入的代码执行通用指令,相当于一个虚拟的CPU。
然而,我们还需要做得一件事情就是如何制作一个人工智能的自动化操控代码。代码组织的结构为:代码保存了一系列的函数,每个函数完成固定的功能。例如:判断对手类型、判断玩家事件类型、受到伤害、使用技能攻击……等。
使用全局变量标志玩家角色。代码能检查各种状态,玩家可以通过代码配置帮助和辅助角色自动操控。
ASSIST(character name) {
接下来使用一系列回调函数对应不同事件。
OnActivate() //协助时调用代码被激活或停用 }
然后使用定时器处理通用状态,主要用于玩家角色状态。
OnTimer() {
变量ME代表玩家角色。
//每秒调用一次,管理所有角色 if(ME.Buffs.Has("专注光环")) { } else {
Cast命令用于释放技能。
Cast("专注光环"); }}
使用伤害检测函数检查当前伤害状态是否容易导致死亡,玩家是否需要对自身进行防护等。
OnDamage() { // 损失时调用 if(RATE > 50%) { // RATE用来衡量对生命值损失的程度 // 在最后一秒采取措施 Cast("圣盾"); //施放咒语 RETURN; }
if(ME.Health < 20%) { TargetSelf(); Cast("圣光"); TargetLastTarget(); } }
下面的代码仅作为示例,回调函数根据每个类型的对手而不同。如果有3个对手在攻击范围内。回调函数对3个对手分别调用,每个一次,最终,每个对手类型对应一个回调函数。
ForHunter() { // 调用任意对手,检查谁是Hunter
在回调函数中,如果玩家瞄准了一个对手,接下来发生一系列攻击步骤。
if(ME.Target == HUNTER) { if(RANGE < 30) {
下列伪代码描述了战士如何操作可以给对手最大化伤害。
if(ME.Buffs.Has("十字军圣印")) { Cast("审判"); Cast("命令圣印"); }
EnsureMeleeMode();
if(ME.Buffs.Has("命令圣印")) {
下列代码检查代码是否有足够的魔法值,以及命令圣印是否小于10秒,如果条件都满足的话则使用审判技能。
if(ME.Mana > 10%) { if(ME.Buffs.TimeLeft("命令圣印") < 10) { Cast("审判") Cast("命令圣印"); }}}} else { if(ME.Mana > 60%) CastIfNotBuffed("十字军圣印"); }}}
有些对手有宠物,也可以在回调函数中处理宠物攻击。本例中,代码会自动攻击宠物,并通知玩家角色,该对手需要优先处理。
ForHunterPet() { // 调用任何Hunter的宠物
if(RANGE < 10) { // 关闭宠物 if(PET.Target == ME OR PET.Owner ==ME) { // 这个对手击晕了我们的宠物,我们将攻击对手 Target(PET); Cast("正义之盾");
// 设置目标优先权5 PriorityTarget(PET.Owner, 5); SelfMessage("攻击HUNTER"); } } }
可以为其他对手类型设置类似的回调函数。当然,这只是一种可能的外挂实现方式,本文采用这种方法的原因是每种类型的对手需要使用不同的战斗策略。
ForMage() { }
ForWarlock() { }
//等等……
外挂用户界面 ~~~~~~~~~~~~~ 本文的此部分概述如何使用基于对话框的应用实现外挂控制功能。采用基于对话框的MFC应用创建外挂程序,可以使用Visual Studio应用向导自动生成应用框架。
使用Visual Studio应用向导自动生成基于对话框的应用框架
生成应用框架后,可在对话框界面添加各种各样的控件,比如列表框控件可以用于列出游戏世界对象,按钮用于控制行为等。下图展示了如何生成对话框程序。使用Visual Studio的向导功能生成外挂界面非常方便。
通过程序向导,可以在对话框中加入各种功能,这可以轻松地使外挂具备丰富的界面
然而,大多数在线游戏使用绚丽的3D渲染效果构建虚拟世界。如果要想使外挂看上去和游戏很匹配的话,外挂界面也应当有3D渲染能力。OGRE库由一系列C++类实现,使得3D图像操作更加容易。可以从http://www.ogre3d.org下载OGRE库,注意,该库的使用需要遵循LGPL许可。
另外,模型可由其它一些三维软件进行输出,如3ds Max、Maya,通过它们还可以制作自己的三维模型文件并使用。如下图所示:
通过3ds Max三维软件建立的角色模型
下面代码用于包含OGRE库头文件,在一个3D虚拟世界里实现基本的渲染: #include "Ogre.h" #include "OgreConfigFile.h" #include "OgreKeyEvent.h" #include "OgreEventListeners.h" #include "OgreStringConverter.h" #include "OgreException.h" #include <map>
using namespace Ogre; RenderWindow * m_renderwindow; Root * m_root; Camera * m_camera; RenderSystem * m_RenderSystem; SceneManager * m_sceneMgr; Viewport * m_viewport;
Entity * m_miner; Entity * m-bldg; void Init(HWND hWindow); void Update(); void Kill(); void UpdateObjectPosition(DWORD id, POINT p);
void MoveCamera(float distance); void RotateCamera(float degrees); void PitchCamera(float degrees); void LookAt(int id);
BOOL selectRenderingPlugin(char *theName); void loadResources(); void createDebugObjectsInScene(); void createPlane();
std::map<DWORD, SceneNode *> g_points;
下面的代码需要调用者传递父窗口句柄。如果使用MFC框架创建应用,可以通过对话框对象的成员m_hWnd来获取窗口句柄。下面的代码创建OGRE 3D渲染窗口,放置该窗口在父窗口上。
void Init(HWND hWindow) { OGRE需要一个Root对象必须首先创建。 m_root=new Root("",""); //Root没有配置文件
现在加载两个图像子系统,使用OpenGL。
m_root->loadPlugin("RenderSystem_Direct3D9"); m_root->loadPlugin("RenderSystem_GL");
// 使用OpenGL渲染 if(FALSE == selectRenderingPlugin("OpenGL")) return;
调用函数initialise后加载资源。资源包含场景中的纹理结构等素材。可以从网络下载到很多工具,用于将对象转换为OGRE资源文件。
m_root->initialise(false); loadResources();
接下来创建渲染窗口。OGRE负责创建渲染窗口,开发人员只需使用getCustomAttribute获取创建的窗口句柄即可。获取窗口句柄后,使用SetWindowLong()修改窗口风格为WS_CHILD。 m_renderwindow = m_root->createRenderWindow( "摄影机", 280, //宽 290, //高 false, //全屏或不是 0); //可选择 HWND aHandle; m_renderwindow->getCustomAttribute("HWND", &aHandle); SetParent(aHandle, hWindow); SetWindowLong(aHandle, GWL_STYLE,WS_CHILD | WS_BORDER | WS_VISIBLE);
现在获取SceneManager,SceneManager为OGRE中非常重要的对象,该对象直接管理所有3D对象。
m_sceneMgr = m_root->getSceneManager(ST_EXTERIOR_CLOSE);
现在创建摄影机,用于移动场景位置,修改视角和观察点。可以使用摄影机查看不同对象。
m_camera = m_sceneMgr->createCamera("MainCam"); m_camera->setNearClipDistance(1.0f); m_camera->setFarClipDistance(50000.0f);
同时需要建立摄影机的视图通道:
m_viewport = m_renderwindow->addViewport(m_camera); m_camera->setAspectRatio(Real(m_viewport->getActualWidth()) / Real(m_viewport->getActualHeight()));
最后,加载资源和对象到创建的世界中。
//加载默认对象 m_miner = m_sceneMgr->createEntity("miner", "knot.mesh"); m_miner->setMaterialName("MinerMaterial");
m_bldg = m_sceneMgr->createEntity("bldg", "knot.mesh"); createPlane(); createDebugObjectsInScene(); } void Kill() { HWND aHandle; m_renderwindow->getCustomAttribute("HWND", &aHandle); DestroyWindow(aHandle); }
void Update() { m_renderwindow->reposition(260,27); m_renderwindow->resize(280.290); m_renderwindow->update(); }
// 用于测试,创建一个调试对象 void createDebugObjectsInScene() { Entity* myKnot = m_sceneMgr->createEntity("knot", "knot.mesh"); myKnot->setCastShadows(true);
SceneNode* myNode1 = m_sceneMgr->getRootSceneNode()->createChildSceneNode("node_1"); myNode1->attachObject(myKnot);
// 设置节点的位置 myNode1->setPosition(Vector3(0,0,0)); m_camera->setPosition(Vector3(0,300,0)); m_camera->lookAt(Vector3(0,0,0));
// 设置环境光 m_sceneMgr->setAmbientLight(ColourValue(0.4,0.4,0.1));
// 创建一个灯光 Light* l = m_sceneMgr->createLight("MainLight"); l->setType(Light::LT_POINT); l->setDiffuseColour(200,200,200); l->setPosition(30,30,30); } BOOL selectRenderingPlugin(char *theName) { assert(m_root != NULL);
// 列出它们 RenderSystemList *rList = m-root->getAvailableRenderers(); RenderSystemList::iterator it = rList->begin();
// 重复它们的实例 while(it != rList->end()) { RenderSystem *rSys = *it; it++; if(rSys->getName().find(theName)) { m_root->setRenderSystem(rSys); m_RenderSystem = rSys; break; } }
// 如果我们不能找到它们如何结束 if(m_root->getRenderSystem() == NULL) { assert(m_RenderSystem == NULL); retrun FALSE; } retrun TRUE; }
void UpdateObjectPosition(DWORD id, POINT p) { if(g_point.find(id) == g_points.end()) { char node_name[64]; _snprintf(node_name, 62, "node_%d", id);
// 它不存在,创建一个新的对象 SceneNode* a_node = m_sceneMgr->getRootSceneNode()->createChildSceneNode(node_name); Entity *e = m_sceneMgr->createEntity(node_name, "ninja.mesh"); e->setMaterialName("MinerMaterial"); a_node->attachObject(e); a_node->setPosition(Vector3(p.x, 0, p.y)); g_points[id] = a_node; } else { //它存在,所以更新其位置 SceneNode *a_node = (SceneNode *)g_points[id]; a_node->setPosition(Vector3(p.x, 0, p.y); } }
void loadResources() { ResourceGroupManager::getSingleton().addResourceLocation( "./media/models", "FileSystem", "General");
ResourceGroupManager::getSingleton().addResourceLocation( "./media/scripts", "FileSystem", "General");
ResourceGroupManager::getSingleton().addResourceLocation( "./media/textures", "FileSystem", "General");
ResourceGroupManager::getSingleton(). initialiseAllResourceGroups(); }
void MoveCamera(float distance) { m_camera->moveRelative(Vector3(0,0,distrance)); char _t[255]; Vector3 v = m_camera->getPosition(); _snprintf(_t, 252, "camera is %f %f %f", v.x, v.y, v.z); OutputDebugString(_t); }
void RotateCamera(float degrees) { m_camera->yaw(Radian(degrees)); char _t[255]; Vector3 v =m_camera->getDirection(); _snprintf(_t,252,"camera direction at %f %f %f", v.x, v.y, v.z); OutputDebugString(_t); }
void PitchCamera(float degrees) { m_camera->pitch(Radian(degrees)); char _t[255]; Vector3 v = m_camera->getDirection(); _snprintf(_t,252,"camera direction at %f %f %f",v.x, v.y, v.z); OutputDebugString(_t); }
Void createPlane() { Plane plane; plane.normal = Vector3::UNIT_Y; plane.d = 0; MeshManager::getSingleton().createPlane("plane_1",ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,plane,50000,50000,10,10,true,1,50,50,Vector3::UNIT_Z);
Entity *aPlaneEntity_1 = m_sceneMgr->createEntity("e_plane_1","plane_1"); aPlaneEntity_1->setCastShadows(false); SceneNode *aSceneNode_1 = m_sceneMgr->getRootSceneNode()->createChildSceneNode("n_plane_1"); aSceneNode_1->attachObject(aPlaneEntity_1); aPlaneEntity_1->setMaterialName("PlaneMaterial"); aSceneNode_1->setPosition(0,-1000,0); }
void LookAt(int id) { SceneNode *a_node = (SceneNode *)g_points[id]; Vector3 v= a_node->getPosition(); m_camera->setPosition(Vector3(v.x,v.y+500,v.z-200)); m_camera->lookAt(v); }
可以将用户界面编写的非常复杂,甚至达到重新创建客户端的程度。实际上,一些外挂确实可以做到完全替换客户端。
尾声 ~~~~~ 本文简单介绍了内核模式外挂和外挂用户界面,外挂程序编写是游戏逆向分析的中心议题,玩游戏的过程中要是有程序能自动操控虚拟角色进行清怪并赢得游戏是件很舒服的事情,特别是这种自动程序还可以带来金钱利益的话就更再好不过了。本文概述的知识不针对某一款游戏,实际上,本文所概述的知识适用于任何在线数字游戏。
下载PDF版 http://www.esnips.com/doc/f145cc80-3658-4ff8-a507-67455c52d828/自动化操控虚拟战争
# HACKER NETSPY [CZY]
|