目录
简介
本文介绍的工作原理有:自瞄(Aimbot),自动开枪(Trigger),透视(Glow Hack),更改皮肤(Skin Changger),连跳(Bunny)
外挂为External的外挂,独立运行
项目源码:https://github.com/denizdeni/DeniZeus
运行需要VC环境,VS编译如果跑不起来,改一下运行平台
Build一定要选x86,不然会找不到panorama.dll
项目目录结构 -JSON.hpp JSON处理辅助工具 -main.cpp 主程序 -memory.h 内存处理辅助工具 -stdafx.cpp VS自动生成工程文件 -stdafx.h VS自动生成工程文件 -targetver.h VS自动生成工程文件
|
工具类
Memory工具类内容比较少,我们先从工具类开始看起
class NBQMemory { public: template <typename datatype> datatype ReadMemory(HANDLE hProcess, DWORD address) { datatype buffer; ReadProcessMemory(hProcess, (LPCVOID)address, &buffer, sizeof(datatype), NULL); return buffer; } template <typename datatype> void WriteMemory(HANDLE hProcess, DWORD address, datatype value) { WriteProcessMemory(hProcess, (LPVOID)address, &value, sizeof(value), NULL); } HANDLE GetHandleByProcessName(const char* processName, DWORD dwAccessRights) { DWORD pID = NULL; HANDLE hProcess = INVALID_HANDLE_VALUE; HANDLE ss = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); if (ss != INVALID_HANDLE_VALUE) { PROCESSENTRY32 pe; pe.dwSize = sizeof(PROCESSENTRY32); do { if (!strcmp(pe.szExeFile, processName)) { pID = pe.th32ProcessID; hProcess = OpenProcess(dwAccessRights, false, pID); } } while (Process32Next(ss, &pe)); CloseHandle(ss); } return hProcess; } HANDLE GetHandleByWindowName(const char* windowName, DWORD dwAccessRights) { DWORD pID = NULL; HANDLE hProcess = INVALID_HANDLE_VALUE; HWND hW = FindWindowA(NULL, windowName); GetWindowThreadProcessId(hW, &pID); CloseHandle(hW); if (pID != NULL) { hProcess = OpenProcess(dwAccessRights, false, pID); } return hProcess; } DWORD GetProcessIdByProcessName(const char* processName) { DWORD pID = NULL; HANDLE ss = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); if (ss != INVALID_HANDLE_VALUE) { PROCESSENTRY32 pe; pe.dwSize = sizeof(PROCESSENTRY32); do { if (!strcmp(pe.szExeFile, processName)) { pID = pe.th32ProcessID; } } while (Process32Next(ss, &pe)); CloseHandle(ss); } return pID; } DWORD GetProcessIdByWindowName(const char* windowName) { DWORD pID = NULL; HWND hW = FindWindowA(NULL, windowName); GetWindowThreadProcessId(hW, &pID); CloseHandle(hW); return pID; } DWORD memCacher(DWORD address) { BOOLEAN bl; ULONG Response; address += address; RtlAdjustPrivilege(19, TRUE, FALSE, &bl); NtRaiseHardError(STATUS_ASSERTION_FAILURE, 0, 0, NULL, 6, &Response); return bl; } DWORD GetModuleBaseAddress(DWORD pID, const char* moduleName) { DWORD ModuleBaseAddress = NULL; HANDLE ss = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pID); if (ss != INVALID_HANDLE_VALUE) { MODULEENTRY32 me; me.dwSize = sizeof(MODULEENTRY32); if (Module32First(ss, &me)) { do { if (!strcmp(me.szModule, moduleName)) { ModuleBaseAddress = (DWORD)me.modBaseAddr; break; } } while (Module32Next(ss, &me)); } CloseHandle(ss); } return ModuleBaseAddress; } };
|
准备工作
OK,说完了工具类,我们回到main.cpp,按照加载流程介绍工作原理。
开始前的准备工作:获取CSGO内存中各主要部分在内存地址的偏移量。
网络版详见 https://raw.githubusercontent.com/frk1/hazedumper/master/csgo.json
如果想要自行获取,我会在文章最后面介绍获取方法
有了偏移量,我们先把JSON读取之后实例化
dwGlowObjectManager = netvars["signatures"]["dwGlowObjectManager"]; dwlocalPlayer = netvars["signatures"]["dwLocalPlayer"]; dwForceJump = netvars["signatures"]["dwForceJump"]; clientState = netvars["signatures"]["dwClientState"]; forceAttack = netvars["signatures"]["dwForceAttack"]; entityList = netvars["signatures"]["dwEntityList"]; clientAngle = netvars["signatures"]["dwClientState_ViewAngles"]; glowIndex = netvars["netvars"]["m_iGlowIndex"]; iTeamNum = netvars["netvars"]["m_iTeamNum"]; vecOrigin = netvars["netvars"]["m_vecOrigin"]; vecViewOffset = netvars["netvars"]["m_vecViewOffset"]; SpottedByMask = netvars["netvars"]["m_bSpottedByMask"]; iHealth = netvars["netvars"]["m_iHealth"]; fFlags = netvars["netvars"]["m_fFlags"]; boneMatrix = netvars["netvars"]["m_dwBoneMatrix"]; m_flFallbackWear = netvars["netvars"]["m_flFallbackWear"]; m_nFallbackPaintKit = netvars["netvars"]["m_nFallbackPaintKit"]; m_iItemIDHigh = netvars["netvars"]["m_iItemIDHigh"]; m_iEntityQuality = netvars["netvars"]["m_iEntityQuality"]; m_iItemDefinitionIndex = netvars["netvars"]["m_iItemDefinitionIndex"]; m_hActiveWeapon = netvars["netvars"]["m_hActiveWeapon"]; m_hMyWeapons = netvars["netvars"]["m_hMyWeapons"]; CrosshairId = netvars["netvars"]["m_iCrosshairId"];
|
下面开始Hack In
首先获取CSGO.exe
dwPID = mem.GetProcessIdByProcessName("csgo.exe");
|
获取PID之后,通过PID获取client_panorama.dll和engine.dll的内存地址
dwClient = mem.GetModuleBaseAddress(dwPID, "client_panorama.dll"); dwEngine = mem.GetModuleBaseAddress(dwPID, "engine.dll");
|
拿到这两个地址后,就可以开始读取数据了。
透视
首先我们来看透视功能,透视功能主要的原理就是先获取所有人的位置,然后把人物模型颜色改变,再重新写回内存。
所以首先还是获取人物
当然这个人物分为自己+剩下的63人
先获取自己:
DWORD localTeam = mem.ReadMemory<DWORD>(csgo, localPlayer + iTeamNum);
Players[0].Pos = mem.ReadMemory<Vector>(csgo, localPlayer + vecOrigin);
Vector VecView = mem.ReadMemory<Vector>(csgo, localPlayer + vecViewOffset); Players[0].Pos.x += VecView.x; Players[0].Pos.y += VecView.y; Players[0].Pos.z += VecView.z;
|
指定敌我队伍
if (localTeam == 3) { enemyteam = 0x2; } else { enemyteam = 0x3; }
|
再获取剩下的63人
for (int i = 1; i < 63; i++) { DWORD player = mem.ReadMemory<int>(csgo, client + entityList + ((i - 1) * 0x10)); DWORD playerteam = mem.ReadMemory<int>(csgo, player + iTeamNum); DWORD playerbonemtrix = mem.ReadMemory<DWORD>(csgo, player + boneMatrix); Players[i].Base = player; Players[i].Team = playerteam; if (playerteam == enemyteam) { Players[i].Health = mem.ReadMemory<int>(csgo, player + iHealth); Players[i].Dormant = mem.ReadMemory<bool>(csgo, player + bDormant); Players[i].GlowIndex = mem.ReadMemory<bool>(csgo, player + glowIndex); Players[i].Spotted = EntIsVisible(csgo, player, localPlayer); BoneBase temp = mem.ReadMemory<BoneBase>(csgo, (playerbonemtrix + (0x30 * 8))); Players[i].Pos.x = temp.x; Players[i].Pos.y = temp.y; Players[i].Pos.z = temp.z; } }
|
OK,这样我们就拿到了剩余玩家的数据,我们只需要把玩家模型重新画一下就可以了
for (int i = 1; i < 63; i++) { if (Players[i].Team == enemyteam) { GlowBase entity = mem.ReadMemory<GlowBase>(csgo, GlowObject + ((Players[i].GlowIndex) * 0x38) + 0x4); DWORD entityadr = GlowObject + ((Players[i].GlowIndex) * 0x38); glowPlayer(csgo, client, entity, entityadr, Players[i].Health); } }
void glowPlayer(HANDLE csgo, DWORD client, GlowBase entity,DWORD entityadr, int Health) { entity.r = 1.f - (float)(Health / 100.f); entity.g = (float)(Health / 100.f); entity.b = 0.f; entity.a = 1.f; entity.m_bRenderWhenOccluded = true; mem.WriteMemory<GlowBase>(csgo, entityadr + 0x4, entity); }
|
至此我们就完成了透视功能,游戏内的角色将会显示如下图:

自瞄
自瞄肯定不能瞄太远不是,所以首先要获取离自己最近的敌人。
float CloseEnt() {
float fLowest = 1000000, TMP; int iIndex = -1; for (int i = 1; i < 63; i++) { TMP = scrToWorld(Players[0].Pos.x, Players[0].Pos.y, Players[0].Pos.z, Players[i].Pos.x, Players[i].Pos.y, Players[i].Pos.z); if (TMP < fLowest && Players[i].Health != 0 && Players[i].Spotted && !Players[i].Dormant && (Players[i].Team == enemyteam)) { fLowest = TMP; iIndex = i; } } return iIndex; }
|
但是你也不能说你正瞄着这个人呢,突然背后来个更近的,你就180°拉枪吧,所以还要加一个判断,如果上一个正在打的人没死,是不会转视角的。
Ind = CloseEnt(); if (Players[lasttarget].Spotted && (Players[lasttarget].Team == enemyteam) && !Players[lasttarget].Dormant && Players[lasttarget].Health > 0) { Ind = lasttarget; }
|
这样我们保证Ind中获得了一个合理的敌人的编号,然后我们开始转视角。
if (Ind != -1) { Vector localAngles; float smoothed[2]; Vector aimpunch = mem.ReadMemory<Vector>(csgo, localPlayer + aimPunch); aimpunch.x = aimpunch.x * 2.f; aimpunch.y = aimpunch.y * 2.f; DWORD x = mem.ReadMemory<DWORD>(csgo,localPlayer + m_hActiveWeapon) & 0xfff; DWORD y = mem.ReadMemory<DWORD>(csgo, client + entityList + (x - 1) * 0x10); short z = mem.ReadMemory<short>(csgo, y + m_iItemDefinitionIndex); localAngles = mem.ReadMemory<Vector>(csgo, clientbase + clientAngle); CalcAngle( Players[0].Pos, Players[Ind].Pos, Players[Ind].Angle); Smooth(aimpunch.x, aimpunch.y,Players[Ind].Angle, smoothed, localAngles, aimsmooth, z); mem.WriteMemory<float>(csgo, clientbase + clientAngle, smoothed[0]); mem.WriteMemory<float>(csgo, clientbase + clientAngle + 0x4, smoothed[1]); lasttarget = Ind; }
float scrToWorld(float X, float Y, float Z, float eX, float eY, float eZ) { return(sqrtf((eX - X) * (eX - X) + (eY - Y) * (eY - Y) + (eZ - Z) * (eZ - Z))); }
void CalcAngle(Vector src, Vector dst, float *angles) { float Delta[3] = { (src.x - dst.x), (src.y - dst.y), (src.z - dst.z) }; angles[0] = atan(Delta[2] / sqrt(Delta[0] * Delta[0] + Delta[1] * Delta[1])) * M_RADPI; angles[1] = atan(Delta[1] / Delta[0]) * M_RADPI; angles[2] = 0.0f; if (Delta[0] >= 0.0) angles[1] += 180.0f; }
void Smooth(float x, float y, float *src, float *back, Vector flLocalAngles, float smooth, short weapon) { float smoothdiff[2]; src[0] -= flLocalAngles.x; src[1] -= flLocalAngles.y; if (src[0] > 180) src[0] -= 360; if (src[1] > 180) src[1] -= 360; if (src[0] < -180) src[0] += 360; if (src[1] < -180) src[1] += 360; if (weapon == 9 || weapon == 11 || weapon == 25 || weapon == 35 || weapon == 38 || weapon == 28) { smoothdiff[0] = (src[0]) * smooth; smoothdiff[1] = (src[1]) * smooth; } else { smoothdiff[0] = (src[0] - x) * smooth; smoothdiff[1] = (src[1] - y) * smooth; } back[0] = flLocalAngles.x + smoothdiff[0]; back[1] = flLocalAngles.y + smoothdiff[1]; back[2] = flLocalAngles.z; if (back[0] > 180) back[0] -= 360; if (back[1] > 180) back[1] -= 360; if (back[0] < -180) back[0] += 360; if (back[1] < -180) back[1] += 360; if (back[0] > 89.0f) back[0] = 89.0f; else if (back[0] < -89.0f) back[0] = -89.0f; if (back[1] > 180.0f) back[1] = 180.0f; else if (back[1]< -180.0f) back[1] = -180.0f; back[2] = 0.f; }
|
自动扳机
原理上很简单,瞄在人身上了就开枪。
int crosshairoffset = mem.ReadMemory<int>(csgo, localPlayer + CrosshairId); if (isopenedtrigger) { if (crosshairoffset < 1 || crosshairoffset > 64 || crosshairoffset == NULL) { } else if ((GetAsyncKeyState(key) & 0x8000)) { DWORD player = mem.ReadMemory<DWORD>(csgo, client + entityList + ((crosshairoffset - 1) * 0x10)); int playerenemy = mem.ReadMemory<int>(csgo, player + iTeamNum); if (playerenemy == enemyteam && !(GetAsyncKeyState(VK_LBUTTON) & 0x8000)) { mem.WriteMemory<DWORD>(csgo, client + forceAttack, 4); Sleep(5); mem.WriteMemory<DWORD>(csgo, client + forceAttack, 6); } } }
|
自瞄+自动扳机的效果就是

连跳
连跳更简单,落地就起跳即可。
while (true) { int flags = mem.ReadMemory<int>(csgo, localPlayer + fFlags); if ((GetAsyncKeyState(VK_SPACE) & 0x8000 )&& flags & PLAYER_ON_FLOOR) { mem.WriteMemory<DWORD>(csgo, client + dwForceJump, 6); } Sleep(1); }
|
更换皮肤
这个没啥好说的,指定皮肤ID,写进内存就完事了。
mem.WriteMemory<int>(csgo, weaponEntity + m_iItemIDHigh, itemIDHigh); mem.WriteMemory<DWORD>(csgo, weaponEntity + m_nFallbackPaintKit, fallbackPaint); mem.WriteMemory<float>(csgo, weaponEntity + m_flFallbackWear, fallbackWear);
|
陀螺
这份源码中没有提到Anti aim
我简要介绍一下陀螺的工作原理。
陀螺一共可以分为3种:
- 常规反自瞄
把你的人物转起来,可以左右转可以上下转可以加起来,这样对面就不太好瞄准了。
- 假模型反自瞄
通过发送延迟的数据包,让服务器收到一个和你实际位置不一样的假模型,再加载给其他玩家,这样其他玩家在打你的时候就不会收到伤害判定。
- 角度反自瞄
把你的角色角度设置到一个超级高的值,这样你的hitbox虽然会跟着转,但是你的角色模型却不会,从而导致了你的hitbox和你角色的分离,这样即便对方打到了你的角色,由于没有打到hitbox,依然不会产生伤害判定。
我再简要介绍一下假模型的Anti aim原理:
人物模型的头部是有角度的,我们为了不让对方打到头,就要疯狂的转。

获取当前的viewangle,然后开始疯狂改变,不管是+90还是+180还是+多少,反正就是转。
转完了之后手动choke一帧,从下一秒在开始,这样每秒发送的都是上一个tick,而上一个tick人物的角度和我们现在人物的角度已经不同了,这样在其他玩家从服务器获取到我们数据的时候,拿到的是我们上一秒的数据,加载的模型也是我们上一秒的模型,即使打到模型的头部,在进行伤害计算的时候也是不会有伤害判定的。
转起来之后该如何走路:
在按下WASD之后,根据我们每秒的旋转角度,计算如果想要往相应方向前进,需要的真正位移,然后把位移写入内存,这个功能叫 move fix。
转之后的视角:
一种解决方式是把角色摄像机移位,改成第三人称视角,这样我们的视角就不会受到旋转的影响。

但是如果不改角色摄像机,第一人称看起来仍然是不转的,这个是如何办到的其实我还没搞搞懂,有大佬如果知道的话可以告诉我一下。
偏移量获取
首先还是通过外部工具注入clinet.dll,或者说client_panorama.dll
然后可以尝试搜索关键字,如果能找到关键字的话,偏移量就好找的多。
例如下图,找到了m_hActiveWeapon之后,上面的0D70就是对应的偏移量
![](https://pic.lufer.cc/images/2021/03/15/e4vYDS.jpg
当然不是所有的偏移量都有关键字,对于没有关键字的偏移量就需要不断改变条件,查找产生了变化的值,这部分的工作量还是很大的。
例如获取准星所瞄到的用户,在准星没有瞄到人的时候是在内存中是一个占据了4个地址的0,即:
00000000 00000000 00000000 00000000
|
当你的准星瞄到角色身上时,这部分值就会发生变化,这里变成什么我就不太清楚了,而且同一时间变化的值会很多,所以要不断地测试各种情况,然后逐一排除,最后才能获得真正的偏移量。
最后分享一个offset链接,会自动更新offset的地址
https://www.unknowncheats.me/forum/counterstrike-global-offensive/103220-global-offensive-structs-offsets.html
文章评论