完全覆盖VMT(虚拟方法表)

Completely overriding the VMT (Virtual Method Table)

本文关键字:方法 虚拟 覆盖 VMT      更新时间:2023-10-16

我通过解引用来调用vmt上的虚拟方法,直到我得到指向该方法的指针。

这一切都很好,然而,我如何完全改变指针VM表上的对象?

例子:

页;//指向默认的VM表

PP B;//指向一个完全不同的VM表

A->MethodOne()//调用如上所述

B->MethodOne()//调用一个完全不同的方法,因为我们将它指向VM表的指针覆盖到一个具有不同方法指针的替代表

我该如何做到这一点?

我的代码:

#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;
}

由于派生另一个类的常用方法对您不起作用,因此我可以想到三种解决方案。

  1. 修改虚表指针。这是不可携带的,有很多地方会出严重的问题。假设虚函数表位于类的开头(WinAPI中对于简单类是这样),您可以将该指针替换为指向您自己的表的指针。

    *(void **)A = newVtable;
    

,用适当的指向成员函数的指针定义newVtable。你必须非常小心地设置它。它还可能搞砸删除和异常处理。

  • 创建自己的虚表。用所需的指向方法函数的指针定义一个类,然后在类中定义指向其中一个方法函数的指针。然后,您可以根据需要更改指向表的指针。虽然可以定义其他成员函数来隐藏丑陋的代码,但在调用时这样做会有点冗长。

    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社区中编译和测试)。这样既方便又安全。

  • 在类中定义方法指针,并单独更新。
  • 如果我正确理解你的问题-你想在运行时替换对象的VM表。不确定为什么要使用c++语言进行如此低级的修改?

    无论如何,Microsoft C/c++支持所谓的"裸"调用约定(与"stdcall","fastcall"等不同的是参数传递给函数的方式/顺序以及它们是在堆栈上传递还是在寄存器中传递)。在裸调用约定中,你可以绝对控制如何传递参数——你编写自己的内联汇编代码片段,负责将内容放入堆栈和展开堆栈。

    https://msdn.microsoft.com/en-us/library/5ekezyy2.aspx

    例如,你可以对构造函数使用裸调用约定(如果编译器不会报错),并将新的VM表作为"隐藏"参数传入,就像"this"参数是传递给成员函数的"隐藏"参数一样(在"thiscall"调用约定中)。您还可以在内联汇编中使用魔法来替换构造函数中的VM。

    这似乎是一个可怕的,脆弱的想法,因为一个新版本的编译器改变了它的内部(通常不暴露给你)可能会破坏你的代码。如果你真的需要某种动态机制来选择调用什么方法,听起来你应该实现你自己的机制,而不是在c++的VMT机制之上附加一个hack。