Skip to content

qubka/DynoHook

Repository files navigation

DynoHook

Build Status ezgif com-webp-to-jpg-removebg-preview (1)

Introduction

DynoHooks is a versatile and powerful C++ library designed to provide developers with an easy way to create dynamic function hooks for any call convention with pre and post callbacks. This library is built using AsmJit machine code generation library and Capstone Disassembler library to achieve dynamic function hooking for x86 architecture with support for 32/64-bit modes on Windows and Linux platforms. The library is designed to be used with C++17 or later. It is based on Ayuto's DynamicHooks library.

Dynamic function hooks are a powerful tool in the arsenal of software developers, as they allow developers to modify the behavior of a function without the need to modify the original code. This is particularly useful in situations where the code is not under the control of the developer or where modifying the original code is not feasible. For example, developers can use dynamic function hooks to debug, profile, or optimize their applications.

DynoHooks offers a simple and intuitive API that makes it easy for developers to add hooks to any function on runtime, including those in third-party libraries. To create a hook, developers provide information about the function pointer and argument and return types for call convention class. Then, DynoHooks generates the necessary machine code to intercept and fully modify the function call or even block execution of original function.

The library comes with pre and post callbacks, enabling developers to catch the behavior of the function before and after its execution. In addition, this allows developers to implement a wide range of use cases, such as logging, tracing, and error handling. Also, the pre and post callback functions used to modify the input and output parameters of the function call.

Library supports different call conventions, such as __cdecl, __stdcall, __fastcall and __vectorcall and other modern x64 call conventions. Developers can customize the callback functions based on their specific use case, and they can choose to override or supplement the behavior of the original function.

Another unique features of DynoHooks is the ability to handle return values from the hooked function callbacks. The library provides return action feature, which defines the possible actions that the callback function can take with respect to the return value. The ReturnAction can be set to Ignored, Handled, Override, or Supercede, depending on the desired behavior.

Advantages

One of the key advantages of DynoHook library over other function hooking implementations is its ability to dynamically create hooks at runtime without requiring knowledge of the function prototype at compile time. Instead, developers only need to provide the address of the function, the argument types, the return type, and the call convention. This feature allows developers to create hooks for functions in third-party libraries, which they may not have access to the source code or the function prototypes. Additionally, DynoHook's dynamic approach makes it well-suited for use in embedded systems, where there may be limited access to C++ features at compile time. This flexibility and versatility make DynoHook an attractive option for developers looking to modify program behavior in real-time, without having to modify the original code or rely on static hooking implementations.

Requirements

This module requires the following modules:

Examples

Static functions

int g_MyFuncCallCount = 0;
int g_PreMyFuncCallCount = 0;
int g_PostMyFuncCallCount = 0;

using namespace dyno;

int __fastcall MyFunc(int x, int y) {
	g_MyFuncCallCount++;
	assert(x == 3);
	assert(y == 10);

	volatile int result = x + y;
	assert(result == 13);

	return result;
}

ReturnAction PreMyFunc(CallbackType type, IHook& hook) {
	g_PreMyFuncCallCount++;
	int x = hook.getArgument<int>(0);
	assert(x == 3);

	int y = hook.getArgument<int>(1);
	assert(y == 10);

	return ReturnAction::Ignored;
}

ReturnAction PostMyFunc(CallbackType type, IHook& hook) {
	g_PostMyFuncCallCount++;
	int x = hook.getArgument<int>(0);
	assert(x == 3);

	int y = hook.getArgument<int>(1);
	assert(y == 10);

	int return_value = hook.getReturn<int>();
	assert(return_value == 13);

	hook.setReturn<int>(1337);

	return ReturnAction::Ignored;
}

void main() {
	HookManager& manager = HookManager::Get();

	// Hook the function
	Hook* hook = manager.hook((uintptr_t) &MyFunc, [] { return new x64WindowsCall({DataType::Int, DataType::Int}, DataType::Int); });

	// Add the callbacks
	hook->addCallback(CallbackType::Pre, (CallbackHandler) PreMyFunc);
	hook->addCallback(CallbackType::Post, (CallbackHandler) PostMyFunc);
	// Add more callbacks if you wish

	// Call the function
	int ret = MyFunc(3, 10);

	assert(g_MyFuncCallCount == 1);
	assert(g_PreMyFuncCallCount == 1);
	assert(g_PostMyFuncCallCount == 1);
	assert(ret == 1337);

	manager.unhookAll();
}

Member functions

int g_MyFuncCallCount = 0;
int g_PreMyFuncCallCount = 0;
int g_PostMyFuncCallCount = 0;

using namespace dyno;

class MyClass;
MyClass* g_pMyClass = nullptr;

class MyClass {
public:
	MyClass() : m_iData{0} {}

	int myFunc(int x, int y) {
		g_MyFuncCallCount++;
		assert(this == g_pMyClass);
		assert(x == 3);
		assert(y == 10);

		volatile int result = x + y;
		assert(result == 13);

		m_iData++;

		return result;
	}
private:
	int m_iData;
};

ReturnAction PreMyFunc(CallbackType type, IHook& hook) {
	g_PreMyFuncCallCount++;
	MyClass* pMyClass = hook.getArgument<MyClass*>(0);
	assert(pMyClass == g_pMyClass);

	int x = hook.getArgument<int>(1);
	assert(x == 3);

	int y = hook.getArgument<int>(2);
	assert(y == 10);

	return ReturnAction::Ignored;
}

ReturnAction PostMyFunc(CallbackType type, IHook& hook) {
	g_PostMyFuncCallCount++;
	MyClass* pMyClass = hook.getArgument<MyClass*>(0);
	assert(pMyClass == g_pMyClass);

	int x = hook.getArgument<int>(1);
	assert(x == 3);

	int y = hook.getArgument<int>(2);
	assert(y == 10);

	int return_value = hook.getReturn<int>();
	assert(return_value == 13);

	hook.setReturn<int>(1337);

	return ReturnAction::Ignored;
}

void main() {
	HookManager& manager = HookManager::Get();

	int (MyClass::*myFunc)(int, int) = &MyClass::myFunc;

	// Hook the function
	Hook* hook = manager.hook((uintptr_t) &myFunc, [] { return new x64WindowsCall({DataType::Pointer, DataType::Int, DataType::Int}, DataType::Int); });

	// Add the callbacks
	hook->addCallback(CallbackType::Pre, (CallbackHandler) &PreMyFunc);
	hook->addCallback(CallbackType::Post, (CallbackHandler) &PostMyFunc);
	// Add more callbacks if you wish

	MyClass a;
	g_pMyClass = &a;

	// Call the function
	int ret = a.myFunc(3, 10);

	assert(g_MyFuncCallCount == 1);
	assert(g_PreMyFuncCallCount == 1);
	assert(g_PostMyFuncCallCount == 1);
	assert(ret == 1337);

	manager.unhookAll();
}

Virtual functions

int g_MyFuncCallCount = 0;
int g_PreMyFuncCallCount = 0;
int g_PostMyFuncCallCount = 0;

using namespace dyno;

class MyClass;
MyClass* g_pMyClass = nullptr;

class MyClass {
public:
    MyClass() : m_iData{0} {}

    virtual int myFunc(int x, int y) {
        g_MyFuncCallCount++;
        assert(this == g_pMyClass);
        assert(x == 3);
        assert(y == 10);

        volatile int result = x + y;
        assert(result == 13);

        m_iData++;

        return result;
    }
private:
    int m_iData;
};

ReturnAction PreMyFunc(CallbackType type, IHook& hook) {
    g_PreMyFuncCallCount++;
    MyClass* pMyClass = hook.getArgument<MyClass*>(0);
    assert(pMyClass == g_pMyClass);

    int x = hook.getArgument<int>(1);
    assert(x == 3);

    int y = hook.getArgument<int>(2);
    assert(y == 10);

    return ReturnAction::Ignored;
}

ReturnAction PostMyFunc(CallbackType type, IHook& hook) {
    g_PostMyFuncCallCount++;
    MyClass* pMyClass = hook.getArgument<MyClass*>(0);
    assert(pMyClass == g_pMyClass);

    int x = hook.getArgument<int>(1);
    assert(x == 3);

    int y = hook.getArgument<int>(2);
    assert(y == 10);

    int return_value = hook.getReturn<int>();
    assert(return_value == 13);

    hook.setReturn<int>(1337);

    return ReturnAction::Ignored;
}

void test() {
    HookManager& manager = HookManager::Get();

    typedef int(__fastcall *MyFunc)(void*, int, int);

    MyClass a;
    g_pMyClass = &a;

    // hook the function
    Hook* hook = manager.hook(&a, 0, [] { return new x64WindowsCall({DataType::Pointer, DataType::Int, DataType::Int}, DataType::Int); });

    // add the callbacks
    hook->addCallback(CallbackType::Pre, (CallbackHandler) &PreMyFunc);
    hook->addCallback(CallbackType::Post, (CallbackHandler) &PostMyFunc);
    // Add more callbacks if you wish

    // call the function
    void** vtable = *(void***)&a;
    MyFunc myFunc = (MyFunc)(vtable[0]);
    int ret = myFunc(&a, 3, 10);

    assert(g_MyFuncCallCount == 1);
    assert(g_PreMyFuncCallCount == 1);
    assert(g_PostMyFuncCallCount == 1);
    assert(ret == 1337);

    manager.unhookAll();
}

Build

git clone https://github.com/qubka/DynoHook.git
cd ./DynoHook
git submodule update --init --recursive
mkdir build
cd build
cmake ..

Credits

  • stevemk14ebr - PolyHook_2_0's range allocation, trampoline creation and disassembling (MIT Licence)
  • Ayuto - DynamicHooks library
  • peace-maker - DHooks with detour support
  • Kailo - Help with assembly porting from x32 to x64 and helping fixing crashes

Links

About

The x86/x64 API Hooking Library for Windows/Linux which can easily embed it into other programming languages.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published