天天动画片 > 八卦谈 > 游戏辅助丨手把手简单实现射击游戏逆向(2)

游戏辅助丨手把手简单实现射击游戏逆向(2)

八卦谈 佚名 2024-01-27 22:26:59

作者:问号哥



2.  子弹无后座,子弹连发实现


同样的,我们依旧是要找到子弹后座力的地址,子弹后座力有很多的保存形式,比如射击的次数,比如一个浮点数的大小--我的子弹扩散的半径,比如离屏幕准心的2个偏移,既然这么多形式我们该如何下手呢,这时候我们先前找到的子弹地址就排上用场了。不妨假设我们武器相关的数据都在一个对象中,那么就有可能他们在内存是一段连续的区域中。根据我们的假设我们要查看子弹地址的内存区域。点击我们的子弹地址,右键点击浏览相关内存区域,然后开几枪试试

不妨将这两个地址记录下来,将我们的子弹指针复制几份,然后改个名字



可疑1和可疑2可以通过子弹指针的带,只要加上地址的差值即可

一个差值为0x70一个差值为0x98

把锁住偏移为0x70的,我们进游戏开枪,

锁前


锁后


很明显的可以看到我们的子弹明显收束了,靠我们的坚持和努力我们的枪法变好了。

那我在把可疑2锁住,用冲锋枪效果可能不是很明显,我们改用狙击枪,我们发现狙击枪可以连发了,直接变身加特林。


那么我们就知道可疑1是子弹后座,可疑2是子弹的发射间隔

找到数据后就是实现功能的思路

1.通过不断的写入值,使得这个值不变,2.改指令,或是跳过后座call,或是nop掉后座call

1如同前面的写入操作,我们直接看第二个

依旧是什么改写了这个地址,然后进游戏开枪,看看谁让枪抖了


我们开了一枪后,出现了三个地方,但是开枪的时候观察到第一个正好是我们开枪的时候跳出来的,所以我们追第一个,显示反汇编程序,进去别管啥,直接nop掉,不执行这个指令,我们的值就不会改,我们的功能就实现了。


我们进游戏开枪试试,我们发现枪的子弹是收束的,大功告成,但是我们的视角还是会抖,不够平滑,怎么办呢,根据常识,我们可以假设开枪的时候我们的视角抖了,我们的枪跟着抖,那么有可能这两个功能的被封装在一个函数里,我们只要把这个函数给nop,我们枪就不抖了,视角也不会抖,岂不是很好,说干就干,那我们就要要追这个函数,点击我们nop掉的地址,右键设置硬件断点


我们进游戏开枪,我们就暂停在了nop指令上,现在有2思路,一是慢慢往上逆,把跳过这条指令的jcc指令全部改成jmp,跳过所有开枪后尝试的作痛,第二种思路就是查看上一层的call,把整个视角抖动call nop掉,第1种大家可以去尝试,这里演示第二种


双击堆栈第一条表达式,我们可以追到离我们最近的call,越近越关键!!!


就是这个call里实现了视角抖动,我们将其nop掉

这时,我们发现我们的枪不抖了,视角也不抖了

接下来追子弹无间隔

同样的右键找出什么改写了这个地址

我们开了一枪,74的肯定不正常,我们追第一个

还是原来的配方上来直接nop,ok功能实现。

接下来时代码部分

子弹无后座,我们只要nop第一层的调用call即可地址是GameAssembly.dll +0x148bbe1

一共填充5个nop

BYTE旧的后坐力硬编码[5] = {};

void主窗口::OnBnClickedCheck2()

{

    //查询到改变子弹后座改变的地址

    ULONGLONG模块地址 = (ULONGLONG)LoadLibraryA("GameAssembly.dll");

    PBYTE后坐力地址 = (PBYTE)(模块地址 + 0x148bbe1);

 

    //保存5个字节

 

    if (子弹无后座.GetCheck()) {

        memcpy_s(旧的后坐力硬编码, 5, 后坐力地址, 5);

        //改变一下内存的读写权限

        //用nop填充

        DWORD后坐力地址读写权限 = 0;

        VirtualProtectEx(GetCurrentProcess(), 后坐力地址, 5, PAGE_EXECUTE_READWRITE, &后坐力地址读写权限);

        memset(后坐力地址, 0x90, 5);

        VirtualProtectEx(GetCurrentProcess(), 后坐力地址, 5, 后坐力地址读写权限, 0);

    }

    else {

       

        DWORD后坐力地址读写权限 = 0;

        VirtualProtectEx(GetCurrentProcess(), 后坐力地址, 5, PAGE_EXECUTE_READWRITE, &后坐力地址读写权限);

        memcpy_s(后坐力地址, 5, 旧的后坐力硬编码, 5);

        VirtualProtectEx(GetCurrentProcess(), 后坐力地址, 5, 后坐力地址读写权限, 0);

    }

 

}

要让子弹没有间隔我们只需要改变改子弹间隔的汇编指令即可即可

地址是GameAssenbly.dll+0xe52b20填充7个字节的0x90即可代码如同上方

3方框透视

要做到方框透视我们需要目标的坐标,和我们人物的矩阵,这样即可做到方框透视。那第一步找敌人的坐标。我这里用用另一个账号和我的账号联机,通过另一个角色的移动,得到敌人移动坐标,坐标一般为浮点数,所以我们进行浮点数搜索。为什么不搜索血量呢。这里我测试了,敌人血量似乎是再服务器端的,本地似乎找不到。

这里我先教找自身的坐标,敌人的坐标同理.

我们先进游戏,然后浮点搜索未知的初始值,然后动一下搜索,变动的数值,不动搜未变动的数值,然后做一些无关的动作,例如甩头,开枪,然后搜未变动的数值。然后我们的坐标就剩下几个了。


不同的地图可能会有不一样的坐标,具体以游戏为准。

将这三个坐标添加下来点击地址栏ctrl+a,然后点右下角的红箭头

先锁住2个移动我们的人物


先锁住一两个,然后移动我们的人物,如果我们的人物被吸回来说明这就是我们人物真实的地址。



就是它了,锁住它我们人物就动不了了,ctrl+b浏览相关内存区域


将我们的数据显示改为单浮点

这三个地址具有相关性,我们跳一跳发现第二个地址的值发生了改变,所以第二个为高度也就是z,我问了一些大佬u3d引擎就是以xzy的顺序进行编写的。


我们跳一跳发现就第二个动了,说明第二个是z

接下来使找敌人坐标,说实话我没啥好方法,这游戏血量在服务器上,本地很难入手,通过血量追人物数组没法下手。我叫了一个小伙伴进来,让他动,我进行搜索,跟前面坐标搜索基本类似。这里给出我的一个指针扫描结果。


这是其中一个人物的世界坐标,一般来说人物的坐标要么是以数组形式或者以链表的形式存储,这个游戏是以数组的形式存储的我点击偏移的左右箭头,观察上方的坐标值,从上往下,一个一个试,如果加超过了0x100都没有观察到可疑的值,基本就不是了就往下方找。

当我试到第3个的时候就可以发现每加0x18就会出现一个类似于坐标的值,我们移去上面两个偏移看30那块区域的的内存


由于我就两个人格式不是很明显,加入多人后就可以很明显的看到第一排格式的一致性,我们可以初步肯定,这个就是我们所要的人物数组,可以写出每个人物坐标的表达式

【【【【【【【【"GameAssembly.dll"+0666C5D8】+0xb8】+0x0】0x18】+0x18】+0x30+0x18*第几个人物】+40】+0x24

接下来就是找矩阵了

矩阵一般的特点是要么最后一行就很大,要么最后一列会很大

行很大简称行大矩阵,本游戏就是行大矩阵

这里xzy的顺序和你游戏的排列有关,根据我们之前找的自身坐标可以知道



这里w计算有关,这里我们先不提

列很大简称列大矩阵

那么如何找矩阵呢,我教大家一个技巧:

首先把将游戏中角色的头抬到最顶上,然后用浮点数搜索0.8到1.1,然后低头到最底下,然后搜-1.1到-0.8,间隔几个选一个然后ctrl+b看他的相关内存区域,

,很快就可以筛选出类似的矩阵,



矩阵是以我们屏幕的中心作为基准,加上对应的算法得到偏移,得到敌人的位置

如图

那么我们就要先得到中心点的坐标,也就是游戏分辨率的一半,我设置的是1024x768


那么我的中心就是512x384

接下来是算x方向上的偏移,这个偏移是通过屏幕的大小的比例来表示的所以我们得到一个表达式:x方向上的偏移=屏幕大小一半*通过矩阵计算得到的百分比(可正可负)

计算偏移百分比::我们将x一列的数用x1,x2,x3,x4表示,其他列相同



(这是列大矩阵的算法,给大家看看)

然后根据如下公式,可以得到4个值,我们只需要相对大小x,y,w

x相对大小=x1*人物坐标X+x2*人物坐标Z+x3*人物坐标Y+x4

z相对大小=z1*人物坐标X+z2*人物坐标Z+z3*人物坐标Y+z4

y相对大小=y1*人物坐标X+y2*人物坐标Z+y3*人物坐标Y+y4

w相对大小=w1*人物坐标X+w2*人物坐标Z+w3*人物坐标Y+w4

其中1/w为距离系数,w越大敌人离我们越远,当1/w<0.01时敌人离我们已经很远很远了,即使画再屏幕上也是很小的。

x的偏移=x的相对大小*1/w*屏幕中心点的x,就是人物在我们屏幕上的x偏移大小

x位置=屏幕的中心+x的偏移,我们就可以得到人物在我们人物的,蓝色那条我们就知道了




y也是相同的

Y的偏移=Y的相对大小*1/w*屏幕中心点的Y

这样我们就知道y的偏移(黄色的那条)

接下来绘制方框只要让我们的敌人的高度高一点,也就是让其z大一点在计算一次y,就可以的得到y2

那我们的方框的位置和大小就确定了

方框的右上点为屏幕中心位置+Y2

方框的宽度为abs(Y2-Y)*2,方框的长度我们可以大致认为时宽度的1/2,此时我们要画的矩形就是这样的:

代码部分:

1.    我们使用外部绘制,在游戏窗口上创建一个和游戏等大小的窗口,置于桌面最上方,透明化,不接受我们的鼠标点击。

2.    初始化D3D

3.    读取敌人的坐标数据,进行计算出我们矩形的大小和位置,进行绘制

创建一个符合我们要求的窗口:

①    注册一个窗口类

WNDCLASSEX绘制窗口类;

    绘制窗口类.cbSize = sizeof(WNDCLASSEX);

    绘制窗口类.lpfnWndProc = 窗口回调函数;

    绘制窗口类.style = CS_HREDRAW | CS_VREDRAW;//未知

    绘制窗口类.cbClSEXtra = 0;

    绘制窗口类.cbWndExtra = 0;

    绘制窗口类.hInstance = 0;

    绘制窗口类.hIcon = 0;//图标

    绘制窗口类.hCursor = LoadCursor(NULL, IDC_ARROW);//使用系统默认的图标

    绘制窗口类.hbrBackground = (HBRUSH)RGB(0, 0, 0);//背景颜色

    绘制窗口类.lpszMenuName = "";

    绘制窗口类.lpszClassName = "绘制窗口";

    绘制窗口类.hIconSm = NULL;

    if (!RegisterClasSEXA(&绘制窗口类)) {

        ::MessageBox(0, "注册窗口失败", 0, 0);

        return 0;

    }

②    创建窗口(置于最上方,大小等同游戏窗口)

HWND hWnd = FindWindowA(0, "PixelStrike3D");

             RECT游戏窗口参数;

    GetWindowRect(游戏窗口句柄, &游戏窗口参数);

HWND 绘制窗口句柄=CreateWindowExA(WS_EX_TOPMOST|WS_EX_LAYERED|WS_EX_TRANSPARENT,//三个参数:最顶上,点了没反应,透明度扩展

        "绘制窗口",

        "",

        WS_VISIBLE | WS_POPUP,

        游戏窗口参数.left,

        游戏窗口参数.top,

        1024,

        768,

        0,0,0,0);

        MARGINS m = {

        游戏窗口参数.left,

        游戏窗口参数.top,

        1024,

        768

    };

③    使窗口透明化

SetLayeredWindowAttributes(绘制窗口句柄, RGB(0, 0, 0), 255,LWA_ALPHA);

④    使窗口不接受点击

DwmExtendFrameIntoClientArea(绘制窗口句柄, &m);//透明

⑤    显示窗口

ShowWindow(绘制窗口句柄, SW_SHOW);



初始化D3D

IDirect3D9* m_d3d = nullptr;

    IDirect3DDevice9* m_device = nullptr;

m_d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS pp;

    ZeroMemory(&pp, sizeof(D3DPRESENT_PARAMETERS));

    pp.Windowed = TRUE;

    pp.SwapEffect = D3DSWAPEFFECT_DISCARD;

    pp.BackBufferFormat = D3DFMT_A8R8G8B8;

    pp.BackBufferHeight = 768;

    pp.BackBufferWidth = 1024;

    pp.hDeviceWindow = 绘制窗口句柄;

    pp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

    if (FAILED(m_d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, 绘制窗口句柄,

        D3DCREATE_HARDWARE_VERTEXPROCESSING, &pp, &m_device))) {

        MessageBoxA(0, "创建d3d设备失败", 0, 0);

        return 0;

    }

    //构造器

    //绘制线的画笔初始化

    ID3DXFont* m_font = nullptr;/

if (FAILED(D3DXCreateLine(m_device, &m_line))) {

        MessageBoxA(0, "线条初始化失败", 0, 0);

        return 0;

    }

完善一下画矩形的方法,就是普通的坐标计算,然后调用接口画出

void画线条(intx, inty, intx1, inty1, ULONGcolor, ID3DXLine* m_line) {

    D3DXVECTOR2 line[2] = { D3DXVECTOR2(x,y),D3DXVECTOR2(x1,y1) };

    m_line->Begin();

    m_line->Draw(line, 2, color);

    m_line->End();

}

void画方框(intx, inty, intw, inth, ULONGcolor, ID3DXLine* m_line) {

    画线条(x, y, x + w, y, color, m_line);

    画线条(x, y, x, y + h, color, m_line);

    画线条(x + w, y, x + w, y + h, color, m_line);

    画线条(x, y + h, x + w, y + h, color, m_line);

}

接下来要获取人物的坐标地址,然后计算出方框的位置和大小,然后画方框即可,由于我们要连续绘制,开始画之前要清空之前屏幕上的绘制,画好之后要清空屏幕上的绘制

void开始绘制(IDirect3DDevice9* m_device) {

    m_device->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_RGBA(0, 0, 0, 0), 1.f, 0);

    m_device->BeginScene();

}

void结束绘制(IDirect3DDevice9* m_device) {

    m_device->EndScene();

    m_device->Present(0, 0, 0, 0);

}

然后我们只需要获取游戏里人物的位置,和自身的矩阵就可以绘制啦

float屏幕中心x = 1024 * 0.5f;

    float屏幕中心y = 768 * 0.5f;

    float人物矩阵[4][4] = { 0 };

    ULONGLONG人物地址 = 0;

    while(true)

    {

        开始绘制(m_device);

        ULONGLONG人物矩阵地址 = getOtherProcessAddr_byOff(hProcess, 人物矩阵偏移数, 人物矩阵偏移, UnityPlayer);

        ReadProcessMemory(hProcess, (LPVOID)人物矩阵地址, 人物矩阵, 64, 0);

        //获取人物数组

        ULONGLONG人物数组地址 = getOtherProcessAddr_byOff(hProcess, 人物数组偏移数, 人物数组偏移, GameAssembly);

        for (size_t i = 0; i < 10; i++)

        {

             ReadProcessMemory(hProcess, (LPVOID)(人物数组地址 + i * 人物之间的间隔), &(人物地址), 8, 0);

             ULONGLONG人物坐标 = getOtherProcessAddr_byOff(hProcess, 人物坐标偏移数, 人物坐标偏移, 人物地址);

             float人物x = 0;

             float人物y = 0;

             float人物z = 0;

            ReadProcessMemory(hProcess, (LPVOID)人物坐标, &人物x, 4, 0);

             if (人物x == 0) {

                 continue;

             }

             ReadProcessMemory(hProcess, (LPVOID)(人物坐标 + 4), &人物z, 4, 0);

             if (人物z == -1000) {

                 continue;

             }

             ReadProcessMemory(hProcess, (LPVOID)(人物坐标 + 8), &人物y, 4, 0);

             if (人物y == 0) {

                 continue;

             }

             //计算

             {

                 float w = 人物矩阵[0][3] * 人物x + 人物矩阵[1][3] * 人物z + 人物矩阵[2][3] * 人物y + 人物矩阵[3][3];

                 if (w < 0.01f) {

                     continue;

                 }

                 float放缩系数 = 1.f / w;

                 float x = 一般屏幕x + 一般屏幕x * 放缩系数 * (人物矩阵[0][0] * 人物x + 人物矩阵[1][0] * 人物z + 人物矩阵[2][0] * 人物y + 人物矩阵[3][0]);

                 float y = 一般屏幕y - 一般屏幕y * 放缩系数 * (人物矩阵[0][1] * 人物x + 人物矩阵[1][1] * 人物z + 人物矩阵[2][1] * 人物y + 人物矩阵[3][1]);

                 float y2 = 一般屏幕y - 一般屏幕y * 放缩系数 * (人物矩阵[0][1] * 人物x + 人物矩阵[1][1] * (人物z+3.f) + 人物矩阵[2][1] * (人物y)+人物矩阵[3][1]);

                 float方框的高度 = y - y2;

                 float方框的宽度 = 方框的高度 / 2;

                 画方框(x - 方框的宽度 / 2, y2, 方框的宽度, 方框的高度, D3DCOLOR_RGBA(200, 66, 55, 255), m_line);

 

             }

             Sleep(10);//可以不用,加了防止无响应,程序会崩

        }

        结束绘制(m_device);

    }

本次教程就到这里,谢谢大家!!!


本文标题:游戏辅助丨手把手简单实现射击游戏逆向(2) - 八卦谈
本文地址:www.ttdhp.com/article/46657.html

天天动画片声明:登载此文出于传递更多信息之目的,并不意味着赞同其观点或证实其描述。
扫码关注我们