1
0
mirror of synced 2025-02-08 15:18:21 +01:00
OpenParrot/deps/inc/injector/hooking.hpp

688 lines
26 KiB
C++

/*
* Injectors - Classes for making your hooking life easy
*
* Copyright (C) 2013-2014 LINK/2012 <dma_2012@hotmail.com>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source
* distribution.
*
*/
#pragma once
#include "injector.hpp"
#include <cassert>
#include <functional>
#include <memory> // for std::shared_ptr
#include <list>
namespace injector
{
/*
* scoped_base
* Base for any scoped hooking type
* !!!! NOTICE !!!! --> Any derived which implements/reimplements restore() should implement a destructor calling it
*/
class scoped_base
{
public:
virtual ~scoped_base() {}
virtual void restore() = 0;
};
/*
* scoped_basic
* Base for scoped types which will need a buffer to save/restore stuff
*/
template<size_t bufsize> // TODO specialize bufsize=0 to be dynamic
class scoped_basic : public scoped_base
{
private:
uint8_t buf[bufsize];// Saved content
memory_pointer_raw addr; // Data saved from this address
size_t size; // Size saved
bool saved; // Something saved?
bool vp; // Virtual protect?
public:
static const bool is_dynamic = false;
// Restore the previosly saved data
// Problems may arise if someone else hooked the same place using the same method
virtual void restore()
{
#ifndef INJECTOR_SCOPED_NOSAVE_NORESTORE
if(this->saved)
{
WriteMemoryRaw(this->addr, this->buf, this->size, this->vp);
this->saved = false;
}
#endif
}
// Save buffer at @addr with @size and virtual protect @vp
virtual void save(memory_pointer_tr addr, size_t size, bool vp)
{
#ifndef INJECTOR_SCOPED_NOSAVE_NORESTORE
assert(size <= bufsize); // Debug Safeness
this->restore(); // Restore anything we have saved
this->saved = true; // Mark that we have data save
this->addr = addr.get<void>(); // Save address
this->size = size; // Save size
this->vp = vp; // Save virtual protect
ReadMemoryRaw(addr, buf, size, vp); // Save buffer
#endif
}
public:
// Constructor, initialises
scoped_basic() : saved(false)
{}
~scoped_basic()
{
this->restore();
}
// No copy construction, we can't do this! Sure we can move construct :)
scoped_basic(const scoped_basic&) = delete;
scoped_basic(scoped_basic&& rhs)
{
*this = std::move(rhs);
}
scoped_basic& operator=(const scoped_basic& rhs) = delete;
scoped_basic& operator=(scoped_basic&& rhs)
{
if(this->saved = rhs.saved)
{
assert(bufsize >= rhs.size);
this->addr = rhs.addr;
this->size = rhs.size;
this->vp = rhs.vp;
memcpy(buf, rhs.buf, rhs.size);
rhs.saved = false;
}
return *this;
}
};
/*
* RAII wrapper for memory writes
* Can save only basic and POD types
*/
template<size_t bufsize_ = 10>
class scoped_write : public scoped_basic<bufsize_>
{
public:
// Save buffer at @addr with @size and virtual protect @vp and then overwrite it with @value
void write(memory_pointer_tr addr, void* value, size_t size, bool vp)
{
this->save(addr, size, vp);
return WriteMemoryRaw(addr, value, size, vp);
}
// Save buffer at @addr with size sizeof(@value) and virtual protect @vp and then overwrite it with @value
template<class T>
void write(memory_pointer_tr addr, T value, bool vp = false)
{
this->save(addr, sizeof(T), vp);
return WriteMemory<T>(addr, value, vp);
}
// Constructors, move constructors, assigment operators........
scoped_write() = default;
scoped_write(const scoped_write&) = delete;
scoped_write(scoped_write&& rhs) : scoped_basic<bufsize_>(std::move(rhs)) {}
scoped_write& operator=(const scoped_write& rhs) = delete;
scoped_write& operator=(scoped_write&& rhs)
{ scoped_basic<bufsize_>::operator=(std::move(rhs)); return *this; }
};
/*
* RAII wrapper for filling
*/
template<size_t bufsize_ = 5>
class scoped_fill : public scoped_basic<bufsize_>
{
public:
// Fills memory at @addr with value @value and size @size and virtual protect @vp
void fill(memory_pointer_tr addr, uint8_t value, size_t size, bool vp)
{
this->save(addr, size, vp);
return MemoryFill(addr, value, size, vp);
}
// Constructors, move constructors, assigment operators........
scoped_fill() = default;
scoped_fill(const scoped_fill&) = delete;
scoped_fill(scoped_fill&& rhs) : scoped_basic<bufsize_>(std::move(rhs)) {}
scoped_fill& operator=(const scoped_fill& rhs) = delete;
scoped_fill& operator=(scoped_fill&& rhs)
{ scoped_basic<bufsize_>::operator=(std::move(rhs)); return *this; }
scoped_fill(memory_pointer_tr addr, uint8_t value, size_t size, bool vp)
{ fill(addr, value, vp); }
};
/*
* RAII wrapper for nopping
*/
template<size_t bufsize_>
class scoped_nop : public scoped_basic<bufsize_>
{
public:
// Makes NOP at @addr with value @value and size @size and virtual protect @vp
void make_nop(memory_pointer_tr addr, size_t size = 1, bool vp = true)
{
this->save(addr, size, vp);
return MakeNOP(addr, size, vp);
}
// Constructors, move constructors, assigment operators........
scoped_nop() = default;
scoped_nop(const scoped_nop&) = delete;
scoped_nop(scoped_nop&& rhs) : scoped_basic<bufsize_>(std::move(rhs)) {}
scoped_nop& operator=(const scoped_nop& rhs) = delete;
scoped_nop& operator=(scoped_nop&& rhs)
{ scoped_basic<bufsize_>::operator=(std::move(rhs)); return *this; }
scoped_nop(memory_pointer_tr addr, size_t size = 1, bool vp = true)
{ make_nop(addr, size, vp); }
};
/*
* RAII wrapper for MakeJMP
*/
class scoped_jmp : public scoped_basic<5>
{
public:
// Makes NOP at @addr with value @value and size @size and virtual protect @vp
memory_pointer_raw make_jmp(memory_pointer_tr at, memory_pointer_raw dest, bool vp = true)
{
this->save(at, 5, vp);
return MakeJMP(at, dest, vp);
}
// Constructors, move constructors, assigment operators........
scoped_jmp() = default;
scoped_jmp(const scoped_jmp&) = delete;
scoped_jmp(scoped_jmp&& rhs) : scoped_basic<5>(std::move(rhs)) {}
scoped_jmp& operator=(const scoped_jmp& rhs) = delete;
scoped_jmp& operator=(scoped_jmp&& rhs)
{ scoped_basic<5>::operator=(std::move(rhs)); return *this; }
scoped_jmp(memory_pointer_tr at, memory_pointer_raw dest, bool vp = true)
{ make_jmp(at, dest, vp); }
};
/*
* RAII wrapper for MakeCALL
*/
class scoped_call : public scoped_basic<5>
{
public:
// Makes NOP at @addr with value @value and size @size and virtual protect @vp
memory_pointer_raw make_call(memory_pointer_tr at, memory_pointer_raw dest, bool vp = true)
{
this->save(at, 5, vp);
return MakeCALL(at, dest, vp);
}
// Constructors, move constructors, assigment operators........
scoped_call() = default;
scoped_call(const scoped_call&) = delete;
scoped_call(scoped_call&& rhs) : scoped_basic<5>(std::move(rhs)) {}
scoped_call& operator=(const scoped_call& rhs) = delete;
scoped_call& operator=(scoped_call&& rhs)
{ scoped_basic<5>::operator=(std::move(rhs)); return *this; }
scoped_call(memory_pointer_tr at, memory_pointer_raw dest, bool vp = true)
{ make_call(at, dest, vp); }
};
#if __cplusplus >= 201103L || _MSC_VER >= 1800 // C++11 or MSVC 2013 required for variadic templates
/*
* function_hooker_manager
* Manages many function_hookers that points to the same address
* The need for this function arises because otherwise we would only be able to allow one hook per address using function_hookers
* This manager takes care of the amount of hooks placed in a particular address, calls the hooks and unhooks when necessary.
*/
template<class ToManage, class Ret, class ...Args>
class function_hooker_manager : protected scoped_call
{
private:
using func_type_raw = typename ToManage::func_type_raw;
using func_type = typename ToManage::func_type;
using functor_type = typename ToManage::functor_type;
using assoc_type = std::list<std::pair<const ToManage*, functor_type>>;
// Only construction is allowed... by myself ofcourse...
function_hooker_manager() = default;
function_hooker_manager(const function_hooker_manager&) = delete;
function_hooker_manager(function_hooker_manager&&) = delete;
//
func_type_raw original; // Pointer to the original function we've replaced
assoc_type assoc; // Association between owners of a hook and the hook (map)
bool has_hooked = false; // Is the hook already in place?
// Find assoc iterator for the content owned by 'owned'
typename assoc_type::iterator find_assoc(const ToManage& owner)
{
for(auto it = assoc.begin(); it != assoc.end(); ++it)
if(it->first == &owner) return it;
return assoc.end();
}
// Adds a new item to the association map (or override if already in the map)
void add(const ToManage& hooker, functor_type functor)
{
auto it = find_assoc(hooker);
if(it != assoc.end())
it->second = std::move(functor);
else
assoc.emplace_back(&hooker, std::move(functor));
}
public:
// Forwards the call to all the installed hooks
static Ret call_hooks(Args&... args)
{
auto& manager = *instance();
if(manager.assoc.size() == 0) // This may be uncommon but may happen (?), no hook installed
return manager.original(args...);
// Functor for the original call
func_type original = [&manager](Args... args) -> Ret {
return manager.original(args...);
};
if(manager.assoc.size() == 1)
{
// We have only one hook, just use it directly no need to go further in complexity
auto& functor = manager.assoc.begin()->second;
return functor(std::move(original), args...);
}
else
{
// Build a serie of functors which captures the previous functor sending it to the next functor,
// that's what would happen if the hooks took place independent of the template staticness (AAAAAAA)
func_type next = std::move(original);
for(auto it = manager.assoc.begin(); it != manager.assoc.end(); ++it)
{
auto& functor = it->second;
next = [functor, next](Args... args) -> Ret
{
return functor(next, args...);
};
}
return next(args...);
}
}
public:
// Installs a hook associated with the function_hooker 'hooker' which would call the specified 'functor'
// We need an auxiliar function pointer 'ptr' (to abstract calling conventions) which should forward itself to ^call_hooks
void make_call(const ToManage& hooker, functor_type functor, memory_pointer_raw ptr)
{
this->add(hooker, std::move(functor));
// Make sure we only hook this address for the manager once
if(!this->has_hooked)
{
// (the following cast is needed for __thiscall functions)
this->original = (func_type_raw) (void*) scoped_call::make_call(hooker.addr, ptr).get();
this->has_hooked = true;
}
}
// Restores the state of the call we've replaced in the game code
// All installed hooks gets uninstalled after this
void restore()
{
if(this->has_hooked)
{
this->has_hooked = false;
this->assoc.clear();
return scoped_call::restore();
}
}
// Replaces the hook associated with 'from' to be associated with 'to'
// After this call the 'from' object has no association in this manager
void replace(const ToManage& from, const ToManage& to)
{
auto it = find_assoc(from);
if(it != assoc.end())
{
auto functor = std::move(it->second);
assoc.erase(it);
this->add(to, std::move(functor));
}
}
// Removes the hook associated with the specified 'hooker'
// If the number of hooks reaches zero after the remotion, a restore will take place
void remove(const ToManage& hooker)
{
auto it = find_assoc(hooker);
if(it != assoc.end())
{
assoc.erase(it);
if(assoc.size() == 0) this->restore();
}
}
// The instance of this specific manager
// This work as a shared pointer to avoid the static destruction of the manager even when a static function_hooker
// still wants to use it.
static std::shared_ptr<function_hooker_manager> instance()
{
static auto fm_ptr = std::shared_ptr<function_hooker_manager>(new function_hooker_manager());
return fm_ptr;
}
};
/*
* function_hooker_base
* Base for any function_hooker, this class manages the relationship with the function hooker manager
*/
template<uintptr_t addr1, class FuncType, class Ret, class ...Args>
class function_hooker_base : public scoped_base
{
public:
static const uintptr_t addr = addr1;
using func_type_raw = FuncType;
using func_type = std::function<Ret(Args...)>;
using functor_type = std::function<Ret(func_type, Args&...)>;
using manager_type = function_hooker_manager<function_hooker_base, Ret, Args...>;
public:
// Constructors, move constructors, assigment operators........
function_hooker_base(const function_hooker_base&) = delete;
function_hooker_base& operator=(const function_hooker_base& rhs) = delete;
function_hooker_base() : manager(manager_type::instance())
{}
//
virtual ~function_hooker_base()
{
this->restore();
}
// The move constructor should do a replace in the manager
function_hooker_base(function_hooker_base&& rhs)
: scoped_base(std::move(rhs)), has_call(rhs.has_call),
manager(rhs.manager) // (don't move the manager!, every function_hooker should own one)
{
manager->replace(rhs, *this);
}
// The move assignment operator should also do a replace in the manager
function_hooker_base& operator=(function_hooker_base&& rhs)
{
scoped_base::operator=(std::move(rhs));
manager->replace(rhs, *this);
this->has_call = rhs.has_call;
this->manager = rhs.manager; // (don't move the manager! every function_hooker should own one)
return *this;
}
// Deriveds should implement a proper make_call (yeah it's virtual so derived-deriveds can do some fest)
virtual void make_call(functor_type functor) = 0;
// Restores the state of the call we've replaced in the game code
virtual void restore()
{
this->has_call = false;
manager->remove(*this);
}
// Checkers whether a hook is installed
bool has_hooked()
{
return this->has_call;
}
private:
bool has_call = false; // Has a hook installed?
std::shared_ptr<manager_type> manager; // **EVERY** function_hooker should have a ownership over it's manager_type
// this prevents the static destruction of the manager_type while it may be still needed.
protected: // Forwarders to the function hooker manager
void make_call(functor_type functor, memory_pointer_raw ptr)
{
this->has_call = true;
manager->make_call(*this, std::move(functor), ptr);
}
static Ret call_hooks(Args&... a)
{
return manager_type::call_hooks(a...);
}
};
/*
* function_hooker
* For standard conventions (usually __cdecl)
*/
template<uintptr_t addr1, class Prototype>
struct function_hooker;
template<uintptr_t addr1, class Ret, class ...Args>
class function_hooker<addr1, Ret(Args...)>
: public function_hooker_base<addr1, Ret(*)(Args...), Ret, Args...>
{
private:
using base = function_hooker_base<addr1, Ret(*)(Args...), Ret, Args...>;
// The hook caller
static Ret call(Args... a)
{
return base::call_hooks(a...);
}
public:
// Constructors, move constructors, assigment operators........
function_hooker() = default;
function_hooker(const function_hooker&) = delete;
function_hooker(function_hooker&& rhs) : base(std::move(rhs)) {}
function_hooker& operator=(const function_hooker& rhs) = delete;
function_hooker& operator=(function_hooker&& rhs)
{ base::operator=(std::move(rhs)); return *this; }
// Makes the hook
void make_call(typename base::functor_type functor)
{
return base::make_call(std::move(functor), raw_ptr(call));
}
};
/*
* function_hooker_stdcall
* For stdcall conventions (__stdcall)
*/
template<uintptr_t addr1, class Prototype>
struct function_hooker_stdcall;
template<uintptr_t addr1, class Ret, class ...Args>
struct function_hooker_stdcall<addr1, Ret(Args...)>
: public function_hooker_base<addr1, Ret(__stdcall*)(Args...), Ret, Args...>
{
private:
using base = function_hooker_base<addr1, Ret(__stdcall*)(Args...), Ret, Args...>;
// The hook caller
static Ret __stdcall call(Args... a)
{
return base::call_hooks(a...);
}
public:
// Constructors, move constructors, assigment operators........
function_hooker_stdcall() = default;
function_hooker_stdcall(const function_hooker_stdcall&) = delete;
function_hooker_stdcall(function_hooker_stdcall&& rhs) : base(std::move(rhs)) {}
function_hooker_stdcall& operator=(const function_hooker_stdcall& rhs) = delete;
function_hooker_stdcall& operator=(function_hooker_stdcall&& rhs)
{ base::operator=(std::move(rhs)); return *this; }
// Makes the hook
void make_call(typename base::functor_type functor)
{
return base::make_call(std::move(functor), raw_ptr(call));
}
};
/*
* function_hooker_fastcall
* For fastcall conventions (__fastcall)
*/
template<uintptr_t addr1, class Prototype>
struct function_hooker_fastcall;
template<uintptr_t addr1, class Ret, class ...Args>
struct function_hooker_fastcall<addr1, Ret(Args...)>
: public function_hooker_base<addr1, Ret(__fastcall*)(Args...), Ret, Args...>
{
private:
using base = function_hooker_base<addr1, Ret(__fastcall*)(Args...), Ret, Args...>;
// The hook caller
static Ret __fastcall call(Args... a)
{
return base::call_hooks(a...);
}
public:
// Constructors, move constructors, assigment operators........
function_hooker_fastcall() = default;
function_hooker_fastcall(const function_hooker_fastcall&) = delete;
function_hooker_fastcall(function_hooker_fastcall&& rhs) : base(std::move(rhs)) {}
function_hooker_fastcall& operator=(const function_hooker_fastcall& rhs) = delete;
function_hooker_fastcall& operator=(function_hooker_fastcall&& rhs)
{ base::operator=(std::move(rhs)); return *this; }
// Makes the hook
void make_call(typename base::functor_type functor)
{
return base::make_call(std::move(functor), raw_ptr(call));
}
};
/*
* function_hooker_thiscall
* For thiscall conventions (__thiscall, class methods)
*/
template<uintptr_t addr1, class Prototype>
struct function_hooker_thiscall;
template<uintptr_t addr1, class Ret, class ...Args>
struct function_hooker_thiscall<addr1, Ret(Args...)>
: public function_hooker_base<addr1, Ret(__thiscall*)(Args...), Ret, Args...>
{
private:
using base = function_hooker_base<addr1, Ret(__thiscall*)(Args...), Ret, Args...>;
// The hook caller
static Ret __thiscall call(Args... a)
{
return base::call_hooks(a...);
}
public:
// Constructors, move constructors, assigment operators........
function_hooker_thiscall() = default;
function_hooker_thiscall(const function_hooker_thiscall&) = delete;
function_hooker_thiscall(function_hooker_thiscall&& rhs) : base(std::move(rhs)) {}
function_hooker_thiscall& operator=(const function_hooker_thiscall& rhs) = delete;
function_hooker_thiscall& operator=(function_hooker_thiscall&& rhs)
{ base::operator=(std::move(rhs)); return *this; }
// Makes the hook
void make_call(typename base::functor_type functor)
{
return base::make_call(std::move(functor), raw_ptr(call));
}
};
/******************* HELPERS ******************/
/*
* Adds a hook to be alive for the entire program lifetime
* That means the hook received will be alive until the program dies.
* Note: Parameter must be a rvalue
*/
template<class T> inline
T& add_static_hook(T&& hooker)
{
static std::list<T> a;
return *a.emplace(a.end(), std::move(hooker));
}
/*
* Makes a hook which is alive until it gets out of scope
* 'T' must be any function_hooker object
*/
template<class T, class F> inline
T make_function_hook(F functor)
{
T a;
a.make_call(std::move(functor));
return a;
}
/*
* Makes a hook which is alive for the entire lifetime of this program
* 'T' must be any function_hooker object
*/
template<class T, class F> inline
T& make_static_hook(F functor)
{
return add_static_hook(make_function_hook<T>(std::move(functor)));
}
// TODO when we have access to C++14 add a make_function_hook, make_stdcall_function_hook, and so on
// the problem behind implement it with C++11 is that lambdas cannot be generic and the first param of a hook is a functor pointing
// to the previous call pointer
#endif
}