-
-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Can this be a valid EasyHook replacement? #7
Comments
I've never worked in Kernel Mode (or much with other libraries for that matter) so I can't answer that part. In order to get your code executing inside a native program in the first place (if that's a goal), you can use Reloaded-II or if you would prefer something more custom, use Reloaded.Core.Bootstrap (and the linked articles within) as an inspiration.
I don't advertise, simple as that. For better or worse I'd rather spend my time developing something than letting people know about it. It's kind of futile in a way, you get super jealous of people doing the simplest of things and feel generally unappreciated but it is how it is.
My hobby is to tamper with games and as such I use the library on almost a daily basis. It's not as frequent these days as I don't run into scenarios where I need to update often but when there is a good reason, there is an update; so yes, by all means the project is actively maintained. |
Hello @Sewer56 and many thanks for your answer. I fully understand why you don't advertise your project and I can easily feel your frustration. People got used to take advantage of these kind of open source projects without expressing a minimum of gratitude. Not only any gratitude, but sometimes they open issues with questions like "What this project is used for?". It seems a mockery than anything else. They expect the developer to explain everything without doing any effort on their own. However I fell that your projects really deserve to be known from the community to make it even better with bug reports, pull request, new features, issues etc. But the right persons! I am glad to see that this project will be maintained. If you need to update, then I also need to update...therefore I believe that I don't have to care. Ok. Let me go into details of what are my findings, otherwise you will take me for someone of that people :-)
Now, it appears to be that I must focus my efforts into the right injector, but as stated previously, if something does not work, I always assume it is my fault. If it isn't and it's just a limitation of reloaded-injector, then these are what could be the possible alternatives:
I lost lot of time in experiments, therefore this time I need to be very careful in choosing the right tool otherwise I will risk to enter into a very long tunnel. It's soon to talk since I still have to manage to complete a working sample with reloaded hook, but I feel to say people reading this thread that it is not a replacement for EasyHook. Without having some background on EasyHook, an user will not be able to use reloaded other than local hooks and even for a sample basic project like the one I posted. There's much more involved. |
It's around 4:50am in the morning for me right now; and as such I'm a bit sleepy but let me at the very least drop a few comments. Edit: I've spent more time writing this than I thought I would; oof.
Actually injecting the code itself in the first place isn't normally a feature of hooking libraries; it's just been a thing in other .NET libraries because it's a non-trivial operation.
For the record, it wasn't a recommendation; just an example/pointer. That post is a bit dated, from a time where Personally I wouldn't use
It's been a while but if I recall correctly it was due to the process not yet having the modules loaded if started suspended, meaning that I couldn't get the handle/address of Kernel32. Problem: Getting address of Kernel32 for x86 process in x64 program. I originally built this injector for Reloaded-II but sadly... for this very reason... never got to use it in practice. I ended up writing a tiny very basic DLL injector directly into R-II instead. I ended up using a "trick" where I launch a separate 32-bit application from the 64-bit process to get the address of function I wanted (LoadLibrary) and passed it to current application using Memory Mapped Files. I did consider implementing this "trick" into this library but I felt like including an EXE in the build output would have raised a lot of flags for other people (aside being just generally weird and making initialization slow).
I can explain what's going on here! The default AssemblyLoadContext into which your program is being loaded into does not know where to probe for libraries/DLLs in; or more specifically, it doesn't know it should check in the same location as the DLL. The easiest way to resolve this issue is probably to overwrite the legacy In the old Reloaded (not Reloaded II), when things were on .NET Framework, I used to do this, so if you really want to try going this direction, feel free to use this class as inspiration 2 Important Note: It's entirely possible that DllExport loads a DLL into the main On Framework, EasyHook normally solves this assembly loading and isolation problem for you automatically. DllExport doesn't however.
DllExport's Problem.
The .NET Core runtime and default Additional created
I'm not really bothered about any internal errors of a 3rd party library; that's on the library authors; as long as things work. I might even replace it with my own minimalistic PE parser from Reloaded-II if I get motivated since it's much faster, smaller and more performant while only parsing the very basics I need.
Yeah I wouldn't suggest communication through executing remote functions. Not specifically for the way things are passed/marshalled but because creating a thread in a remote process every single time is really wasteful. I've personally never really used named pipes but I made a small library for asynchronous handling of messages sent by a client named Reloaded.Messaging, which runs ontop of LiteNetLib. You can get going with a simple server that handles serialization and asynchronous messages for you with really little code. I use it in Reloaded-II to communicate with the injected DLL and load/unload mods in real time.
Some important notes.
Reloaded-II is a standalone program though so you're probably looking for one of the two options above it. // Is tehere a way to make members non-static as the original sample???
private static IHook<ShowWindowDelegate> _createShowWindowHook; You're creating the hook in a static method which can only access static members. e.g. public class CreateShowWindowHooker
{
private IHook<ShowWindowDelegate> _createShowWindowHook;
public CreateShowWindowHooker(long showWindowPointer) => _createShowWindowHook = ReloadedHooks.Instance.CreateHook<ShowWindowDelegate>(ShowWindowCallback, (long)showWindowPointer).Activate();
bool ShowWindowCallback(HWND hWnd, ShowWindowCommand nCmdShow)
{
if (!string.Equals(GetWindowName(hWnd.DangerousGetHandle()), "Notepad3", StringComparison.OrdinalIgnoreCase))
return _createShowWindowHook.OriginalFunction(hWnd, nCmdShow);
MessageBox((IntPtr)0, "You're trying to open Notepad! Press OK to continue...", "Awoooo 56709!!", 0);
return _createShowWindowHook.OriginalFunction(hWnd, nCmdShow);;
}
} _continueHooking = false; // unlike easyhook, it seems we need to close the hook from the client... As per above (scroll a bit); it should be noted that the .NET Runtime and default Hooks created by _createShowWindowHook.Disable(); You can enable and disable them as many times as you like. Unlike many other libraries, native and managed, Unloading after disabling hooks is technically a memory leak as the This should be everything, sleeptime! |
Many thanks for your support. You're really an amazing guy.
Please accept my apologies if I misunderstood something of what you said (it was late even here when I responded you and I was nearly in black-out with all of that code inside my brain!) like for example the purpose of Reloaded.Core.Bootstrap. I had to investigate and study in order to understand and respond to your comments because a great number of things were new to my knowledge.
Please take the time you need because I really don't want to abuse your time. The only reason for which my comments are so long is not because I want your comments for everything, but only to let you have a clear and detailed idea of what I am doing. You have already given to me the right directions especially in this response and it's my responsibility all the rest. If I ask your help is only when I tried everything on my own without success. So, this should be the starting point for running in .Net core without any old framework dependency. To be honest, I don't have any idea on how to integrate this with EasyHook without going into heavy modification of its managed and unmanaged parts. Even the author has some concerns on how to manage this and I suppose this is the reason for which the .Net Standard is the only target framework taken in consideration for the upgrade. So... the right road is to return to Reloaded sample project since it is the only place where I can see the light in this tunnel...
I am also not happy to depend from Dllexport since it adds complexity to the project even if they managed to automate the whole process. I believed it was the only way to do things without going into complicated hard coding.
Then, the problem is not on the injector side. So, if I'm not wrong their .Net core support is some sort of "fake" support and there are the serious dangers you mentioned. Oh my God!
I am not sure that I have understood well, but I believe the reason for which reloaded-injector fails to write memory in a process created suspended (with error "only a part of memory has been written") should be due to the fact that it uses CreateRemoteThread api. This thread describes the reasons for the failure. Basically kernel32.dll is not loaded yet (only ntdll.dll is loaded), so it cannot use createprocess to call LoadLibrary (in the kernel32.dll). One solution is to use LdrLoadDll and this is how EasyHook managed to make it working.
I tried to play with this without success and I didn't spent further time to make it work because I realized that I am forced to target .Net Framework and basically I am going to recreate the same EasyHook loader. Once the .Net Framework is loaded I am afraid that I will go into the same issue I have gone with EH.
|
I specifically said you cannot unload the default EH puts your injected library into a new
Pretty sure this will be the case regardless of whether you roll for CoreRT/NativeAOT might be an option but it's really experimental and hacky; you'll likely have a bad time.
No, this leftover memory is specific to my library implementation and has to-do with how I handle disabling/enabling hooks. It specifically has to do with the fact that one of The common way to disable a hook seen in many tutorials and libraries is to just restore the original overwritten instructions that were made when the hook was installed, however, this has a serious flaw. If you install two hooks on top of each other, and disable the first one, the second hook is disabled too. To give a real world example: If you, for example hook DirectX for whatever reason, then an external overlay comes along (e.g. Steam Overlay) and hooks after you; you wouldn't want unhooking your function to kill the Steam overlay. Note: I specified less than 30 bytes, not sure where the 30-60 number came from. That number reaches around 30 when the
There is a solution, employed at the operating system level. A proper answer would be really complicated but an important takeaway is that memory occupied by DLLs are shared across processes. Machine code compiled by the JIT will of course be local to the current process and not shared but the runtime is compiled with an optimization called ReadyToRun which includes machine code alongside IL code, and, because it's part of the DLL it will be shared. I can't give you any hard numbers on memory usage though but if I were to have a guess 16-24MB of Physical RAM after unloading because of the runtime's managed heap and whatever else may be left. .NET itself does have some initial overhead but it actually becomes efficient once you start loading lots of stuff. Here's Some Numbers for Reloaded though
Assuming you mean WPF libraries that are part of the runtime, I'm going to have a guess about what's going on based on my experiments with tampering with When loading WPF libraries by name inside your own Note: Here's a commit where I removed loading WPF & ASP.NET into separate ALCs from an experimental branch of Reloaded-II. I'll only ever re-consider adding it when it's necessary and take the minor memory savings for now with the default behaviour :P.
I mentioned it because it's a really easy way to get your .NET code running inside a native process; so you could use it for testing the hook code in the DLL you plan to inject. |
Minimalist example of what you're trying to achieve as a Reloaded-II mod. |
Hello @Sewer56, I must admit I had very hard time especially with x86-x64 architectures. At the beginning I hoped that AnyCPU would be enough for the reloaded injector, but it wasn't. The functions failed and I had been forced to hardcode the loader csproj in order to build automatically x64 and x86 binaries at once (I had to learn how to deal with this in csproj and msbuild, something that I never had to do in the past). DNNE also gave problems in x86 and you can see what I have been forced to do in the NetCoreLoader.csproj (along with the required conditional compilation). Basically, in x86, if the functions does not have the DNNE Cdecl attribute, the function call of your injector fails. There are several concerns I commented in the sample, but my priority was to get the engine on and I left them in standby.
The new sample runs both EH and RH and shows memory usage: You can see that EH adds around 1.5 Mb of memory (I don't know why memory usage is the same even after calling LocalHook.Release). In RH, memory usage remains similar to EH until injection, but once it starts hooking it suddenly reach more than 31 Mb which remains pretty much the same once the hook is closed.
I totally agree with your design. However, shouldn't be better some sort of switch available to the user in order to override this behaviour? I suspect that these references may be what cause for the below assemblies loaded from RH to not unload (see
Unlike EH, RH forced me to decorate
Yes, I see! Fortunately I don't have to deal with games hooking, otherwise I would become crazy!
I suppose that I should expect at least the same memory usage of EH and that those crazy 31 Mb on a 5-6 Mb Notepad x86 app are due to something wrong with my code.
This means that, in the specific attached sample, there's no way to also unload NetCoreLoader and its DNNE CoreCLR once I am done with hooking. It will remain into the application domain for all the lifetime of the app.
It seems that this does not even happens automatically and requires entering into more deep stuff. Thanks for sharing the article. There are really so many complicated things that one should be aware of. However, as a general rule, I learnt that when task manager shows low memory usage there's nothing to worrying about. When memory showed is high, may be it's still does not make difference, but we need to check with tools (vmmap and similars, actually I'm using TaskExplorer...I really like how it works). The sample uses the PerformanceCounter package from Microsoft which I saw is the most recommended one for debugging.
Oh yes, very complicated. Today we have so many apps running simultaneously and in the background, therefore this should help to decrease memory usage. Problem is that I would like to know how much memory I am saving when running x number of applications in order to have an idea and do better optimization, but I am afraid this is not an easy job.
Oh yes, In fact I can see that the memory occupied by my Loader and Hooker is nearly nothing. However what they reference is much more. Start up time is something that I have not taken in consideration for now. it's too soon to give any judge with a rude prototype like mine. I hope at least to not experience delay which would require further investigations.
Yes, I mean WPF libraries. Mhhh...if I remember well in my case the ALC found the libraries without problems. I means, the resolver got them and loaded in the custom ALC (WindowsBase.dll, PresentationCore.dll, PresentationFoundation.dll and probably their related dependencies. The core stuff clearly said that there's no support for unload framework assemblies either as a whole or per-assembly and even dynamic loading. I am not sure on the exact reason you failed to load into the custom context, but you can stay sure that once they enter in your custom context, you will be unable to kick them away. And the core team have no intention to implement such capability because is very hard to make it work as expected. If you have a console application or whatever and need to occasionally display a wpf library or app for which you have the source code, then the best way I can think of is to recur to IPC for data exchange and then close the connection once done. There will be no trace left and the IPC implementation is very easy.
Mhh...very interesting! In the next few months I would like to resume a few projects where I need some WPF on demand. One of them is just a tray icon which stay on the task bar for all the time, but occasionally needs to display custom user interface, progress bar etc. I will remember to check your experimental branch when it will come the time. My plan was to exchange data through IPC, but would be amazing if I could access the whole thing.
@Sewer56 I don't know where all of this will bring me and If I will ever come with a final working project. If this happens what you think to make it part of your respository? May be a new entry like Reloaded.RemoteHooks ? After all we put efforts on this and leaving it as something to just for my personal use would be a shame. What you think? For sure this will be a very unique library, not just like all the others. So...for now my next step is to see what I can do with CoreRT/NativeAOT. Is there any other alternative? Just to see if there is something better. This one comes from Microsoft and this is for sure a valid reason to stick with it without looking elsewhere. I hope that luck will assist me! |
Just some news and new discoveries to integrate with my previous response...
And I have just discovered this as soon as the RH instance is created:
Basically, to me seems to be that we're running .Net Framework and .Net core at the same time.
I have not been able to discover where you reference PeNet.dll, but looking on Nuget I noticed some dependencies on netstandard2.1 and I am not sure if this library is responsible for loading .Net Framework runtime. I will have to completely revise the NetCoreLoader.Loader. Everything I call here will stay loaded in the default context for the whole life of the application. I will have to see what I can take out and if possible, even reflection (I don't have any idea on how). This should be no more than a loader and I am wondering if could work if I do something like:
This should allow to save on memory and on the number of assemblies loaded into the target application. I don't need any further communication once the loader has done its launching process because NetCoreHook will communicate with the client through IPC. P.S. I forgot to tell you that in the new sample, RH cause Notepad to crash finally, but I didn't searched further (I just didn't thought to write a perfect closure and wanted something quick to test). Also, the interception is semi-working. You have to manually activate Notepad focus and/or manually move mouse cursor over its window in order to raise the ShowWindow event. In EasyHook it works as expected, but only because I can launch the process suspended, activate the hook and then resume the process and catch its first ShowWindow event. This is not a problem of RH, but it relates to the injector which does not support such kind of injection scenario. I'll think to this at the final stage. |
...OK! |
Hi @netcorefan1. Did you ever find a working library to get managed code injected and running within the context of an unmanaged process? I've tried so many different libraries and code examples and I've found nothing that works. |
Hello @netcorefan1 I'm also looking for an EH alternative especially that works across .NET 6. I noticed you've got already an example project running there, would you mind sharing it? It could be a starting point for me to understand how to glue Reloaded into my own project, replacing EH. Thank you in advance! //EDIT: Nvm, I didn't notice this link: https://github.com/Reloaded-Project/Reloaded.Hooks/files/5915723/EasyHook_Vs_ReloadedHooks.zip thank you again! |
Hello,
first of all let me congratulate for this wonderful project!
I was wondering if this library could be a valid EasyHook replacement. If yes, are there any other limitations (except kernel mode hooking not supported in RH) or we can do everything we do in EH?
I like EH, but recently I abandoned .Net Framework completely in favour of .Net 5 and discovered that EH is not compatible. I tried to upgrade the library to .Net 5 and the injection works, but I don't get the remote event in the loader. It uses the old remoting API which I had to replace with named pipes. I get the remote event only if I target .Net Standard, but then I can't use AssemblyLoadContext and needed Net core dependencies for the implementation (if you have some suggestion, please let me know, thanks!).
it is now a couple of days that I am stuck with this and I discovered your library by pure accidence. Given that EH development is stuck from very long time (except some recent updates containing a rude .Net standard version only compatible with local hooks), I am asking myself if I should switch.
I am surprised that your library is very well hidden. I scanned Github and the web to find alternatives and your RH never appeared. I only found CoreHook which is great, but then I quickly discovered that the project has been abandoned and had to return back to EH.
Have you intention to maintain this project over the time? The last thing I want is to modify my code base and then suddenly discover a repo archived as read only.
Thanks
The text was updated successfully, but these errors were encountered: