Skip to content

Instantly share code, notes, and snippets.

@altalk23
Last active December 19, 2023 02:56
Show Gist options
  • Save altalk23/29b97969e9f0624f783b673f6c1cd279 to your computer and use it in GitHub Desktop.
Save altalk23/29b97969e9f0624f783b673f6c1cd279 to your computer and use it in GitHub Desktop.
Getting the address of a virtual function in c++, works both in msvc x86 and clang macos x64
#include <cstdlib>
#include <stddef.h>
#include <iostream>
#define CONCAT2(x, y) x##y
#define CONCAT(x, y) CONCAT2(x, y)
#if defined(_WIN64) || defined(__x86_64__)
#define NEST1(macro, begin) \
macro(CONCAT(begin, 0)) \
macro(CONCAT(begin, 8))
#else
#define NEST1(macro, begin) \
macro(CONCAT(begin, 0)) \
macro(CONCAT(begin, 4)) \
macro(CONCAT(begin, 8)) \
macro(CONCAT(begin, c))
#endif
#define NEST2(macro, begin) \
NEST1(macro, CONCAT(begin, 0)) \
NEST1(macro, CONCAT(begin, 1)) \
NEST1(macro, CONCAT(begin, 2)) \
NEST1(macro, CONCAT(begin, 3)) \
NEST1(macro, CONCAT(begin, 4)) \
NEST1(macro, CONCAT(begin, 5)) \
NEST1(macro, CONCAT(begin, 6)) \
NEST1(macro, CONCAT(begin, 7)) \
NEST1(macro, CONCAT(begin, 8)) \
NEST1(macro, CONCAT(begin, 9)) \
NEST1(macro, CONCAT(begin, a)) \
NEST1(macro, CONCAT(begin, b)) \
NEST1(macro, CONCAT(begin, c)) \
NEST1(macro, CONCAT(begin, d)) \
NEST1(macro, CONCAT(begin, e)) \
NEST1(macro, CONCAT(begin, f)) \
#define NEST3(macro, begin) \
NEST2(macro, CONCAT(begin, 0)) \
NEST2(macro, CONCAT(begin, 1)) \
NEST2(macro, CONCAT(begin, 2)) \
NEST2(macro, CONCAT(begin, 3)) \
NEST2(macro, CONCAT(begin, 4)) \
NEST2(macro, CONCAT(begin, 5)) \
NEST2(macro, CONCAT(begin, 6)) \
NEST2(macro, CONCAT(begin, 7)) \
NEST2(macro, CONCAT(begin, 8)) \
NEST2(macro, CONCAT(begin, 9)) \
NEST2(macro, CONCAT(begin, a)) \
NEST2(macro, CONCAT(begin, b)) \
NEST2(macro, CONCAT(begin, c)) \
NEST2(macro, CONCAT(begin, d)) \
NEST2(macro, CONCAT(begin, e)) \
NEST2(macro, CONCAT(begin, f)) \
/**
* static ptrdiff_t function0x000() {return 0x000;}
* static ptrdiff_t function0x004() {return 0x004;}
* static ptrdiff_t function0x008() {return 0x008;}
* ...
*/
#define METHOD_DEFINE(hex) static ptrdiff_t CONCAT(function, hex)() {return hex;}
/**
* virtual ptrdiff_t vfunction0x000() {}
* virtual ptrdiff_t vfunction0x004() {}
* virtual ptrdiff_t vfunction0x008() {}
* ...
*/
#define VMETHOD_DEFINE(hex) virtual void CONCAT(vfunction, hex)() {}
/**
* (intptr_t)FunctionScrapper::function0x000,
* (intptr_t)FunctionScrapper::function0x004,
* (intptr_t)FunctionScrapper::function0x008,
* ...
*/
#define TABLE_DEFINE(hex) (intptr_t)CONCAT(FunctionScrapper::function, hex),
/**
* &FunctionScrapper::vfunction0x000,
* &FunctionScrapper::vfunction0x004,
* &FunctionScrapper::vfunction0x008,
* ...
*/
#define VTABLE_DEFINE(hex) &CONCAT(FunctionScrapper::vfunction, hex),
#define METHOD_SET() NEST3(METHOD_DEFINE, 0x)
#define VMETHOD_SET() NEST3(VMETHOD_DEFINE, 0x)
#define TABLE_SET() NEST3(TABLE_DEFINE, 0x)
#define VTABLE_SET() NEST3(VTABLE_DEFINE, 0x)
class FunctionScrapper {
protected:
static constexpr ptrdiff_t table_size = 0x1000 / sizeof(intptr_t);
using tablemethodptr_t = ptrdiff_t(FunctionScrapper::*)();
using methodptr_t = void(FunctionScrapper::*)();
using table_t = intptr_t[table_size + 0x1];
using tableptr_t = table_t*;
using vtable_t = methodptr_t[table_size + 0x1];
using vtableptr_t = vtable_t*;
private:
template<typename T>
static intptr_t pointerOf(T func) {
return reinterpret_cast<intptr_t&>(func);
}
template<typename T>
static ptrdiff_t indexOf(T ptr) {
auto func = reinterpret_cast<tablemethodptr_t&>(ptr);
return (instance->*func)();
}
template<typename T>
static ptrdiff_t thunkOf(T ptr) {
if (sizeof(T) == sizeof(ptrdiff_t)) return 0;
return *(reinterpret_cast<ptrdiff_t*>(&ptr)+1);
}
template<typename T>
static bool isVirtual(T ptr) {
for (int i = 0; i < table_size; ++i) if (vtable[i] == reinterpret_cast<methodptr_t&>(ptr)) return true;
return false;
}
public:
/**
* Generalized functions
*/
template <typename R, typename T, typename ...Ps>
static intptr_t addressOf(R(T::*func)(Ps...)) {
if (!isVirtual(func)) return addressOfNonVirtual(func);
return addressOfVirtual(func);
}
template <typename R, typename T, typename ...Ps>
static intptr_t addressOf(T* ins, R(T::*func)(Ps...)) {
if (!isVirtual(func)) return addressOfNonVirtual(func);
return addressOfVirtual(ins, func);;
}
template <typename R, typename ...Ps>
static intptr_t addressOf(R(*func)(Ps...)) {
return addressOfNonVirtual(func);
}
/**
* Specialized functions
*/
template <typename R, typename T, typename ...Ps>
static intptr_t addressOfVirtual(R(T::*func)(Ps...)) {
static_assert(std::is_copy_constructible<T>::value, "must be copy constructable");
auto ptr = reinterpret_cast<T*>(operator new(sizeof(T)));
auto ins = new T(*ptr);
auto address = *(intptr_t*)(*(intptr_t*)(pointerOf(ins) + thunkOf(func)) + indexOf(func));
operator delete(ins);
operator delete(ptr);
return address;
}
template <typename R, typename T, typename ...Ps>
static intptr_t addressOfVirtual(T* ins, R(T::*func)(Ps...)) {
auto address = *(intptr_t*)(*(intptr_t*)(pointerOf(ins) + thunkOf(func)) + indexOf(func));
return address;
}
template <typename R, typename T, typename ...Ps>
static intptr_t addressOfNonVirtual(R(T::*func)(Ps...)) {
return pointerOf(func);
}
template <typename R, typename ...Ps>
static intptr_t addressOfNonVirtual(R(*func)(Ps...)) {
return pointerOf(func);
}
protected:
VMETHOD_SET()
METHOD_SET()
static ptrdiff_t function() {return -1;}//because c++ cries when there is a trailing comma
virtual void vfunction() {}
inline static table_t table = {
TABLE_SET()
(intptr_t)FunctionScrapper::function
};
inline static tableptr_t tableptr = &table;
inline static FunctionScrapper* instance = reinterpret_cast<FunctionScrapper*>(&tableptr);
inline static vtable_t vtable = {
VTABLE_SET()
&FunctionScrapper::vfunction
};
};
class Class1 {
public:
Class1() = delete;
~Class1() {
std::exit(EXIT_FAILURE);
}
virtual void func1() {}
virtual int func2() {return 5;}
virtual bool func3() {return true;}
};
class Class2 {
public:
Class2() = delete;
~Class2() {
std::exit(EXIT_FAILURE);
}
virtual void* func4() {return nullptr;}
virtual long func5() {return -1;}
virtual void func6() {}
};
class Class3 : public Class1, public Class2 {
public:
Class3() = delete;
~Class3() {
std::exit(EXIT_FAILURE);
}
virtual bool func3() {return false;}
virtual void* func4() {return (void*)5;}
int func7() {
std::cout << "lol" << std::endl;
return 2;
}
static void func8() {}
};
int main() {
std::cout << "Class1:\n" << (void*)FunctionScrapper::addressOf(&Class1::func1) << std::endl;
std::cout << (void*)FunctionScrapper::addressOf(&Class1::func2) << std::endl;
std::cout << (void*)FunctionScrapper::addressOf(&Class1::func3) << std::endl << std::endl;
std::cout << "Class2:\n" << (void*)FunctionScrapper::addressOf(&Class2::func4) << std::endl;
std::cout << (void*)FunctionScrapper::addressOf(&Class2::func5) << std::endl;
std::cout << (void*)FunctionScrapper::addressOf(&Class2::func6) << std::endl << std::endl;
std::cout << "Class3:\n" << (void*)FunctionScrapper::addressOf(&Class3::func1) << std::endl;
std::cout << (void*)FunctionScrapper::addressOf(&Class3::func2) << std::endl;
std::cout << (void*)FunctionScrapper::addressOf(&Class3::func3) << std::endl;
std::cout << (void*)FunctionScrapper::addressOf(&Class3::func4) << std::endl;
std::cout << (void*)FunctionScrapper::addressOf(&Class3::func5) << std::endl;
std::cout << (void*)FunctionScrapper::addressOf(&Class3::func6) << std::endl;
std::cout << (void*)FunctionScrapper::addressOf(&Class3::func7) << std::endl;
std::cout << (void*)FunctionScrapper::addressOf(&Class3::func8) << std::endl << std::endl;
return 0;
}
@altalk23
Copy link
Author

Keep in mind that it has to create an instance of the class, if there is any way of getting the address of a vtable without an instance that can be done with macros let me know :)

@altalk23
Copy link
Author

Now it can get the address of any function and check if a function is virtual

@altalk23
Copy link
Author

altalk23 commented Nov 1, 2021

For msvc x86 the functions should be optimized for isVirtual function to work, so the linker option /OPT:ICF=1 should be used when compiling

@altalk23
Copy link
Author

altalk23 commented Dec 2, 2021

Update: now it doesn't need an instance

@TDC0471
Copy link

TDC0471 commented Dec 19, 2023

Very nice dude!!! Saved me a ton of trouble

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment