Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/158.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 挂钩(热补丁)类成员函数。修改vtable条目_C++_Winapi_Hook_Direct3d - Fatal编程技术网

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

C++ 挂钩(热补丁)类成员函数。修改vtable条目,c++,winapi,hook,direct3d,C++,Winapi,Hook,Direct3d,在相当长的序言之后,我有两个问题 通过查看位于void*的任何函数指针,我能够修改它的第一条指令,将它们转换为jmp(通过r11,可以是32位相对指令,也可以是64位绝对指令,具体取决于x86/x86-64)。我相信在C和C++中查看函数代码作为数据是非法的,但是它似乎以一种不支持的方式工作在MSVC(Win32)和GCC(OS X)中。互联网上有几个地方说,将函数指针强制转换为void* 我们不能简单地得到一个指向类成员的指针。我的意思是,编译器在构建时直接抛出错误,就像我在查看void*时一

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

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

我们不能简单地得到一个指向类成员的指针。我的意思是,编译器在构建时直接抛出错误,就像我在查看
void*
时一样,这种做法似乎对非成员函数很有效

幸运的是,为了钩住Direct3D9,我正在使用像
IDirect3DDevice9
这样的东西,它有一个
vtable
。对于
IDirect3DDevice9*
类型的
pDev
,我将
pDev
视为
PVOID*
。然后,
pDev
处的第一个值是指向函数的指针数组的地址(
vtable
):

没有错误

<> >强>第一个问题。< /强>我不是一个很棒的C++程序员;通常,如何获取指向成员函数的指针,以便覆盖该函数的可执行字节。对于非会员,我会:

#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;
}
第二个问题。我不能简单地修改对象的
vtable
中的条目吗?下面是一个例子,不用说,它不适用于直接从Direct3D获取的
pDev
对象,但似乎使用了以下方法:

#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;
}
#包括
使用名称空间std;
类基类
{
公众:
基类(inta=0,intb=0);
int GetA();
int GetB();
虚空试验();
私人:
国际组织;
int_b;
};
基类::基类(int a,int b):
_a(a),,
_b(b)
{
}
int基类::GetA()
{
返回a;
}
int基类::GetB()
{
返回b;
}
void基类::Test()
{
printf(“测试%d;%d\n”,\u a,\u b);
}
使newfunction无效(基类*bc)
{
printf(“我是入侵者”\n);
}
typedef void(*PROC_TYPE)(基类*);
int main()
{
基类foo(5,56);
PROC_类型PROC=0;
proc=重新解释铸件(
*重新解释(
*重新解释演员阵容(&F)
)
);
proc&foo;
重新解释(
*重新解释演员阵容(&F)
)[0]=重新解释强制转换(新函数);
foo.Test();//运行相同的旧测试();可能是由于编译器优化?
proc=重新解释铸件(
*重新解释(
*重新解释演员阵容(&F)
)
);
proc(&foo);//运行newfunction
基类*goo=&foo;
goo->Test();//运行newfunction
返回0;
}

> > P>在C++中没有可移植的方法,因为语言规范说,任何类型的转换都可能是从指向成员函数的指针或函数指针到空洞*的结果,导致了未定义的行为。如果您想进行这种动态代码重写,您需要查阅您正在使用的特定平台的编译器文档


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

在某些情况下,您肯定是正确的,比如这一次

BaseClass foo(5, 56);
//...
foo.Test(); // runs same old Test(); maybe due to compiler optimization?
编译器可以很容易地看到
foo
的类型,并且根本不需要使用vtable。它知道要调用什么函数

当您通过指向该对象的指针调用时,编译器设计者不会费心检查它是否总是相同的类型。在您的代码中,他们当然可以看到这一点,但也许这样的优化在实际代码中还不够有用?或者他们可能会在下一版本的编译器中添加它。:-)


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

正如您所指出的,您的方法不是“可移植的”,但它实际上在您的特定情况下工作。我觉得没问题

第一个答案:

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

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
,以指向新的函数表。

几乎可移植的解决方案-
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);
瓦尔多敲定了第二个。

class Original{
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;
}
公众: INTA; 原始*打开(常量字符*文件名,整数openmode){ printf(“原始:%s和%d//%d\n”,文件名,openmode,此->a); 这->a=openmode; 归还这个; } }; 班级{ 公众: 原始*my_open(const char*文件名,int openmode){ 原始*自我=(原始*)本; printf(“假::%s和%d//%d\n”,文件名,openmode,self->a); 回归自我; } }; #定义GetPointerToClassMethod(返回、类、方法等)u GetPointerToClassMethod(&CLASS::METHOD) 模板 LPVOID\uuuGetPointerToClassMethod(\uMethodPrototype方法){ 返回*(LPVOID*)和方法; }
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
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);
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;
}
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);