CSGO辅助制作思路与VAC保护分析 – 您所在的位置:网站首页 csgo游戏测试失败 CSGO辅助制作思路与VAC保护分析 –

CSGO辅助制作思路与VAC保护分析 –

2024-01-07 20:54| 来源: 网络整理| 查看: 265

这个月找了点资料,整了整CSGO的外挂。总体感觉CSGO官匹的保护还是很宽松的,这里记录一下设计思路与过程(这博客我竟然鸽了一个多月….老拖延症了

地址寻找

既然是实现辅助,那我们必然要对游戏中一些内存的值做读写,而实现读写的前提是我们知道它们在内存中的地址。所以做辅助前需要花大量的时间去寻找我们所需变量在内存中的地址。

寻找地址是一项极为枯燥繁琐的工作,大体思路是控制游戏的一些可变量,使其改变或者不变,同时搜索内存中发生改变或者不变的值,以此来缩小筛选范围

PS: 地址搜索一定要通过 “csgo.exe -insecure"不安全模式运行游戏,这样游戏不会去连接VAC服务器执行安全策略,特别是当CE还是用的官网版不是魔改版的情况下,一定不要头铁去官匹搜内存。不然分分钟你就变成封号斗罗(不要问我为什么这么清楚)

查找视野矩阵

当我们进行游戏时,准星处其实是一个视野矩阵,当我们跳一下时会发现上下左右四个点会向外扩展,静步时会向内收缩。找到这个矩阵在内存中存在的位置是实现自瞄的关键。

这个矩阵在内存中是以浮点数的二维数组形式表示,并且其不会随着人物移动而改变,只有控制准星的移动,它的值才会改变。根据这个特性,我们可以使用CE搜索浮点数内存将变量筛选到100个

然后视野矩阵还有个特征,就是在不开镜的情况下,其首元素值只会在 -1 ~ 1 之间,开镜后其值会大于1。根据这个特征,搜索 -1.5 ~ 1.5间的浮点数,可以再度收缩筛选范围。而后拿狙击枪开镜再筛选一次,就可以将筛选范围降的很低了。接下来对剩下的几个地址右键浏览相关内存区域,可以看到当我们准星移动时,该内存数值一直在发生变化,当我们准星不移动时该内存不变。这样就可以确认我们成找到了视野矩阵地址

并且可以看到这个地址是绿色的,也就是说其本身就是基址,那记录下来就可以了,不需要再费工夫去找它的基址了

查找自己角度

相比于查找视野矩阵来说,查找自己角度又要简单很多了。对于这类沙盒游戏,其实只需要一个表示左右的角度和一个表示上下的角度就可以表示全部的方向了。 查找方法于前面类似,通过找改变角度查找变动的浮点数即可得到。

同时,CSGO的角度查找还有一个特征,当准星指向最上方的时候,上下角度角度值为-89,当准星指向最下方的时候,上下角度的角度值为89。通过这个特征,就能很容易的确定角度指针

查找自己坐标值

同样的,既然是个沙盒游戏,其本身其实就是模拟了一个三位空间,那必然有变量表示着我当前的X Y Z的坐标值。我们可以通过敌人坐标值与我们坐标值做运算得到与敌人的相对距离,即自瞄距离。

这个坐标值的寻找也非常容易,就是控制自己的移动与不移动来用CE查找变动与不变动的浮点数

查找敌人与队友的信息

这个信息的查找应该就是整个外挂实现最重要的部分了,因为得到了敌人的结构体,我们就能得到关于敌人血量、护甲、武器、坐标值在内的大量信息。按常理,敌人指针应该是非常难找的,但CSGO给我们提供了一个非常好用的工具————“开发者控制台”。通过这个神器,我们可以控制机器人的动与不动,进而搜索得到机器人的XYZ坐标值,再上推出角色结构体

首先先用指令 “bot_kick”踢出所有机器人,然后用 “bot_add” 指令增加一个敌方机器人,接着通过 “bot_stop” 这条指令控制机器人的移动与不移动,通过CE搜索改变与不改变的浮点数值。然后还可以自己与机器人站在一起,通过自己的坐标判断机器人坐标的大致范围,进行数值筛选

筛选到这一步其实已经很难通过控制机器人是否移动再来筛选了,但是还是会发现列表中有许多相差很近的数值,一般认为这是敌人某些骨骼的坐标值,所以我们可以通过浏览内存区域功能来判断出敌人这个值是敌人本身的XYZ坐标值还是骨骼的XYZ坐标值。(骨骼XYZ坐标值往往是连成一片的,而敌人本身的XYZ坐标值则是连续的三个浮点变量)

其中还可以看到一个带绿字的地址,根据前面找自身XYZ坐标时的经验可以知道server.dll这个位置保存着所有角色的XYZ坐标值,我们可以通过这个值拿到敌人的XYZ坐标,然后以这个坐标值为筛选条件筛掉一部分骨骼地址

然后就是漫长的、枯燥乏味的搜索,如果运气好,能很快找到这样一个地址

这个 “client.dll+4D523AC”这个地址非常眼熟,在前面搜索自身的XYZ值时可以看到其机制为 “client.dll+4D5239C”,它们直接仅相差了0x10的偏移。这里可以有一个大胆的猜想,游戏将所有的玩家实例基址做到了一个数组里,并且每个玩家的基址相差0x10

为了验证这个猜想,再添加了2个机器人,然后用CE的结构分析工具观察“client.dll+4D5239C”这个数组,可以看到,数组里有4个成员了

可以跟入这些指针,可以看到包括生命值、护甲、金钱、坐标值、阵营标识在内的所有信息,并且偏移值均相同,说明用于初始化他们的类是同一个。至此,我们得到了该局游戏所有玩家的数组

查找敌人骨骼

虽然根据前面的方法,我们得到了敌人的坐标,已经可以计算角度实现方框透视和自瞄了,但是如果希望实现骨骼透视和锁头,还需要得到敌人的骨骼坐标。

先来解释一下骨骼是什么,在这类3D游戏的建模中,为了实现人物模型的可变化,一个人物模型其实是由多个模型共同组成的,头、手、腿、脚、身体等都是不同的模型,将他们拼装在一起在是一个人物的完整模型。这样的设计模式可以实现各个骨骼的各自移动,让人物看起来更真实自然。因此,每块骨骼都应该具有一个独立的坐标点,我们得到了其中某些骨骼的坐标点,才可以实现出骨骼透视和锁头。

其实大家应该已经猜到,在前面搜索敌人坐标点时,看到的很多极为相近的数值,就应该是某些骨骼的坐标点,那我们现在要做的就是搜索敌人骨骼结构体的地址。 搜索骨骼地址,有个非常关键的技巧就是,当我们看向或者靠近敌人的时候,敌人的骨骼坐标就会发生变动,而当我们远离且不看向敌人时,这个值就不变。这其实是游戏在模拟敌人呼吸而产生的全身器官的轻微摆动。 通过这一特征,我们可以用指令禁止掉机器人的行动,然后控制自己看向或不看向敌人来搜索变动或不变动的浮点数。

成功找到后,看向敌人时CE浏览内存应该显示这样的图案,可以看到,下面这个数组均带XYZ坐标值,并且都在浮动

而当我们离敌人有一段距离且不看向敌人时,这块内存区域的值就不变

这样就成功找到了敌人骨骼结构体的地址,然后回溯找基址,就会发现在敌人角色结构体的某一偏移处保存着这一地址,那就说明成功找对了。角色结构体保存着血量、护甲、金钱、阵营标识、骨骼地址等等于该角色有关的信息,并且由一个列表保存着每个角色实例的指针,一切的逆向分析的结果都非常合理,说明我们成功得到了我们需要实现外挂功能的所有信息,下面就可以进行外挂的实现了。

外置挂制作

外置挂指的是外挂模块并不注入到游戏进程空间内,而仅仅作为一个外部的进程通过其他的手段来读写游戏进程内存。在CSGO游戏的外置挂中,主要表现为创建一个透明窗体并覆盖在游戏窗体上,通过外部的内存读写读取到敌人的信息,然后在透明的窗体上绘制出敌人纹理实现透视。同样的,因为涉及透明窗体的创建,游戏可以通过枚举窗体来发现这个用于纹理绘制的透明窗体的存在。截至至2020年11月(写这篇博客时),CSGO官匹还没有启用窗体枚举的检测,但是基于5E、BE等平台的CSGO启用了这个检测,因此正常来说这个方法仅限于CSGO官匹的作弊。 这里先附张效果图

运行外挂后可以看到生成了一个窗体名为随机字符串的透明窗体

对于CSGO来说外置挂的核心就是透明窗体的创建,可以设计一个窗口覆盖类来完成窗体的初始化和绘制等一系列操作

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 /* 窗口覆盖类 */ class Overlay { private: IDirect3D9* m_IDirect3D9; IDirect3DDevice9* m_IDirect3DDevice9; ID3DXLine* m_ID3DXLine; ID3DXFont* m_ID3DXFont; D3DPRESENT_PARAMETERS m_D3DPRESENT_PARAMETERS; HWND m_hwnd; HWND m_game; /* 随机化字符串 */ char* random_string() { static std::vector maps{ 'q','w','e','r','t','y','u','i','o','p','l','k','j','h','g','f','d','s','a','z','x','c','v','b','n','m','Q','A','Z','W','S','X','E','D','C','R','F','V','T','G','B','Y','H','N','U','J','M','I','K','O','L','P','1','2','3','4','5','6','7','8','9','0' }; static char buffer[100]{ 0 }; srand((unsigned)time(nullptr)); for (int i = 0; i CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hwnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &m_D3DPRESENT_PARAMETERS, &m_IDirect3DDevice9); if (result != D3D_OK) { MessageBoxA(nullptr, "CreateDevice", "错误", MB_OK | MB_ICONHAND); exit(-1); } result = D3DXCreateLine(m_IDirect3DDevice9, &m_ID3DXLine); if (result != D3D_OK) { MessageBoxA(nullptr, "D3DXCreateLine", "错误", MB_OK | MB_ICONHAND); exit(-1); } result = D3DXCreateFontA(m_IDirect3DDevice9, 20, 0, FW_DONTCARE, D3DX_DEFAULT, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE, "Arial", &m_ID3DXFont);//Arial Vernada if (result != D3D_OK) { MessageBoxA(nullptr, "D3DXCreateFontA", "错误", MB_OK | MB_ICONHAND); exit(-1); } g_client = GameControler.FindModule("client.dll"); g_engine = GameControler.FindModule("engine.dll"); g_server = GameControler.FindModule("server.dll"); return true; } void do_cheat() { // 作弊函数 ... ... } void render(struct player_list* players) {...} // 渲染函数

有了这个透明窗体就可以用各种D3DX函数来绘制纹理了

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /* 渲染矩形 */ void render_rect(float x, float y, float width, float height, D3DCOLOR color = D3DCOLOR_ARGB(255, 0, 0, 255), float size = 1.0f) { D3DXVECTOR2 vextor[5]{ {x,y},{x + width,y},{x + width,y + height},{x,y + height},{x,y} }; m_ID3DXLine->SetWidth(size); int nRet = m_ID3DXLine->Draw(vextor, 5, color); } /* 渲染文本 */ void render_text(long x, long y, const char* text, D3DCOLOR color = D3DCOLOR_ARGB(255, 0, 0, 255)) { RECT rect{ x,y }; m_ID3DXFont->DrawTextA(nullptr, text, -1, &rect, DT_CALCRECT, color); m_ID3DXFont->DrawTextA(nullptr, text, -1, &rect, DT_LEFT, color); } /* 渲染线段 */ void render_line(float left, float top, float right, float down, D3DCOLOR color = D3DCOLOR_ARGB(255, 0, 0, 255), float size = 1.0f) { D3DXVECTOR2 vextor[2]{ {left,top},{right,down} }; m_ID3DXLine->SetWidth(size); m_ID3DXLine->Draw(vextor, 2, color); }

而对于敌人信息的获取,可以定义一个玩家结构体,然后循环从角色结构列表中读取玩家各项信息的内存并保存到玩家结构体列表中

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 const int g_players_count = 32; // 游戏玩家数量 struct player_list { bool effective;//是否有效 int aimbot_len;//自瞄长度 bool self;//是自己 float location[3];//身体位置 float head_bone[3];//头骨位置 int camp;//阵营 int blood;//血量 int entity_glow_index; // 辉光index DWORD BoneMatrix; // 骨骼基地址 DWORD SpottedByMask; // 敌人是否可见 float distance; // 与我的距离 }; extern CGameControler GameControler; //获取玩家列表 void get_player_list(struct player_list* players) { system("cls"); // 起一个终端 方便输出数据调试 DWORD local = GameControler.read(g_client + dwLocalPlayer); DWORD health = GameControler.read(local + m_dwHP); DWORD selfTeamNum = GameControler.read(local + m_dwTeamNum); //自己的阵营序号 DWORD EntityList = g_client + dwEntityList; for (int i = 0; i


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有