完全覆盖VMT(虚拟方法Table)
Completely overriding the VMT (Virtual Method Table)
我通过取消引用在 vmt 上调用虚拟方法,直到我获得指向该方法的指针。
一切都很好,但是我如何才能完全更改对象上指向 VM table 的指针?
示例:
PP A; // points to its default VM table
PP B; // points to a completely different VM table
A->MethodOne() // calls as mentioned above
B->MethodOne() // calls a completely different method since we override its pointer to the VM table to an alternate table with different method pointers
我该如何完成?
我的代码:
#include <Windows.h>
#include <iostream>
class PP
{
public:
PP() { }
~PP() { }
virtual void MethodOne() { std::cout << "1" << std::endl; }
virtual void MethodTwo() { std::cout << "2" << std::endl; }
};
typedef void (*MyFunc)(void);
int main()
{
PP* A = new PP();
//(*(void(**)(void))(*(DWORD*)A + (4*1)))();
( *(MyFunc*) ( *(DWORD*)A + (4*0) ) )(); // call index 0 (4bytes*0)
A->MethodOne();
A->MethodTwo();
system("PAUSE");
delete A;
return 0;
}
如果我没有正确理解你的问题 - 你想在运行时替换对象的 VM table。不确定为什么要使用 C++ 语言进行如此低级别的修改?
无论如何,Microsoft C/C++ 支持称为 "naked" 调用约定的东西(与 "stdcall"、"fastcall" 等不同,它们在 how/what order params 被传递给一个函数,无论它们是在堆栈上还是在寄存器中传递)。在裸调用约定中,您可以绝对控制如何传递参数 - 您可以编写自己的内联汇编代码段,负责将内容放入堆栈和展开堆栈。
https://msdn.microsoft.com/en-us/library/5ekezyy2.aspx
例如,您可以对构造函数使用裸调用约定(如果编译器不会抱怨)并将新 VM table 作为 "hidden" 参数传递,就像 "this" 参数是传递给成员函数的 "hidden" 参数(在 "thiscall" 调用约定中)。你可以在内联汇编中发挥你的魔力来替换构造函数中的 VM。
这整个事情看起来像是一个可怕的、脆弱的想法,因为更改其内部结构(通常不会向您公开)的新版本编译器可能会破坏您的代码。如果您真的需要某种动态机制来选择要调用的方法,听起来您应该实现自己的机制,而不是在 C++ 的 VMT 机制之上搭载作为 hack。
由于推导另一个 class 的常用方法对您不起作用,所以我可以想到三种解决方案。
改变vtable指针。这是非 portable 并且有很多方法会出错。假设 vtable 位于 class 的开头(它用于 WinAPI 中的简单 classes),您可以将该指针替换为 table你自己的。
*(void **)A = newVtable;
with newVtable 用适当的指向成员函数的指针定义。您必须格外小心才能进行设置。它还可能搞乱删除和异常处理。
创建您自己的 vtable。使用所需的指向方法函数的指针定义一个 class,然后在 class 中定义一个指向其中之一的指针。然后,您可以根据需要将指针更改为 table。虽然您可以定义其他成员函数来隐藏丑陋的代码,但这样调用会更冗长。
class vtable;
class PP {
public:
PP();
~PP() { }
void MethodOne() { std::cout << "1" << std::endl; }
void MethodTwo() { std::cout << "2" << std::endl; }
const vtable *pVtable;
};
class vtable {
public:
void (PP::*MethodOne)();
};
vtable One = {&PP::MethodOne};
vtable Two = {&PP::MethodTwo};
PP::PP(): pVtable(&One) { }
void main() {
PP* A = new PP();
A->pVtable = &One;
// call with
(A->*(A->pVtable->MethodOne))(); // calls MethodOne
A->pVtable = &Two;
(A->*(A->pVtable->MethodOne))(); // calls MethodTwo
}
(使用 VS2015 社区编译和测试)。这将是 portable 并且安全。
- 在 class 中定义方法指针,并单独更新它们。
我通过取消引用在 vmt 上调用虚拟方法,直到我获得指向该方法的指针。
一切都很好,但是我如何才能完全更改对象上指向 VM table 的指针?
示例:
PP A; // points to its default VM table
PP B; // points to a completely different VM table
A->MethodOne() // calls as mentioned above
B->MethodOne() // calls a completely different method since we override its pointer to the VM table to an alternate table with different method pointers
我该如何完成?
我的代码:
#include <Windows.h>
#include <iostream>
class PP
{
public:
PP() { }
~PP() { }
virtual void MethodOne() { std::cout << "1" << std::endl; }
virtual void MethodTwo() { std::cout << "2" << std::endl; }
};
typedef void (*MyFunc)(void);
int main()
{
PP* A = new PP();
//(*(void(**)(void))(*(DWORD*)A + (4*1)))();
( *(MyFunc*) ( *(DWORD*)A + (4*0) ) )(); // call index 0 (4bytes*0)
A->MethodOne();
A->MethodTwo();
system("PAUSE");
delete A;
return 0;
}
如果我没有正确理解你的问题 - 你想在运行时替换对象的 VM table。不确定为什么要使用 C++ 语言进行如此低级别的修改?
无论如何,Microsoft C/C++ 支持称为 "naked" 调用约定的东西(与 "stdcall"、"fastcall" 等不同,它们在 how/what order params 被传递给一个函数,无论它们是在堆栈上还是在寄存器中传递)。在裸调用约定中,您可以绝对控制如何传递参数 - 您可以编写自己的内联汇编代码段,负责将内容放入堆栈和展开堆栈。
https://msdn.microsoft.com/en-us/library/5ekezyy2.aspx
例如,您可以对构造函数使用裸调用约定(如果编译器不会抱怨)并将新 VM table 作为 "hidden" 参数传递,就像 "this" 参数是传递给成员函数的 "hidden" 参数(在 "thiscall" 调用约定中)。你可以在内联汇编中发挥你的魔力来替换构造函数中的 VM。
这整个事情看起来像是一个可怕的、脆弱的想法,因为更改其内部结构(通常不会向您公开)的新版本编译器可能会破坏您的代码。如果您真的需要某种动态机制来选择要调用的方法,听起来您应该实现自己的机制,而不是在 C++ 的 VMT 机制之上搭载作为 hack。
由于推导另一个 class 的常用方法对您不起作用,所以我可以想到三种解决方案。
改变vtable指针。这是非 portable 并且有很多方法会出错。假设 vtable 位于 class 的开头(它用于 WinAPI 中的简单 classes),您可以将该指针替换为 table你自己的。
*(void **)A = newVtable;
with newVtable 用适当的指向成员函数的指针定义。您必须格外小心才能进行设置。它还可能搞乱删除和异常处理。
创建您自己的 vtable。使用所需的指向方法函数的指针定义一个 class,然后在 class 中定义一个指向其中之一的指针。然后,您可以根据需要将指针更改为 table。虽然您可以定义其他成员函数来隐藏丑陋的代码,但这样调用会更冗长。
class vtable; class PP { public: PP(); ~PP() { } void MethodOne() { std::cout << "1" << std::endl; } void MethodTwo() { std::cout << "2" << std::endl; } const vtable *pVtable; }; class vtable { public: void (PP::*MethodOne)(); }; vtable One = {&PP::MethodOne}; vtable Two = {&PP::MethodTwo}; PP::PP(): pVtable(&One) { } void main() { PP* A = new PP(); A->pVtable = &One; // call with (A->*(A->pVtable->MethodOne))(); // calls MethodOne A->pVtable = &Two; (A->*(A->pVtable->MethodOne))(); // calls MethodTwo }
(使用 VS2015 社区编译和测试)。这将是 portable 并且安全。
- 在 class 中定义方法指针,并单独更新它们。