0

我正在做逆向工程并通过 DLL 修补游戏的内存。通常我会坚持在一个或多个函数中修补所有内容的旧方法。但是感觉它可以通过使用一个结构数组来更好地完成,该数组定义了需要发生的内存写入并一次循环遍历它们。更容易管理,IMO。

不过,我想让它保持不变。因此,数据一次性全部存在(在 .rdata 中),而不必为每个补丁动态分配内存,这是一个简单的“字节大小”数据任务,例如:

struct struc_patch
{
    BYTE val[8];    // max size of each patch (usually I only use 5 bytes anyway for call and jmp writes)
                    // I can of course increase this if really needed
    void *dest;
    char size;
} patches[] =
{
    // simply write "01 02 03 04" to 0x400000
    {{0x1, 0x2, 0x3, 0x4}, (void*)0x400000, 4},
};
//[...]
for each(struc_patch p in patches)
{
    memcpy(p.dest, p.val, p.size);
}

但是当我想对这些类型更感兴趣时,我找不到将像“0x90909090”这样的整数指定为字节数组“90 90 90 90”的方法。所以这行不通:

struct struc_patch
{
    BYTE val[8];    // max size of each patch (usually I only use 5 bytes anyway for call and jmp writes)
                    // I can of course increase this if really needed
    void *dest;
    char size;
} patches[] =
{
    // how to write "jmp MyHook"? Here, the jmp offset will be truncated instead of overlapping in the array. Annoying.
    {{0xE9, (DWORD)&MyHook - 0x400005}, (void*)0x400000, 5},
};

当然,主要问题是 &MyHook 必须由编译器解决。还有其他方法可以获得所需的结果并保持不变吗?

老实说,我对 STL 的经验很少。因此,如果有使用它的解决方案,我可能需要对其进行详细解释,以便正确理解代码。我是一个大 C/C++/WinAPI 瘾君子,哈哈,但它是为类似性质的游戏编写的,所以它适合。

4

3 回答 3

2

我认为 STL 中的任何内容都不会帮助您解决这个问题,而不是在编译时。可能有一种奇特的方式来处理模板,就像处理宏一样。(逗号分隔字节)

但我建议做这样简单的事情:

struct jump_insn
{
    unsigned char opcode;
    unsigned long addr;
} jump_insns[] = {
    {0xe9, (unsigned long)&MyHook - 0x400005}
};

struct mem
{
   unsigned char val[8];
} mems[] = {
    {1,2,3,4}
};

struct struc_patch
{
    unsigned char *val;    // max size of each patch (usually I only use 5 bytes anyway for call and jmp writes)
                    // I can of course increase this if really needed
    void *dest;
    char size;
} patches[] =
{
    // simply write "01 02 03 04" to 0x400000
    {(unsigned char*)(&mems[0]), (void*)0x400000, 4},

    // how to write "jmp MyHook"? Here, the jmp offset will be truncated instead of overlapping in the array. Annoying.
    {(unsigned char*)(&jump_insns[0]), (void*)0x400000, 5},
};

你不能内联做所有事情,你需要为不同类型的补丁提供新类型,但它们可以任意长(不仅仅是 8 个字节),并且所有内容都在 .rodata 中。

于 2012-09-29T00:09:30.053 回答
0

处理这个问题的更好方法是即时计算地址差异。例如(来源):

#define INST_CALL    0xE8

void InterceptLocalCode(BYTE bInst, DWORD pAddr, DWORD pFunc, DWORD dwLen)
{
    BYTE *bCode = new BYTE[dwLen];
    ::memset(bCode, 0x90, dwLen);
    DWORD dwFunc = pFunc - (pAddr + 5);

    bCode[0] = bInst;
    *(DWORD *)&bCode[1] = dwFunc;
    WriteBytes((void*)pAddr, bCode, dwLen);

    delete[] bCode;
}

void PatchCall(DWORD dwAddr, DWORD dwFunc, DWORD dwLen)
{
    InterceptLocalCode(INST_CALL, dwAddr, dwFunc, dwLen);
}

dwAddr 是要放入 call 指令的地址,dwFunc 是要调用的函数,dwLen 是要替换的指令的长度(基本上用来计算要放入多少 NOP)。

于 2012-09-28T23:01:41.340 回答
0

总而言之,我的解决方案(感谢 Nicolas 的建议):

#pragma pack(push)
#pragma pack(1)
#define POFF(d,a) (DWORD)d-(a+5)
struct jump_insn
{
    const BYTE opcode = 0xE9;
    DWORD offset;
};

struct jump_short_insn
{
    const BYTE opcode = 0xEB;
    BYTE offset;
};

struct struc_patch
{
    void *data;
    void *dest;
    char size;
};
#pragma pack(pop)

并在使用中:

// Patches
jump_insn JMP_HOOK_LoadButtonTextures = {POFF(&HOOK_LoadButtonTextures, 0x400000)};

struc_patch patches[] =
{
    {&JMP_HOOK_LoadButtonTextures, IntToPtr(0x400000)},
};

使用类成员 const,我可以更轻松、更清晰地定义所有内容,并且可以简单地全部使用 memcpy。pack pragma 当然需要确保 memcpy 不会复制 BYTE 操作码和 DWORD 值之间的 3 个对齐字节。

谢谢大家,帮助我使我的修补方法更加强大。

于 2012-09-29T02:57:48.770 回答