Commit f8ba23a4 authored by SR_team's avatar SR_team 💬

Added SRHook

parent 32ad3b87
#ifndef SRALLOCATOR_H
#define SRALLOCATOR_H
#ifdef WIN32
# include <windows.h>
#else
# include <sys/mman.h>
#endif
namespace SRHook::Allocator {
namespace private_ {
template<typename T> static bool unprotect( T *ptr, size_t count ) {
size_t size = count * sizeof( T );
#ifdef WIN32
size_t address = (size_t)ptr;
do {
MEMORY_BASIC_INFORMATION mbi;
if ( !::VirtualQuery( reinterpret_cast<PVOID>( address ), &mbi, sizeof( mbi ) ) ) return false;
if ( size > mbi.RegionSize )
size -= mbi.RegionSize;
else
size = 0;
DWORD oldp;
if ( !::VirtualProtect( mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &oldp ) )
return false;
if ( reinterpret_cast<size_t>( mbi.BaseAddress ) + mbi.RegionSize < address + size )
address = reinterpret_cast<size_t>( mbi.BaseAddress ) + mbi.RegionSize;
} while ( size );
#else
if ( ::mprotect( (void *)ptr, size, PROT_READ | PROT_WRITE | PROT_EXEC ) ) return false;
#endif
return true;
}
}
template<typename T> void dealloc( T *&ptr ){
if ( ptr ) ::free( (void*)ptr );
ptr = nullptr;
}
template<typename T> T *alloc( size_t count ) {
auto addr = (T *)::malloc( count * sizeof( T ) );
if ( addr ) {
if ( private_::unprotect( addr, count * sizeof( T ) ) ) return addr;
dealloc( addr );
}
return nullptr;
}
template<typename T> T *realloc( T *ptr, size_t count ) {
auto addr = (T *)::realloc( (void *)ptr, count * sizeof( T ) );
if ( addr ) {
if ( private_::unprotect( addr, count * sizeof( T ) ) ) return addr;
dealloc( addr );
}
return ptr; // BUG: Не дает понять, по какой причине указатель не изменилась - память аллоцирована по тому же адресу, или произошла ошибка реаллокации?
}
}
#endif // SRALLOCATOR_H
#ifndef SRHOOK_HPP
#define SRHOOK_HPP
// C++17
#include <string>
#include <string_view>
#include <vector>
#include <atomic>
// https://gitlab.com/SR_team/SRSignal
#include <SRSignal.hpp>
// This project
#include <SRAllocator.h>
#include <fn2void.hpp>
#include <memsafe.h> // winonly!
namespace SRHook {
struct CPU {
size_t EAX;
size_t ECX;
size_t EDX;
size_t EBX;
size_t ESP;
size_t EBP;
size_t ESI;
size_t EDI;
union {
struct {
union {
struct {
uint8_t CF : 1;
uint8_t RESERVE1 : 1;
uint8_t PF : 1;
uint8_t RESERVE3 : 1;
uint8_t AF : 1;
uint8_t RESERVE5 : 1;
uint8_t ZF : 1;
uint8_t SF : 1;
uint8_t TF : 1;
uint8_t IF : 1;
uint8_t DF : 1;
uint8_t OF : 1;
uint8_t IOPL : 2;
uint8_t NT : 1;
uint8_t RESERVE15 : 1;
};
uint16_t FLAGS;
};
uint8_t RF : 1;
uint8_t VM : 1;
uint8_t AC : 1;
uint8_t VIF : 1;
uint8_t VIP : 1;
uint8_t ID : 1;
uint8_t RESERVE22 : 2;
uint8_t RESERVE24;
};
size_t EFLAGS;
};
};
template<typename... Args> class Hook {
enum class CallingStage { wait = 0, before, after, install, remove };
size_t addr;
ssize_t size;
std::string module;
public:
Hook( size_t addr, ssize_t size, std::string_view module = "" )
: addr( addr ), size( size ), module( module ) {}
Hook( size_t addr, std::string_view module = "" ) : Hook( addr, -1, module ) {}
virtual ~Hook() {
while ( stage != CallingStage::wait )
;
remove();
if ( originalCode ) Allocator::dealloc( originalCode );
if ( !hooked && code ) Allocator::dealloc( code );
}
SRSignal<Hook<Args...> *, Args &...> onBeforeFull;
SRSignal<Hook<Args...> *, Args &...> onAfterFull;
SRSignal<CPU &, Args &...> onBeforeDetail;
SRSignal<CPU &, Args &...> onAfterDetail;
SRSignal<Args &...> onBefore;
SRSignal<Args &...> onAfter;
SRSignal<> onBeforeShort;
SRSignal<> onAfterShort;
virtual bool skipOriginal() {
if ( stage != CallingStage::before ) return false;
skip = true;
return true;
}
virtual void changeRetAddr( size_t out ) {
retAddr = out;
}
CPU cpu;
virtual bool install( ssize_t stackOffsetBefore = 0, ssize_t stackOffsetAfter = 0 ) {
if ( stage != CallingStage::wait ) return false;
stage = CallingStage::install;
codeLength = 0;
// Копирование оригинального кода в класс
if ( originalCode == nullptr ) {
originalAddr = addr;
if ( !module.empty() ) {
#ifdef WIN32
originalAddr += (size_t)GetModuleHandleA( module.data() );
#else
Allocator::dealloc( code );
stage = CallingStage::wait;
return false;
#endif
}
if ( size == -1 ) size = tryToDetectSize( originalAddr );
if ( !size ) {
Allocator::dealloc( code );
return false;
}
originalCode = Allocator::alloc<uint8_t>( size );
memsafe::copy( originalCode, (void *)originalAddr, size );
}
auto cpu_offset = (CPU *)( (uint32_t)this + offsetof( SRHook::Hook<Args...>, cpu ) );
// Копирование EAX в класс
pusha<uint8_t>( 0xA3, &cpu_offset->EAX );
// Перемещение адреса возврата в класс (обязательно до копирования ESP)
auto ret_offset = ( (uint32_t)this + offsetof( SRHook::Hook<Args...>, retAddr ) );
pusha<uint8_t, uint8_t>( 0x58, 0xA3, ret_offset );
// Копирование остальных регистров в класс
pusha<uint8_t, uint8_t>( 0x89, 0x0D, &cpu_offset->ECX );
pusha<uint8_t, uint8_t>( 0x89, 0x15, &cpu_offset->EDX );
pusha<uint8_t, uint8_t>( 0x89, 0x1D, &cpu_offset->EBX );
pusha<uint8_t, uint8_t>( 0x89, 0x25, &cpu_offset->ESP );
pusha<uint8_t, uint8_t>( 0x89, 0x2D, &cpu_offset->EBP );
pusha<uint8_t, uint8_t>( 0x89, 0x35, &cpu_offset->ESI );
pusha<uint8_t, uint8_t>( 0x89, 0x3D, &cpu_offset->EDI );
// Копирование флагов в класс
pusha<uint8_t, uint8_t, uint8_t>( 0x9C, 0x58, 0xA3, &cpu_offset->EFLAGS );
if ( sizeof...( Args ) ) {
// Копирование аргументов со стека
push<uint8_t>( 0x52 ); // push edx
for ( int i = sizeof...( Args ) - 1; i >= 0; --i ) {
pusha<uint8_t, uint8_t>( 0x89, 0xE2 ); // mov edx, esp
pusha<uint8_t, uint8_t>( 0x81, 0xC2, stackOffsetBefore + i * 4 ); // add edx, offset
push<uint8_t>( 0x52 ); // push edx
}
}
// Вызов обработчика перед оригинальным кодом
pusha<uint8_t>( 0xB9, (uint32_t)this ); // mov ecx, this
auto relAddr = getRelAddr( (size_t)code + codeLength, (size_t)fn2void( &Hook<Args...>::before ) );
pusha<uint8_t>( 0xE8, relAddr ); // call before
if ( sizeof...( Args ) ) push<uint8_t>( 0x5A ); // pop edx
// Пропуск оригинального кода
pusha<uint8_t, uint8_t>( 0x85, 0xC0 ); // test eax, eax
pusha<uint8_t, uint8_t>( 0x0F, 0x85, 108 + size ); // jnz j_after
// Восстановление флагов из класса
pusha<uint8_t>( 0xA1, &cpu_offset->EFLAGS );
pusha<uint8_t, uint8_t>( 0x50, 0x9D );
// Восстановление регистров из класса
pusha<uint8_t>( 0xA1, &cpu_offset->EAX );
pusha<uint8_t, uint8_t>( 0x8B, 0x0D, &cpu_offset->ECX );
pusha<uint8_t, uint8_t>( 0x8B, 0x15, &cpu_offset->EDX );
pusha<uint8_t, uint8_t>( 0x8B, 0x1D, &cpu_offset->EBX );
pusha<uint8_t, uint8_t>( 0x8B, 0x25, &cpu_offset->ESP );
pusha<uint8_t, uint8_t>( 0x8B, 0x2D, &cpu_offset->EBP );
pusha<uint8_t, uint8_t>( 0x8B, 0x35, &cpu_offset->ESI );
pusha<uint8_t, uint8_t>( 0x8B, 0x3D, &cpu_offset->EDI );
// оригинальный код
if ( !pushOriginal() ) {
stage = CallingStage::wait;
return false;
}
// Копирование регистров в класс
pusha<uint8_t>( 0xA3, &cpu_offset->EAX );
pusha<uint8_t, uint8_t>( 0x89, 0x0D, &cpu_offset->ECX );
pusha<uint8_t, uint8_t>( 0x89, 0x15, &cpu_offset->EDX );
pusha<uint8_t, uint8_t>( 0x89, 0x1D, &cpu_offset->EBX );
pusha<uint8_t, uint8_t>( 0x89, 0x25, &cpu_offset->ESP );
pusha<uint8_t, uint8_t>( 0x89, 0x2D, &cpu_offset->EBP );
pusha<uint8_t, uint8_t>( 0x89, 0x35, &cpu_offset->ESI );
pusha<uint8_t, uint8_t>( 0x89, 0x3D, &cpu_offset->EDI );
// Копирование флагов в класс
pusha<uint8_t, uint8_t, uint8_t>( 0x9C, 0x58, 0xA3, &cpu_offset->EFLAGS );
// j_after
if ( sizeof...( Args ) ) {
// Копирование аргументов со стека
push<uint8_t>( 0x52 ); // push edx
for ( int i = sizeof...( Args ) - 1; i >= 0; --i ) {
pusha<uint8_t, uint8_t>( 0x89, 0xE2 ); // mov edx, esp
pusha<uint8_t, uint8_t>( 0x81, 0xC2, stackOffsetAfter + i * 4 ); // add edx, offset
push<uint8_t>( 0x52 ); // push edx
}
}
// Вызов обработчика после оригинального кода
pusha<uint8_t>( 0xB9, (uint32_t)this ); // mov ecx, this
relAddr = getRelAddr( (size_t)code + codeLength, (size_t)fn2void( &Hook<Args...>::after ) );
pusha<uint8_t>( 0xE8, relAddr ); // call after
if ( sizeof...( Args ) ) push<uint8_t>( 0x5A ); // pop edx
// j_restore
// Восстановление флагов из класса
pusha<uint8_t>( 0xA1, &cpu_offset->EFLAGS );
pusha<uint8_t, uint8_t>( 0x50, 0x9D );
// Восстановление регистров из класса
pusha<uint8_t, uint8_t>( 0x8B, 0x3D, &cpu_offset->EDI );
pusha<uint8_t, uint8_t>( 0x8B, 0x35, &cpu_offset->ESI );
pusha<uint8_t, uint8_t>( 0x8B, 0x2D, &cpu_offset->EBP );
pusha<uint8_t, uint8_t>( 0x8B, 0x25, &cpu_offset->ESP );
pusha<uint8_t, uint8_t>( 0x8B, 0x1D, &cpu_offset->EBX );
pusha<uint8_t, uint8_t>( 0x8B, 0x15, &cpu_offset->EDX );
pusha<uint8_t, uint8_t>( 0x8B, 0x0D, &cpu_offset->ECX );
// Восстановление адреса возврата из класса
ret_offset = ( (uint32_t)this + offsetof( SRHook::Hook<Args...>, retAddr ) );
pusha<uint8_t>( 0xA1, ret_offset ); // mov eax, retAddr
push<uint8_t>( 0x50 ); // push eax
// Восстановление EAX из класса
pusha<uint8_t>( 0xA1, &cpu_offset->EAX );
// Выход из хука
push<uint8_t>( 0xC3 ); // ret
// Вход в хук
if ( !hooked ) {
relAddr = getRelAddr( originalAddr, (size_t)code );
memsafe::write<uint8_t>( (void *)originalAddr, 0xE8 );
memsafe::write<size_t>( (void *)( originalAddr + 1 ), relAddr );
if ( size > 5 ) memsafe::set( (void *)( originalAddr + 5 ), 0x90, size - 5 );
hooked = true;
}
stage = CallingStage::wait;
return true;
}
virtual bool remove() {
if ( stage != CallingStage::wait ) return false;
if ( !hooked ) return false;
if ( originalCode == nullptr ) return false;
if ( code == nullptr ) return false;
stage = CallingStage::remove;
if ( memsafe::read<uint8_t>( (void *)originalAddr ) == 0xE8 ) {
auto rel = memsafe::read<size_t>( (void *)( originalAddr + 1 ) );
auto dest = getDestAddr( originalAddr, rel );
if ( dest == (size_t)code ) {
memsafe::copy( (void *)originalAddr, originalCode, size );
hooked = false;
stage = CallingStage::wait;
return true;
}
}
codeLength = 0;
// оригинальный код
if ( !pushOriginal() ) {
stage = CallingStage::wait;
return false;
}
push<uint8_t>( 0xC3 ); // ret
stage = CallingStage::wait;
return true;
}
/**
* @brief Автоматическое определение размера хука
* @details Решение основывается только на основе первого опкода по указанному адресу. Если размер
* данного опкодаменее 5 байт, или опкод не задан в функции, результат будет 0
* @param address Адрес хука
* @return Размер хука
*/
static size_t tryToDetectSize( size_t address ) {
auto pCode = reinterpret_cast<uint8_t *>( address );
switch ( *pCode ) {
case 0xE8:
[[fallthrough]];
case 0xE9:
[[fallthrough]];
case 0xA3:
[[fallthrough]];
case 0xA1:
[[fallthrough]];
case 0xA0:
[[fallthrough]];
case 0xA2:
[[fallthrough]];
case 0xA9:
[[fallthrough]];
case 0xB8:
[[fallthrough]];
case 0xB9:
[[fallthrough]];
case 0xBA:
[[fallthrough]];
case 0xBB:
[[fallthrough]];
case 0xBD:
[[fallthrough]];
case 0xBE:
[[fallthrough]];
case 0xBF:
[[fallthrough]];
case 0xBC:
[[fallthrough]];
case 0x35:
[[fallthrough]];
case 0x3D:
[[fallthrough]];
case 0x0D:
[[fallthrough]];
case 0x15:
[[fallthrough]];
case 0x1D:
[[fallthrough]];
case 0x25:
[[fallthrough]];
case 0x2D:
[[fallthrough]];
case 0x68:
[[fallthrough]];
case 0x05:
return 5;
case 0x89:
[[fallthrough]];
case 0x8B:
[[fallthrough]];
case 0x69:
[[fallthrough]];
case 0x81:
[[fallthrough]];
case 0xC7:
[[fallthrough]];
case 0xF7:
[[fallthrough]];
case 0x0F:
return 6;
case 0xEA:
[[fallthrough]];
case 0x9A:
return 7;
default:
return 0;
}
}
static ssize_t getRelAddr( size_t from, size_t to, size_t opLen = 5 ) {
return to - ( from + opLen );
}
static ssize_t getDestAddr( size_t from, size_t relAddr, size_t opLen = 5 ) {
return relAddr + ( from + opLen );
}
protected:
bool skip;
std::atomic<CallingStage> stage = CallingStage::wait;
bool before( Args &... args ) {
stage = CallingStage::before;
skip = false;
onBeforeFull( this, args... );
onBeforeDetail( cpu, args... );
onBefore( args... );
onBeforeShort();
return skip;
}
void after( Args &... args ) {
stage = CallingStage::after;
onAfterFull( this, args... );
onAfterDetail( cpu, args... );
onAfter( args... );
onAfterShort();
stage = CallingStage::wait;
}
private:
size_t originalAddr;
uint8_t *originalCode = nullptr;
size_t codeLength = 0;
size_t codeAllocated = 0;
uint8_t *code = nullptr;
size_t retAddr;
bool hooked = false;
void push( uint8_t *data, size_t length ) {
if ( length + codeLength >= codeAllocated ) alloc();
for ( int i = 0; i < length; ++i ) code[codeLength++] = data[i];
}
template<typename T> void push( const T &value ) {
if ( sizeof( T ) + codeLength >= codeAllocated ) alloc();
if constexpr ( sizeof( T ) > 1 ) {
union _ {
_( T value ) : value( value ) {}
T value;
uint8_t bytes[sizeof( T )];
} dec( value );
for ( int i = 0; i < sizeof( T ); ++i ) code[codeLength++] = dec.bytes[i];
} else
code[codeLength++] = (uint8_t)value;
}
template<typename T, typename... Ts> void pusha( T value, Ts... values ) {
push( value );
if constexpr ( sizeof...( Ts ) > 1 )
pusha( values... );
else
push( values... );
}
bool pushOriginal() {
// Вставка оригинального кода
push( originalCode, size );
// Модификация оригинального кода
auto firstOpcode = originalCode[0]; // NOTE: Только первый опкод, потому что в последним может
// быть не опкод, а операнд одного из опкодов
if ( firstOpcode == 0xE8 || firstOpcode == 0xE9 || firstOpcode == 0x0F ) {
if ( firstOpcode == 0x0F ) {
firstOpcode = originalCode[1];
if ( firstOpcode >= 0x81 && firstOpcode <= 0x8F ) {
auto dest = getDestAddr( originalAddr, *(size_t *)&originalCode[2], 6 );
auto rel = getRelAddr( (size_t)&code[codeLength - size], dest, 6 );
*(size_t *)&code[codeLength - ( size - 2 )] = rel;
}
} else {
auto dest = getDestAddr( originalAddr, *(size_t *)&originalCode[1] );
auto rel = getRelAddr( (size_t)&code[codeLength - size], dest );
*(size_t *)&code[codeLength - ( size - 1 )] = rel;
}
}
return true;
}
void alloc() {
// FIXME: Не обрабатывается ошибка нехватки памяти
if ( code == nullptr ) {
codeAllocated = 256;
code = Allocator::alloc<uint8_t>( codeAllocated );
} else {
codeAllocated *= 2;
code = Allocator::realloc<uint8_t>( code, codeAllocated );
}
code[codeAllocated - 1] = 0xCC; // int3
}
};
template<typename... Args>
void make_for( Hook<Args...> *&hook, size_t addr, size_t size, std::string_view module = "" ) {
hook = new Hook<Args...>( addr, size, module );
}
template<typename... Args>
void make_for( Hook<Args...> *&hook, size_t addr, std::string_view module = "" ) {
hook = new Hook<Args...>( addr, module );
}
}
#endif // SRHOOK_HPP
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment