【问题标题】:const struct Declaration For Memory Patching用于内存修补的 const struct 声明
【发布时间】:2012-09-28 21:03:21
【问题描述】:

我正在做逆向工程并通过 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 的狂热爱好者,哈哈,但它是为类似性质的游戏编写的,所以很合适。

【问题讨论】:

  • 我发现了一个使用宏的肮脏解决方案,我认为它应该可以正常工作,但我仍然更喜欢更自然的方式。虽然它已由编译器解决,但它完全符合我的要求:#define SPLITDW(v) (BYTE)(v),(BYTE)(v>>8),(BYTE)(v>>16),(BYTE)(v>>24){{0xE9, SPLITDW((DWORD)&HOOK_LoadHudTextures - 0x400005)}, (void*)0x400000, 5}, 任何替代(和更好)的建议仍然值得赞赏。

标签: c++ c hook code-injection


【解决方案1】:

我认为 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 中。

【讨论】:

  • 啊,是的,这很不错,而且是一种看似“标准”的做法。比我的要好得多,因为数组中没有浪费字节,因为声明它是最大可能的大小,而且我可以轻松设置操作码写入。谢谢!
【解决方案2】:

解决这个问题的更好方法是即时计算地址差异。例如(source):

#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)。

【讨论】:

  • 谢谢,但这正是我想要摆脱的东西,因为这需要分配数据以动态写入并为每个补丁做很多幕后工作,我真的觉得最好将所有数据预先确定并作为 const 数据,而不是即时进行。使用我当前的代码,唯一要做的就是复制编译后的数据(以及任何所需的保护 - 尽管我更喜欢一次性取消对整个 EXE 的保护并保持这种状态)。
  • 好吧,如果您想避免动态内存方面,那么您所要做的就是将 BYTE *bCode 更改为 BYTE bCode[8]。我认为您会发现“在幕后”动态生成内容会减少麻烦(例如,在您的示例中,如果您需要将 0x400005 更改为其他内容,则必须更改两次)。我不确定您为什么偏向于使用 const 进行修补(您仍然可以在结构中拥有 const 数据并在 const 结构上反复调用 PatchCall)。
  • 我只是认为不在编译时完成所有工作有点浪费编译器的能力。当然,我必须更改 2 个地址,但我现在已经获得了 {PATCH_JMP_DATA(0x400000, &HOOK_MyFunc), Ptr32ToPtr(0x400000), 5}, 之类的地址,所以几乎没有麻烦。还允许我有更多的控制权,如果我想编写一个不相关的指针等。所有这些都是在编译时完成的,这在编译的代码中节省了很多混乱。我也从来没有看到过把其余的写成功能的意义。如果你的钩子可以跳过损坏的代码,那就浪费时间了。
【解决方案3】:

总结一下,我的解决方案(感谢 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 个对齐字节。

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

【讨论】:

    猜你喜欢
    • 2021-09-21
    • 2015-01-02
    • 2014-10-24
    • 1970-01-01
    • 2019-03-21
    • 1970-01-01
    • 2011-01-10
    • 1970-01-01
    • 2022-01-20
    相关资源
    最近更新 更多