gpt4 book ai didi

c++ - Hook (热修补)类成员函数。修改 vtable 条目

转载 作者:太空狗 更新时间:2023-10-29 23:25:51 29 4
gpt4 key购买 nike

在相当长的序言之后,我有 2 个问题。

通过在 void* 中查看任何函数指针,我能够修改它的第一条指令,将它们转换为 jmp(32 位相对或 64 位绝对,通过 r11,取决于 x86/x86-64)。我认为将函数代码视为数据在 C 和 C++ 中都是非法的,但它似乎以某种不受支持的方式在 MSVC (Win32) 和 GCC (OS X) 中工作。网上有好几处说将函数指针强制转换为void* is illegal .

一个人不能简单地获得指向类成员的指针。我的意思是,当编译器试图以与查看 void * 相同的方式查看此类指针时,编译器会在构建时直接抛出错误,这种做法似乎适用于非成员函数。

幸运的是,为了连接 Direct3D9,我正在使用类似 IDirect3DDevice9 的东西,它有一个 vtable。对于 IDirect3DDevice9* 类型的 pDev,我将 pDev 视为 PVOID* 就足够了。然后,pDev 的第一个值是函数指针数组(vtable)的地址:

// IDirect3DDevice9::Present()
typedef HRESULT (CALLBACK *PRESENT_PROC)(
LPDIRECT3DDEVICE9, const RECT*,
const RECT*,
HWND,
const RGNDATA*
);

PVOID (*vPtr)[] = reinterpret_cast<PVOID (*)[]>(
*reinterpret_cast<PVOID*>(pDev)
);
PRESENT_PROC pDevicePresent = reinterpret_cast<PRESENT_PROC>(
(*vPtr)[17]
);

因为 Present 是第 18 个条目。

The first answer from here给出了一个更优雅、更高级的方法,从定义 CINTERFACE 开始。我还没有测试过,但是根据它我可以做类似的事情

reinterpret_cast<PVOID>(pDev->lpVtbl->Present)

没有错误。

第一个问题。我不是一个出色的 C++ 程序员;通常,我如何获得一个指向成员函数的指针,以便我可以覆盖该函数的可执行字节。对于非成员(member),我这样做:

#include <windows.h>
#include <cstdio>
using namespace std;

const unsigned char OP_JMP = 0xE9; // 32 bit relative jmp
const SIZE_T SIZE_PATCH = 5; // jmp dword ptr distance; 1 byte + 4 bytes
typedef void (*MyProc)();

void SimpleFunction1()
{
printf("foo\n");
}

void SimpleFunction2()
{
printf("bar\n");
}

int main()
{
PBYTE foo = reinterpret_cast<PBYTE>(SimpleFunction1);
PBYTE bar = reinterpret_cast<PBYTE>(SimpleFunction2);

DWORD oldProtection;
// make sure the bytes of the function are writable
// by default they are only readable and executable
BOOL res = VirtualProtect(
foo,
SIZE_PATCH,
PAGE_EXECUTE_READWRITE,
&oldProtection
);
if (!res) return 1;

// be mindful of pointer arithmetic
// works with PBYTE, won't with PDWORD
DWORD distanceToNewFoo = bar - foo - SIZE_PATCH;

*foo = OP_JMP;
*reinterpret_cast<PDWORD>(foo + 1) = distanceToNewFoo;

// called though the pointer instead of foo()
// to make sure the compiler won't inline or do some other stupid stuff
reinterpret_cast<MyProc>(foo)(); // will print "bar\n"
return 0;
}

x86-64 也有类似的东西。对于对象的虚拟成员,我从 vtable 本身获取 foo 指针,如上所示:

reinterpret_cast<FUNC_TYPE>(
*(reinterpret_cast<void**>(
*reinterpret_cast<void**>(objptr)) + n
)
)

第二个问题。我不能仅仅修改对象的 vtable 条目吗?这是一个示例,不用说,它不适用于直接从 Direct3D 获取的 pDev 对象,但是 Taksi似乎使用这种方法:

#include <cstdio>
using namespace std;

class BaseClass
{
public:
BaseClass(int a = 0, int b = 0);
int GetA();
int GetB();
virtual void Test();
private:
int _a;
int _b;
};

BaseClass::BaseClass(int a, int b) :
_a(a),
_b(b)
{
}

int BaseClass::GetA()
{
return _a;
}

int BaseClass::GetB()
{
return _b;
}

void BaseClass::Test()
{
printf("test %d; %d\n", _a, _b);
}

void TheNewFunction(BaseClass *bc)
{
printf("I am an intruder\n");
}

typedef void (*PROC_TYPE)(BaseClass *);

int main()
{
BaseClass foo(5, 56);
PROC_TYPE proc = 0;
proc = reinterpret_cast<PROC_TYPE>(
*reinterpret_cast<void**>(
*reinterpret_cast<void**>(&foo)
)
);
proc(&foo);
reinterpret_cast<void**>(
*reinterpret_cast<void**>(&foo)
)[0] = reinterpret_cast<void*>(TheNewFunction);

foo.Test(); // runs same old Test(); maybe due to compiler optimization?
proc = reinterpret_cast<PROC_TYPE>(
*reinterpret_cast<void**>(
*reinterpret_cast<void**>(&foo)
)
);

proc(&foo); // runs TheNewFunction
BaseClass *goo = &foo;
goo->Test(); // runs TheNewFunction
return 0;
}

最佳答案

执行这种丑陋转换(void * 的成员函数)的最快方法是臭名昭著的 union_cast<> :

template <class T1, class T2>
T1 union_cast(T2 v)
{
static_assert(sizeof(T1) >= sizeof(T2), "Bad union_cast!");
union UT {T1 t1; T2 t2;} u {};
u.t2 = v;
return u.t1;
}

像这样使用:

class MyClass
{
public:
void foo(int);
};

auto p = union_cast<void *>(&MyClass::foo);

现在,我给了你一把上膛的枪,保险已关闭。请谨慎使用...

关于c++ - Hook (热修补)类成员函数。修改 vtable 条目,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6979558/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com