Полностью переопределяя VMT (таблица виртуальных методов)

Я вызываю виртуальный метод в vmt путем разыменования, пока не получу указатель на метод.

Это все хорошо, однако, как бы я полностью изменил указатель на таблицу VM на объекте?

Пример:

PP A; // указывает на свою таблицу виртуальных машин по умолчанию

ПП Б; // указывает на совершенно другую таблицу виртуальных машин

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

2 ответа

Решение

Поскольку обычный метод получения другого класса не будет работать для вас, я могу придумать три решения.

  1. Измените указатель vtable. Это непереносимо и имеет много способов просто пойти ужасно неправильно. Предполагая, что vtable находится в начале класса (что и для простых классов в WinAPI), вы можете заменить этот указатель указателем на собственную таблицу.

    *(void **)A = newVtable;
    

с newVtable, определенным с соответствующими указателями на функции-члены. Вы должны будете быть предельно осторожны, чтобы настроить это. Это также может испортить удаление и обработку исключений.

  1. Создайте свой собственный vtable. Определите класс с необходимыми указателями на метод-функции, затем определите указатель в вашем классе на один из них. Затем вы можете изменить указатель на таблицу по мере необходимости. Это было бы более многословно при вызове, хотя вы могли бы определить другие функции-члены, чтобы скрыть уродливый код.

    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). Это было бы портативно и безопасно.

  1. Определите указатели методов в классе и обновите их индивидуально.

Если я правильно понимаю ваш вопрос - вы хотите заменить таблицу VM объекта во время выполнения. Не уверен, почему вы используете язык C++ для таких низкоуровневых модификаций?

В любом случае, Microsoft C/C++ поддерживает нечто, называемое "голым" соглашением о вызовах (в отличие от "stdcall", "fastcall" и т. Д., Которые отличаются тем, как / в каком порядке параметры передаются в функцию и передаются ли они в стеке). или в регистрах). В соглашении об открытом вызове ВЫ полностью контролируете передачу параметров - вы пишете свои собственные фрагменты встроенных сборок, отвечающие за размещение материала в стеке и раскручивание стека.

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

Например, вы можете использовать соглашение об открытых вызовах для конструктора (если компилятор не будет жаловаться) и передать новую таблицу VM как "скрытый" параметр, точно так же, как "этот" параметр является "скрытым" параметром, передаваемым в функции-члены (в соглашении о вызовах thiscall). И вы могли бы сделать свое волшебство во встроенной сборке, чтобы заменить виртуальную машину в конструкторе.

Все это кажется ужасной, хрупкой идеей, потому что новая версия компилятора, которая меняет свои внутренние компоненты (как правило, не подвергается вам), может потенциально сломать ваш код. Если вам действительно нужен какой-то динамический механизм выбора того, какой метод вызывать, звучит так, как будто вы должны реализовать свой собственный механизм вместо того, чтобы использовать его как хак поверх механизма VMT в C++.

Другие вопросы по тегам