游戏训练者如何更改内存中的动态地址?

发布于 2024-09-03 19:11:17 字数 267 浏览 7 评论 0原文

假设我是一个游戏,并且我有一个包含我的健康状况的全局 int* 。游戏训练师的工作是将这个值修改为任意值以实现上帝模式。我查阅了游戏训练器的教程来了解它们是如何工作的,总体思路是使用内存扫描器来尝试找到某个值的地址。然后通过注入dll或者其他什么的方式修改这个地址。

但我用全局 int* 制作了一个简单的程序,每次运行应用程序时它的地址都会发生变化,所以我不明白游戏训练者如何对这些地址进行硬编码?或者我的例子是错误的?

我缺少什么?

Lets assume I am a game and I have a global int* that contains my health. A game trainer's job is to modify this value to whatever in order to achieve god mode. I've looked up tutorials on game trainers to understand how they work, and the general idea is to use a memory scanner to try and find the address of a certain value. Then modify this address by injecting a dll or whatever.

But I made a simple program with a global int* and its address changes every time I run the app, so I don't get how game trainers can hard code these addresses? Or is my example wrong?

What am I missing?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

挖鼻大婶 2024-09-10 19:11:17

通常完成此操作的方法是跟踪从静态变量到包含相关变量的堆地址的指针链。例如:

struct CharacterStats
{
    int health;
    // ...
}

class Character
{
public:
    CharacterStats* stats;

    // ...

    void hit(int damage)
    {
        stats->health -= damage;
        if (stats->health <= 0)
            die();
    }
}


class Game
{
public:
    Character* main_character;
    vector<Character*> enemies;
    // ...
}

Game* game;

void main()
{
    game = new Game();
    game->main_character = new Character();
    game->main_character->stats = new CharacterStats;

    // ...

}

在这种情况下,如果您遵循 mikek3332002 的建议,在 Character::hit() 函数内设置断点并 nop 执行减法,则将导致所有角色(包括敌人)无懈可击。解决方案是找到“game”变量的地址(它应该驻留在数据段或函数的堆栈中),并跟踪所有指针,直到找到 health 变量的地址。

某些工具(例如 Cheat Engine)具有自动执行此操作的功能,并尝试自行查找指针链。不过,对于更复杂的情况,您可能不得不诉诸逆向工程。

The way this is usually done is by tracing the pointer chain from a static variable up to the heap address containing the variable in question. For example:

struct CharacterStats
{
    int health;
    // ...
}

class Character
{
public:
    CharacterStats* stats;

    // ...

    void hit(int damage)
    {
        stats->health -= damage;
        if (stats->health <= 0)
            die();
    }
}


class Game
{
public:
    Character* main_character;
    vector<Character*> enemies;
    // ...
}

Game* game;

void main()
{
    game = new Game();
    game->main_character = new Character();
    game->main_character->stats = new CharacterStats;

    // ...

}

In this case, if you follow mikek3332002's advice and set a breakpoint inside the Character::hit() function and nop out the subtraction, it would cause all characters, including enemies, to be invulnerable. The solution is to find the address of the "game" variable (which should reside in the data segment or a function's stack), and follow all the pointers until you find the address of the health variable.

Some tools, e.g. Cheat Engine, have functionality to automate this, and attempt to find the pointer chain by themselves. You will probably have to resort to reverse-engineering for more complicated cases, though.

无法回应 2024-09-10 19:11:17

访问指针的发现相当麻烦,并且静态内存值很难适应不同的编译器或游戏版本。

对于 malloc()、free() 等 API 挂钩,有一种与跟随指针不同的方法。发现从记录所有动态内存分配并并行执行内存搜索开始。然后将找到的堆内存地址与记录的内存分配进行反向匹配。您可以了解对象的大小以及对象内值的偏移量。您可以通过回溯重复此操作,并获取 malloc() 调用或 C++ 构造函数的跳回代码地址。有了这些信息,您可以跟踪和修改从那里分配的所有对象。您转储对象并比较它们,并发现更多有趣的值。例如,通用精英游戏训练器“ugtrain”在 Linux 上就是这样做的。它使用LD_PRELOAD。
适应通过基于“objdump -D”的反汇编进行,并且仅搜索其中具有已知内存大小的库函数调用。

请参阅:http://en.wikipedia.org/wiki/Trainer_%28games%29

Ugtrain 来源: https://github.com/sriemer/ugtrain

malloc() 钩子看起来像this:

static __thread bool no_hook = false;

void *malloc (size_t size)
{
    void *mem_addr;
    static void *(*orig_malloc)(size_t size) = NULL;

    /* handle malloc() recursion correctly */
    if (no_hook)
        return orig_malloc(size);

    /* get the libc malloc function */
    no_hook = true;
    if (!orig_malloc)
        *(void **) (&orig_malloc) = dlsym(RTLD_NEXT, "malloc");

    mem_addr = orig_malloc(size);

    /* real magic -> backtrace and send out spied information */
    postprocess_malloc(size, mem_addr);
    no_hook = false;

    return mem_addr;
}

但是如果找到的内存地址位于内存中的可执行文件或库中,则 ASLR 可能是动态的原因。在 Linux 上,库是 PIC(与位置无关的代码),并且在最新发行版中,所有可执行文件都是 PIE(与位置无关的可执行文件)。

Discovery of the access pointers is quite cumbersome and static memory values are difficult to adapt to different compilers or game versions.

With API hooking of malloc(), free(), etc. there is a different method than following pointers. Discovery starts with recording all dynamic memory allocations and doing memory search in parallel. The found heap memory address is then reverse matched against the recorded memory allocations. You get to know the size of the object and the offset of your value within the object. You repeat this with backtracing and get the jump-back code address of a malloc() call or a C++ constructor. With that information you can track and modify all objects which get allocated from there. You dump the objects and compare them and find a lot more interesting values. E.g. the universal elite game trainer "ugtrain" does it like this on Linux. It uses LD_PRELOAD.
Adaption works by "objdump -D"-based disassembly and just searching for the library function call with the known memory size in it.

See: http://en.wikipedia.org/wiki/Trainer_%28games%29

Ugtrain source: https://github.com/sriemer/ugtrain

The malloc() hook looks like this:

static __thread bool no_hook = false;

void *malloc (size_t size)
{
    void *mem_addr;
    static void *(*orig_malloc)(size_t size) = NULL;

    /* handle malloc() recursion correctly */
    if (no_hook)
        return orig_malloc(size);

    /* get the libc malloc function */
    no_hook = true;
    if (!orig_malloc)
        *(void **) (&orig_malloc) = dlsym(RTLD_NEXT, "malloc");

    mem_addr = orig_malloc(size);

    /* real magic -> backtrace and send out spied information */
    postprocess_malloc(size, mem_addr);
    no_hook = false;

    return mem_addr;
}

But if the found memory address is located within the executable or a library in memory, then ASLR is likely the cause for the dynamic. On Linux, libraries are PIC (position-independent code) and with latest distributions all executables are PIE (position-independent executables).

如果没有 2024-09-10 19:11:17

编辑:没关系,这似乎只是运气好,但是指针的最后 3 个数字似乎保持不变。也许这是 ASLR 启动并更改基本映像地址或其他什么?

aaahhhh 我的错,我使用 %d 作为 printf 来打印地址,而不是 %p。使用 %p 之后地址保持不变

#include <stdio.h>

int *something = NULL;

int main()
{
    something = new int;
    *something = 5;

    fprintf(stdout, "Address of something: %p\nValue of something: %d\nPointer Address of something: %p", &something, *something, something);
    getchar();
    return 0;
}

EDIT: never mind it seems it was just good luck, however the last 3 numbers of the pointer seem to stay the same. Perhaps this is ASLR kicking in and changing the base image address or something?

aaahhhh my bad, i was using %d for printf to print the address and not %p. After using %p the address stayed the same

#include <stdio.h>

int *something = NULL;

int main()
{
    something = new int;
    *something = 5;

    fprintf(stdout, "Address of something: %p\nValue of something: %d\nPointer Address of something: %p", &something, *something, something);
    getchar();
    return 0;
}
谁把谁当真 2024-09-10 19:11:17

动态分配变量的示例

我想要找到的值是阻止我的生命减少到 0 并游戏结束的生命数。

  1. 玩游戏并搜索此实例的 life 变量的位置。
  2. 一旦找到,请使用反汇编器/调试器来观察该位置的更改。
  3. 失去一条生命。
  4. 调试器应该报告发生减量的地址。
  5. 用 no-ops 替换该指令

从名为 tsearch 的程序中获取此模式


通过研究此主题发现的一些相关网站:

Example for a dynamicaly allocated varible

The value I want to find is the number of lives to stop my lives from being reduced to 0 and getting game over.

  1. Play the Game and search for the location of the lifes variable this instance.
  2. Once found use a disassembler/debugger to watch that location for changes.
  3. Lose a life.
  4. The debugger should have reported the address that the decrement occurred.
  5. Replace that instruction with no-ops

Got this pattern from the program called tsearch


A few related websites found from researching this topic:

卖梦商人 2024-09-10 19:11:17

像 Gameshark 代码这样的事情的计算方式是通过转储应用程序的内存映像,然后做一件事,然后查看发生了什么变化。可能会有一些事情发生变化,但应该寻找一些模式。例如转储内存,射击,转储内存,再次射击,转储内存,重新加载。然后寻找变化并了解弹药的存储位置/方式。对于健康来说,情况会是相似的,但更多的事情将会发生变化(因为你至少会移动)。不过,在最小化“外部影响”时最容易做到这一点,例如,不要在交火期间尝试比较内存转储,因为发生了很多事情,站在熔岩中或从建筑物上掉下来时进行比较,或者具有这种性质的东西。

The way things like Gameshark codes were figured out were by dumping the memory image of the application, then doing one thing, then looking to see what changed. There might be a few things changing, but there should be patterns to look for. E.g. dump memory, shoot, dump memory, shoot again, dump memory, reload. Then look for changes and get an idea for where/how ammo is stored. For health it'll be similar, but a lot more things will be changing (since you'll be moving at the very least). It'll be easiest though to do it when minimizing the "external effects," e.g. don't try to diff memory dumps during a firefight because a lot is happening, do your diffs while standing in lava, or falling off a building, or something of that nature.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文