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负轴
最终得到结果
也就是我们不同朝向的值
人物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
从而可以实现开关的功能
微调
发现效果是有了,但是并不精准
优点是:不需要矩阵 ,完全的数学公式可以计算出来,而且公式是固定的,
缺点:
但是不精准
有可视角的关系,运算的误差等等导致的,算出来的误差虽然不是特别大
而且也可以通过调整的方式让其精确,但是不够完美.
我们可以选择微调可视角度,微调屏幕X,Y坐标等等方式来处理
反正这种方法想要完全的精密是不太可能的
但是达到基本效果是没问题的,虽然达到精密没有那么的重要,但是完美主义者
是一定会使用矩阵来计算的
那么我们继续学习矩阵,用矩阵的方式 绘制方框
下节课见!
参考视频:链接: https://pan.baidu.com/s/1dN63zxgQECeeH1lzQ1P9cg 提取码: dgs9