目录

FPS骨骼,u3d,UE4(二)

目录

我们接着上文继续,如果没有看上文基础的同学建议先看完上文再继续本文章

本文章中均以单机游戏为例,每一种功能仅提供给网络安全工作者反外挂建议和安全对抗方法.请勿用作非法用途

另外提示对于此类游戏安全和反外挂研究,单机和网络游戏的原理毫无区别,区别仅仅在于个别数据网络验证部分

先整理cs1.6数据如下:

(属于基础范畴,任鸟飞2020前80课即可轻松搞定这里不赘述)

矩阵地址 hl.exe+1820100//这个暂时先不要管,下文会有详细讲解的地方

高低朝向值 hl.exe+19E10C4 //从低到高 89 到 -89

水平朝向值 hl.exe+19E10C8 // 逆时针 从 0 到 360

朝向值找到以后我们发现水平转一圈 是0-360的变化规律

其中朝向算法需要我们详细分析一下

方法很简单,

把朝向写入0 然后W走路 看看坐标的变化规律发现是X增加其他不变,那么0对应X正轴

把朝向写入90 然后W走路 看看坐标的变化规律发现是Y增加其他不变,那么0对应Y正轴

把朝向写入180 然后W走路 看看坐标的变化规律发现是X减少其他不变,那么0对应X负轴

把朝向写入270 然后W走路 看看坐标的变化规律发现是Y减少其他不变,那么0对应Y负轴

最终得到结果

也就是我们不同朝向的值

https://cdn.jsdelivr.net/gh/xinqinew/pic@main/img/716348dc5c008c2b244793e3cea6fa9c58cb9b9b.png@942w_602h_progressive.png

人物X坐标:hl.exe+195fe58

人物Y坐标:hl.exe+195fe5C

人物Z坐标:hl.exe+195fe60

周围数组

数组最大数量1F 以下n 通用 0为自己

hl.exe+1B5A5C4+24C*n+0 等于0 数组结束

hl.exe+1B5A5C4+24C*n+190 DWORD ==0 跳过 有可能数组不是顺序存放

对象X坐标 hl.exe+1B5A5C4+24C*n+18C

对象Y坐标 hl.exe+1B5A5C4+24C*n+18C

对象Z坐标 hl.exe+1B5A5C4+24C*n+190

1为土匪 2为警察 hl.exe+62565C+n*68+4E

血量 hl.exe+62565C+n*68+68

死亡标志位 hl.exe+62565C+n*68+60

得到的结果 就可以提供给我们封装数据所用了,这已经足够了

说到这,

我们来聊聊为什么FPS类型的游戏安全性及不高 和 反外挂

主要的2个原因

第一设计简单,数据少,通过上面的需要数据就已经知道了,真的很少

第二个原因是特性导致,透视和自瞄等功能都是服务器无法验证的本地操作

所以加大了反外挂的难度.

那么其实针对于FPS的外挂特征,反外挂可以做的事情也是不少的

第一,加大对周围数据的保护,尤其获取范围,不要在极大范围就像玩家投递全地图数据

第二,对hookd3d的检测应该是比较容易的

第三,对绘制函数的检测,当然如果是窗口的覆盖窗口那是存在一定检测难度的

第四,自瞄准星的数据写入检测

第五,鼠标准星移动轨迹的检测

第六,不定时截图上传

等等

封装数据

封装代码如下,因为这里我都使用了中文命名

相信大家都可以看懂了,如果有什么不懂可以 ,可以找我探讨

struct 朝向结构_2

{

float 水平朝向;

float 高低朝向;

};

struct 对象结构

{

float X_j;

float Y_j;

float Z_j;

float X_H;

float Y_H;

float Z_H;

int Hp;

BYTE 死亡标志位;

BYTE 阵营;

朝向结构_2 角度_j;

朝向结构_2 角度_H;

朝向结构_2 角度差_j;

朝向结构_2 角度差_H;

};

class 周围对象

{

public:

对象结构 对象列表[0x100];

DWORD 对象数量;

public:

void 刷新周围数据_Cs();

private:

void 计算朝向_Cs(坐标结构_3 目标, 朝向结构_2& 角度, 朝向结构_2& 角度差);

};

DWORD Cs_周围基地址 = (DWORD)GetModuleHandleA(“hl.exe”) + 0x1B5A5C4;

DWORD Cs_周围基地址2 = (DWORD)GetModuleHandleA(“hl.exe”) + 0x62565C;

void 周围对象::刷新周围数据_Cs()

{

对象数量 = 0;

for (int i = 1; i < 0x20; i++)// 第一个位置空出来

{

if ((DWORD)(Cs_周围基地址 + 0x24C * i + 0) == 0)// 直接结束

{

break;

}

if ((DWORD)(Cs_周围基地址 + 0x24C * i + 0x190) == 0)// 碰到空坐标对象 跳过

{

continue;

}

//哪里不懂可以2217777779 探讨

对象列表[对象数量].X_j = (FLOAT)(Cs_周围基地址 + 0x24C * i + 0x188);

对象列表[对象数量].Y_j = (FLOAT)(Cs_周围基地址 + 0x24C * i + 0x18C);

对象列表[对象数量].Z_j = (FLOAT)(Cs_周围基地址 + 0x24C * i + 0x190) - 40;

对象列表[对象数量].X_H = (FLOAT)(Cs_周围基地址 + 0x24C * i + 0x188);

对象列表[对象数量].Y_H = (FLOAT)(Cs_周围基地址 + 0x24C * i + 0x18C);

对象列表[对象数量].Z_H = (FLOAT)(Cs_周围基地址 + 0x24C * i + 0x190) + 23;

对象列表[对象数量].阵营 = (BYTE)(Cs_周围基地址2 + 0x68 * i + 0x4E);

对象列表[对象数量].Hp = (DWORD)(Cs_周围基地址2 + 0x68 * i + 0x68);

对象列表[对象数量].死亡标志位 = (BYTE) * (DWORD*)(Cs_周围基地址2 + 0x68 * i + 0x60);

坐标结构_3 目标;

朝向结构_2 角度;

朝向结构_2 角度差;

目标.x = 对象列表[对象数量].X_j;

目标.y = 对象列表[对象数量].Y_j;

目标.z = 对象列表[对象数量].Z_j;

计算朝向_Cs(目标, 角度, 角度差);

对象列表[对象数量].角度_j = 角度;

对象列表[对象数量].角度差_j = 角度差;

目标.x = 对象列表[对象数量].X_H;

目标.y = 对象列表[对象数量].Y_H;

目标.z = 对象列表[对象数量].Z_H;

计算朝向_Cs(目标, 角度, 角度差);

对象列表[对象数量].角度_H = 角度;

对象列表[对象数量].角度差_H = 角度差;

对象数量 += 1;

}

}

朝向值 和角度差的计算过程,根据上面已经得到的朝向数据

我们编写如下代码,为了让理解更简单 这里我分成了4个象限来讲解

如果还是不能完全理解的话,建议翻看我们之前的 关于朝向的课程,当然朝向 有很多种 这里属于最简单的一种

大家可能问算出来的角度差是干什么用的,还记得上篇文章,不用矩阵转换屏幕坐标吗,里面我们是需要用到这个角度差的

void 周围对象::计算朝向_Cs(坐标结构_3 目标, 朝向结构_2& 角度, 朝向结构_2& 角度差)

void 周围对象::计算朝向_Cs(坐标结构_3 目标, 朝向结构_2& 角度, 朝向结构_2& 角度差)

{

FLOAT FOV_x = (FLOAT)((DWORD)GetModuleHandleA(“hl.exe”) + 0x195fe58);

FLOAT FOV_y = (FLOAT)((DWORD)GetModuleHandleA(“hl.exe”) + 0x195fe5C);

FLOAT FOV_z = (FLOAT)((DWORD)GetModuleHandleA(“hl.exe”) + 0x195fe60);

FLOAT 水平朝向 = (FLOAT)((DWORD)GetModuleHandleA(“hl.exe”) + 0x19E10C8);

FLOAT 高低朝向 = (FLOAT)((DWORD)GetModuleHandleA(“hl.exe”) + 0x19E10C4);

if (目标.x > FOV_x && 目标.y >= FOV_y)//第一象限

{

角度.水平朝向 = (FLOAT)((double)atan2(目标.y - FOV_y, 目标.x - FOV_x) * 180 / 3.1415);

}

if (目标.x <= FOV_x && 目标.y > FOV_y)//第二象限

{

角度.水平朝向 = 180 - (FLOAT)((double)atan2(目标.y - FOV_y, FOV_x - 目标.x) * 180 / 3.1415);

}

if (目标.x < FOV_x && 目标.y <= FOV_y)//第三象限

{

角度.水平朝向 = 180 + (FLOAT)((double)atan2(FOV_y - 目标.y, FOV_x - 目标.x) * 180 / 3.1415);

}

if (目标.x >= FOV_x && 目标.y < FOV_y)//第四象限

{

角度.水平朝向 = 360 - (FLOAT)((double)atan2(FOV_y - 目标.y, 目标.x - FOV_x) * 180 / 3.1415);

}

FLOAT 平面距离 = sqrt((目标.x - FOV_x) * (目标.x - FOV_x) + (FOV_y - 目标.y) * (FOV_y - 目标.y));

if (目标.z >= FOV_z)//上方

{

角度.高低朝向 = (FLOAT)(-(double)atan2(目标.z - FOV_z, 平面距离) * 180 / 3.1415);

}

if (目标.z <= FOV_z)//下方

{

角度.高低朝向 = (FLOAT)((double)atan2(FOV_z - 目标.z, 平面距离) * 180 / 3.1415);

}

角度差.水平朝向 = 水平朝向 - 角度.水平朝向;

if (角度差.水平朝向 <= -180)//跨0轴的两种情况

{

角度差.水平朝向 += 360;

}

if (角度差.水平朝向 >= 180)

{

角度差.水平朝向 -= 360;

}

角度差.高低朝向 = 角度.高低朝向 - 高低朝向;

}

万事俱备

然后我们写个绘制类

数据全部完毕我们开始写代码绘制了,这部分是固定的代码,很简单

完全中文编程,代码已经很清晰了

大家跟着我敲写一遍即可

struct 坐标结构_2

{

float x, y;

};

struct 坐标结构_3

{

float x, y, z;

};

struct 坐标结构_4

{

float x, y, z, w;

};

class 绘制

{

public:

HWND m_窗口句柄;

RECT m_窗口;

int m_分辨率宽;

int m_分辨率高;

RECT m_外窗口;

int m_外窗口宽;

int m_外窗口高;

float m_矩阵[16];

DWORD m_矩阵地址;

public:

绘制(HWND 窗口句柄, DWORD 矩阵地址);

绘制()

{

}

private:

void 取窗口信息();

public:

void 绘制矩形(HDC HDC句柄, HBRUSH 画刷句柄, int x, int y, int w, int h);

void 画框(HDC HDC句柄, HBRUSH 画刷句柄, int x, int y, int w, int h, int 厚度);

void 画线(HDC HDC句柄, int X, int Y);

void 绘制字符串(HDC HDC句柄, int x, int y, COLORREF color, const char* text);

//bool 世界坐标转屏幕坐标(坐标结构_3 游戏坐标, 坐标结构_2& 屏幕坐标);

bool 世界坐标转屏幕坐标_非矩阵(坐标结构_2& 屏幕坐标, FLOAT 水平角度差, FLOAT 高低角度差);//传递真实角度

};

绘制::绘制(HWND 窗口句柄, DWORD 矩阵地址)

{

m_窗口句柄 = 窗口句柄;

m_矩阵地址 = 矩阵地址;

memcpy(&m_矩阵, (PBYTE*)(m_矩阵地址), sizeof(m_矩阵));

取窗口信息();

}

void 绘制::取窗口信息()

{

GetClientRect(m_窗口句柄, &m_窗口);

m_分辨率宽 = m_窗口.right - m_窗口.left;

m_分辨率高 = m_窗口.bottom - m_窗口.top;

GetWindowRect(m_窗口句柄, &m_外窗口); //含有边框及全屏幕坐标

m_外窗口宽 = m_外窗口.right - m_外窗口.left;

m_外窗口高 = m_外窗口.bottom - m_外窗口.top;

}

void 绘制::绘制矩形(HDC HDC句柄, HBRUSH 画刷句柄, int x, int y, int w, int h)

{

RECT 矩形 = { x,y,x + w,y + h };

FillRect(HDC句柄, &矩形, 画刷句柄);//绘制矩形

}

void 绘制::画框(HDC HDC句柄, HBRUSH 画刷句柄, int x, int y, int w, int h, int 厚度)

{

绘制矩形(HDC句柄, 画刷句柄, x, y, w, 厚度);//顶边

绘制矩形(HDC句柄, 画刷句柄, x, y + 厚度, 厚度, h - 厚度);//左边

绘制矩形(HDC句柄, 画刷句柄, (x + w) - 厚度, y + 厚度, 厚度, h - 厚度);//右边

绘制矩形(HDC句柄, 画刷句柄, x + 厚度, y + h - 厚度, w - 厚度 * 2, 厚度);//底边

}

void 绘制::画线(HDC HDC句柄, int X, int Y)

{

取窗口信息();

MoveToEx(HDC句柄, m_分辨率宽 / 2, m_分辨率高, NULL);

LineTo(HDC句柄, X, Y);

}

HFONT 字体属性;

void 绘制::绘制字符串(HDC HDC句柄, int x, int y, COLORREF color, const char* text)

{

SetTextAlign(HDC句柄, TA_CENTER | TA_NOUPDATECP);

SetBkColor(HDC句柄, RGB(0, 0, 0));

SetBkMode(HDC句柄, TRANSPARENT);

SetTextColor(HDC句柄, color);

SelectObject(HDC句柄, 字体属性);

TextOutA(HDC句柄, x, y, text, strlen(text));

DeleteObject(字体属性);

}

bool 绘制::世界坐标转屏幕坐标_非矩阵(坐标结构_2& 屏幕坐标, FLOAT 水平角度差, FLOAT 高低角度差)

{

取窗口信息();

FLOAT 高低可视角度 = (FLOAT)((double)atan2(m_分辨率高, m_分辨率宽)*180/3.1415);

if (fabs(水平角度差) > 45 || fabs(高低角度差) > 高低可视角度)

{

​ return false;// 不在屏幕范围内

}

int 水平差 = (int)(tan(水平角度差 * 3.1416 / 180) * ((m_分辨率宽) / 2));

屏幕坐标.x = (float)(m_分辨率宽 / 2 + 水平差);

int 高度差 = (int)(tan(高低角度差 * 3.1416 / 180) * ((m_分辨率宽) / 2));

屏幕坐标.y = (float)(m_分辨率高 / 2 + 高度差);

return true;

}

到这里 非矩阵绘制的所有准备工作都已经完毕了 ,就看你要怎么调用了

编写循环绘制代码

通过上面的编写 我们终于可以调用观看效果了

HBRUSH 画刷句柄;

HDC HDC句柄;

COLORREF 文本颜色_亮红 = RGB(255, 0, 0);

COLORREF 文本颜色_红 = RGB(128, 0, 0);

COLORREF 文本颜色_黄 = RGB(200, 200, 0);

COLORREF 文本颜色_黑 = RGB(0, 0, 0);

COLORREF 文本颜色_白 = RGB(255, 255, 255);

COLORREF 文本颜色_常用 = RGB(158, 255, 0);

坐标结构_2 屏幕坐标_j;

坐标结构_2 屏幕坐标_H;

周围对象 周围;

int mainThread()

{

绘制 FPS_绘制(FindWindow(L"Valve001", 0), (DWORD)GetModuleHandleA(“hl.exe”) + 0x1820100);

while (true)

{

​ HDC句柄 = GetDC(FPS_绘制.m_窗口句柄);

​ 周围.刷新周围数据_Cs();

​ for (int i = 0; i < (int)周围.对象数量; i++)

​ {

​ if (周围.对象列表[i].死亡标志位 == 1)

​ {

​ continue;

​ }

​ 坐标结构_3 坐标_j = { 周围.对象列表[i].X_j,周围.对象列表[i].Y_j ,周围.对象列表[i].Z_j };

​ if (FPS_绘制.世界坐标转屏幕坐标(坐标_j,屏幕坐标_j))

​ {

​ 坐标结构_3 坐标_H = { 周围.对象列表[i].X_H,周围.对象列表[i].Y_H ,周围.对象列表[i].Z_H };

​ // 哪里不懂可以v hdlw312 探讨

​ if (FPS_绘制.世界坐标转屏幕坐标(坐标_H,屏幕坐标_H))

​ {

​ if (周围.对象列表[i].阵营 == 2)

​ {

​ 画刷句柄 = CreateSolidBrush(文本颜色_常用);

​ }

​ else

​ {

​ 画刷句柄 = CreateSolidBrush(文本颜色_亮红);

​ }

​ float 高度 = 屏幕坐标_j.y - 屏幕坐标_H.y;

​ float 宽度 = 高度 / 2;

​ FPS_绘制.画框(HDC句柄, 画刷句柄, (int)(屏幕坐标_H.x - 宽度/2), (int)屏幕坐标_H.y-2, (int)宽度, (int)(高度+4), 1);

​ DeleteObject(画刷句柄);

​ char healthChar[255];

​ sprintf_s(healthChar, sizeof(healthChar), “%d”, 周围.对象列表[i].Hp);

​ FPS_绘制.绘制字符串(HDC句柄, (int)屏幕坐标_j.x, (int)屏幕坐标_j.y, 文本颜色_常用, healthChar);

​ if (GetKeyState(VK_F2) & 1)

​ {

​ FPS_绘制.画线(HDC句柄, (int)屏幕坐标_j.x, (int)屏幕坐标_j.y);

​ }

​ }

​ }

​ }

​ Sleep(1);

​ DeleteObject(HDC句柄);

}

}

BOOL APIENTRY DllMain(HMODULE hModule,

DWORD ul_reason_for_call,

LPVOID lpReserved

)

{

switch (ul_reason_for_call)

{

case DLL_PROCESS_ATTACH:

​ DisableThreadLibraryCalls(hModule);

​ CreateThread(0, 0, (LPTHREAD_START_ROUTINE)mainThread, 0, 0, 0);

case DLL_THREAD_ATTACH:

case DLL_THREAD_DETACH:

case DLL_PROCESS_DETACH:

​ break;

}

return TRUE;

}

if (GetKeyState(VK_F2) & 1)

这里面学习一个快捷键指令 GetKeyState

每次按下切换他的状态 大于0 或则小于0

从而可以实现开关的功能

https://cdn.jsdelivr.net/gh/xinqinew/pic@main/img/c2d7ac1870445265e5341dfde78f77100d031592.jpg@942w_534h_progressive.jpg

微调

发现效果是有了,但是并不精准

优点是:不需要矩阵 ,完全的数学公式可以计算出来,而且公式是固定的,

缺点:

但是不精准

有可视角的关系,运算的误差等等导致的,算出来的误差虽然不是特别大

而且也可以通过调整的方式让其精确,但是不够完美.

我们可以选择微调可视角度,微调屏幕X,Y坐标等等方式来处理

反正这种方法想要完全的精密是不太可能的

但是达到基本效果是没问题的,虽然达到精密没有那么的重要,但是完美主义者

是一定会使用矩阵来计算的

那么我们继续学习矩阵,用矩阵的方式 绘制方框

下节课见!

参考视频:链接: https://pan.baidu.com/s/1dN63zxgQECeeH1lzQ1P9cg 提取码: dgs9