14

假设我是一个游戏,并且我有一个int*包含我的健康的全局变量。游戏培训师的工作是将此值修改为任何值以实现上帝模式。我查看了有关游戏培训师的教程以了解它们的工作原理,总体思路是使用内存扫描仪尝试查找某个值的地址。然后通过注入 dll 或其他方式修改此地址。

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

我错过了什么?

4

5 回答 5

7

通常这样做的方法是跟踪从静态变量到包含相关变量的堆地址的指针链。例如:

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() 函数中设置断点并取消减法,则会导致包括敌人在内的所有角色都无懈可击。解决方案是找到“游戏”变量的地址(它应该驻留在数据段或函数的堆栈中),并跟踪所有指针,直到找到健康变量的地址。

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

于 2010-05-28T06:15:02.563 回答
1

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

啊啊啊我的错,我使用 %d 来打印地址而不是 %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;
}
于 2010-05-28T05:19:15.117 回答
1

动态分配变量的示例

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

  1. 玩游戏并搜索此实例的 lifes 变量的位置。
  2. 一旦找到,使用反汇编器/调试器来观察该位置的变化。
  3. 失去一条生命。
  4. 调试器应该已经报告了递减发生的地址。
  5. 用无操作替换该指令

从名为 tsearch 的程序中得到这个模式


通过研究这个主题找到了一些相关的网站:

于 2010-05-28T05:52:03.683 回答
1

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

于 2010-05-28T19:23:21.863 回答
1

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

对于 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() 钩子如下所示:

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(与位置无关的可执行文件)。

于 2015-02-19T13:39:37.653 回答