cursey / safetyhook Goto Github PK
View Code? Open in Web Editor NEWC++23 procedure hooking library.
Home Page: https://cursey.dev/safetyhook
License: Boost Software License 1.0
C++23 procedure hooking library.
Home Page: https://cursey.dev/safetyhook
License: Boost Software License 1.0
Every time that I have tried to implement SafetyHook into a new project has resulted in a failure. This is because adding bddisasm to the project is confusing and I'm not sure how to do properly. Everything seems to need a 1,000 different pre-installed files just for this library to work.
I've been wanting to try out SafetyHook as an alternative, "safe" hooking library, but always fail.
Can you assist me, or just create a demo project with a working implementation for MSVC?
Apologies if this comes off rude, just very frustrated; really hoping for a reply, @cursey.
Are there any plans to make this work for GCC so that it can be used in vs code? I've tried linking it just like how the tutorial shows but it seems this was made for Visual Studio in mind, and it throws errors for pegtl with cmake. Is it supposed to only work on the Visual Studio IDE or is there something I am missing?
Example:
mov rcx, [rax+330h]
mov edx, 8003h
Let's say I want to get the rcx register address, with the current implementation, I need to hook mov edx, 8003h
to get rcx address, which is not that intuitive to me. If there is an option to make it call original first, then I just hook mov rcx, [rax+330h]
and grab the address
Will update with more info. First glance looks like an exception occurs inside of trap_threads, causing a nested acquisition of the trap mutex.
I'm looking for a new hooking library replacement and this one seems to suit my purposes except for 1 issue.
There doesn't seem to be a way to quickly toggle the hook on or off. So the only way seems to be completely destroying and recreating the hook which means new memory is allocated, and the prologue has to be disassembled and translated again which seems like a lot of overhead.
Would you be open to having a new member variable indicating whether the hook is on or not, and you could just replace the prologue with the hooked bytes or the original bytes?
example code:
safetyhook::InlineHook hk {};
void some_func(int& ref)
{
ref++;
}
// If I change reference to pointer then it will not crash
void hk_some_func(int& ref)
{
hk.call(ref);
// or call this, this wont crash
// hk.call<void, int&>(ref);
}
Hey, I'm trying to move some hooks that originally used injector's MakeInline, which works in a similar manner, to use midhook instead.
For demo purposes, the hook only replicates original code.
Original game code:
.text:00A200DC F3 0F 11 4C 24 60 movss dword ptr [esp+60h], xmm1
injector version:
void operator()(injector::reg_pack& regs)
{
static float f = 0.0f;
_asm { movss f, xmm1 }
*(float*)(regs.esp + 0x60) = f;
}
MakeNOP(0xA200DC, 6); // because injector does that
safetyhook::create_mid(0xA200DC,
[](SafetyHookContext& regs)
{
*(float*)(regs.esp + 0x60) = regs.xmm1.f32[0];
});
Crashes later in that function.
I've compared the esp value and determined that doing
safetyhook::create_mid(0xA200DC,
[](SafetyHookContext& regs)
{
*(float*)(regs.esp + 0x60 + 8) = regs.xmm1.f32[0];
});
works fine.
I am not sure if this is intended or not, so let me know.
v0.2.0 was used to compile.
In fix_ip
, SetThreadContext
is not called after fixing the thread's IP.
Line 259 in 3f157c8
Shortly before this, I freeze the threads with SafetyhookFactory::acquire()
. In one of the functions I call while threads are frozen, I am calling this function:
std::optional<HMODULE> get_module_within(Address address) {
HMODULE module = nullptr;
if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, address.as<LPCSTR>(), &module)) {
return module;
}
return {};
}
This is the offending mutex in ntdll:
For now, I've gotten around this by acquiring in multiple places instead of in one giant code block, to limit the potential that I will run into this issue.
While testing #13 I've ran into the issue where the library refuses to build under clang-cl
. It seems like commit 3393ced is the cause of it.
The error seems to stem from the use of -pedantic
:
clang-cl: error: unknown argument ignored in clang-cl: '-pedantic' [-Werror,-Wunknown-argument]
The other thing I noticed is clang-cl gets picked up as both "msvc" and "clang" here, so both compile flags are used from these sections.
This is most likely just the fault of cmkr not having great predefined conditions: https://cmkr.build/cmake-toml/#predefined-conditions
msvc.private-compile-options = ["/WX", "/permissive-", "/W4", "/w14640"]
clang.private-compile-options = ["-Werror", "-Wall", "-Wextra", "-Wshadow", "-Wnon-virtual-dtor", "-pedantic"]
# Both of these pass on clang-cl:
if(MSVC) # msvc
if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_C_COMPILER_ID MATCHES "Clang") # clang
The final flags on my compiler look like this (this is only from Builder.cpp
but it happens for all source files):
clang-cl.exe /nologo -TP -DNOMINMAX -DZYDIS_STATIC_BUILD -I[REDACTED]\_deps\safetyhook-src\include -m32 /O2 /Ob2 /DNDEBUG -MD /WX /permissive- /W4 /w14640 -Werror -Wall -Wextra -Wshadow -Wnon-virtual-dtor -pedantic -std:c++17 /showIncludes /Fo_deps\safetyhook-build\CMakeFiles\safetyhook.dir\src\Builder.cpp.obj /Fd_deps\safetyhook-build\CMakeFiles\safetyhook.dir\safetyhook.pdb -c -- [REDACTED]\_deps\safetyhook-src\src\Builder.cpp
From what I know, clang flags passed to clang-cl must be passed like so: /clang:<flag>
. However, It seems like most flags in the MSVC section would work fine. The only ones I cant find in the documentation are: /permissive
and /w<warning id>
. Either way, I don't think it's ideal that both MSVC and clang-cl flags are being mixed on the command line in this case since they're redundant.
I'm sure theres some work around for this but perhaps cmkr could have better predefined compiler detection conditions?
Hello, I've just successfully got the library implemented and working fine. My testing was of course on Debug mode & worked completely fine. But I switched to Release and tried the hook (since that's obviously where it's going to be used in in real-use).
I've discovered a strange issue that the hook isn't applying when I have Optimization enabled on Visual Studio 2022 Preview.
Location: Configuration Properties > Optimization > Optimization (first).
Strangely, when using all other optimization settings, setting them to yes / enabled, no issue occurs. But when I have the first Optimization option not "Disabled (/Od)", it doesn't work.
I've attached some images with different configurations:
Disabled one succeeds, but any other optimization will not.
Here is the code used:
#include <iostream>
#include <Windows.h>
#include "safetyhook/safetyhook.hpp"
SafetyHookInline g_hook{};
void SomeFunction(int arg)
{
printf("SomeHook Called with %i\n", arg);
}
void SomeFunctionHk(int arg)
{
printf("Hook called\n");
return g_hook.call<void>(100);
}
int main()
{
g_hook = safetyhook::create_inline(reinterpret_cast<void*>(SomeFunction), reinterpret_cast<void*>(SomeFunctionHk));
SomeFunction(10);
std::cin.get();
return 0;
}
Hi there, many thanks for the update to use NtGetNextThread - since the change I've been having some strange crashes though, where random threads sometimes had exceptions, and RIP of threads would end up at some odd addresses.
Spent a while trying things out and might have found the cause, looking at ThreadFreezer it seems to now fetch thread context before actually suspending it, which I'd guess could mean the thread might execute some more between the context fetch & the suspend.
Changing this section:
safetyhook/src/ThreadFreezer.cpp
Lines 58 to 67 in 8919568
to something like the older ThreadFreezer used:
auto thread_ctx = CONTEXT{};
thread_ctx.ContextFlags = CONTEXT_FULL;
if (SuspendThread(thread_handle) == (DWORD)-1 || !GetThreadContext(thread_handle, &thread_ctx)) {
continue;
}
m_frozen_threads.push_back({thread_id, thread_handle, thread_ctx});
Seemed to solve all the crashes I was getting at least, strangely was only getting this with the game Cyberpunk 2077, maybe that's doing something odd with it's threads there, not sure though.
It would be nice to see this library in the vcpkg registry, I hate having deps as a target inside Visual Studio.
Anyway, this is a great library, thanks for your work!
Noticed this when upgrading safetyhook from a fairly old version.
Since RSP is pushed after the trampoline:
push qword [rel trampoline]
push rsp
It is off by sizeof(void*). I can fix this in my project by adding 8 to everything, but I'd rather not.
Offending commit: 39dff2c
Sorry if this isn't enough information, threading is not my strong suit. Please let me know.
I'll occasionally get access violation errors when using a midhook for an x64 program. This is happening when the threads are resumed.
ntdll.dll!NtQueryInformationThread() Unknown
KernelBase.dll!GetThreadId() Unknown
BF2VR.dll!safetyhook::execute_while_frozen(const std::function<void __cdecl(void)> & run_fn, const std::function<void __cdecl(unsigned int,void *,_CONTEXT &)> & visit_fn) Line 139 C++
BF2VR.dll!safetyhook::InlineHook::e9_hook(const std::shared_ptr<safetyhook::Allocator> & allocator) Line 323 C++
BF2VR.dll!safetyhook::InlineHook::setup(const std::shared_ptr<safetyhook::Allocator> & allocator, unsigned char * target, unsigned char * destination) Line 191 C++
BF2VR.dll!safetyhook::InlineHook::create(const std::shared_ptr<safetyhook::Allocator> & allocator, void * target, void * destination) Line 147 C++
BF2VR.dll!safetyhook::InlineHook::create<unsigned char *,unsigned char *>(const std::shared_ptr<safetyhook::Allocator> & allocator, unsigned char * target, unsigned char * destination) Line 108 C++
BF2VR.dll!safetyhook::MidHook::setup(const std::shared_ptr<safetyhook::Allocator> & allocator, unsigned char * target, void(*)(safetyhook::Context64 &) destination) Line 110 C++
BF2VR.dll!safetyhook::MidHook::create(const std::shared_ptr<safetyhook::Allocator> & allocator, void * target, void(*)(safetyhook::Context64 &) destination) Line 56 C++
BF2VR.dll!safetyhook::MidHook::create(void * target, void(*)(safetyhook::Context64 &) destination) Line 48 C++
BF2VR.dll!safetyhook::create_mid(void * target, void(*)(safetyhook::Context64 &) destination) Line 13 C++
BF2VR.dll!BF2VR::BF2Service::Initialize() Line 209 C++
BF2VR.dll!BF2VR::MainThread(HINSTANCE__ * hModule) Line 108 C++
kernel32.dll!BaseThreadInitThunk() Unknown
ntdll.dll!RtlUserThreadStart() Unknown
I read up on GetThreadId and from what I understand, it shouldn't be possible to crash the program. It's showing an access violation at 0x0.
Please let me know what I need to send to help with this. Thanks.
Since the update 2 days ago, I get multiple definition errors when linking. I'm using CLion and cmake 3.26 with C++ 23. pulling the repo like in the example.
C:\Program Files\JetBrains\CLion 2023.2.1\bin\mingw\bin/ld.exe: C:/Program Files/JetBrains/CLion 2023.2.1/bin/mingw/bin/../lib/gcc/x86_64-w64-mingw32/13.1.0\libstdc++.a(tinfo.o):(.text$_ZNKSt9type_infoeqERKS_+0x0): multiple definition of `std::type_info::operator==(std::type_info const&) const'; _deps/safetyhook-build/libsafetyhook.a(vmt_hook.cpp.obj):C:/Program Files/JetBrains/CLion 2023.2.1/bin/mingw/lib/gcc/x86_64-w64-mingw32/13.1.0/include/c++/typeinfo:194: first defined here
After a bit of research, this seems to be an issue with static linking in C++ 23.
skeeto/w64devkit#86
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110572
InlineHook::~InlineHook() {
destroy();
}
For example, if dll function was hooked, and this dll was unloaded, std::copy(m_original_bytes.begin(), m_original_bytes.end(), m_target);
will crash on app exit(or whenever the destructor is called). Would be good to have an option to avoid that, or check for pointer validity inside ::destroy()
; Also applies to mid hook.
Thanks for the great library. Now I am trying to implement a midhook that need to replace the original instruction. The original instruction is something like
rcpps xmm0, xmm1
and I need to replace it with exact computation so that the result will be consistent on both Intel and AMD.
Is it possible? Maybe related to #37?
I'm trying to place a mid-function hook on the jl
instruction here, but the detour code is causing a crash. I've hooked somewhere else as a workaround, but was curious as to whether it's a library issue or if this was just a bad place to put it.
1803C9F59 - 7C 53 - jl 1803C9FAE
1803C9F5B - 48 8B 84 24 90020000 - mov rax,[rsp+00000290]
1803C9F63 - 83 38 3C - cmp dword ptr [rax],3C { 60 }
hook = builder.create_mid(reinterpret_cast<void*>(0x1803C9F59), [] (safetyhook::Context& ctx) {});
Here's what everything looks like after the hook is created:
1803C9F59 - E9 B160C2FF - jmp 17FFF000F { ->->1A601FB0000 }
1803C9F5E - 90 - nop
1803C9F5F - 90 - nop
1803C9F60 - 90 - nop
1803C9F61 - 90 - nop
1803C9F62 - 90 - nop
1803C9F63 - 83 38 3C - cmp dword ptr [rax],3C { 60 }
17FFF000F - FF25 00000000 0000FB01A6010000 - jmp 1A601FB0000
1A601FB0000 - 54 - push rsp
1A601FB0001 - 55 - push rbp
1A601FB0002 - 50 - push rax
1A601FB0003 - 53 - push rbx
1A601FB0004 - 51 - push rcx
1A601FB0005 - 52 - push rdx
1A601FB0006 - 56 - push rsi
1A601FB0007 - 57 - push rdi
1A601FB0008 - 41 50 - push r8
1A601FB000A - 41 51 - push r9
1A601FB000C - 41 52 - push r10
1A601FB000E - 41 53 - push r11
1A601FB0010 - 41 54 - push r12
1A601FB0012 - 41 55 - push r13
1A601FB0014 - 41 56 - push r14
1A601FB0016 - 41 57 - push r15
1A601FB0018 - 9C - pushfq
1A601FB0019 - 48 8D 0C 24 - lea rcx,[rsp]
1A601FB001D - 48 83 EC 28 - sub rsp,28 { 40 }
1A601FB0021 - 48 F7 C4 08000000 - test rsp,00000008 { 8 }
1A601FB0028 - 0F84 10000000 - je 1A601FB003E
1A601FB002E - 48 83 EC 08 - sub rsp,08 { 8 }
1A601FB0032 - 48 C7 C3 30000000 - mov rbx,00000030 { 48 }
1A601FB0039 - E9 07000000 - jmp 1A601FB0045
1A601FB003E - 48 C7 C3 28000000 - mov rbx,00000028 { 40 }
1A601FB0045 - FF 15 22000000 - call qword ptr [1A601FB006D] { ->demo_hooks.dll+CE6C0 }
1A601FB004B - 48 01 DC - add rsp,rbx
1A601FB004E - 9D - popfq
1A601FB004F - 41 5F - pop r15
1A601FB0051 - 41 5E - pop r14
1A601FB0053 - 41 5D - pop r13
1A601FB0055 - 41 5C - pop r12
1A601FB0057 - 41 5B - pop r11
1A601FB0059 - 41 5A - pop r10
1A601FB005B - 41 59 - pop r9
1A601FB005D - 41 58 - pop r8
1A601FB005F - 5F - pop rdi
1A601FB0060 - 5E - pop rsi
1A601FB0061 - 5A - pop rdx
1A601FB0062 - 59 - pop rcx
1A601FB0063 - 5B - pop rbx
1A601FB0064 - 58 - pop rax
1A601FB0065 - 5D - pop rbp
1A601FB0066 - 5C - pop rsp
1A601FB0067 - FF 25 08000000 - jmp qword ptr [1A601FB0075] { ->17FFF0000 }
1A601FB006D - C0 E6 D3 - shl dh,-2D { 211 }
1A601FB0070 - EB FD - jmp 1A601FB006F
1A601FB0072 - 7F 00 - jg 1A601FB0074
1A601FB0074 - 00 00 - add [rax],al
1A601FB0076 - 00 FF - add bh,bh
1A601FB0078 - 7F 01 - jg 1A601FB007B
1A601FB007A - 00 00 - add [rax],al
But here's where things go wrong:
17FFF0000 - 7C 53 - jl 17FFF0055 ; jumps into a bunch of zeroes and crashes here
17FFF0002 - 48 8B 84 24 90020000 - mov rax,[rsp+00000290]
17FFF000A - E9 549F3D00 - jmp 1803C9F63
17FFF000F - FF25 00000000 0000FB01A6010000 - jmp 1A601FB0000
Either way, thanks for your work on this library! It's made prototyping things a lot faster. ๐
I think functions with RIP-relative instructions at the start are common enough to justify such a feature. I did a bunch of research, and it looks like PolyHook was able to do it by translating the instructions into equivalent ones with absolute addressing.
stevemk14ebr/PolyHook_2_0#119
I tried making some changes in the inline hook creator, specifically ff_hook, but I can't quite figure it out. I've gotten fairly familiar with the library but this assembly stuff is still a challenge I'm trying to learn from and get through. Is this something that could be implemented?
Thanks.
Code to reproduce:
SafetyHookInline hook;
__declspec(noinline) void SayHello(int times)
{
std::cout << "Hello #" << times << std::endl;
}
void Hooked_SayHello(int times)
{
hook->call<void, int>(1337);
}
void SayHelloInfinitely()
{
int count = 0;
while (true)
{
SayHello(count++);
}
}
int main()
{
// Starting a thread for SayHello
std::thread t(SayHelloInfinitely);
t.detach();
{
auto builder = SafetyHookFactory::acquire();
hook = builder.create_inline(SayHello, Hooked_SayHello);
}
std::this_thread::sleep_for(std::chrono::seconds(1));
hook.reset();
std::this_thread::sleep_for(std::chrono::seconds(1));
return 0;
}
Although this can be "fixed" by checking nullptr for hook
, it doesn't really live up to the library name
Note: The library used in the code is from Update README.md
shLoadLibraryExA = safetyhook::create_inline(LoadLibraryExA, shCustomLoadLibraryExA);
shLoadLibraryExW = safetyhook::create_inline(LoadLibraryExW, shCustomLoadLibraryExW);
This is not something I can personally reproduce, since launching from steam that launches RGL that launches the game (GTA5 in this case) works fine. However I was sent a crash dump that has this:
KERNELBASE.dll!RaiseException() Unknown Non-user code. Symbols loaded without source information.
VCRUNTIME140D.dll!_CxxThrowException(void * pExceptionObject=0x000000a2b9b1cab8, const _s__ThrowInfo * pThrowInfo=0x00007ffb6e4f1520) Line 81 C++ Non-user code. Symbols loaded.
MSVCP140D.dll!std::_Throw_Cpp_error(int code=0x00000005) Line 33 C++ Symbols loaded.
dinput8.dll!std::_Mutex_base::lock() Line 54 C++ Symbols loaded.
dinput8.dll!std::scoped_lock<std::mutex>::scoped_lock<std::mutex>(std::mutex & _Mtx={...}) Line 503 C++ Symbols loaded.
> dinput8.dll!safetyhook::TrapManager::trap_handler(_EXCEPTION_POINTERS * exp=0x000000a2b9b1cf50) Line 230 C++ Symbols loaded.
ntdll.dll!RtlpCallVectoredHandlers() Unknown Non-user code. Symbols loaded without source information.
ntdll.dll!RtlDispatchException() Unknown Non-user code. Symbols loaded without source information.
ntdll.dll!KiUserExceptionDispatch() Unknown Non-user code. Symbols loaded without source information.
kernel32.dll!VirtualProtectStub() Unknown Non-user code. Symbols loaded without source information.
dinput8.dll!safetyhook::trap_threads(unsigned char * from=0x00007ffba724b0d0, unsigned char * to=0x00007ffba6e1001a, unsigned __int64 len=0x0000000000000007, const std::function<void __cdecl(void)> & run_fn={...}) Line 285 C++ Symbols loaded.
dinput8.dll!safetyhook::InlineHook::enable() Line 383 C++ Symbols loaded.
dinput8.dll!safetyhook::InlineHook::create(const std::shared_ptr<safetyhook::Allocator> & allocator={...}, void * target=0x00007ffba724b0d0, void * destination=0x00007ffb5fa6ab25, safetyhook::InlineHook::Flags flags=Default) Line 132 C++ Symbols loaded.
dinput8.dll!safetyhook::InlineHook::create(void * target=0x00007ffba724b0d0, void * destination=0x00007ffb5fa6ab25, safetyhook::InlineHook::Flags flags=Default) Line 118 C++ Symbols loaded.
dinput8.dll!safetyhook::create_inline(void * target=0x00007ffba724b0d0, void * destination=0x00007ffb5fa6ab25, safetyhook::InlineHook::Flags flags=Default) Line 5 C++ Symbols loaded.
dinput8.dll!safetyhook::create_inline<HINSTANCE__ * (__cdecl*)(wchar_t const *,void *,unsigned long),HINSTANCE__ * (__cdecl*)(wchar_t const *,void *,unsigned long)>(HINSTANCE__ *(*)(const wchar_t *, void *, unsigned long) target=0x00007ffba724b0d0, HINSTANCE__ *(*)(const wchar_t *, void *, unsigned long) destination=0x00007ffb5fa6ab25, safetyhook::InlineHook::Flags flags=Default) Line 26 C++ Symbols loaded.
//safetyhook::create_inline calls here
The reason of exception is this
void lock() {
if (_Mtx_lock(_Mymtx()) != _Thrd_result::_Success) {
// undefined behavior, only occurs for plain mutexes (N4950 [thread.mutex.requirements.mutex.general]/6)
_STD _Throw_Cpp_error(_RESOURCE_DEADLOCK_WOULD_OCCUR);
}
I'm not sure if anything can be done to fix/avoid this, since it's not easy to reproduce, maybe it's anticheat kicks in, maybe something else.
For some reason in Minecraft NtGetNextThread
throws an exception with code 0xC0000008: An invalid handle was specified.
, causing game crash, this is a UWP game afaik but I did use some launcher to set it up to let me copy DLLs into it.
IIRC some APIs are restricted in UWP land, guess maybe NtGetNextThread is one of them? Never had this exception in any non-UWP apps at least.
Call stack:
ntdll.dll!KiRaiseUserExceptionDispatcher() Unknown
> dxgi.dll!safetyhook::ThreadFreezer::ThreadFreezer(void) C++
dxgi.dll!safetyhook::InlineHook::e9_hook(class std::shared_ptr<class safetyhook::Allocator> const &) C++
dxgi.dll!safetyhook::InlineHook::create(class std::shared_ptr<class safetyhook::Allocator> const &,unsigned __int64,unsigned __int64) C++
dxgi.dll!safetyhook::InlineHook::create(unsigned __int64,unsigned __int64) C++
dxgi.dll!safetyhook::create_inline(void *,void *) C++
dxgi.dll!nvngx::init(struct HINSTANCE__ *) C++
FWIW the older toolhelp method did used to work fine on this game too.
If you own Minecraft Bedrock (or have Xbox Game Pass PC) you can use https://github.com/bedrockLauncher/BedrockLauncher to set the game up in a writable folder, then build a wrapper DLL to load into it and try creating an inline hook.
E: strange, if I run with debugger attached and manually continue past the exception it runs fine, seems that some of the calls to NtGetNextThread are also successful too, not sure why this is causing a crash when debugger isn't attached...
(also disabling the exception type in VS debugger is still making it prompt for each exception too, not sure if that means anything)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.