Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reimplement 3rd party non-virtual function

Tags:

c++

I'm wondering if there is a way how to reimplement a non-virtual function foo in a 3rd party class Base.

The motivation is that I just need to append a callback into foo. However, the function is called from many places of the class Base, and since it is not virtual, it would require to either change the Base in place, or to dramatically rewrite implementation of the class that will derive from Base. I would like to avoid both scenarios.

I do not need polymorphism at all, the derived class will have just one instance and the type will be known at compile-time (so e.g. CRTP instead of virtualization would suffice too).

I tried to use a class, which inherits from an auxiliary class that declares foo as virtual, but with no luck. Here is an example, where bar simulates any place of Bases implementation from which foo could be called:

/// Ideally, do not modify the `Base` at all
struct Base {
    void foo()
    {
        cout << "Base::foo" << endl;
    }

    void bar()
    {
        cout << "bar" << endl;
        foo();  //< foo is not virtual !
    }
};

struct Virtual {
    virtual void foo() = 0;
};

struct Virtual_base : Virtual, Base {
    void foo() override = 0;  //< it still does not affect Base !
};

struct Virtual_derived : Virtual_base {
    void foo() override
    {
        cout << "Virtual_derived::foo" << endl;
        Base::foo();
    }
};

Well, Virtual_derived::foo does override, but the Base::bar, unsurprisingly, is still unchanged. I also tried the CRTP approach, but obviously with no luck, as it still clashes with the very same issue that Base stays encapsulated.

I'm little afraid that the answer is that it is just impossible.. is it?

like image 362
Tomáš Kolárik Avatar asked Oct 20 '20 16:10

Tomáš Kolárik


People also ask

What happens if we don't use virtual function in inheritance?

If you don't use virtual functions, you don't understand OOP yet. Because the virtual function is intimately bound with the concept of type, and type is at the core of object-oriented programming, there is no analog to the virtual function in a traditional procedural language.

When would you not use a virtual function?

Virtual functions are dangerous, somebody that derives your class can break your code by overriding the function and, say, not call the base function when they should. In Java you have the final keyword, that's not available in C++. Don't declare functions virtual unless you expect the function to be overridden.

Can we call virtual function in constructor in C++?

You can call a virtual function in a constructor, but be careful. It may not do what you expect. In a constructor, the virtual call mechanism is disabled because overriding from derived classes hasn't yet happened. Objects are constructed from the base up, “base before derived”.

Do virtual functions have to be public?

A virtual function can be private as C++ has access control, but not visibility control. As mentioned virtual functions can be overridden by the derived class but under all circumstances will only be called within the base class. Example: C++


1 Answers

On Windows you can use hotpatching: https://jpassing.com/2011/05/03/windows-hotpatching-a-walkthrough/ .

Compile with /hotpatch. This will add a two-byte NOP to the beginning of every function, and a 6-byte nop (5 on 32-bit) before, allowing you to patch in a redirection. What you want to do is modify the two-byte nop at the beginning to jump back into the 6-byte nop block, which then can jump to your callback wrapper, which then calls your callback and then jumps back into the function proper. To implement it, add this to a C++ source file:

void pages_allow_all_access(void* range_begin, size_t range_size) {
    DWORD new_settings = PAGE_EXECUTE_READWRITE;
    DWORD old_settings;
    VirtualProtect(
        range_begin,
        range_size,
        new_settings,
        &old_settings
    );
}

void patch(void* patch_func, void* callback_wrapper) {
    char* patch_func_bytes = (char*)patch_func;
    char* callback_wrapper_bytes = (char*)callback_wrapper;
    
    pages_allow_all_access(patch_func_bytes - 6, 8);

    // jmp short -5 (to the jmp rel32 instruction)
    patch_func_bytes[0] = 0xEB;
    patch_func_bytes[1] = 0x100 - 0x7;
    // nop (probably won't be executed)
    patch_func_bytes[-6] = 0x90;
    // jmp rel32 to callback_wrapper
    patch_func_bytes[-5] = 0xE9;
    *(int32_t*)&patch_func_bytes[-4]
        = (int32_t)(callback_wrapper_bytes - patch_func_bytes);
}

The callback wrapper might need to be defined in an assembly file:

callback_wrapper:
    ; save registers
    pushad
    pushfd
    call QWORD PTR [callback]
    popfd
    popad
    jmp QWORD PTR [after_trampoline]

The symbols callback and after_trampoline should be exposed in a C++ file (so at global scope).

void* callback = &callback_func;
void* after_trampoline = (char*)&patch_func + 2;

Then call patch at the top of main or some other suitable initialization time and you're set.

Also, you may have to allow write permissions on the memory pages you're modifying (the ones that patch_func is in) using a VirtualProtect call: https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect . EDIT: I've added this code to the example above.

I may add ways of doing this on Linux or other Unixy systems later.

When you don't have a convenient set of NOPs to use in the function, hooking becomes more difficult, particularly on the x86 architecture, since instructions have widely varying lengths and so it's difficult to find out programmatically where an instruction ends so you can jump back to the next instruction. @ajm suggests this library: https://github.com/kubo/funchook for Linux & OSX. However, personally, when I don't have hotpatching, I usually use a debugger to find out a sequence of instructions in the patch target of length at least 9 bytes that I can replace. Then in the program I replace those instructions with a jump to absolute immediate 64-bit, using a technique similar to the above, but I also add those replaced instructions to be executed near the end of the callback wrapper. Avoid replacing call or jmp instructions as these are often relative to the instruction pointer, which will have a different value in the callback wrapper than in the original function.

like image 176
Anonymous1847 Avatar answered Nov 10 '22 01:11

Anonymous1847