完全覆盖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 的常用方法对您不起作用,所以我可以想到三种解决方案。

  1. 改变vtable指针。这是非 portable 并且有很多方法会出错。假设 vtable 位于 class 的开头(它用于 WinAPI 中的简单 classes),您可以将该指针替换为 table你自己的。

    *(void **)A = newVtable;
    

with newVtable 用适当的指向成员函数的指针定义。您必须格外小心才能进行设置。它还可能搞乱删除和异常处理。

  1. 创建您自己的 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 并且安全。

  1. 在 class 中定义方法指针,并单独更新它们。