Last active
December 19, 2023 02:56
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
Now it can get the address of any function and check if a function is virtual
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
Update: now it doesn't need an instance
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
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 :)