挂钩(热补丁)类成员函数。修改vtable条目

时间:2011-08-08 08:42:22

标签: c++ winapi hook direct3d

经过相当长的序言后,我有两个问题。

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

不只是获得指向类成员的指针。我的意思是,当我尝试以与查看void*相同的方式查看这样的指针时,编译器直接在构建时抛出错误,这种做法似乎对非成员函数起作用。< / p>

幸运的是,为了挂钩Direct3D9,我正在处理void *之类的IDirect3DDevice9之类的东西。如果vtable类型为pDev,那么我IDirect3DDevice9*就像pDev一样。然后,PVOID*处的第一个值是函数指针数组的地址(pDev):

vtable

因为现在是第18个条目。

The first answer from here提供了一种更优雅,更高级别的方法,从定义// 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] ); 开始。我还没有测试过,但根据它,我可以做像

这样的事情
CINTERFACE

没有错误。

第一个问题。我不是一个很棒的C ++程序员;我如何得到一个指针,一般来说,到一个成员函数,这样我就可以覆盖该函数的可执行字节。对于非会员我做:

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

与x86-64相同的东西。对于对象的虚拟成员,我从#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; } 本身获得了foo指针,如上所示:

vtable

第二个问题。我不能简单地修改reinterpret_cast<FUNC_TYPE>( *(reinterpret_cast<void**>( *reinterpret_cast<void**>(objptr)) + n ) ) 中我的对象的条目吗?这是一个例子,不用说,它不适用于直接来自Direct3D的vtable对象,但Taksi似乎使用此方法:

pDev

6 个答案:

答案 0 :(得分:6)

执行这个丑陋的演员(成员函数到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);

现在,我已经给你一把安全关闭的装枪。请小心使用......

答案 1 :(得分:5)

正如您所指出的,您的方法不是“可移植的”,但它实际上适用于您的具体情况。并且没有问题恕我直言。

第一个回答:

这是使用成员函数指针的语法:

class SomeClass {
    int SomeFunc(int, int);
};

int (SomeClass::* pfn)(int, int); // variable pfn is a pointer to a SomeClass's member function

pfn = &SomeClass::SomeFunc; // assign this variable to the member function with the adequate prototype.

SomeClass obj; // instance of this class

int res = (obj.*pfn)(1, 2); // call the member function pointer

第二个回答:

你可以直接修改vtable的成员,但是你应该知道你通过这个继承所有这个类的对象(可能还有一些派生类)。

如果你想只创建一个特定的obj,你应该创建另一个函数表,并覆盖对象的vtable以指向新的函数表。

答案 2 :(得分:2)

在C ++中没有可移植的方法来执行此操作,因为语言规范指出,您可能从指向成员函数或指向函数的指针到void *执行任何类型的转换会导致未定义的行为。如果你想进行这种动态代码重写,你需要查阅你正在处理的特定平台的编译器文档。

如果您使用的是生成vtable的编译器,则应该能够通过对vtable进行爆破来更改成员函数代码所在的位置,假设vtable是可变的。我相信大多数编译器会将vtable放在只读段中,所以你不能意外地(或恶意地)这样做。您还必须担心编译器优化以内联调用,因为在某些情况下,没有什么能阻止编译器识别方法调用的目标,只是硬编码调用。

答案 3 :(得分:2)

class Original {
public:
    int a;

    Original* open(const char *filename, int openmode) {
        printf("Original: %s and %d // %d\n", filename, openmode, this->a);
        this->a = openmode;
        return this;
    }
};

class Group {
public:
    Original* my_open(const char *filename, int openmode) {
        Original *self = (Original*) this;
        printf("Fake:: %s and %d // %d\n", filename, openmode, self->a);
        return self;
    }
};

#define GetPointerToClassMethod(RETURN, CLASS, METHOD, ...) __GetPointerToClassMethod<CLASS, RETURN (CLASS::*)(__VA_ARGS__)>(&CLASS::METHOD)

template <class _Class, class _MethodPrototype>
LPVOID __GetPointerToClassMethod(_MethodPrototype method) {
    return *(LPVOID*) &method;
}

int main() {
    Original o;
    o.open("Dorian", 15);

    DirectHookProcedure(
        GetPointerToClassMethod(Original*, Original, open, const char*, int),
        GetPointerToClassMethod(Original*, Group, my_open, const char*, int)
    );

    o.open("Langbeck", 20);
    return 0;
}

答案 4 :(得分:1)

在某些情况下,你肯定是正确的,比如这个

BaseClass foo(5, 56);
//...
foo.Test(); // runs same old Test(); maybe due to compiler optimization?

编译器可以很容易地看到foo的类型,并且根本不需要使用vtable。它知道无论如何要调用什么函数。

当您通过指向对象的指针进行调用时,编译器设计人员并不打算检查它是否始终是相同的类型。在你的代码中,他们肯定可以看到,但也许这样的优化在实际代码中是不够用的?或者他们可能会在下一版本的编译器中添加它。 : - )

我们不应该忘记vtable,它们的存在和可能的布局都是语言标准所没有的实现细节。

答案 5 :(得分:0)

第一个问题的几乎可移植的解决方案 - 辅助函数中的va_list

void *DisMember(size_t size, ...)
{
    if (size != sizeof(void *)) return NULL;
    va_list args;
    va_start(args, size);
    void *res = va_arg(args, void *);
    va_end(args);
    return res;
}

// snip
void Base::MyMethod() { /* ... */ }
// snip

void *anything = DisMember(sizeof(void (Base::*)()), &Base::MyMethod);

valdo钉第二个。