【C#】从零开始的CSGO透视外挂制作过程

Mangofang 发布于 2022-11-20 4412 次阅读


使用的软件:

  • Cheat Engine
  • Visual Studio

使用的编程语言:

  • C#

前言

这一文章将向你展示从基址寻找内存读取,再通过矩阵算法绘制方框。从0开始制作一款CSGO透视辅助软件。

在其中我会提供我寻找到的内存基址和寻找方法,由于游戏版本更新迭代,很有可能部分基址已经无法使用,所以我将着重强调这一方法,而非基址信息。

注:本教程纯属学习用途,制作全过程将会在线下游戏进行,不可在线上游戏中使用!


1.外挂等辅助软件的基本原理

如果稍微了解一点计算机编程开发的都应该知道,我们在对对象或者数据进行初始化个调用的过程中,计算机需要先在内存区域中新增加一个区域用来存放这个数据,或者从磁盘中获取数据存放到内存中的某一个位置。

理解了这个,接下来就好说很多了,在游戏运行中,人物的状态位置等信息均存在内存中。我们只需要读取并获取这些信息,甚至在有必要的时候我们还可以修改这些信息,从而达到作弊的目的。

举个例子,游戏在开局运行当中,人物的状态、位置等信息也被存在内存中,我们只需要知道人物的位置信息被存在内存当中的哪一个地方,就能顺理成章的获取到人物的位置从而达到透视的效果。


2.人物部分重要基址的获取

首先,需要实现透视功能,当然需要获取到人物的位置信息。在CSGO中,人物数据是用一种链表结构进行存储的,链表的大概格式如下:

Player{
  数据地址
  ID
  上一个结构体地址
  下一个结构体地址
}

通过链表就可以依次偏移出所有玩家的人物的信息。

进入游戏,按“~”键打开控制台,键入指令方便我们的后续操作

sv_cheats 1 //开启作弊
mp_roundtime_defuse 60 //设置回合时间60分钟
bot_stop 1 //禁止bot移动
mp_restartgame 1 //1秒后重新开始

我们可以通过已知的数据,比如角色血量的方式来索引链表(血量更加直观,通过链表中其他已知的角色数据都可以尝试搜索链表)

当然我的任务血量为“97”,使用CE搜索“97”,出现八千多个结果,回到游戏使用“hurtme <血量>”指令自扣1点血,再次搜索“96

循环上述操作,我们最终得到了13个结果

一般由ECX的偏移得到数据,举个例子,ECX+血量的偏移量得到血量。所以我们只需要寻找ECX的数据即可。

先看一下第一个结果,右键点击“找出是什么访问了这个地址

结果如下,有一条move指令,将ecx+18的值存储进ecx,我们可以试试这不是我们需要的链表地址,此时ecx为0000005F,通过十六进制搜索0000005F。

直接寻找绿色的静态地址即可,右键“浏览相关内存区域”查看这块内存中是否符合我们之前提到的链表特征,如果不符合就继续查看下一条静态地址。

如果这些静态地址中都不符合我们的链表特诊,大概率说明,这个ECX地址并不是我们想要的,寻找下一个ECX的地址即可,比如下面这个EXC地址为“656B5840”。偏移为0x100

我们试试这个,重复上述步骤,十六进制搜索这个值,发现四个绿色静态地址,第一个经过还不符合链表特诊,我们再打开第二个试试,右键浏览相关内存区域。如果是首次打开CE,你可能需要调整一下显示类型为“4字节(HEX)

我们发现这块内存非常符合链表特诊,且也符合这局10名玩家的条件,上下均为空值。那么基本这个可以判断“client.dll+4E021E4”是在指向这个链表的一个基址。

我们可以来验证一下,之前血量的偏移是0x100,我们可以在CE中手动添加地址和再加上偏移试试能不能得到血量的值

ok,我们继续向下索引,我们转到这个链表地址

因为人物位置坐标数据类型为单浮点,我们把显示类型改为单浮点

尝试在游戏中移动,观察内存区域中的变动情况,得到存放玩家坐标的地址分别是“656B58E0”、“656B58E4”、“656B58E8

我们得到的这个地址是动态地址,我们需要做一个简单的十六进制加减来得到它对基址的偏移量,方便我们之后在编程环节中得到这个值。用“656B58E0-656B5840(链表地址)”得到偏移量“A0”,然后通过横向加上0x4就可以得到另外两个方向的值了。

接下来我们还需要确定一下角色阵营的数据,将显示类型调整为4字节(DEC)方便查看。不断切换阵营,观察变化的数值

观察得出,在CSGO中,2代表T阵营,3代表CT阵营

重复上述计算偏移的操作,就可以得到玩家阵营的偏移了。

3.视角矩阵基址的获取

CSGO中的矩阵是4X4的矩阵,特诊主要如下图

有两个我们可以确定的数据,就是这个1和-1,当指向天空时为1,当指向地面时为-1,通过单浮点,反复搜索这两个值,并在绿色的静态地址中就能够找到矩阵

1.代码

我们已经详细讲解过如何通过CE寻找CSGO的内存基址了,在这一篇中,我将直接展示代码

github: https://github.com/Mangofang/CSGOCheat

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Drawing;

namespace CSGOCheat3._0
{
    class Program
    {
        struct Player //人物struct
        {
            public static int[] Address = new int[20];
            public static float[] LocatX = new float[20];
            public static float[] LocatY = new float[20];
            public static float[] LocatZ = new float[20];
            public static float[] Head = new float[20];
            public static int[] Heal = new int[20];
            public static int[] Camp = new int[20];
        }
        public static float clipCoords_x;
        public static float clipCoords_y;
        public static float clipCoords_z;
        public static float clipCoords_w;
        public static float clipCoords_x_head;
        public static float clipCoords_y_head;
        public static float clipCoords_z_head;
        public static float clipCoords_w_head;
        public static float Rectangle_wight;
        public static float Rectangle_hight;
        public static float NDC_x;
        public static float NDC_y;
        public static float NDC_x_head;
        public static float NDC_y_head;
        public static float[] Matrix = new float[16];
        public static float[] screen_x = new float[20];
        public static float[] screen_y = new float[20];
        public static float[] screen_x_head = new float[20];
        public static float[] screen_y_head = new float[20];
        public static string gamename = "csgo";
        public static int windows_x = 1920;
        public static int windows_y = 1080;
        public static IntPtr hAddress = OpenProcess(0x1F0FFF, false, GetPidByProcessName(gamename));//获取游戏进程
        public static IntPtr csgo_handle = Process.GetProcessById(GetPidByProcessName(gamename)).MainWindowHandle;//获取游戏句柄
        static void Main(string[] args)
        {
            int Player_Num;
            int dev = 0x0;
            GetWindowsXandY();
            //如果无法显示方框只需要修改下面的两个地址即可
            IntPtr MainAddress = (IntPtr)(GetDllAddress("client.dll") + 0x4DFFF14);//人物链表地址
            IntPtr MatrixAddress = new IntPtr(GetDllAddress("client.dll") + 0x4DF0D6C - 0x28);//视角矩阵

            for (Player_Num = 0; Player_Num <= 20; Player_Num++)//计算链表内角色数量
            {
                int tem_Address = 0;
                ReadProcessMemory((int)hAddress, (IntPtr)MainAddress + dev, out tem_Address, 4, 0);
                Player.Address[Player_Num] = tem_Address;
                if (tem_Address == 0)
                {
                    break;
                }
                dev = dev + 0x10;
            }

            ThreadPool.QueueUserWorkItem(item =>
            {
                Pen pen_DR = new Pen(Color.FromArgb(0x66ff0000), 2);
                Pen pen_DY = new Pen(Color.Green, 2);

                Graphics e = Graphics.FromHwnd(csgo_handle);
                e.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                e.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                byte x = 0;
                byte y = 0;
                byte z = 0;
                byte[] x_ = new byte[4];
                byte[] y_ = new byte[4];
                byte[] z_ = new byte[4];
                while (true)
                {
                    for (int i = 0; i <= Player_Num - 1; i++)
                    {
                        for (int a = 0; a < 4; a++)
                        {
                            ReadProcessMemory((int)hAddress, (IntPtr)Player.Address[i] + 0xA0 + a, out x, 4, 0);
                            x_[a] = x;
                            ReadProcessMemory((int)hAddress, (IntPtr)Player.Address[i] + 0xA4 + a, out y, 4, 0);
                            y_[a] = y;
                            ReadProcessMemory((int)hAddress, (IntPtr)Player.Address[i] + 0xA8 + a, out z, 4, 0);
                            z_[a] = z;
                        }
                        ReadProcessMemory((int)hAddress, (IntPtr)Player.Address[i] + 0xF4, out Player.Camp[i], 4, 0);
                        ReadProcessMemory((int)hAddress, (IntPtr)Player.Address[i] + 0x100, out Player.Heal[i], 4, 0);
                        Player.Head[i] = Player.LocatZ[i] + 70f;
                        Player.LocatX[i] = ToFloat(x_);
                        Player.LocatY[i] = ToFloat(y_);
                        Player.LocatZ[i] = ToFloat(z_);

                        Matrix[0] = MatrixToFloat(MatrixAddress);
                        Matrix[1] = MatrixToFloat(MatrixAddress + 0x4);
                        Matrix[2] = MatrixToFloat(MatrixAddress + 0x8);
                        Matrix[3] = MatrixToFloat(MatrixAddress + 0x8 + 0x4);
                        Matrix[4] = MatrixToFloat(MatrixAddress + 0x10);
                        Matrix[5] = MatrixToFloat(MatrixAddress + 0x14);
                        Matrix[6] = MatrixToFloat(MatrixAddress + 0x18);
                        Matrix[7] = MatrixToFloat(MatrixAddress + 0x18 + 0x4);
                        Matrix[8] = MatrixToFloat(MatrixAddress + 0x20);
                        Matrix[9] = MatrixToFloat(MatrixAddress + 0x24);
                        Matrix[10] = MatrixToFloat(MatrixAddress + 0x28);
                        Matrix[11] = MatrixToFloat(MatrixAddress + 0x28 + 0x4);
                        Matrix[12] = MatrixToFloat(MatrixAddress + 0x30);
                        Matrix[13] = MatrixToFloat(MatrixAddress + 0x34);
                        Matrix[14] = MatrixToFloat(MatrixAddress + 0x38);
                        Matrix[15] = MatrixToFloat(MatrixAddress + 0x38 + 0x4);
                        GetclipCoord(Player.LocatX[i], Player.LocatY[i], Player.LocatZ[i], Player.Head[i], i);

                        Rectangle_hight = (screen_y_head[i] - screen_y[i]) * -1;
                        Rectangle_wight = 0.5f * Rectangle_hight;

                        if (Player.Address[i] != Player.Address[0] && Player.Heal[i] != 0)
                        {
                            if (Player.Camp[i] != Player.Camp[0])
                            {
                                e.DrawLine(pen_DR, windows_x / 2, windows_y, screen_x[i], screen_y[i]);
                                e.DrawRectangle(pen_DR, screen_x[i] - Rectangle_wight / 2, screen_y[i] - Rectangle_hight, Rectangle_wight, Rectangle_hight);
                            }
                            else
                            {
                                e.DrawLine(pen_DY, windows_x / 2, windows_y, screen_x[i], screen_y[i]);
                                e.DrawRectangle(pen_DY, screen_x[i] - Rectangle_wight / 2, screen_y[i] - Rectangle_hight, Rectangle_wight, Rectangle_hight);
                            }
                        }


                    }
                }
            });

            //Console.WriteLine(Player_Num);
            Console.ReadKey();
        }
        public static float MatrixToFloat(IntPtr MatrixAddress)
        {
            byte data;
            byte[] C = new byte[4];
            for (int i = 0; i < 4; i++)
            {
                ReadProcessMemory((int)hAddress, MatrixAddress + i, out data, 4, 0);
                C[i] = data;
            }
            return ToFloat(C);
        }
        public static bool GetclipCoord(float pos_x, float pos_y, float pos_z, float head, int i)
        {
            clipCoords_x = pos_x * Matrix[0] + pos_y * Matrix[1] + pos_z * Matrix[2] + Matrix[3];
            clipCoords_y = pos_x * Matrix[4] + pos_y * Matrix[5] + pos_z * Matrix[6] + Matrix[7];
            clipCoords_z = pos_x * Matrix[8] + pos_y * Matrix[9] + pos_z * Matrix[10] + Matrix[11];
            clipCoords_w = pos_x * Matrix[12] + pos_y * Matrix[13] + pos_z * Matrix[14] + Matrix[15];

            clipCoords_x_head = pos_x * Matrix[0] + pos_y * Matrix[1] + head * Matrix[2] + Matrix[3];
            clipCoords_y_head = pos_x * Matrix[4] + pos_y * Matrix[5] + head * Matrix[6] + Matrix[7];
            clipCoords_z_head = pos_x * Matrix[8] + pos_y * Matrix[9] + head * Matrix[10] + Matrix[11];
            clipCoords_w_head = pos_x * Matrix[12] + pos_y * Matrix[13] + head * Matrix[14] + Matrix[15];
            if (clipCoords_w < 0.1f || clipCoords_w_head < 0.1f)
            {
                return false;
            }
            NDC_x = clipCoords_x / clipCoords_w;
            NDC_y = clipCoords_y / clipCoords_w;

            NDC_x_head = clipCoords_x_head / clipCoords_w_head;
            NDC_y_head = clipCoords_y_head / clipCoords_w_head;

            screen_x[i] = (windows_x / 2 * NDC_x) + (NDC_x + windows_x / 2);
            screen_y[i] = -(windows_y / 2 * NDC_y) + (NDC_y + windows_y / 2);
            screen_x_head[i] = (windows_x / 2 * NDC_x_head) + (NDC_x_head + windows_x / 2);
            screen_y_head[i] = -(windows_y / 2 * NDC_y_head) + (NDC_y_head + windows_y / 2);
            return true;
        }
        public static float ToFloat(byte[] data)
        {
            float a = 0;
            byte i;
            byte[] x = data;
            unsafe
            {
                void* pf;
                fixed (byte* px = x)
                {
                    pf = &a;
                    for (i = 0; i < data.Length; i++)
                    {
                        *((byte*)pf + i) = *(px + i);
                    }
                }
            }
            return a;
        }
        public static int GetDllAddress(string DllName)
        {
            int address = 0;
            foreach (Process p in Process.GetProcessesByName(gamename))
            {
                foreach (ProcessModule m in p.Modules)
                {
                    if (m.ModuleName == DllName)
                    {
                        address = m.BaseAddress.ToInt32();
                    }
                }
            }
            return address;
        }
        public static int GetPidByProcessName(string processName)
        {
            Process[] arrayProcess = Process.GetProcessesByName(processName);

            foreach (Process p in arrayProcess)
            {
                return p.Id;
            }
            return 0;
        }

        [DllImportAttribute("kernel32.dll", EntryPoint = "ReadProcessMemory")]
        static extern bool ReadProcessMemory(int hProcess, IntPtr lpBaseAddress, out int lpBuffer, int nSize, int lpNumberOfBytesRead);
        [DllImportAttribute("kernel32.dll", EntryPoint = "ReadProcessMemory")]
        static extern bool ReadProcessMemory(int hProcess, IntPtr lpBaseAddress, out byte lpBuffer, int nSize, int lpNumberOfBytesRead);
        [DllImportAttribute("kernel32.dll", EntryPoint = "OpenProcess")]
        public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left;//最左坐标
            public int Top;//最上坐标
            public int Right;//最右坐标
            public int Bottom;//最下坐标
        }
        public static void GetWindowsXandY()
        {
            RECT rect = new RECT();
            GetWindowRect(csgo_handle, ref rect);
            windows_x = rect.Right - rect.Left;
            windows_y = rect.Bottom - rect.Top;
        }
    }
}
此作者没有提供个人介绍。
最后更新于 2025-04-22